§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?
The lead authorised changes to the grammar vocabulary and a patch release in the same instruction, so the intermediate candidate step buys nothing here. The consumer waiting on it is a single blocked standard name whose spelling cannot be composed at all until the vocabulary admits it, and that consumer is inside the same operator's control rather than an external audience needing a soak period.
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
Three physical_base tokens admitted, and the unit question settled by splitting rather than choosing. signal_to_noise_ratio is the linear dimensionless power quotient and signal_to_noise_level its logarithmic decibel representation, kept as separate bases because collapsing them would either lose the decibel form diagnostics actually report or hang a decibel unit on a ratio. event_instant is a scalar for the time-coordinate value at which a discrete event occurs, excluding elapsed duration and the sampled axis. The node ran the precondition and reported honestly that instantaneous_time already parses as qualifier plus base; the lead reviewed that and kept the new base, because base time IS the coordinate axis so instantaneous_time reads as the axis sampled instantaneously rather than the instant an event occurred. Verified by reading the built vocabulary rather than the edited source, which matters because a spec and its generated snapshot have diverged in this repository before.
Correctly refused rather than bypassing the documented release path: the release CLI exits 1 with Not on main branch from a detached worktree, and the node rules forbid both branch switching and a manual tag. Establishes that release cutting cannot be delegated to a worktree worker and belongs to the coordinator in the main checkout.
inner_hard_xray_half_width_of_emissivity_peak parses and round-trips: base half_width, qualifiers inner and hard_xray, locus emissivity_peak as a position under an of relation. Three routes measured, two rejected on evidence. The qualifier route worked but put emissivity in both physical_bases and qualifiers, which the cross-segment uniqueness guard forbids; the worker refused to promote its own commit and restored both qualifier files byte-for-byte. No existing construction expressed the quantity. Route one via the locus registry survives because a locus occupies its own segment. Full suite 2147 passed, 0 failed, added failures 0, uniqueness guard passing, plus a guard test pinning emissivity as a base only. Census unchanged over 2955 names.
This plan's work is already released and its premise no longer holds, measured 2026-09-15 rather than inferred. The thirteen commits it describes are contained in the
v0.9.3tag:git log v0.9.3..HEADon this repository returns nothing but a plan-state edit, so main carries no unreleased grammar work. The consumer pin it was written to correct has also moved — the catalog's dependency group now declaresimas-standard-names[quality,docs]==0.9.3and its lock file resolves 0.9.3, where both had been behind.The downstream consumer it names as blocked is unblocked.
hard_x_rays/emissivity_profile_1d/half_width_internalcarriesinner_hard_xray_half_width_at_emissivity_peakat name and documentation accepted, and its external sibling carriesouter_hard_xray_half_width_at_emissivity_peak, both composed against theemissivity_peaklocus this release added. The spelling placeshard_xrayas a leading qualifier rather than folding the radiation band into the physical base, which is why earlier attempts at an..._of_hard_xraytail were refused; the grammar was never missing the capability, only the spelling was wrong.Nothing here needs shipping. The plan is left in place as the record of why 0.9.3 was cut rather than closed as abandoned, because its decisions — releasing as a stable patch rather than a candidate, and pinning the physical-base kind contract by named keys — are the reasoning behind a release that did happen.
My dispatch error, caught before it cost anything. I passed --checkout-path pointing at imas-standard-names and assumed that selected the worktree repository; it selects the flight config layer only, so reckon cut the worktree from the enclosing repo - imas-codex - and the node landed in a checkout containing no imas_standard_names source at all. The fix is --repo, and the node was stopped and re-dispatched as n-grdp-path-qualification-field-selection against the correct repository, whose worktree now sits under the imas-standard-names root and links ISN's own environment. Nothing was written and no ISN file was touched
One line, and the check got no weaker. The rule now reads entity = getattr(parsed,'object',None) or getattr(parsed,'geometry',None), which is the field the parser actually populates for a qualified outline, so radial_outline_of_plasma_boundary and its siblings stop being rejected for an entity they always carried. Two parametrised tests cover all five required cases: the three qualified outlines return no path-qualification issue, and bare outline and bare trajectory still return the error, so the guard is proved to fire rather than merely to stop complaining. Full ISN suite 2139 passed, 34 skipped, 82 xfailed, 0 failures against a clean base. This unblocks nine live downstream rows carrying the same issue phrase, three of them WEST batch members the lead identified as correctly named. Note for the ledger: the manifest cited commit ec1fd50c7e53a4d97f67e8c97eb8a7b8e643a812, which does not exist - the real commit is ec1fd50dbccdaf2761f8df93ae216489f489a9e7, sharing only the seven-character prefix