2026.9.21.2: the macros mcpp owns are spelt in upper case, and the borrowed name is withdrawn - #681
Conversation
…rrowed name is withdrawn
`__mcpp_target_<os>__` becomes `__MCPP_TARGET_<OS>__` and `__openkal__`
becomes `__OPENKAL__`. The spelling is still the triple's own `os` field, now
upper-cased; the engine still learns no operating-system name.
THE CONVENTION SPLITS BY WHAT A NAME IS, NOT BY WHO WRITES IT. A vendor or
product name is upper --- `__APPLE__`, `_WIN32`, `__MINGW32__`, `__GNUC__`. A
kind-of-system name is lower --- `__linux__`, `__unix__`, `__gnu_linux__`.
Every row this engine OWNS is of the first kind: it names mcpp, or it names
openkal. The kind-of-system question is answered by `__linux__` and its
family, which mcpp SUPPLIES rather than owns and which therefore keep their
lower-case spelling, for that exact reason.
The lower-case spelling shipped in 2026.9.21.1 on the reverse reading: that
these names sit beside `__linux__` in real guards, so matching it was
consistency. THAT CONFUSES ADJACENCY WITH KIND. `__APPLE__` sits in those
same guards and is upper, because it belongs to somebody.
`__MCPP_` is a prefix, not the whole rule: `__OPENKAL__` is owned and names
*openkal* rather than mcpp. The assertion in `test_predefines.cpp` therefore
checks the SPELLING CONVENTION --- upper case, `__`-wrapped --- instead of
the prefix, which would have judged `__OPENKAL__` a violation when it is not.
WITHDRAWING A MACRO IS DECIDED BY A COUNT, NOT BY READING
An entry in this contract is a published interface, and withdrawing one is
SILENT: a `#if` selects the other branch and compiles. No mechanism available
to a build tool makes that loud. So a withdrawal is decided by a measurement
that ENUMERATES READERS, and what it finds sets how many steps the withdrawal
takes. The two this project has performed came out differently:
withdrawn readers found steps
__CYGWIN__ 4 third-party members, 3
+ 6 sites in the two
headers we INSTALL
__mcpp_target_<os>__, __openkal__ none 1
The second row's denominator is every repository of this ecosystem, swept by
file type: no source file and no manifest reads either name, and the only
occurrences are this engine's emitter, its tests, and prose. Both names were
invented here, so no upstream code can hold one; exposure was three days for
`__openkal__` and a single release for the target macro. The rule did not
change between the two rows. The count did.
A new assertion takes its denominator from the target registry rather than
from a list written beside the test: every canonical triple is parsed and its
emitted macro checked to be a valid identifier. The spelling comes from the
`os` field, so an `os` carrying a dot or a version suffix would produce a
macro no compiler accepts, and the failure would land in a user's build.
__CYGWIN__ IS WITHDRAWN --- THE LAST STEP OF A THREE-REPOSITORY SEQUENCE
The Windows `presents = "posix"` realisation now adds `-U__CYGWIN__` and
`-U__CYGWIN32__` to the compile line. `--target=x86_64-pc-cygwin` stays: it
is what supplies `__unix__` and suppresses `_WIN32`, which is what presenting
POSIX means. Only the borrowed NAME was unwanted.
1. 2026.9.21.1 defined mcpp's own name beside the borrowed one; this
release re-spells it, while it still has no consumer. Purely additive.
2. `openkal-musl@0.19.0` and `openkal-llvm-runtime@0.14.0` read the new
name and keep `|| defined(__CYGWIN__)`, so they build on an engine from
either side of this change. PUBLISHED BEFORE THIS RELEASE.
3. this release stops defining the borrowed name.
THE ORDERING IS MEASURED, NOT ASSERTED. It holds between repositories, so no
test in this one can check it. Building an openkal program for
`x86_64-windows-gnu` against the PUBLISHED `openkal-llvm-runtime@0.13.0` on
this engine fails on libunwind's two `static_assert`s --- `x86_64 registers
do not fit into unw_context_t`, `UnwindCursor<> does not fit in
unw_cursor_t`. That is step three taken first, and it is why step two ships
first. `setjmp.h` is the silent half: its own comment says a mismatch is
reported by nothing until the record overruns.
THE RESIDUAL WINDOW IS NAMED RATHER THAN CLAIMED AWAY. A project pinning
`openkal-musl` at 0.18.0 or earlier EXACTLY, and upgrading past this release,
gets that silent `#else`. Moving the index's `latest` onto 0.19.0 first keeps
the window to exact pins; nothing available here closes it, because the
engine cannot know which macros a package's installed headers read.
Bootstrap pin advances from 2026.9.20.1 to 2026.9.21.1.
… is verified against is overridable
E3: A REQUIREMENT NOBODY ANSWERED READ EXACTLY LIKE A CONFIRMED ONE.
Three situations exist and two of them build: the resolved implementation
states a `provides-interfaces` list containing the requirement (build), states
a list without it (refused), or states nothing at all (build). The third is
deliberate --- the key postdates the implementations, and a graph that has not
adopted it must keep building --- but until now it produced output identical
to the first, so a consumer inspecting a green build could not tell "checked
and agreed" from "never asked".
note kernel-abi interfaces: fakekernel@0.1.0 states none, 2 requirements unchecked
The note names the implementation taken from the RESOLVED layer: a reader
told only that something went unchecked cannot act on it.
`tests/e2e/743` gains two legs rather than one. Leg C asserts the note is
present, leg D that it is absent when the provider does state its list.
WITHOUT LEG D, LEG C PASSES AGAINST AN ENGINE THAT PRINTS THE LINE
UNCONDITIONALLY, which measures nothing. Leg C also asserts the COUNT, the
one part of the message the fixture determines: a note reporting "1" or "0"
would satisfy every assertion that matches only an identifier.
THE OPENKAL BRANCH IS OVERRIDABLE FOR ONE RUN
This repository already has one half of a cross-verification protocol: the
ecosystem repositories build against an mcpp PR branch through
`MCPP_SOURCE_REF`, so an engine change is measured against them before it
merges. The reverse was hard-coded to `main`, which makes a change that
REQUIRES a coordinated ecosystem commit unverifiable until after that commit
lands --- and unmergeable until then, since `openkal-cross` is the job that
fails.
`__CYGWIN__`'s withdrawal is the case that showed it. `openkal-cross` builds
`openkal-llvm-runtime@main`, whose installed header read only the borrowed
name, so this engine's own CI reproduced the ordering constraint as a red
cell. The cell is CORRECT --- the packages must publish first --- but
verifying the engine before that publish needs the input. Left empty, nothing
changes.
E2 (the link-time set difference) is recorded in the plan as withdrawn from
this release, with its blocker corrected: it is not the workload the plan
claimed but a design decision. `SURFACE.txt` is not installed by any package,
and docs/22 states that the engine knows no member of either set --- so where
an interface-to-symbol map lives, and whether the engine can read one without
learning this ecosystem's vocabulary, has to be answered before the feature
has a shape.
Reading the measured diagnostics and the recipes apart: curl lib/setopt.c:31: 'linux/tcp.h' file not found cmp-module asio/detail/config.hpp:899: 'linux/version.h' file not found CURL IS A RECIPE DEFECT, the same shape as C3/expat. `compat.curl.lua` generates `#define HAVE_LINUX_TCP_H 1` inside `#if defined(__linux__)`. openkal runs on the Linux kernel, so `__linux__` is CORRECT; what is wrong is the recipe reading it as "glibc's whole Linux userspace is installed". The same block asserts `HAVE_GLIBC_STRERROR_R` --- openkal-musl is musl, so that one is actively false --- along with `HAVE_SYS_EVENTFD_H`, `HAVE_FSETXATTR` and a hard-coded host path in `CURL_CA_BUNDLE`. The honest test is `__has_include(<linux/tcp.h>)`: standard C, and it asks the question being asked rather than inferring which headers exist from which kernel. CMP-MODULE IS THE REAL C2. asio's `#include <linux/version.h>` sits outside every `ASIO_DISABLE_*` guard, so no configuration macro prevents it, and no manifest key reaches inside a third-party header --- which is justification one in `predefines.cppm`. `members.toml` already has an `[excluded]` table whose stated semantics are exactly "cannot be built in any openkal graph". What is missing is a second reason category, not a second table. E2's blocker is corrected in the same pass: not the workload the plan claimed but a design decision, since SURFACE.txt is installed by nothing and docs/22 states the engine knows no member of either set.
…ile using it
Both ecosystem packages went green against the mcpp PR branch, AND NEITHER
GREEN TOUCHED `__MCPP_TARGET_WINDOWS__`:
openkal-musl linux/gcc, linux/llvm, macos/llvm; the cross-link row
is Linux<->macOS
openkal-llvm-runtime x86_64-linux-gnu and riscv64-none-elf
No package's CI in this ecosystem builds `x86_64-windows-gnu`, and the whole
subject of this change is a macro that exists only on Windows targets. The
greens are real and they measure something else --- a criterion that ran,
passed, and whose object was not present.
The only job that builds that target is mcpp's own `openkal-cross`, and it
had the ecosystem branch hard-coded to `main`, so an engine change requiring
a coordinated ecosystem commit was unverifiable until after that commit
landed. `openkal_ref` closes it; empty means today's behaviour.
Consequence for ordering: the example in that job reaches the runtime through
`path = "../.."` and the runtime's manifest pins `openkal-musl = "0.19.0"`
from the INDEX, so musl 0.19.0 has to be registered before the input can be
used.
…that leg
Read job by job instead of from the first job of a run:
openkal-llvm-runtime `host-dimension` has a Windows-host row that builds
every target, including x86_64-windows-gnu, and it
resolved openkal-musl@0.19.0 on the PR engine. The
Windows leg of E1 WAS verified.
openkal-musl linux/gcc, linux/llvm, macos/llvm, a Linux<->macOS
cross-link, and a start-on-the-other-system job. No
Windows cell at all.
I concluded from the first job alone that no package in the ecosystem builds
Windows-over-openkal. That was wrong and the conclusion it cast doubt on
stands.
The real gap is smaller and still a gap: the package whose change this round
is `bits/setjmp.h` --- an installed header whose only branch is about
Windows --- has no Windows cell of its own. It is verified today only
transitively, because the runtime's CI pulls musl in as a path dependency and
compiles it along the way.
…othesis was right
Following the criterion the finding itself wrote down --- instrument
`run_dtors` --- with `&dtors` printed at both sites:
registered dtor, dtors=0x7ffffe994680, &dtors=0x7ffffe9946a8
run_dtors called, dtors=0, &dtors=0x7ffffe9946c8
`run_dtors` IS called, which retires one branch. `&dtors` DIFFERS three times
in one thread, which gives the other: `__thread` is emutls here, emutls keeps
its per-thread blocks behind a pthread key of its own, that key's destructor
had already released this thread's block, and every read afterwards allocates
a fresh zeroed one.
HYPOTHESIS ONE IN SECTION 4 WAS CORRECT AND WAS RECORDED AS REFUTED. What was
wrong was the probe: musl runs key destructors in creation order, and that
probe created its own key BEFORE first touching a thread-local, so emutls
outlived it. The real ordering is the reverse. A probe cannot report an
ordering it was constructed to avoid --- the predicate was right and the
object's construction excluded the condition under test.
Fixed in openkal-llvm-runtime 0.15.0 by keeping the list in the TLS key's own
value, which no other key's teardown can reach. Two conditions asserted, in
examples/cxx, on both targets: the thread_local is constructed, and its
destructor runs.
Both layers located and fixed, shipping in openkal-llvm-runtime 0.15.0. The second layer: `__thread` is emutls under `-femulated-tls`, emutls keeps its per-thread blocks behind a pthread key of its own, and that key's destructor releases this thread's block before libc++abi's runs. Criterion: `&dtors` differs three times in one thread. It was closed by the criterion the finding document itself wrote down. And the hypothesis that document recorded as refuted was correct --- the probe created its own key before first touching a thread-local, and musl runs key destructors in creation order, so emutls outlived it. The predicate was right; the object's construction excluded the condition under test.
`status: resolved` is not in the vocabulary the structure check accepts (active | landed | superseded | abandoned), and the agents index had not been regenerated. Both caught by `check_docs_structure.sh`, which is the check that exists so a front-matter word nobody reads does not drift.
Measured rather than assumed. A minimal criterion --- a setjmp/longjmp
program with `_Static_assert(sizeof(jmp_buf) >= 32 * sizeof(unsigned long
long))`, declaring `openkal-musl` alone, built for x86_64-windows-gnu:
compiles which is the layer that matters, since a short jmp_buf is
silent and a compile-time assertion is the only thing that
makes it loud
does not link cpow.o and others leave references unresolved; openkal-musl
alone is not a complete link, because the openkal implementation
and compiler-rt are assembled by openkal-llvm-runtime
So a Windows cell there is either compile-only (which does catch this defect,
and is cheap) or pulls the runtime in as a path dependency --- the mirror of
what the runtime's own CI already does. Either is new work.
Ecosystem review before mergePer repository, what this release does to something already published. Published packages
The ordering is the mitigation and it is not optional. 0.19.0 and 0.14.0 are What is not mitigated, and is named rather than claimed awayA project pinning The renamed macros
IndexNo new manifest key, so Build cacheThe macro rename changes field 7 of the fingerprint. It costs nothing: field 8 What this release is expected to FIXFour members of the compatibility measurement — |
Six sections. B and C are CHANGE and must fail on the previous release; E and
F are GUARD and must pass on both. Measured, on the host, against the genuine
published archive of 2026.9.21.1:
2026.9.21.1 fails=2 B: no upper-case target macro
C: an unanswered requirement produced no note
2026.9.21.2 fails=0
C'\''s second leg passes on both by design: it asserts the note is ABSENT when
the provider does state its list. Without it, the first leg would pass against
an engine that printed the line unconditionally --- a negative control is not
a hole in a CHANGE section.
B carries both directions in ONE translation unit, because either alone passes
for the wrong reason: an engine defining NEITHER spelling satisfies "the
lower-case one is gone", and one defining BOTH satisfies "the upper-case one
is here". Its second leg asks a freestanding target for its own macro, which
is what says the spelling is DERIVED from the triple rather than enumerated.
D and F need openkal-llvm-runtime 0.14.0 from the index and reported NOT RUN
rather than passing when it was not yet registered.
E1b and E1c did not exist when the table was written; C2 turned out to be two items with two different owners; E2 left this round with its blocker corrected. Each row now says what state it is in rather than only what would count as passing, because a table of criteria with no readings is a list of intentions. Recorded readings: the verification script's control run (2026.9.21.1 fails=2, 2026.9.21.2 fails=0), the nine green cells of openkal-cross, and the two directions the A3 check was measured in.
__mcpp_target_<os>__becomes__MCPP_TARGET_<OS>__and__openkal__becomes
__OPENKAL__. The spelling is still the triple's ownosfield, nowupper-cased; the engine still learns no operating-system name.
THE CONVENTION SPLITS BY WHAT A NAME IS, NOT BY WHO WRITES IT. A vendor or
product name is upper ---
__APPLE__,_WIN32,__MINGW32__,__GNUC__. Akind-of-system name is lower ---
__linux__,__unix__,__gnu_linux__.Every row this engine OWNS is of the first kind: it names mcpp, or it names
openkal. The kind-of-system question is answered by
__linux__and itsfamily, which mcpp SUPPLIES rather than owns and which therefore keep their
lower-case spelling, for that exact reason.
The lower-case spelling shipped in 2026.9.21.1 on the reverse reading: that
these names sit beside
__linux__in real guards, so matching it wasconsistency. THAT CONFUSES ADJACENCY WITH KIND.
__APPLE__sits in thosesame guards and is upper, because it belongs to somebody.
__MCPP_is a prefix, not the whole rule:__OPENKAL__is owned and namesopenkal rather than mcpp. The assertion in
test_predefines.cppthereforechecks the SPELLING CONVENTION --- upper case,
__-wrapped --- instead ofthe prefix, which would have judged
__OPENKAL__a violation when it is not.WITHDRAWING A MACRO IS DECIDED BY A COUNT, NOT BY READING
An entry in this contract is a published interface, and withdrawing one is
SILENT: a
#ifselects the other branch and compiles. No mechanism availableto a build tool makes that loud. So a withdrawal is decided by a measurement
that ENUMERATES READERS, and what it finds sets how many steps the withdrawal
takes. The two this project has performed came out differently:
The second row's denominator is every repository of this ecosystem, swept by
file type: no source file and no manifest reads either name, and the only
occurrences are this engine's emitter, its tests, and prose. Both names were
invented here, so no upstream code can hold one; exposure was three days for
__openkal__and a single release for the target macro. The rule did notchange between the two rows. The count did.
A new assertion takes its denominator from the target registry rather than
from a list written beside the test: every canonical triple is parsed and its
emitted macro checked to be a valid identifier. The spelling comes from the
osfield, so anoscarrying a dot or a version suffix would produce amacro no compiler accepts, and the failure would land in a user's build.
CYGWIN IS WITHDRAWN --- THE LAST STEP OF A THREE-REPOSITORY SEQUENCE
The Windows
presents = "posix"realisation now adds-U__CYGWIN__and-U__CYGWIN32__to the compile line.--target=x86_64-pc-cygwinstays: itis what supplies
__unix__and suppresses_WIN32, which is what presentingPOSIX means. Only the borrowed NAME was unwanted.
release re-spells it, while it still has no consumer. Purely additive.
openkal-musl@0.19.0andopenkal-llvm-runtime@0.14.0read the newname and keep
|| defined(__CYGWIN__), so they build on an engine fromeither side of this change. PUBLISHED BEFORE THIS RELEASE.
THE ORDERING IS MEASURED, NOT ASSERTED. It holds between repositories, so no
test in this one can check it. Building an openkal program for
x86_64-windows-gnuagainst the PUBLISHEDopenkal-llvm-runtime@0.13.0onthis engine fails on libunwind's two
static_asserts ---x86_64 registers do not fit into unw_context_t,UnwindCursor<> does not fit in unw_cursor_t. That is step three taken first, and it is why step two shipsfirst.
setjmp.his the silent half: its own comment says a mismatch isreported by nothing until the record overruns.
THE RESIDUAL WINDOW IS NAMED RATHER THAN CLAIMED AWAY. A project pinning
openkal-muslat 0.18.0 or earlier EXACTLY, and upgrading past this release,gets that silent
#else. Moving the index'slatestonto 0.19.0 first keepsthe window to exact pins; nothing available here closes it, because the
engine cannot know which macros a package's installed headers read.
Bootstrap pin advances from 2026.9.20.1 to 2026.9.21.1.