The DD subtree summary/local/separatrix_average/* stores flux-surface-averaged
quantities evaluated at the separatrix (the plasma boundary). Its StandardNames are a
multiplicity mess: of 54 dd-sourced leaves, 24 carry more than one live
name. Three coexisting forms were composed for the same physical quantity, and all are
name_stage='accepted':
Long form — flux_surface_averaged_<q>_at_plasma_boundary
(e.g. flux_surface_averaged_carbon_density_at_plasma_boundary).
Short form — <q>_at_plasma_boundary
(e.g. carbon_density_at_plasma_boundary). 14 leaves carry BOTH the long and
short form — a straight duplicate pair.
Locus-stripped — no _at_plasma_boundary at all
(e.g. flux_surface_averaged_carbon_density, lithium_density).
6 leaves carry a locus-stripped name. Some are strict duplicates of a with-locus
sibling; others (e.g. flux_surface_averaged_boron_density,
flux_surface_averaged_total_ion_density, flux_surface_averaged_electron_temperature,
flux_surface_averaged_magnetic_shear) are the only name for that leaf and
simply lack the locus. One degenerate exhausted name (flux_surface_averaged_velocity,
no species) also exists.
§1a — Correction: the operator and the locus are orthogonal (2026-07-10)
This plan's original framing — "the plasma boundary IS a flux surface, so the
flux_surface_averaged_ prefix is redundant with _at_plasma_boundary" —
was wrong in general, in both directions:
The locus is never redundant. The plasma boundary is one surface in a
continuum of nested flux surfaces; _at_plasma_boundary pins which surface.
Every name in this family keeps the locus. The locus-stripped names are wrong in all cases.
The operator is not implied by the locus. A quantity evaluated at the plasma
boundary can still vary along that surface (poloidal/toroidal asymmetry). The DD encodes
exactly this distinction with two parallel 64-leaf subtrees:
summary/local/separatrix/* (the local value) and
summary/local/separatrix_average/* (the flux-surface average). A locus-only name
cannot denote both.
The redundancy is parameter-dependent. It exists only for bases that are
constant on a flux surface (flux functions): the safety factor is already a
flux-surface quantity (averaging it is an FSA of an FSA), magnetic shear derives from the q
profile, psi/rho_tor are flux labels, and pressure is a flux function
in static ideal MHD. For these, FSA is a no-op: the local and averaged DD leaves
are the same physical quantity and should share ONE name (two sources → one StandardName).
For surface-varying bases (rotation velocities, momentum, parallel electric field, impurity
densities with centrifugal poloidal asymmetry), the prefix carries physical meaning and stays.
Graph evidence (2026-07-10): 54 separatrix_average leaves carry live
names (24 with 2–3); 30 separatrix (local) leaves carry names; and 15 names are
live on BOTH subtrees. For flux labels this sharing is correct
(poloidal_magnetic_flux_at_plasma_boundary, toroidal_flux_radius_at_plasma_boundary,
safety_factor_at_normalized_poloidal_magnetic_flux_equal_to_0_95); for surface-varying
bases it is a collision (e.g. toroidal_argon_velocity_at_plasma_boundary,
toroidal_plasma_momentum_at_plasma_boundary, iron_density_at_plasma_boundary) —
the name can no longer distinguish the local value from the average. An anomaly to verify during the
collapse: the momentum_phi/momentum_tor → name mapping is swapped
between the two subtrees.
So the fix is not "pick one of long vs short" — it is a conditional canonical rule
(locked in §Decisions): locus always; averaging prefix iff the FSA is not a no-op for that base.
A sibling instance: summary/composition/* yields 18 density-ratio names
with inconsistent naming — ratio_of_<species>_density_to_electron_density vs
<species>_fraction vs volume_averaged_<species>_..._to_..._electron_density.
Same class (one quantity, many name shapes), lower priority.
§2 — Why it matters (motivation)
Multiple accepted names for one physical quantity is a first-order catalog defect: consumers cannot
tell which name is authoritative, cross-references fork, and the same DD leaf appears under two or
three ISN identifiers. It also breaks the ledger's implicit "one live name per quantity" expectation
and inflates the catalog. The locus-stripped names are additionally wrong — a
separatrix-averaged quantity that drops _at_plasma_boundary reads as a global quantity.
This surfaced during sn-pedestal-source-recovery's
blast-radius assessment (f-psr-002): the compose-grounding gap fixed there (WS-1) explains the
locus-stripped variants, but the long/short multiplicity is a pre-existing naming-convention gap that
the grounding fix does not resolve on its own.
The collisions are worse than the duplicates. A duplicate pair (long + short on the
same leaf) merely forks references; a cross-subtree collision makes the name semantically
ambiguous — toroidal_argon_velocity_at_plasma_boundary currently denotes both the
local separatrix rotation and its flux-surface average, which are physically different numbers.
Physics-relevant naming requires understanding, per base, whether the flux-surface average is a
no-op; that classification must live in the vocabulary, not in per-name judgement calls, or the
generation pipeline will keep re-minting the wrong forms (§4).
✓ landed 2026-07-10
§3 — Collapse executed
46 deterministic graph actions (6 rename cascades, 27 supersede-merges, 11 targeted
source moves, 2 detach+create) — no LLM recompose, $0 spent. Result: 84 leaves all
single-name (was 24 multi), 0 cross-subtree collisions (was 15), 0 locus-stripped
names, 4 correct flux-function shares, 0 orphans/desyncs, gate audit clean. Momentum
phi/tor resolved as a DD v3/v4 alias pair sharing one name per subtree. Two new FSA
names (effective_charge, ion_density) sit drafted for the next
review rotation. Full record:
§3 landed.
§3 — The rule + approach (reference)
The canonical form is a conditional rule (locked below), applied per base:
Locus always. Every name in the family carries _at_plasma_boundary.
The ~6 locus-stripped names are wrong regardless of base class.
Prefix iff non-no-op.flux_surface_averaged_ appears iff the base is
NOT constant on a flux surface. Which bases count as flux functions is the
flux-function-base-list decision (open below); the definitional set is
safety_factor, magnetic_shear, the flux labels
(psi/rho_tor/rho_tor_norm position leaves), and
pressure.
Execution, once the base list is locked and the §4 guards are in place:
Flux-function leaves — the local and averaged DD leaves attach to ONE shared
name <q>_at_plasma_boundary (two sources → one StandardName, as
poloidal_magnetic_flux_at_plasma_boundary already does). Supersede any
flux_surface_averaged_-prefixed variant onto the shared name; attach detached sources
deterministically (bind_recovery_sources-style targeted attach, never a
--focus recompose).
Surface-varying leaves — the averaged leaf's canonical name keeps the prefix:
flux_surface_averaged_<q>_at_plasma_boundary. Supersede the short duplicate on
the averaged leaf onto it — UNLESS the short name is also produced by the local
separatrix/* leaf (a collision, not a duplicate): then the short name stays
as the local leaf's name and only the averaged source is detached/re-attached to the prefixed
name. Check each short name's full source set before superseding; superseding a collided name
would steal the local leaf's identity.
Add the missing locus — locus-stripped names rename to their canonical target
via the rename cascade, or supersede onto an existing canonical sibling.
Verify the momentum swap — the momentum_phi/momentum_tor
→ name mapping is inverted between the two subtrees; resolve against DD semantics (the
phi/tor pair is a DD v3/v4 alias set) while touching these leaves.
Generalise — the same conditional rule governs other flux-surface reduction
contexts (summary/volume_average/* is a volume average — different operator,
same no-op question for flux functions is NOT applicable there since a volume average of a flux
function is not a no-op; scope it explicitly), pedestal fits, and
maximum_over_flux_surface/minimum_over_flux_surface (max = min = value
for a flux function — equally banned on flux-function bases).
Composition family — normalise the 18 summary/composition/*
density-ratio names to one shape (lower priority; separate decision).
Mechanism: the deterministic rename/supersede cascade
(graph_ops._execute_rename_cascade / sn run --rename OLD:NEW --include-accepted),
NOT a --focus recompose — a recompose mints a fresh drafted variant and leaves the
accepted duplicates, worsening the multiplicity (demonstrated in sn-pedestal-source-recovery).
Verify 0 orphans / 0 desyncs and that every leaf resolves per the conditional rule.
Encoded at all four levels: constant_on_flux_surface flag in ISN vocab
(definitional set + flux-label carriers), an IR-level gate failing parse AND compose for
flux-surface reductions of flagged bases (19 new tests; ISN suite 1665 passed), the
conditional rule in generate_name_dd.md/generate_name_system.md/review.md
(new check I2.8), a live-catalog gate-violation probe in sn run (pre-collapse: exactly
1 offender, flux_surface_averaged_magnetic_shear), and harmonize family eligibility
extended to the three reduction operators via the edge operator token.
derive_edges/parents.py confirmed correct, untouched.
Full record: §4 landed
(ISN b8a2024, imas-codex 8d6cb77b; ISN pin bumped).
The q95/momentum anomalies traced to a systemic defect: 17,216 of 61,366 IMASNodes (28%)
are absent from DD 4.1.0, removal was recorded only as DEPRECATED_IN edges no seeding
query consulted, and 1,535 dd sources (857 feeding live names) sat on DDv3-only paths.
Root fix (imas-codex 51c19815): new graph/dd_lifecycle.py stamps
absent nodes lifecycle_status='removed' (+ NBC renamed_to), wired
into the DD build; all three seeding queries now exclude removed nodes; an
sn run probe flags survivors. Ledger cleanup: 300 redundant v3 sources retired,
37 re-anchored to their DD4 path, 28 semantic-rename conflicts resolved in favour of the
DD4 name, 492 truly-removed detached, 111 names with no DDv4 anchor exhausted.
Post-state: 0 v3 sources on live names, 0 orphans/desyncs, both probes clean.
Full record: followups landed
(also covers f-bam-003 composition family + f-bam-004 q95 fix).
§ Decisions
What is the canonical form for a flux-surface-averaged quantity evaluated at the plasma boundary (separatrix)?
The locus and the averaging operator are orthogonal: _at_plasma_boundary pins WHICH flux surface (the boundary is one of a continuum, so the locus always carries weight), while flux_surface_averaged_ states a reduction that is only meaningful when the base varies over the surface. The DD encodes this with parallel separatrix/ (local) and separatrix_average/ subtrees, and the graph shows 15 live cross-subtree collisions from locus-only naming. For flux-function bases (safety factor — already a flux-surface quantity, so averaging it is an FSA of an FSA; magnetic shear; flux labels; pressure) FSA is a no-op and one shared name serves both leaves. Prefix iff non-no-op.
Which bases are flagged constant_on_flux_surface (FSA is a no-op → no averaging prefix, one shared name for the local and averaged leaves)?
Canonical shape for the summary/composition density-ratio names (ratio_of_X_density_to_electron_density vs X_fraction vs volume_averaged_…)?
§ Followups
Collapse the separatrix/separatrix_average multiplicity under the conditional rule (AFTER f-bam-002)
Apply the locked conditional rule (§3) across BOTH sibling subtrees: flux-function
leaves converge on one shared <q>_at_plasma_boundary name; surface-varying averaged
leaves get flux_surface_averaged_<q>_at_plasma_boundary, breaking the 15 live
cross-subtree collisions without stealing the local leaf's name. Runs after the §4 guards
(f-bam-002) so regeneration cannot re-mint the old forms. Spun out of
sn-pedestal-source-recovery f-psr-005.
Project: imas-standard-names (naming/grammar) + imas-codex (standard_names) + imas graph
Plan: sn-boundary-averaged-name-multiplicity (http://localhost:8765/imas-standard-names/sn-boundary-averaged-name-multiplicity.html)
Section: §3 collapse — separatrix + separatrix_average subtrees
Tier: opus
Context
PREREQUISITE: f-bam-002 (vocab flag + grammar gate + prompt updates) must be landed first.
Two parallel 64-leaf DD subtrees: summary/local/separatrix/* (local value) and
summary/local/separatrix_average/* (flux-surface average). 24 averaged leaves carry 2-3 live names;
15 names are live on BOTH subtrees. Honour the locked canonical-boundary-form and
flux-function-base-list decisions shown live above: locus _at_plasma_boundary ALWAYS;
flux_surface_averaged_ prefix IFF the base is not flagged constant_on_flux_surface.
State to read (CODE / FILES / DATA)
imas-codex graph_ops.py: _execute_rename_cascade, bind_recovery_sources (targeted source attach),
HAS_SUCCESSOR/superseded_by convention. CLI: sn run --rename OLD:NEW --include-accepted (--dry-run
first). ISN grammar: every target must round-trip (compose(parse(name))==name) and pass the new
constant_on_flux_surface gate. Enumerate BOTH subtrees per leaf (the collision check needs the union):
MATCH (s:StandardNameSource {source_type:'dd'})-[:PRODUCED_NAME]->(n:StandardName)
MATCH (s)-[:FROM_DD_PATH]->(l:IMASNode)
WHERE l.id STARTS WITH 'summary/local/separatrix'
AND NOT coalesce(n.name_stage,'') IN ['superseded','exhausted']
RETURN l.id, collect(DISTINCT n.id)
Scope locks / constraints (non-decision)
- Per-leaf resolution:
* Flux-function base (constant_on_flux_surface): local + averaged leaves attach to ONE shared
name _at_plasma_boundary (two sources, one StandardName — the poloidal_magnetic_flux pattern).
Supersede flux_surface_averaged_-prefixed variants onto the shared name.
* Surface-varying base: averaged leaf's canonical name is flux_surface_averaged__at_plasma_boundary.
Before superseding a short name, CHECK ITS FULL SOURCE SET: if it is also produced by the local
separatrix/* leaf it is a COLLISION, not a duplicate — the short name STAYS as the local leaf's
name; only detach the averaged source and re-attach it to the prefixed name (targeted attach,
bind_recovery_sources-style). Superseding a collided name steals the local leaf's identity.
- Verify/fix the momentum_phi vs momentum_tor mapping swap between the two subtrees against DD
semantics while touching those leaves.
- Deterministic cascade only, NEVER a --focus recompose (mints a new drafted variant, worsens
multiplicity — proven in sn-pedestal-source-recovery). Never raw-delete accepted names; preserve
provenance; repoint inbound links.
- Generalisation check: apply the same audit to maximum_over_flux_surface / minimum_over_flux_surface
names and report on summary/volume_average/* (volume average of a flux function is NOT a no-op —
scope explicitly, do not blanket-apply the rule there).
Done-when
1. Every separatrix + separatrix_average leaf resolves per the conditional rule: flux-function leaves
share ONE name across both subtrees; surface-varying leaves have TWO distinct names; 0 remaining
cross-subtree collisions on surface-varying bases; locus-stripped names gone.
2. Momentum swap resolved; generalisation check reported (fix or explicit scope-out).
3. 0 orphans / 0 desyncs; provenance + content preserved; composition density-ratio followup written
if not already done.
4. This followup resolved.
Encode the conditional rule at vocab/grammar/prompt/grouper level (prerequisite for f-bam-001)
Per-name renames do not survive regeneration. Land the §4 persistent fixes first: a constant_on_flux_surface flag in ISN physical_bases.yml, a grammar gate rejecting flux-surface reduction operators on flagged bases (the FSA-of-an-FSA class), the conditional path rule in the imas-codex generation/review prompts (replacing the unconditional "NEVER drop the prefix" row in generate_name_dd.md), a graph audit, and averaging-operator family eligibility in harmonize.py so the local/averaged pair docs-harmonize together.
Project: imas-standard-names (grammar/vocab) + imas-codex (prompts, audits, harmonize)
Plan: sn-boundary-averaged-name-multiplicity (http://localhost:8765/imas-standard-names/sn-boundary-averaged-name-multiplicity.html)
Section: §4 persistent fixes
Tier: opus
Context
The conditional canonical rule is locked (locus always; flux_surface_averaged_ prefix iff the base is
not constant on a flux surface). Encode it so the generation pipeline can never re-mint the wrong
forms. Honour the flux-function-base-list decision shown live above for the flag's membership; if it
is still open, implement the mechanism with the definitional-only set and flag the membership as
pending.
State to read (CODE / FILES / DATA)
ISN: imas_standard_names/grammar/vocabularies/physical_bases.yml (per-entry schema, see the
inherently_dimensional precedent), operators.yml (flux_surface_averaged, maximum_over_flux_surface,
minimum_over_flux_surface), imas_standard_names/vocabulary/audit.py and the validator that enforces
inherently_dimensional; tests under tests/grammar/.
imas-codex: imas_codex/llm/prompts/sn/generate_name_dd.md (path-rule row for separatrix_average —
currently mandates the prefix unconditionally), generate_name_system.md (averaging-operator bullet),
review.md; imas_codex/standard_names/harmonize.py (_FAMILY_OPERATOR_KINDS excludes unary_prefix, so
flux_surface_averaged_X_at_plasma_boundary never harmonizes with X_at_plasma_boundary);
imas_codex/standard_names/audits.py (add a live-name gate-violation audit surfaced by sn run).
Scope locks / constraints (non-decision)
- Vocab: add constant_on_flux_surface: true per-base flag + schema comment; membership per the locked
flux-function-base-list decision.
- Grammar gate: reject flux_surface_averaged / maximum_over_flux_surface / minimum_over_flux_surface
applied to a flagged base, at the same layer that enforces inherently_dimensional, with unit tests
(positive: flux_surface_averaged_electron_density_at_plasma_boundary parses; negative:
flux_surface_averaged_safety_factor_* rejected).
- Prompts: replace the unconditional separatrix_average rule with the conditional rule; state that
flux-function leaves share the local leaf's name (two DD sources, one StandardName). Keep the
collision warning for surface-varying bases.
- harmonize.py: extend _FAMILY_OPERATOR_KINDS to cover averaging reduction operators (or record in
the plan why not); families.py (vector-component grouping) is out of scope.
- derive_edges/parents.py automatic-parent behaviour is already correct — do not change it.
- ISN + imas-codex tests green (run graph-marked tests against live Neo4j).
Done-when
1. constant_on_flux_surface flag live in physical_bases.yml; grammar gate enforced with tests.
2. generate_name_dd.md / generate_name_system.md / review.md carry the conditional rule.
3. Gate-violation audit runs in sn run; harmonize family eligibility extended or explicitly declined
in the plan.
4. Both repos' tests green; plan §4 updated with outcomes; f-bam-001 unblocked; this followup resolved.
Normalise the summary/composition density-ratio family (18 names) per the explicit-ratio style
Decision composition-ratio-form: prefer explicit ratio form; survey other catalog areas and follow one consistent style for averages and ratios. Lower priority; deterministic cascade once the target shapes are enumerated.
Project: imas-standard-names + imas-codex graph
Plan: sn-boundary-averaged-name-multiplicity
Section: composition density-ratio family
Tier: opus
Context
summary/composition/* yields 18 density-ratio names in three shapes (ratio_of_X_density_to_electron_density vs X_fraction vs volume_averaged_...). Decision locked: prefer explicit ratio form; survey other catalog ratio/average families first and apply ONE consistent style.
State to read
Graph enumeration of summary/composition/* sources and their live names; ISN grammar binary ratio operator; the §3 collapse driver pattern (supersede-merge onto canonical, deterministic only).
Done-when
1. One canonical shape enumerated and locked for all 18 names; renames/supersedes applied via the deterministic cascade with full source-set checks.
2. 0 orphans/desyncs; every target round-trips.
3. Plan updated; this followup resolved.
Landed — DD defines composition as ratio of volume-averaged species density to volume-averaged n_e; canonical shape ratio_of_volume_averaged__density_to_volume_averaged_electron_density. 14 cascade renames + 3 already canonical + hydrogen collision resolved (its old name correctly serves the isotope_fraction leaves; composition leaf got a fresh drafted canonical name). All 18 leaves single-name canonical; 0 orphans/desyncs.
Verify the separatrix q → q95 locus mapping (psi_N=0.95 vs 1.0)
Both summary/local/separatrix*/q leaves map to safety_factor_at_normalized_poloidal_magnetic_flux_equal_to_0_95 (q95), but the separatrix is psi_N=1.0. The flux-function sharing is correct; the locus value looks wrong (q diverges at the separatrix in diverted plasmas, so q95 may be a deliberate convention — check the DD documentation before renaming).
Project: imas-codex graph + imas-standard-names
Plan: sn-boundary-averaged-name-multiplicity
Section: q95 locus anomaly (f-bam-004)
Tier: sonnet
Context
summary/local/separatrix/q and separatrix_average/q both attach to safety_factor_at_normalized_poloidal_magnetic_flux_equal_to_0_95. The separatrix is psi_N=1.0; q95 is the standard proxy because q diverges at the X-point. Check the DD leaf documentation: if the DD defines these leaves AS q95, the name is right and this closes with a note; if the DD means q at the boundary, rename via the deterministic cascade to a psi_N=1.0 or boundary form that round-trips.
Done-when
1. DD semantics established and recorded in the plan.
2. Name kept or renamed accordingly (deterministic cascade, full source-set check).
3. This followup resolved.
Landed — DD defines summary/local/separatrix*/q as q AT the LCFS, not q95. Revived the dangling-superseded safety_factor_at_plasma_boundary (accepted) and moved both separatrix q sources onto it; the q95 name keeps only the two genuine q_95 sources. Finding escalated into the DD-version gate (§5).
Review-rotation pickup: 3 drafted names from the deterministic collapses
flux_surface_averaged_effective_charge_at_plasma_boundary, flux_surface_averaged_ion_density_at_plasma_boundary and ratio_of_volume_averaged_hydrogen_density_to_volume_averaged_electron_density were persisted deterministically as name_stage='drafted'; the next routine sn run review rotation scores them. Also observed (low priority): ratio_of_hydrogen_density_to_total_hydrogenic_density serves BOTH the line_average and volume_average isotope_fraction leaves — an averaging-domain collision of the same class.
Project: imas-codex graph
Plan: sn-boundary-averaged-name-multiplicity
Section: drafted-name pickup (f-bam-005)
Tier: sonnet
Context
Three deterministic-collapse names sit drafted awaiting review pools; a routine sn run covers them. While closing, check the isotope_fraction line/volume averaging-domain collision on ratio_of_hydrogen_density_to_total_hydrogenic_density and either split (line_averaged_/volume_averaged_ prefixes) or record why one name suffices.
Done-when
1. The 3 drafted names reviewed (accepted or refined) by a routine rotation.
2. Isotope-fraction collision resolved or explicitly declined.
3. This followup resolved.
Landed — flush-mode sn run reviewed the drafted queue: all collapse names accepted (fsa effective_charge/ion_density/hydrogen_density at plasma boundary, toroidal fsa ion velocity, volume-averaged hydrogen ratio). Isotope-fraction collision SPLIT: line/volume averaged ratio_of_*_hydrogen_density_to_*_total_hydrogenic_density minted per leaf; old domain-less name superseded onto the volume form. Line twin was exhausted by a refine-worker structuring failure (line_averaged mis-routed as qualifier), not review — restored to accepted; defect noted in f-bam-006.
refine_name structuring failure must not exhaust a reviewed name (line_averaged mis-segmenting)
During the f-bam-005 flush, the refine worker emitted line_averaged as a qualifier segment (it is a transformation operator), the RefinedName validation correctly rejected it, and the deterministic-failure branch marked the REVIEWED name exhausted — an infrastructure failure retiring a semantically fine name (score 0.812; its volume twin accepted). Fix the worker segment routing for averaging operators and/or make structuring failures release the claim back to reviewed instead of exhausting.
Project: imas-codex
Plan: sn-boundary-averaged-name-multiplicity
Section: refine exhaustion defect (f-bam-006)
Tier: sonnet
Context
workers.py refine_name path: on a deterministic RefinedName validation error the name is marked exhausted. Seen with qualifier 'line_averaged' is not a registered grammar token — the LLM structured an averaging TRANSFORMATION as a qualifier. Reproduce with ratio_of_line_averaged_hydrogen_density_to_line_averaged_total_hydrogenic_density.
Done-when
1. Averaging operators route to the transformation slot in refine structuring (prompt or post-fix), OR structuring failures leave the name reviewed (retryable), with a unit test.
2. This followup resolved.
§ Research
Graph + DD evidence for the conditional rule (2026-07-10)