§1 — Why this release exists
A downstream catalog consumer resolves this package as an editable
path install rather than from a released version. That arrangement was
convenient while the grammar was moving and is now actively harmful: the
consumer's own dependency declaration pins 0.8.9, its environment
resolves a development tree, and the version string it reports is frozen
build-time metadata read from the installed distribution. So the consumer
records a version that does not describe the code it ran, and any release it
cuts would be built against a tree nobody declared.
Thirteen commits on this repository's main carry the work the
consumer needs. Unary operators now render adjacent to their base group when an
of, at, or due_to tail is present, with
parser symmetry and recursive placement through composite operand trees. An
indexed operator renders its operand before its coordinate index. Twenty-five
bare vocabulary tokens the closed vocabulary was missing are defined, six more
are added from data-dictionary evidence, and the beta inclination
qualifier is withdrawn because every data-dictionary leaf named exactly
beta is the oblique-element inclination angle and the token invited a
collision with plasma beta.
The grammar suite passes with zero failures at both ends — 1148 tests before and 1170 after, 22 added — and a census over the consumer's live catalog recorded 149 renderer changes across 2974 names with zero reparse failures and zero intermediate-representation mismatches.
The chain is strictly ordered, and each link is blocked by the one before it:
§2 — A contract test pins the entire vocabulary
The upstream pull request is mergeable and its fast build passes, but
build (3.12), build (3.13) and coverage
all fail on a single test:
tests/test_physical_base_kinds.py::test_only_momentum_and_torque_bases_change_to_vector.
The failure is a digest mismatch. _kind_map_digest hashes every
name=kind pair in the physical-base vocabulary except three named
vector corrections, and the expected digest is frozen as a literal in the test
module. Adding any new base therefore changes the digest, even when no
existing base has changed kind. Thirty-one tokens were deliberately added, so
the digest moved for exactly the reason the vocabulary work intended.
Reading the test exposes a second problem worth fixing in the same pass. Its
three assertions do not carry equal weight. The first — that the three named
corrections are vector — is real. The third derives its own baseline
from the current kind map by overriding those same three entries to
scalar, then asserts that precisely those three differ; given the
first assertion that is true by construction and can never fail, so it is
vacuous. That leaves the frozen digest as the only assertion pinning anything,
and it pins far more than the test's name claims: the whole vocabulary rather
than the kinds of the bases that existed when the baseline was taken.
The repair is to assert what the test says it asserts. Pin the kind of each base present in the recorded baseline by name, so that altering an existing base's kind fails while adding a new base does not. A digest over a growing map is a moving value frozen in a contract, and it will break on every legitimate vocabulary addition until it is replaced.
§3 — The tag, and moving the consumer off its override
Releases here are tag-driven through the standard-names release
command, which refuses unless the working tree is clean, the branch is
main, the local branch is synced with upstream, and continuous
integration has passed for HEAD. Those preconditions are the reason
§2 blocks everything downstream rather than merely being untidy. Tagging pushes
to upstream and triggers the publish workflow.
Once a version is published, the consumer drops the editable source override from its dependency declaration and pins the released version in both places it names this package. That single change also repairs the consumer's frozen version stamp, because the reported version and the resolved code become the same fact again rather than two facts that agreed once at install time.
The consumer's change cannot be verified from an isolated worktree: a pin-changing worktree resolves the main checkout's dependency declaration through a shared environment, so any concurrent command in that environment reverts the install mid-measurement. It must be sequenced when the consumer's fleet is at rest.
§ Decisions
Does this publish as a release candidate first, or straight to a stable patch?
How should the physical-base kind contract be pinned?
Refreshing the digest restores the suite and leaves the defect in place, so the next vocabulary addition breaks it again. Pinning by name distinguishes an added base from a changed kind, which is what the test claims to measure.
THE CONTRACT NOW MEASURES WHAT ITS NAME CLAIMS. A frozen SHA-256 digest over the entire physical-base vocabulary is replaced by an explicit 177-entry baseline mapping measured from the loader, projected onto only those named keys - so an existing base changing kind fails while an added base passes. 1 failed at base against 3 passed after, and the whole grammar suite is green at 2110 passed, 34 skipped, 82 xfailed, exit 0. THE WORKER DISPUTED MY SPEC AND WAS RIGHT, which is the most valuable thing here. I told it to also assert that no base outside the three vector corrections carries kind vector, reading the test name as only these are vector. The name says only these CHANGE TO vector, and the loader holds 16 established vector bases outside the corrections, so my assertion was false of this vocabulary and it stopped rather than writing something that could not pass. It also confirmed my reading of the third assertion with a proper argument rather than by agreement: the old baseline copied every current value and overrode only the corrections to scalar, so once the first assertion held, exactly those three necessarily differed and no other key could - no independent failure case existed. TWO-DIRECTION PROOF, both concrete: flipping loaded acceleration from vector to scalar must raise, and inserting a synthetic base must still pass. The census is the part a reader gains most from - 177 bases, 19 vectors, 16 of them established outside the corrections, zero missing current keys and zero stale baseline keys - because the frozen digest had hidden every one of those facts behind one opaque hash. RECEIPT, and the first codex run today to take two turns: 15m50s of a 40m fence, 65 tool calls, 2 turns, zero provider refusals, three self-corrected local retries (an unsupported patch operation, a malformed poll, an over-escaped f-string). Its named discovery was measuring and transcribing the complete base map, which is exactly the work a digest exists to avoid and exactly why the digest was the wrong instrument
A DOMAIN REDUCTION NOW LEADS ITS NAME AND WRAPS THE PROJECTION, and I verified the deliverable rather than the gate because the second attempt passed its whole suite while being wrong. The measure holds on every axis: IR of the lead form equals IR of the authored form, compose of the authored form returns flux_surface_averaged_toroidal_lithium_velocity_at_plasma_boundary which is the specified spelling exactly, and the reduction appears in operators rather than qualifiers. Both negative controls stay byte-identical including the recursive one. All 21 live migrations are the leading form, 2326 of 2347 byte-identical, full suite 2132 passed and 0 failed against a 2110 baseline. THE SECOND ATTEMPT WAS THE INSTRUCTIVE ONE: it made the correct spelling legal and round-tripping while leaving the authored names classifying the reduction as a qualifier, so the grammar admitted TWO canonical spellings and treated them as different meanings - worse than the single wrong answer it started from, and entirely invisible to a green suite. Only comparing representations caught it. IT CORRECTED ME THREE TIMES ACROSS THREE ATTEMPTS and every correction stood. My either-parser-or-renderer framing was a false dichotomy: a base-qualifier-projection expression outranks the operator peel, then the renderer faithfully trails what the parser mis-classified. My instruction to revert the parser edit and fix only the flat model was WRONG because _strict_validate holds two independent canonicality checks and only one passes through _model_to_ir - my six-call-site measurement found every caller of that function but never asked whether canonicality had a path around it. And my placement rule needed two conditions, joiner-free AND precedence 30, where I gave one; the single-condition version breaks seven change_in and normalized contracts. The final shape encodes both as _REDUCTION_PRECEDENCE with a bare_prefix test, keeps the parser edit that could not be reverted, reverts render.py entirely, and records the rule beside the vocabulary so it is not read as an accident. Eight contract parameters were repointed under my authorisation and the ir.py comment documenting the old form as intent was updated with them. gyroaveraged stays unary_postfix at precedence 10 against bare-prefix-30 siblings, reported and unfixed as instructed