Direction locked (2026-06-30): Option B — full grammar decomposition.
A survey of the live catalogue (1364 names, 100% parse) proved the open qualifiers.yml "generic"
class is a catch-all conflating ≥4 grammatical roles with opposite positional behaviour. The fix is structural:
route every token to its true segment, add an ordered zone segment, pull the mis-filed
tokens (operators, coordinates, lexical bases) out of qualifiers.yml, and enforce one canonical
order at generate time (compose canonicalizes; validator rejects). Breaking renames are authorised (early RC);
renamed names re-pass review, with before/after semantic-quality sampling.
Prereq qualifier-vocabulary-categorization shipped (12-category map). Migration runs through imas-codex (graph), not by hand-editing the catalogue YAML.
§1 — The problem: "generic qualifier" is a catch-all, not a grammar role
The ISN grammar (specification.yml) fixes the order of the named qualifier sub-kinds
(aggregation → orbit → population → subject) and the model (grammar/model.py) already
carries clean named segments for them. But the open generic qualifier class
(qualifiers.yml, 161 tokens) has no defined position: the parser collects every
generic token into a flat IR qualifiers list and render_qualifiers emits them in parse
order. Whatever order a name was authored in is preserved, so the catalogue has drifted.
A full survey of the live catalogue (1364 names; all parse) shows the order problem lives
entirely inside this class, and that the class is not one role:
0 names violate the named sub-kind order — the named segments are clean.
8 tokens are literally double-registered as operators
(normalized, perturbed, volume_averaged, line_integrated, accumulated, gyroaveraged, maximum, minimum):
they live in both qualifiers.yml and operators.yml, get parsed as flat qualifiers,
then heuristically folded back into the model's transformation slot via
_BARE_PREFIX_TRANSFORMATIONS. This is the smell, concretely.
The residual generics get glued into physical_base as an opaque compound
(major_radius, anomalous_current_density), hiding structure and leaving intra-compound
order undefined.
The class mixes at least four behaviours with opposite binding: base-bound lexical modifiers
(major 31/33 adjacent, surface, vector, angular) that glue to the base;
reductions/operators (always outermost); region/geometric sub-selectors
(core/edge/inner/outer/upper/lower); and genuine regime/source/diagnostic qualifiers
(anomalous/ohmic/bootstrap/doppler). A single flat "generic position" cannot be correct for all of them —
e.g. normalized wants to be outermost while major must hug the base. A binding-depth split
(wrapper → zone → qualifier → base-bound) already fits 117/134 multi-generic names with
no hand-tuning: the authors have been encoding binding depth by hand all along.
The decomposition routes each qualifiers.yml token to its true segment. The named sub-kinds
already work; the new zone segment and the refined qualifier segment give
the remaining tokens a defined position, and operators/coordinates/lexical-bases leave the qualifier vocab entirely.
✓ grammar core landed 2026-06-30 commit ef4db62
§2 + §3e structural scaffold shipped: ordered zone segment (13 tokens, generated Zone enum), 8 operator dupes removed from qualifiers.yml, compose() canonicalizes + validator rejects. Verified independently: engine self-consistency 1357/1357 accepted names round-trip (0 inverse bugs); suite 1572 passed; codegen in sync. Migration backlog (expected): 13 names — 7 parse-fail (all gyroaveraged mid-prefix, now postfix-only) + 6 reorder (zone-before-qualifier, e.g. major_inner_radius_of_strike_point→inner_major_radius_of_strike_point). §3v guard tightened: qualifiers∩operators now empty + enforced.
✓ clean mis-files 2026-06-30 commit 6ba0ef2 — first §3 slice: removed 4 mis-filed tokens from qualifiers.yml (flux_surface, inlet, outlet → loci; state → subject component). Prefix-form names migrate to locus form (coolant_outlet_temperature→coolant_temperature_at_outlet). Guard ratcheted: qualifiers∩locus_registry now empty + enforced. Suite green; engine 1356/1356; backlog 14 (8 parse-fail + 6 reorder). Remaining §3 = the dual-role classes (transport-channel, regime/mechanism, domain-overlap, base-bound).
✓ Class 1: channel segment 2026-06-30 commits 5b20757+0c81e9d — dedicated channel segment (heat/particle/energy/momentum), generated Channel enum. Removed the four *_flux compound bases + particle from subjects; energy/momentum stay bases (documented dual-role). Channel renders outer of residual qualifiers (semantics: "the convection velocity of momentum"). Guard ratcheted: qualifiers∩{physical_bases, generic_physical_bases, subjects} now empty. Suite green; engine 1356/1356; backlog 19 (remainder → Class 2/4). Next: Class 2 (regime/mechanism → due_to_ process).
✓ Class 2: regime/mechanism → process 2026-06-30 commit 5ed13b8 — moved 12 transport-regime/drive tokens (anomalous, bootstrap, collisional, e_cross_b_drift, fusion, inertial, neoclassical, non_inductive, ohmic, resistive, turbulent, wave_driven) out of qualifiers to the process segment (X_due_to_<mechanism>, additive-decomposition semantics). The regime current-density/flux names (anomalous_current_density, …) become §5 migration targets. Suite green; engine 1344/1344; backlog 38.
✓ Class 3: domain overlap 2026-06-30 equilibrium/mhd/nbi kept as documented dual-role qualifiers (genuine modifiers that also name a physics domain — benign, like physics_domains↔tags); no migration. Guard ratcheted: qualifiers∩{subjects, regions} now empty + enforced; only qualifiers∩processes={convection, heating} remains (→ Class 4). Next: Class 4 (base-bound → atomic bases).
✓ Class 4: base-bound → atomic bases 2026-06-30 commit cba6f2e — promoted 11 lexicalised compounds to atomic physical_bases (convection_velocity, heating_power, vector_potential, center_of_mass_velocity, angular_momentum/frequency/velocity, internal_inductance/energy, kinetic_energy, surface_area) and removed the 6 fully-subsumed tokens (convection, heating, vector, center_of_mass, angular, internal). surface/kinetic kept as qualifiers (coexist via longest-base-match). vector_potential promoted from alias to base. Suite green; engine 1344/1344; no new backlog.
✓✓ Qualifier-class decomposition COMPLETE. qualifiers.yml is now disjoint from every segment vocabulary except the documented physics_domains dual-role — the flat catch-all is gone, replaced by strict ordered segments (operator · aggregation · orbit · population · subject · device · zone · channel · qualifier · base · loci · process). Consolidated §5 migration backlog: 38 names (20 parse-fail + 18 reorder). Remaining: §4/§4b (LLM context + SPA), §5 (graph migration + re-review), §6 (radial-coordinate refactor).
✓ §4 + §4b 2026-06-30 commits 4c77091 + 490319b — §4: the LLM grammar context (context.py) now templates the full ordered grammar including zone+channel (auto-derived from codegen SEGMENT_ORDER; descriptions added). §4b: zones+channels emitted in GRAMMAR_VOCAB (canonical intra-order) and rendered as their own segments in the SPA Grammar composer + ParseBreakdown. Fidelity held: SPA round-trip harness 19/19 (new core_electron_temperature, scrape_off_layer_density, heat_flux, radial_electron_energy_flux cases); Python tests/catalog 127 passed. Remaining: §5 (graph migration of the 38 backlog), §6 (radial-coordinate refactor — fixes the major_inner_radius nonsense).
✓ channel/base qualifier split + operator consistency 2026-06-30 commit 35eb1f6 — new channel_qualifier segment (kinetic, plasma, diamagnetic — qualifiers that bind to the channel), positioned zone → channel_qualifier → channel → base. kinetic_energy_flux/plasma_momentum_flux now canonical (were bogus reorders). qualifiers.yml retained as the base-qualifier vocab. Operator form A locked: averaging reductions render as participle bare prefixes (flux_surface_averaged); time_average→time_averaged for family consistency. angular_width regression fixed (b63e8ad). ISN grammar is now FINALIZED. Catalogue grew to 2194 names; engine self-consistency 2151/2151; §5 migration backlog 68 (43 parse-fail + 25 reorder). §6 (radial) migrates coordinate spellings from major_radius_of_X to radial_coordinate_of_X; the semantic validator rejects the retired locus form while retaining bare major_radius as a length scalar.
✓ process mech-nouns + prompt hardened + generation validated 2026-06-30 ISN: bare regime/drive process tokens lifted to mechanism nouns (anomalous→anomalous_transport, ohmic→ohmic_heating/ohmic_current_drive, bootstrap/non_inductive/wave_driven→*_current_drive, ion_inertia, resistive_diffusion, fusion_reactions; commit cacb8ca). imas-codex compose prompt hardened for the new grammar (channel/channel_qualifier/zone + mechanism-noun due_to_ rule + §6 radial + diamagnetic→channel_qualifier; imas-codex 42d7638e). DSv4 generation validated: 14/14 (100%) canonical on a representative sample (Neo4j-free direct-LLM spot-check) — e.g. radial_current_density_due_to_anomalous_transport, parallel_current_density_due_to_bootstrap_current_drive, electron_particle_flux_due_to_turbulent_transport. ISNC grammar tab: channel_qualifier wired through the SPA (dd4c846). Remaining (env-gated): the --focus graph rotations + review-scores + sn release run in the Neo4j-connected env, with imas-codex bumped off the ISN rc48 pin to an RC/main containing the grammar.
✓ §5 deploy (bulk) + grammar hardening 2026-07-01 ISN pin bumped off rc48 to the grammar commit (durable, no --no-sync). Landed this session: grammar_parse_fallback removed (parse_standard_name via validate_worker is the single grammar gate); process-vocab curated (97→89: dropped superseded bare regimes + 5 literal dupes + weak generics); segment_edge_specs empty/multi-token '()' bug fixed; §6 enforced (major_radius+locus rejected → radial_coordinate_of_<carrier>); compositional locus qualifiers (qualifiable feature + ordered inner/outer/upper/lower/primary/secondary replaces 11 flat tokens → radial_coordinate_of_upper_outer_strike_point composes for free, scales to snowflake/X/super-X). imas-codex: compose flags vocab_gap instead of substituting a wrong-but-registered locus; name-review hard-caps source-fidelity (strike_point↛divertor_target); the --reset-to connector fixed (was dead code after the pool-path early-return). ISN 1585 tests green; imas-codex green modulo 4 pre-existing. Deploy: bench 100% grammar-valid on DSv4; dd-sourced affected bulk recomposed canonical (parallel_current_density_due_to_ohmic_current_drive, time_due_to_resistive_diffusion, …); the strike_point→inner_divertor_target drift removed. Remaining (f-cqo-tail): ~67-name sourceless/derived non-canonical tail + strike_point recompose + source-fidelity re-review + sn release + ISNC grammar-tab confirm. imas-codex pinned ISN 047ec85.
✓ review + time-base + tooling 2026-07-01
Full accepted-name review + audit (2480 live names). Catalogue is semantically sound: the due_to_ mechanism cleanup holds (proper nouns), flagged "duplicated tokens" (deuterium_deuterium, beam_beam_fusion) are legitimate reaction-pairs. Findings: (a) time-base — the lead's time_due_to_<process> concern confirmed; Model A locked (named timescales are atomic bases; bare time = coordinate, time_due_to rejected). Landed ISN c32004a: validator guard + resistive_diffusion_time/exposure_time/rise_time/fall_time bases (resolves 4 vocab gaps); 1603 tests green. (b) 3 malformed at_along/of_along loci → standalone along-connector change (deferred, not a release blocker). (c) 83-name tail (41 parse-fail + 42 non-canonical), all quarantined.
imas-codex re-pinned to ISN c32004a (commit e983c58e) + generate-prompt time guidance. §5-tail regen blocker DIAGNOSED (root cause of the repeated stall): the --reset-to tooling keys on legacy source/pipeline_status props that are null on the current name_stage/validation_status staging model — so neither reset variant selects the 84 quarantined nodes (only 3 drafted cleared). CLI --include-accepted threaded into --reset-to (commit bd135e47) — necessary but not sufficient; the selection must align to validation_status, and the 4 sourceless names must be renamed (not cleared) to avoid loss. Release is SAFE now (export excludes quarantined → 0 leak).
✓ §5 tail CLOSED — released v0.2.0rc55 2026-07-02
Root-caused and removed the legacy pipeline blocker instead of working around it: reset_standard_names/clear_standard_names selected on a pipeline_status property that was null/stale on every current node — the staging model had moved to name_stage/validation_status without the selection logic following, so every prior regen attempt silently cleared ~0–3 nodes. Rewrote both functions onto name_stage/validation_status with an explicit include_accepted gate, deleted 3 dead functions, and stripped the legacy properties graph-wide (2466 nodes). Purge landed across 20 imas-codex files + the LinkML schema (commit 099123a6) plus a 33-file test migration (e9b5ef12); full suite green (3885 passed in tests/standard_names/).
Tail regenerated: the 4 sourceless non-canonical roots renamed to canonical form (energy_implicit_source_rate, kinetic_energy_incident_flux_at_wall, perturbed_gyrocenter_pressure, perturbed_particle_pressure); the 79 sourced quarantined nodes cleared and regenerated on DSv4 across 5 drain passes (avg reviewer score 0.966, min 0.856 on the newly-accepted sample). Two second-order bugs found and fixed mid-regen: (1) derive_edges seeded non-canonical derived-parent ids for bare-prefix-transformation qualifiers — every derived-parent endpoint now round-trips through parse_standard_name; (2) the canonical-locus audit flagged secondary_separatrix as a plasma_boundary synonym — a geometrically-qualified separatrix is a distinct surface, fixed in both audits.py and its duplicate in workers.py (consolidation tracked as follow-on debt). The strike_point→*_divertor_target source-fidelity drift (4 names) was cleared and recomposed correctly.
Final numbers: 0 live names fail parse, 0 live quarantined names, 2119 names exported. Released v0.2.0rc55 → origin (imas-codex commit 03bcc84e). Remaining backlog (non-blocking): §3v vocab-disjointness guard + §3 deep per-token review (f-cqo-003/f-cqo-004), the along-connector hardening (f-cqo-along), and the audits/workers duplication consolidation (f-cqo-dupe-consolidate).
✓ royal adjective order — released v0.2.0rc57 2026-07-02
Lead's second challenge (ion_state_implicit…: should implicit lead the subject too?) grounded the segment order in English adjective-order research. Forsyth's royal order (opinion–size–age–shape–colour–origin–material–purpose–NOUN) + Scontras et al. 2017 (subjectivity hierarchy: less subjective composes closer to the noun; noun adjuncts innermost) map onto the grammar as: base=noun · channel pair=purpose adjuncts · subject=material/type adjunct · orbit/population=species classifiers · zone=origin/location · scoping qualifiers=classifying adjectives · aggregation=quantifier · component=projection (operator-like, exempt with rationale). ISN f4773f1: new scoping_qualifiers.yml per-token binding-class verdict. The current set is implicit, explicit, effective, incident, fluctuating, saturated, linear, and stray; these route to the qualifier segment and lead the species phrase. Every other qualifier token is kind-forming and stays glued to the base (ion_atomic_mass, argon_prefill_count, breakdown_current, and the recipient-possessives electron_deposited_power/plasma_deposited_power). Segment order: component → aggregation → qualifier → zone → orbit → population → subject → device → channel_qualifier → channel → base; zone moved outer of the species block so core_electron_temperature is canonical again. Suite 1604 green; SPA 159/159.
Migration = pure pipeline acid test (user directive; zero renames): all 11 affected names dd-sourced → quarantine-cleared → regenerated on DSv4 (imas-codex 10f138b8 pin+prompt). The B2 grammar retry converged live (total_ion_energy_implicit_source_rate → total_implicit_ion_energy_source_rate); the reviewer improved on predictions (neutron_lower_energy → lower_bound_energy_of_neutron_detector). Final census: 2392 live, 0 reorder, 0 parse-fail, 0 quarantined. Released v0.2.0rc57 (ISNC 49268cc7, 2091 names).
✓ §3 pulled forward: qualifier-before-channel — released v0.2.0rc56 2026-07-02
Lead's review of the rc55 renames caught a real grammar defect, not a style nit: the model had no first-class qualifier segment, so a residual qualifier was glued onto physical_base and trapped INNER of the channel — producing backwards spellings (energy_implicit_source_rate, kinetic_energy_incident_flux). A refined qualifier scopes over the whole channel phrase (English adjective order: "the implicit part of the energy source rate"), so it must lead. ISN 216770c: first-class multi-token qualifier segment declared in specification.yml at zone → qualifier → channel_qualifier → channel → base — codegen/SEGMENT_ORDER/LLM-context/SPA all derive it, and the codegen's legacy inject-qualifier-last hack is removed. Routing is channel-conditional: without a channel phrase the historical base-glue spelling is unchanged (bootstrap_current_density-class names unaffected). decay_length/decay_time promoted to atomic physical_bases (λ_q + named-timescale per the locked time-base decision); the fully-subsumed decay qualifier retired. ISN 1603 green; SPA 159/159.
imas-codex a1e02071: ISN pin bump; compose prompt gains the qualifier(s)-before-channel rule with ✓/✗ examples; segment-edge fixture de-staled (derives SEGMENT_ORDER indices from the installed grammar). Full codex SN suite exit 0. Migration (through the pipeline, no hand edits): 28 affected — 7 sourceless renamed via deterministic rename_cascade (implicit_energy_source_rate, effective_momentum_convection_velocity, deposited_plasma_power, …), 21 dd-sourced quarantine-cleared + regenerated on DSv4 with review scoring; the reviewer sometimes chose better formulations outright (total_radiated_power, incident_power_at_wall, parallel_ion_momentum_convection_velocity; avg 0.921 on the fresh batch). Final census: 2399 live names, 0 reorder, 0 parse-fail, 0 live quarantined. Released v0.2.0rc56 (ISNC 3524f545, 2105 names). Also landed: canonical-locus check consolidated into audits.py (f-cqo-dupe-consolidate resolved, codex e7eb86fc).
✓ scaffold §2 — The decomposition: route every token to its true segment
The output of this section is (a) the canonical segment order above written into specification.yml,
(b) a new zone segment, and (c) a per-token disposition for all 161 qualifiers.yml
tokens. Dispositions (corpus-derived, refined by physics in §3):
→ operators (remove from qualifiers.yml): the 8 literal dupes, plus reconcile the
near-dupes (time_averaged→time_average, flux_surface, average/mean,
current_weighted). Parser already accepts operator tokens via its acceptance union, so removal is
round-trip-safe; they route to the transformation/decomposition slot. Demonstrably
correct (user, 2026-06-30).
→ BASE / coordinate (token-by-token, no blanket rule): lexicalised quantities that are distinct
physics (surface_area, vector_potential, angular_momentum,
kinetic_energy) become atomic bases; coordinate uses dissolve into the
<axis>_coordinate_of_<carrier> form — see §6.
→ zone (new ordered segment):core, edge, inner, outer, upper, lower, pedestal,
separatrix, divertor, scrape_off_layer, back/front_surface, wetted — plasma region / geometric
sub-selectors used as prefixes, positioned between subject and qualifier, with a
defined intra-order (so lower_outer_squareness is deterministic).
→ qualifier (keep, ordered): the genuine regime/source/diagnostic modifiers, ordered by the
12 normalized categories from qualifier-vocabulary-categorization.
Many tokens are unused in the live catalogue but physically real (bootstrap, neoclassical, rf, nbi) —
keep; prune only demonstrable mis-files.
§3 curation complete. The canonical enforcement (§3e) and disjointness
guard (§3v, test_vocab_uniqueness.py) shipped with the scaffold and the
rc55–rc59 slices; the guard's ratchet is now at its intentional floor —
qualifiers is disjoint from every other segment except documented dual-roles
(physics_domains, channels-dual energy/momentum, fusion-reactant pairs). The last grammar
residual, the normalized-direction shadow, is resolved: ISN d150b6c
removes normalized_parallel/normalized_perpendicular (field-relative
directions with no normalized variant — the token could only encode the GyroBohm operator),
so normalized_parallel_momentum_flux peels the operator instead of fusing the
projection; the genuine normalized coordinate-frame directions are retained. ISN suite 1822
passed; codegen in sync.
Follow-on (imas-codex 5a08c744): the decomposition_audit that had
flagged 2252 accepted names was retired as obsolete — the base slot is a
controlled vocabulary, so a composer cannot hide structure in physical_base;
the audit was comparing the surface base phrase (with legitimately-glued
kind-forming qualifiers) against the base-token set. Every finding, including the
interim "270 rename queue", was a historic false positive; all were drained (0 remain), no
name renamed. Full record:
§3 landed
(closes f-cqo-normdir-shadow, f-cqo-003, f-cqo-004).
§4 — Template the order into the LLM name-generation prompts
get_grammar_context / _build_canonical_pattern / _build_segment_order_constraint
(grammar/context.py) must surface the full segment order — including the new zone segment
and the refined qualifier ordering — so generating agents (imas-codex sn) emit canonical names first
time. Verify the codex compose prompt renders it. (The LLM has already been encoding binding depth via latent
knowledge — this formalizes it.)
§4b — Wire the canonical order into the catalog SPA grammar bar
Emit the canonical segment order (and per-category rank) in GRAMMAR_VOCAB from
dataset.py::_build_grammar_vocab, and have site/src/views/Grammar.jsx render the
qualifier/zone instances in that order instead of insertion order. Gated on §2/§3e. Round-trip of seeded names must
still hold (a seeded non-canonical name, until migrated in §5, displays in its own order). This is the "ship a
stable grammar builder on the ISNC site" requirement.
§5 — Re-generate the affected names through the pipeline (NOT a mechanical rename)
Decision (2026-06-30): abandon the mechanical rename_cascade migration. A dry-run
showed string-transformed renames produce low-quality names (e.g. current_density_due_to_anomalous —
"due to anomalous what?"; the mechanism is anomalous transport). Names are guidance, and the
right way to lift quality is to re-pass the affected names through imas-codex's existing
generate→review→refine pipeline, which now consumes the finalized ISN grammar context (§4,
get_grammar_context, injected at runtime — zone/channel/channel_qualifier already included) and writes
real descriptions with review scoring.
Procedure: (a) ISN prep — lift the process vocabulary to proper mechanism nouns
(anomalous → anomalous_transport, etc.) and close any vocab gaps the regeneration needs
(ISN is the owner of all grammar/vocab); (b) repoint generation to the local DSv4 model
([tool.imas-codex.sn-compose] api-base → local /v1 endpoint; zero cost); (c)
validate the procedure with sn bench --max-candidates N (generates target names,
checks ISN round-trip + quality) before any graph write; (d) re-pass the affected names via
sn run --focus <paths> (resets to pending + nulls review scores → full generate→review→refine on
DSv4); (e) before/after sample the review scores to confirm the quality lift; (f)
sn release. The §6 radial-coordinate names and the regime/reduction/operator-stack backlog all
re-derive correctly this way rather than via hand-built rename targets.
§5′ runbook — run in the Neo4j-connected, ISN@main-synced environment
Prerequisites (the blockers from a generic shell):
imas-codex on an ISN that contains the grammar — imas-codex pins ISN at v0.8.0rc48 in pyproject.toml + uv.lock (predates this work). Bump the pin to a new ISN RC tag (or ISN@main) containing the grammar, then uv sync. (A bare uv run reverts any ad-hoc editable install back to the rc48 pin — the pin bump is the durable fix.)
Neo4j graph reachable — the live 2194-name graph (active graph profile / SSH tunnel). Not reachable from a generic login shell.
major currently does double duty as a coordinate marker, producing the clunky asymmetric pair
major_radius_of_flux_surface / vertical_coordinate_of_flux_surface. Both are coordinates of
the flux surface and should be symmetric: standardize on the <axis>_coordinate_of_<carrier>
form → radial_coordinate_of_flux_surface + vertical_coordinate_of_flux_surface. The vocab
is currently inconsistent (radial_coordinate exists as a standalone carrier, but vertical
uses axis+coordinate carrier). Maintain the decision that distinguishes the
radius-of-curvature / length scalar (keep radius, minor_radius, larmor_radius)
from the radial coordinate R. This is a wider refactor of radial-coordinate names; it is unblocked by the
grammar landing and shares the §5 migration machinery.
§ Decisions
Where do generic qualifiers sit relative to the named sub-kinds?
Survey decided it: a flat position is provably wrong (normalized wants outermost, major must hug the base). The class is decomposed into ordered segments by binding depth (operator → zone → qualifier → base-bound); 117/134 multi-generic names already obey it. "semantic-binding" generalised to a segment decomposition, not per-token binding metadata.
Do generic qualifiers need a defined order amongst themselves?
The refined qualifier segment orders by the 12 normalized categories already shipped in qualifier_categories.yml; gives a total, semantically-grouped order. Zone tokens get their own intra-order in the zone segment.
How strictly is canonical order enforced?
Strict structure mandated by the user. NonCanonicalNameError already implements reject-with-canonical-form; extend it to the new segment order so a non-canonical name can never be (re)introduced.
Do renamed names keep a deprecation alias?
Early RC, breaking changes expected (user, 2026-06-30). Hard rename via rename_cascade; renamed names re-pass review (null reviewer_score_name) with before/after semantic-quality sampling.
Introduce a new ordered 'zone' segment for region / geometric sub-selectors used as prefixes?
User-selected (2026-06-30). core/edge/inner/outer/upper/lower are neither loci (postfix of_/over_) nor regime qualifiers; they select a plasma region or geometric quadrant in prefix position and need a defined intra-order (lower_outer_squareness).
How are base-bound lexical modifiers (major, surface, vector, angular, kinetic…) represented?
User clarification (2026-06-30): no blanket rule. major as a coordinate must become radial_coordinate (§6), NOT an atomic base; genuine lexicalised quantities (surface_area, vector_potential) do become atomic bases; productive regime modifiers stay qualifiers. Resolved per token in §3 with physics rationale.
What about tokens double-registered in both qualifiers.yml and operators.yml?
User: "the pull of the mis-filled tokens out of the qualifiers vocab is demonstrably correct." normalized/perturbed/volume_averaged/line_integrated/accumulated/gyroaveraged/maximum/minimum are operators; the parser still accepts them via its operator acceptance union, so removal is round-trip-safe and drops them from the qualifier_categories invariant.
How are radial-coordinate names normalized (major_radius vs radial_coordinate)?
User (2026-06-30): major_radius_of_flux_surface / vertical_coordinate_of_flux_surface is a clunky asymmetric split; use radial_coordinate_of_… for both. Maintain the curvature-radius vs radial-coordinate distinction (radius stays a length scalar). Wider radial-coordinate refactor planned in §6.
§ Followups
§1–§2 — Full order audit + lock the canonical order
Produce the complete per-name audit and lock the canonical-order decisions.
Superseded by the Option B redesign. The audit ran (1364 names, 100% parse); see §1 evidence and the disposition funnel figure.
Audit + design landed. Survey proved the flat "generic" class is a catch-all (8 operator dupes; 0 named-sub-kind violations; 117/134 multi-generic names obey binding depth). Direction pivoted to Option B (decompose into ordered segments + zone segment + pull mis-filed tokens). 8 decisions locked: semantic-binding decomposition, by-category-rank intra-order, compose-canonicalizes+validator-rejects, hard-rename+re-review, new zone segment, token-by-token base-bound disposition, operator-dupe removal, radial-coordinate refactor (§6). Next: implement the ISN grammar core (§2/§3e).
§2/§3e — Implement the ISN grammar core: zone segment, remove operator dupes, enforce canonical order
Land the structural grammar change in ISN: add the ordered zone segment, remove the 8 operator-dupe tokens from qualifiers.yml (+ qualifier_categories.yml + the coverage test), wire compose() to emit canonical order and the validator to reject non-canonical order. Regenerate codegen types, keep the full suite + 1364/1364 round-trip green. Gates §3 (deep token review), §4/§4b (context + SPA), and §5 (migration).
Project: imas-standard-names
Plan: canonical-qualifier-order (http://localhost:8765/imas-standard-names/canonical-qualifier-order.html)
Section: §2 + §3e
Tier: opus
Context
The "generic qualifier" class is being decomposed into ordered segments. This
section lands the grammar CORE: a new ordered `zone` segment, removal of the 8
operator-dupe tokens from qualifiers.yml, and canonical-order enforcement
(compose canonicalizes; validator rejects). Honour the locked decisions
(zone-segment, operator-dupe-removal, enforcement, generic-inter-order).
State to read (code/files, not the plan)
imas_standard_names/grammar/specification.yml (segments, canonical_pattern)
imas_standard_names/grammar/parser.py (_match_base_with_qualifiers, load_default_vocabularies)
imas_standard_names/grammar/model.py (_ir_to_model_dict, StandardName, _BARE_PREFIX_TRANSFORMATIONS, NonCanonicalNameError)
imas_standard_names/grammar/render.py (render_qualifiers, compose)
imas_standard_names/grammar/ir.py (StandardNameIR, Qualifier)
imas_standard_names/grammar/context.py (canonical pattern builders)
imas_standard_names/grammar/vocabularies/{qualifiers,qualifier_categories,operators,regions}.yml
imas_standard_names/grammar_codegen/generate.py + tests/grammar/test_codegen_drift.py
tests/grammar/test_grammar_round_trip.py, tests/grammar/test_qualifier_categories.py
Scope locks / constraints
- Add a `zone` segment (new vocabulary zones.yml) positioned between subject and
qualifier; single-or-multi token with a defined intra-order. Model gets a
`zone` field; codegen emits the Zone enum.
- Remove from qualifiers.yml AND qualifier_categories.yml: normalized, perturbed,
volume_averaged, line_integrated, accumulated, gyroaveraged, maximum, minimum.
They remain operators (operators.yml) and still parse via the acceptance union.
- compose() must SORT segments into canonical order; validator (parse_standard_name)
rejects non-canonical via NonCanonicalNameError with the canonical form.
- Do NOT regress parse()/compose round-trip: build_site_dataset over the ISNC
catalog must stay 1364/1364 BEFORE migration (names not yet migrated must still
parse; canonical-order rejection applies at validate, round-trip at compose).
Re-run the round-trip harness before/after.
- Regenerate grammar/types.py via build-grammar; test_codegen_drift must pass.
- Defer the per-token base-bound/coordinate verdicts to §3 and the radial-coordinate
refactor to §6 — this section is the structural scaffold + the unambiguous dupe removal.
Done-when
1. zone segment + Zone enum landed; specification.yml canonical_pattern updated.
2. 8 operator dupes removed from qualifiers.yml + qualifier_categories.yml; coverage test green.
3. compose canonicalizes the new order; validator rejects non-canonical (tests added).
4. Engine self-consistency: for every catalogue name parse() ACCEPTS,
compose(parse(name).ir)==name (100% of accepted names; the accepted COUNT may
drop — that is expected). Full suite green (fix only regressions you introduce);
codegen drift clean. REPORT (do not gate on) the migration backlog: names that no
longer parse + names strict parse_standard_name() flags non-canonical, with examples.
5. Followups written for §3 (deep token review) and §4/§4b + this one resolved.
Landed commit ef4db62; verified independently. Ordered zone segment (13 tokens, generated Zone enum) between device and base; 8 operator dupes removed from qualifiers.yml + qualifier_categories.yml (gyroaveraged left postfix-only per the corrected gate, not hacked back as a prefix); compose() canonicalizes, parse_standard_name rejects non-canonical with the canonical form. Engine self-consistency 1357/1357 accepted names (0 inverse bugs); suite 1572 passed / 0 failed; codegen in sync. Migration backlog (expected, not gated): 7 parse-fail (gyroaveraged mid-prefix) + 6 reorder. Edge cases for §3: subject sorts before zone (core_electron_temperature→electron_core_temperature); major/minor remain base-bound qualifiers sorting after zone (feeds §6). Next: §3 deep per-token review.
§3v — Add a vocabulary-disjointness guard test
PARTIALLY DONE: tests/grammar/test_vocab_uniqueness.py already lints cross-segment collisions; the qualifiers∩operators AND qualifiers∩locus_registry allowlist entries were REMOVED (both intersections now empty — operator dupes in Phase 2; flux_surface/inlet/outlet loci in the first §3 slice — so the guard actively prevents re-introduction). Remaining: ratchet out the rest as §3 resolves each mis-file — qualifiers∩{physical_bases (energy/momentum), subjects (particle), processes (anomalous/ohmic/…14), physics_domains (equilibrium/mhd/nbi), generic_physical_bases (energy)}. Each removal = one mis-file fixed in §3. Intentional overlaps stay (components∩coordinate_axes; zones∩regions/locus; generic_bases⊂bases).
Project: imas-standard-names
Plan: canonical-qualifier-order
Section: §3v
Tier: sonnet
Context
A grammar redesign exposed 8 tokens double-registered in qualifiers.yml AND
operators.yml. Add a permanent guard so this class of bug can't recur. Run AFTER
Phase 2 (f-cqo-002) has removed the operator dupes and added zones.yml.
State to read (code/files)
imas_standard_names/grammar/vocabularies/*.yml (all segment vocab files)
imas_standard_names/grammar/vocab_loaders.py (loaders; canonical token sets)
tests/grammar/test_qualifier_categories.py (sibling invariant test, for style)
Scope locks / constraints
- New test file only: tests/grammar/test_vocab_disjoint.py. Do not edit vocab files
to make it pass — if it finds an UNexpected intersection, that is a real finding:
report it (it may be a mis-file for the §3 review), do not silence it.
- Assert pairwise-disjointness of the SEGMENT-ROLE token sets: qualifiers, subjects,
aggregations, populations, orbits, zones, operators, components, coordinate_axes,
regions, processes, physical_bases, geometry_carriers, binary_operators.
- ALLOWLIST (intentional, documented in the test): components ∩ coordinate_axes
(shared directional vocab by design); objects/positions both alias locus_registry.
Every other intersection fails the test. Keep the allowlist minimal + commented with
rationale so it stays a ratchet.
Done-when
1. test_vocab_disjoint.py added and GREEN against the post-Phase-2 vocab (or reporting a
documented, plan-tracked exception if an intentional overlap remains).
2. Any UNexpected intersection found is reported into the plan as a §3 mis-file finding.
3. Full suite green; this followup resolved.
COMPLETE. tests/grammar/test_vocab_uniqueness.py is the permanent cross-segment disjointness guard (green, in the 1822-passed suite). The ratchet reached its intentional floor: qualifiers ∩ {operators, physical_bases, generic_physical_bases, subjects(particle/state), processes, locus_registry} are ALL now empty (mis-filed tokens pulled to their true segments across §2 dupe removal + the rc55–rc59 curation). Remaining allowlist entries are documented-intentional dual-roles, not mis-files: components∩coordinate_axes (shared directional vocab), channels∩physical_bases/generic_physical_bases (energy/momentum channels-dual design), zones∩regions/locus_registry (prefix zone vs postfix locus), generic_bases⊂bases (subset), the 3 fusion-reactant qualifiers∩subjects pairs, normalizing_qualifiers⊂subjects, and qualifiers∩physics_domains (equilibrium/mhd/nbi — genuine modifier that also names a domain tag, same benign dual use as physics_domains↔tags). The guard actively blocks re-introduction. (Implemented as an extension of the existing test_vocab_uniqueness.py rather than a new test_vocab_disjoint.py file — the guard exists and is enforced.)
§3 — Deep per-token review + segment coverage widening
The structural scaffold is in (zone segment, dupe removal, enforcement). Now the curation pass: an authoritative per-token verdict for the remaining qualifiers.yml tokens (and the cross-segment mis-files the disjointness scan surfaced), with physics rationale. Resolve base-bound tokens token-by-token (atomic-base vs coordinate-carrier vs qualifier), reconcile qualifier∩processes (regime qualifier vs due_to_ process), qualifier∩physical_bases (energy/momentum), qualifier∩subjects (particle/state). Each resolution ratchets a §3v allowlist entry out. Needs the lead's physics input on edge cases.
Project: imas-standard-names
Plan: canonical-qualifier-order (http://localhost:8765/imas-standard-names/canonical-qualifier-order.html)
Section: §3 (+ feeds §3v, §6)
Tier: opus
Context
Phase 2 (ef4db62) landed the zone segment + operator-dupe removal + canonical-order
enforcement. This section is the deep per-token curation: route every remaining
qualifiers.yml token to its true role and widen other segments where the
decomposition exposed a gap. Honour the locked decisions (base-bound-disposition=
token-by-token; radial-coordinate-refactor=§6; current names are NOT the oracle —
breakage is acceptable migration backlog).
Cross-segment mis-files to adjudicate token-by-token (from the disjointness scan)
- qualifiers ∩ processes (14): anomalous, bootstrap, collisional, convection,
e_cross_b_drift, fusion, heating, inertial, neoclassical, non_inductive, ohmic,
resistive, turbulent, wave_driven — regime qualifier (X_transport) vs due_to_ process?
- qualifiers ∩ physical_bases: energy, momentum (base vs transport-channel qualifier)
- qualifiers ∩ subjects: particle, state
- qualifiers ∩ physics_domains: equilibrium, mhd, nbi
- qualifiers ∩ locus_registry: flux_surface, inlet, outlet
- base-bound lexical tokens: which become atomic bases (surface_area, vector_potential,
angular_momentum, kinetic_energy) vs stay qualifiers; major/minor feed §6.
Constraints
- Produce an authoritative per-token verdict table (committed) with physics rationale.
- For each mis-file resolved, REMOVE its allowlist entry in test_vocab_uniqueness.py
(ratchet) and keep the suite green.
- Do NOT preserve non-canonical names; record renames as §5 migration backlog.
- Surface edge cases needing the lead's physics judgement; do not guess on ambiguous ones.
Done-when
1. Per-token verdict table committed; vocab files updated; codegen regenerated.
2. Disjointness allowlist ratcheted down (entries removed for each resolved mis-file).
3. Full suite green; engine self-consistency holds for accepted names.
4. Followups written for §4/§4b (context+SPA) and §5/§6 (migration) + this resolved.
§3 curation COMPLETE. The per-token verdicts landed incrementally, each recorded in the vocab file it touched: base-bound/compound-base rule + verdicts in physical_bases.yml (f-cqo-area-quals: surface/spectral/cross_sectional splits; channels-dual energy/momentum kept); scoping-vs-kind-forming binding classes in scoping_qualifiers.yml (f-cqo-royal-order, grounded in Forsyth/Scontras adjective-order research); regime-qualifier-vs-due_to_-process curation (process vocab 97→89, qualifiers∩processes now empty); qualifier-segment-outer-of-channel (f-cqo-qualifier-flip). The enumerated cross-segment mis-files (qualifiers∩processes/physical_bases/subjects/locus_registry) are all resolved; qualifiers∩physics_domains is documented-intentional. The last open grammar residual of the components-vs-operators class — the normalized_ shadow — is now resolved (f-cqo-normdir-shadow, ISN d150b6c). No un-adjudicated cross-segment mis-file remains (the §3v guard proves it). FOLLOW-ON (not §3 grammar; staged for lead review in model-selection-and-global-refine · f-decomp-reparse-triage): the decomposition_audit substring-scans raw names and left 2252 stale findings on accepted names; the new $0 sn decomp-triage (imas-codex e3d20e11) buckets them 1809 drain / 173 suppress (1982 clearable free) / 270 rename queue (~$21.60), 0 parse-fail. Full record: docs/archive/canonical-qualifier-order-s3-landed.html.
§5 — Deploy the regeneration to the ISNC graph (blocked in-session by the auto-mode permission classifier)
ALL upstream work is done + verified (grammar+vocab stable on main; generation 100% canonical on DSv4; prompt hardened imas-codex 42d7638e; SPA grammar tab wired dd4c846). The catalogue regeneration was NOT run: the auto-mode permission classifier repeatedly denied every production Neo4j write (rederive, mark-quarantined, sn run) — new this session, heightened after a scope-alarm. Needs a permission rule (or user-run) then executes turnkey. Affected set computed: 66 live names (7 parse-fail + 59 reorder) + 93 pre-existing quarantined (vocab-gaps the grammar update resolves — sweeping them via --retry-quarantined is intended). Chains verified INTACT (rederive touches only segment columns/edges).
Project: imas-codex (+ imas-standard-names as the grammar source)
Task: deploy the canonical-qualifier-order regeneration to the live ISNC Neo4j graph.
PREREQUISITE — grant graph-write permission (the whole prior session was blocked here):
add a Bash allow-rule for ~/Code/imas-codex `uv run`/`sn`/GraphClient writes, or run the
commands via the `!` prefix, or switch to a permission mode that allows production writes.
ENVIRONMENT (all confirmed reachable in the prior session):
- Neo4j: bolt://98dci4-clu-3052:7687 (auto-tunnel via active graph profile). ~3476 nodes.
- DSv4: http://98dci4-gpu-0003:18800/v1 (model deepseek-v4-flash, empty API key). Up.
- imas-codex venv ships a STALE ISN (rc48). Install the grammar-bearing ISN and use
--no-sync for ALL commands (bare `uv run` reverts to rc48):
cd ~/Code/imas-codex && uv pip install ~/Code/imas-standard-names
Durable fix: bump imas-codex pyproject+uv.lock off `v0.8.0rc48` to a grammar-bearing
ISN RC/main (consider tagging a new ISN RC first).
Verify grammar active:
uv run --no-sync python -c "from imas_standard_names.grammar.model_types import Process as P,Channel,Zone,ChannelQualifier; v=[x.value for x in P]; print('anomalous_transport' in v,'bootstrap_current_drive' in v)"
REPOINT generation+refine to DSv4 (zero cost):
export IMAS_CODEX_SN_COMPOSE_MODEL=hosted_vllm/deepseek-v4-flash IMAS_CODEX_SN_COMPOSE_API_BASE=http://98dci4-gpu-0003:18800/v1 AMBIX_API_KEY=
export IMAS_CODEX_SN_REFINE_MODEL=hosted_vllm/deepseek-v4-flash IMAS_CODEX_SN_REFINE_API_BASE=http://98dci4-gpu-0003:18800/v1
STEPS (all via `uv run --no-sync`):
1. Re-derive grammar segments (idempotent; already run once; flags grammar_parse_fallback):
python -c "from imas_codex.standard_names.graph_ops import rederive_structural_edges as r; print(r())"
2. Compute affected (read) + mark them quarantined so --retry-quarantined includes them.
Affected = live (name_stage in accepted/reviewed/pending) names that are parse-fail
(grammar_parse_fallback) OR non-canonical (parse_standard_name raises NonCanonicalNameError).
(~66; recompute in-process — sn.id is the name; do NOT rely on scratchpad/affected_ids.json.)
SET sn.validation_status='quarantined' for those ids.
3. Regenerate (66 + 93 legacy quarantined) via generate->review->refine on DSv4, NON-DESTRUCTIVE:
imas-codex sn run --retry-quarantined --reset-to drafted -c 50 --min-score 0.80
(--reset-to drafted keeps nodes+docs; --reset-to extracted deletes+recomposes from source —
only for sourced names; 6 affected are derived/sourceless, keep them on drafted.)
4. VERIFY: re-derive; assert grammar_parse_fallback -> 0 and the affected ids round-trip
canonical (compose(parse_standard_name(id))==id); sample before/after reviewer_score_name
for the quality lift. Iterate prompt/vocab in ISN if any score is low (regenerate, don't
hand-fix); keep changes general (wide application), not over-fit.
5. RELEASE: imas-codex sn release.
AFTER DEPLOY (the goal's "then continue with all pending tasks"):
- Confirm the ISNC published grammar tab reflects the new segments (SPA rebuilds from ISN@main).
- Set this plan status=shipped, impl=1.0; resolve this followup with the before/after numbers.
SAFETY: chains verified intact this session (rederive never writes reviewer_score/docs/chain/
name_stage). Keep the regen scoped; don't mass-mutate beyond the affected+quarantined set.
§5 deploy substantially landed (this session), plus three grammar-hardening threads the deploy surfaced. GRAMMAR (ISN, all committed + 1585 tests green): (a) durable imas-codex ISN pin off rc48 → grammar commit (no more --no-sync fragility); (b) grammar_parse_fallback REMOVED — parse_standard_name via validate_worker is the single grammar gate; (c) process-vocab CURATED (97→89: dropped superseded bare regimes collisional/inertial/resistive/non_inductive_current/_drive/wave_driven + 5 literal dupes + weak generics drift/driven); (d) segment_edge_specs empty/multi-token '()' bug fixed; (e) §6 ENFORCED — major_radius+locus rejected → radial_coordinate_of_; (f) COMPOSITIONAL LOCUS QUALIFIERS — qualifiable feature + ordered inner/outer/upper/lower/primary/secondary replaces 11 flat tokens, so radial_coordinate_of_upper_outer_strike_point composes for free (scales to snowflake/X/super-X). IMAS-CODEX (committed): compose hardened to flag vocab_gap not substitute a wrong-but-registered locus; name-review hard-caps source-fidelity (strike_point↛divertor_target); --reset-to connector FIXED (was dead code after the pool-path early-return → silent no-op); benchmark enum + all grammar-caused test fallout fixed; suite green except 4 pre-existing/orthogonal failures. DEPLOY: bench 100% grammar-valid on DSv4; the dd-sourced affected bulk recomposed correctly (parallel_current_density_due_to_ohmic_current_drive / _due_to_anomalous_transport, time_due_to_resistive_diffusion, radial_coordinate_of_… verified); the strike_point→inner_divertor_target drift removed. REMAINING (see f-cqo-tail): ~67-name long tail (mostly sourceless/derived non-canonical names that don't recompose from a DD source) + strike_point sources pending recompose to radial_coordinate_of_inner_strike_point + final source-fidelity re-review + sn release + ISNC grammar-tab confirm. Env: Neo4j bolt://98dci4-clu-3052:7687, DSv4 http://98dci4-gpu-0003:18800/v1; imas-codex pinned ISN 047ec85.
§5 tail — regenerate the sourceless/derived non-canonical remainder, source-fidelity re-review, then sn release
The dd-sourced affected bulk recomposed correctly; ~67 live names still fail parse — ~11 dd-sourced (recompose via another --reset-to extracted pass) and ~56 SOURCELESS/DERIVED (no DD source to recompose from: derived names, binary-op/magnitude names, and non-canonical reorderables like implicit_energy_source_rate→energy_implicit_source_rate, launched_neutral_beam_power→neutral_beam_launched_power). These need per-case handling (re-derivation or targeted rename), not a blanket clear. Also: the strike_point_inner_r/outer_r sources are pending recompose to radial_coordinate_of_inner/outer_strike_point (now grammar-expressible); run a source-fidelity re-review (hardened reviewer) over the recomposed set to catch any remaining locus/feature drift; then sn release + confirm the ISNC published grammar tab. Do NOT release until the tail is clean (the single-pipeline gate holds quarantined names back, but valid-but-wrong-feature names like the strike_point drift are not auto-flagged).
Project: imas-codex (+ imas-standard-names grammar @ pinned 047ec85)
Task: finish the canonical-qualifier-order §5 regeneration tail, then release.
ENV: Neo4j bolt://98dci4-clu-3052:7687 (auto-tunnel); DSv4 http://98dci4-gpu-0003:18800/v1 (deepseek-v4-flash, empty key). imas-codex pins ISN 047ec85 (grammar final). Use `uv run --no-sync`; DSv4 repoint: IMAS_CODEX_SN_{COMPOSE,REFINE}_MODEL=hosted_vllm/deepseek-v4-flash, _API_BASE=http://98dci4-gpu-0003:18800/v1, AMBIX_API_KEY= .
The --reset-to connector is FIXED and the grammar/prompts are hardened (compositional loci, §6, source-fidelity review, gap-flagging compose). The regime/channel/major_radius bulk is done.
STEPS:
1. Recompute the affected set in-process: live names (name_stage accepted/reviewed/pending/named/drafted) where compose(parse_standard_name(id))!=id OR parse raises. Split by whether an IMASNode-[:HAS_STANDARD_NAME]->sn dd source exists.
2. DD-SOURCED remainder (~11): set validation_status='quarantined'; run `imas-codex sn run --retry-quarantined --reset-to extracted --skip-clear-gate -c 50 --names-only` (clear re-seeds sources to 'extracted' via Step E; generate recomposes with the hardened prompt). Verify each now round-trips canonical.
3. SOURCELESS/DERIVED remainder (~56): these have no DD source to recompose from. For DERIVED names (magnitude_of_, binary-op, projection), re-run rederive_structural_edges + the derivation pool so they re-derive from their (now-canonical) parents. For genuine non-canonical reorderables with no source, either seed a source or record a targeted rename; do NOT leave them silently quarantined.
4. SOURCE-FIDELITY re-review: run the (hardened) name reviewer over the session-recomposed names; the source-fidelity rule flags any name whose locus/feature disagrees with its DD source path (e.g. a *_divertor_target for a strike_point path). Quarantine + regenerate the flagged ones.
5. Confirm 0 live names fail parse (or only documented, sourced vocab_gaps).
6. RELEASE: `imas-codex sn release`. Confirm the ISNC published grammar tab reflects the new segments (SPA rebuilds from ISN@main).
7. Set this plan status=shipped, impl=1.0; resolve this followup with final before/after round-trip + review-score numbers.
§5 tail CLOSED — released v0.2.0rc55 (imas-codex commit 03bcc84e, pushed to origin). Legacy pipeline blocker fixed at the root cause: the graph's reset_standard_names/clear_standard_names selection keyed on a `pipeline_status` property that was null/stale on every current node (staging model moved to name_stage/validation_status) — every prior regen attempt silently selected ~0-3 nodes. Rewrote both functions onto name_stage/validation_status with an explicit include_accepted gate; deleted 3 dead functions (get_validated_standard_names, update_review_status, get_enrichment_candidates) and stripped the pipeline_status/status/source properties graph-wide (2466 nodes). Purge landed across 20 imas-codex files + schema (commit 099123a6) plus a 33-file test migration (e9b5ef12) — full suite green (3885 passed in tests/standard_names/); 5 unrelated dd_extract_breakdown failures are cross-directory test-order pollution, reproduced independent of this change. Tail regen: renamed the 4 sourceless non-canonical roots to canonical form (energy_implicit_source_rate, kinetic_energy_incident_flux_at_wall, perturbed_gyrocenter_pressure, perturbed_particle_pressure — rename_cascade, include_accepted=True); cleared the 79 sourced quarantined nodes and regenerated on DSv4 across 5 drain passes (avg reviewer_score_name 0.966, min 0.856 on the newly-accepted sample). Found + fixed a second-order bug during regen: derive_edges (imas-codex derivation.py) seeded non-canonical derived-parent ids for bare-prefix-transformation qualifiers (normalized_gyrocenter_perturbed_pressure → wrongly peeled to gyrocenter_perturbed_pressure instead of perturbed_gyrocenter_pressure) — every derived-parent endpoint now round-trips through parse_standard_name/NonCanonicalNameError.canonical_form. Found + fixed the strike_point→*_divertor_target source-fidelity drift (4 names cleared + recomposed correctly: radial/vertical_coordinate_of_{inner,outer}_strike_point etc.) and a canonical-locus audit false-positive that flagged secondary_separatrix as a plasma_boundary synonym (a geometrically-qualified separatrix is a distinct surface, not the boundary) — fixed in both imas-codex/audits.py and its duplicate in workers.py (consolidation left as follow-on debt). Final: 0 live names fail parse, 0 live quarantined names, 2119 names exported. Remaining backlog (not blocking, tracked in f-cqo-003/f-cqo-004): §3v vocab-disjointness guard + §3 deep per-token review of the qualifier vocabulary.
Standalone — harden at_along/of_along as a locus connector
3 malformed at_along/of_along loci were found during the accepted-name review (2026-07-01) and deferred as non-blocking. Make `along` a proper locus connector in the ISN grammar so these compose correctly instead of leaving a dangling preposition pair.
Project: imas-standard-names
Task: make `along` a first-class locus connector in the grammar (parser + render + vocab), fixing the 3 malformed at_along/of_along names surfaced in the 2026-07-01 accepted-name review. Not a release blocker — standalone hardening pass.
Landed (ISN f5f2cda + tools follow-up): `along` is a first-class LocusRelation (peer of of/at/over). The four flat along_* position tokens are deleted; beam, beam_path, pellet_path and a promoted line_of_sight locus take the `along` relation; model gains a Position-typed `path` field rendered as along_; compose tool + overview pattern updated. Round-trip verified (toroidal_angle_along_line_of_sight parses + composes; the old of_along/at_along forms are ParseErrors); 148-line dedicated test module; full ISN suite 1618 green. MIGRATION BACKLOG (imas-codex, needs an ISN pin bump first): 8 catalog names still spell the malformed form — toroidal_angle_of_along_line_of_sight (accepted), electron_density_at_along_pellet_path + toroidal_average_center_of_mass_velocity_at_along_line_of_sight (reviewed), toroidal_angle_of_along_pellet_path (exhausted), 4 superseded — rename/regen via the pipeline on the next deploy pass.
Consolidate the duplicated canonical-locus check (audits.py vs workers.py)
imas-codex carries two independent implementations of the canonical-locus/field-at-region synonym check (standard_names/audits.py::canonical_locus_check, used at compose/review time; standard_names/workers.py::_check_canonical_locus_and_preposition, used by publish.py's release staging-validate gate). Both needed the identical qualifier-sensitive-synonym fix this session (2026-07-02) — a sign they will keep drifting. Consolidate into one implementation (workers.py importing from audits.py, or a shared module) so future rule changes land once.
Project: imas-codex
Task: consolidate imas_codex/standard_names/audits.py::canonical_locus_check and workers.py::_check_canonical_locus_and_preposition into a single implementation. Check the return-string format publish.py expects (workers.py's `WARNING - ...` bare form vs audits.py's `audit:canonical_locus_check: name '...' ...` form) before switching publish.py's caller.
Consolidated (imas-codex e7eb86fc): workers.py's duplicate _check_canonical_locus_and_preposition and its private synonym/field-base maps deleted; both the validate_worker call site and publish.py's staging-validate gate now call audits.canonical_locus_check directly (which also carries the substring-scan fallback and the outboard_midplane_separatrix mapping the duplicate lacked). Suite green.
§3 forward — refined qualifier segment renders OUTER of the channel phrase
User review of the rc55 renames caught a real grammar bug, not a style nit: the model had no first-class qualifier segment, so residual qualifiers were glued onto physical_base and trapped INNER of the channel — producing backwards spellings (energy_implicit_source_rate, kinetic_energy_incident_flux). A refined qualifier scopes over the whole channel phrase (English adjective order), so it must lead.
done — landed and released as rc56
LANDED + RELEASED v0.2.0rc56 (ISNC commit 3524f545, 2105 names). ISN 216770c: first-class multi-token `qualifier` segment declared in specification.yml at zone → qualifier → channel_qualifier → channel → base (codegen/SEGMENT_ORDER/LLM-context/SPA all derive it; the codegen's inject-qualifier-last hack removed); model routes qualifier tokens to the segment only when a channel phrase is present (base-glue spelling unchanged otherwise — bootstrap_current_density-class names unaffected); decay_length/decay_time promoted to atomic physical_bases (lambda_q + named-timescale per the locked time-base decision) and the fully-subsumed decay qualifier retired. ISN suite 1603 green, SPA harness 159/159. imas-codex a1e02071: ISN pin bump, compose-prompt gains the qualifier(s)-before-channel rule with ✓/✗ examples, segment-edge fixture de-staled (derives SEGMENT_ORDER indices instead of hardcoding); full codex SN suite exit 0. MIGRATION (through the pipeline, no hand edits): 28 affected — 7 sourceless renamed via deterministic rename_cascade (implicit_energy_source_rate, effective_momentum_convection_velocity, deposited_plasma_power, effective_particle_energy, ion_effective_momentum_convection_velocity, total_deposited_plasma_power, normalized_effective_particle_energy), 21 dd-sourced quarantine-cleared + regenerated on DSv4 with review scoring (reviewer sometimes chose better formulations outright: total_radiated_power, incident_power_at_wall, parallel_ion_momentum_convection_velocity; avg score 0.921 on the fresh batch). Final census: 2399 live names, 0 reorder, 0 parse-fail, 0 live quarantined. Whole implicit family now qualifier-first (electron_implicit_energy_source_rate, ion_state_implicit_energy_source_rate).
Royal adjective order — scoping qualifiers and zone lead the species block
Lead challenged ion_state_implicit_energy_source_rate: should implicit lead the SUBJECT too? Directed a review of English adjective order (Guardian article on Forsyth's Elements of Eloquence) lensed into the grammar. Verdict: yes — and the channel-conditional routing from rc56 was a proxy for the real per-token distinction.
done — landed and released as rc57
LANDED + RELEASED v0.2.0rc57 (ISNC 49268cc7, 2091 names). RESEARCH GROUNDING: Forsyth's royal order (opinion-size-age-shape-colour-origin-material-purpose-NOUN) + Scontras et al. 2017 (Open Mind): subjectivity predicts adjective order — less subjective/more intrinsic composes closer to the noun; noun adjuncts (species, channel) sit innermost, true adjectives precede them. LENSED MAPPING: base=noun; channel/channel_qualifier=purpose adjuncts (innermost); subject=material/type adjunct; orbit/population=species classifiers; zone=origin/location; scoping qualifiers=classifying adjectives; aggregation=quantifier; component=projection (operator-like, exempt — the 24 parallel_total_current_density names read as 'the parallel component OF the total current density' and projection/summation commute, so left outermost with rationale). ISN f4773f1 introduced the per-token binding-class verdict. The maintained phrase-scoping set is implicit, explicit, effective, incident, fluctuating, saturated, linear, and stray; breakdown is kind-forming, as in breakdown_current. Segment order now component→aggregation→qualifier→zone→orbit→population→subject→device→channel_qualifier→channel→base; zone moved outer of the species block so core_electron_temperature (the universal literature order) is canonical again. Generic-base guard accepts the qualifier segment (incident_power valid); subset guard scoping⊆qualifiers added. ISN 1604 green; SPA 159/159. imas-codex 10f138b8: pin + prompt rewritten with the scoping-vs-kind-forming rule and corrected zone examples. MIGRATION = PURE PIPELINE ACID TEST (user directive; zero renames): all 11 affected names dd-sourced → quarantine-cleared (sources reseeded via Step E) → regenerated on DSv4; the B2 grammar-retry log shows the mechanism converging live (total_ion_energy_implicit_source_rate → total_implicit_ion_energy_source_rate); reviewer improved on predictions again (neutron_lower_energy → lower_bound_energy_of_neutron_detector). Final census: 2392 live, 0 reorder, 0 parse-fail, 0 quarantined; fresh batch avg score 0.891.
§3 slice — compound bases whose modifier is a registered qualifier split to qualifier+base
Lead review (2026-07-03, via the sn-edit plasma-boundary demo) rejected the asymmetry wetted_area (qualifier+area) vs surface_area (compound base): compound bases whose modifier already exists as a qualifier SHADOW the qualifier form via longest-base-match and hog vocabulary. Detailed 77-compound review performed; rule + verdicts below.
done — landed as ISN c3dd83e
LANDED ISN c3dd83e. RULE (recorded in physical_bases.yml): a modifier that selects WHICH quantity (dimensions/semantics of the head unchanged) is a QUALIFIER; compound bases are reserved for lexicalized quantities whose modifier changes what the quantity IS. Never mint a qualifier to split one compound unless it completes an existing family. VERDICTS from the 77-multi-word-base scan: SPLIT surface_area (surface already qualifiers.yml:88 — surface_temperature/surface_roughness), SPLIT spectral_radiance (spectral already qualifiers.yml — spectral_flux + 10 accepted names), SPLIT cross_sectional_area → NEW cross_sectional qualifier (geometry category) completing the area family wetted/effective/surface/cross_sectional. This PARTIALLY REVERSES the base-bound-disposition rationale's surface_area-as-atomic-base example — the token-by-token decision itself stands. KEEP compound: channels-dual energy/momentum + kinetic_energy/internal_energy/photon_energy/angular_momentum (documented channels.yml design, still open in f-cqo-004); dimension-changing/lexicalized current_density(79)/power_density(60)/torque_density(37)/magnetic_flux(24)/mass_density/number_density/solid_angle/safety_factor/larmor_radius/debye_length etc.; one-off descriptive compounds (decay_length, rise_time, tilt_angle…) whose modifiers are NOT qualifiers — splitting would ADD single-use tokens. Zero catalog strings changed (parse IR only); ISN 1618 py + 159 JS round-trip green; imas-codex pin b1f901b8; 10 graph names re-stamped physical_base (surface_area→area, spectral_radiance→radiance, cross_sectional_area→area).
§3 — normalized_<direction> components shadow the normalized operator (longest-match ambiguity)
Found during the rc59 unit-integrity RCA: the components.yml 'normalized spatial directions' family (normalized_radial/vertical/parallel/perpendicular/toroidal/poloidal) SHADOWS operator normalized + component via longest-match — normalized_parallel_momentum_flux_due_to_X parses as component=normalized_parallel (a direction along the normalized-parallel axis), NOT operator normalized applied to the parallel component, silently changing the meaning of any GyroBohm-normalized projected quantity. Three freshly-accepted GK names (normalized_parallel/perpendicular_momentum_flux_due_to_perturbed_parallel_*) carry the fused parse today; the unit-suspicion validator was patched to exempt them (ISN 1cda724) but the AMBIGUITY itself is unresolved. Adjudicate in §3: either the parser prefers the operator split when the residue parses (breaking any genuine normalized-direction component names), or normalized-direction components get renamed tokens that don't collide with the operator (e.g. rho_radial), or the fused reading is declared canonical and the GK names need a different normalized marker.
Project: imas-standard-names
Plan: canonical-qualifier-order
Section: §3 (vocabulary curation — components vs operators)
Tier: opus
Context
components.yml lines 39–45 define normalized_<direction> tokens ('component along normalized X direction', D.3 review §5). Longest-match makes them shadow operator normalized + component <direction>. Live collisions: normalized_parallel_momentum_flux_due_to_perturbed_parallel_vector_potential, normalized_perpendicular_momentum_flux_due_to_perturbed_parallel_{magnetic_field,vector_potential}, normalized_perpendicular_momentum_flux (all accepted; all GyroBohm-normalized GK quantities whose INTENT is the operator reading).
Work
1. Inventory genuine uses of normalized_<direction> AS components (flux-coordinate directions) in catalog + DD; if zero, consider renaming the tokens (e.g. flux-coordinate-specific spellings) to free the operator reading.
2. Decide the canonical parse for normalized_<dir>_<base> and enforce (parser preference or vocab rename); update the unit-suspicion exemption in validation/semantic.py accordingly (ISN 1cda724 added a component-prefix exemption as a stopgap).
3. Re-round-trip the catalog; migrate any affected names through the pipeline.
Done-when: ambiguity resolved by design (not exemption); collisions re-parsed or renamed; suites green; followup resolved.
RESOLVED by design (ISN d150b6c). normalized_parallel/normalized_perpendicular removed from components.yml AND coordinate_axes.yml: parallel/perpendicular are field-relative directions (along/across B) with no distinct normalized-direction variant, so a leading `normalized` on them could only ever encode the GyroBohm-normalization operator. The genuine normalized coordinate-frame directions (normalized_radial→ρ, normalized_vertical, normalized_toroidal→φ, normalized_poloidal→θ) are retained. Verified: normalized_parallel_momentum_flux now peels the operator (projection=parallel, normalized in the qualifier/operator set) instead of fusing to projection=normalized_parallel; normalized_radial_magnetic_field unchanged. The 4 affected GyroBohm names re-parse to canonical _normalized_ form (component leads the normalization qualifier, per royal-order) and are surfaced as migration backlog, not papered over. The stopgap component-prefix unit-suspicion exemption (ISN 1cda724) is REMOVED — the operator is exempted via the existing dimensionless-operator path, restoring correct unit-suspicion for genuine normalized_radial-class components. Codegen regenerated (Component enum drops the two members); ISN suite 1822 passed (the lone failing test_find_git_root_raises_when_no_repo is a pre-existing environmental tmp-path flake, orthogonal to grammar).