Symbol definition policy — define identity, don't restate units

✓ SHIPPED 2026-07-20. Policy landed across the pipeline: latex_def_check requires symbol identity not a bare unit (a27beea6); no-units prompt across generate/refine/review seats + shared partial, Spectrum rule reworded, both narrow exceptions removed (38dfeb97); advisory symbol_units_check + audit:symbol_units selector, FP-hardened against label subscripts / variable exponents / unit vectors (ee22b1f4ac75e0df). Smoke: 30/30 unit-free, 10/10 stripped, $13. Cleanup: 357 unit-docs selected → 356 refined unit-free ($89, $0.25/name) + 8 detector-FPs resolved → audit:symbol_units now selects 0; zero genuine units remain catalog-wide. A full audit re-stamp surfaced 43 latent deterministic defects (not new) that the fast-grep path had masked — folded into the docs campaign.

Follow-on from model-selection-and-global-refine §5. The docs-refinement campaign produces high-quality normative docs, but the refine seat annotates a unit on nearly every LaTeX symbol (see before/after evidence §2b, Example 7). This plan makes the policy explicit: a symbol's identity (which quantity it denotes, ideally a name: link) is the non-redundant thing docs must supply; its unit is already carried elsewhere and restating it in prose is redundant and drift-prone. Three coupled changes: the prompt, the audit, and a scoped cleanup.

§1 — Why per-symbol units are the wrong target

A standard name's documentation contains a defining relation whose symbols fall into three classes. For every class, the unit is recoverable without restating it in prose:

Symbol classWhere its unit already livesProse restatement is…
The primary quantity the name denotesthe structured unit field (rendered on the name's page)duplicating a machine-readable field
An auxiliary quantity that is itself a standard nameits own unit, reached via the name: linkredundant and drift-prone — goes stale if that name's unit changes
A non-name math/geometry symbol (arclength ℓ, an angle)nowhere structuredthe only case with any argument — and the unit is usually trivial

What is genuinely non-redundant is the symbol's identity: binding it to the quantity it denotes. "$B_p$ is the poloidal magnetic-field magnitude [name:poloidal_magnetic_field]" carries information a reader cannot derive; "$B_p$ in T" they can. Restating units in prose is the same anti-pattern the campaign already removes (facts that belong in structured fields — like device-specific "typical values"), one level down.

Root cause — the audit rewards the wrong thing

The refine seat is partly incentivised to add units by the deterministic audit. latex_def_check (imas_codex/standard_names/audits.py) counts a symbol as "defined" if a nearby sentence contains a definition word (is / are / denotes / represents / where / defined / being / given by / …) OR a unit in brackets. So a bare unit satisfies the symbol-definition check — the audit conflates "has a unit" with "is defined." A symbol can carry a unit and still have no stated identity. Fixing the audit removes the incentive at its source.

§2 — The three changes

  1. Refine + docs-gen prompt. Require every LaTeX symbol to be defined by identity in its where-clause, preferring a name: link when the symbol is a catalog quantity. And — no units in documentation prose at all (lead directive 2026-07-20): a unit states dimension, not identity; the quantity's unit is the authoritative structured unit field and a linked quantity carries its own via its link. There is no "non-name symbol" exception — units simply do not belong in the description/documentation text.
  2. latex_def_check audit (landed a27beea6). Stops accepting a bare unit-in-brackets as the definition satisfier — requires a definition word or a resolvable name: identity within the definition window. Tests: unit-only fails, name:-link passes, def-word passes. This removes the incentive to unit-annotate but does not itself ban units from prose — that is change 3.
  3. Audit audit:symbol_units — flag any unit token in documentation prose. The enforcement side of the "no units in prose" policy: a low-false-positive check that flags accepted docs carrying unit statements ("with unit X", "in <UNIT>", unit expressions inside \mathrm{…} such as m^{-3}/s^{-1}), while excluding \mathrm{} subscript labels (\mathrm{axis}, \mathrm{eff}). Doubles as the campaign selection predicate so the cleanup rotation is self-pruning. FP rate validated against the live catalog before it lands as a hard audit.

Changes 1–2 are a small, testable prompt + audit edit (tier: sonnet). They are not a defect that halts the running campaign — units-in-prose is redundant, not wrong — so rotation 3 continues untouched.

§3 — Sequencing and the regeneration price

The cheapest moment to land this is now, while the current campaign is still running. The docs-refinement campaign has ~502 names left in its selection; if the new prompt policy lands before those rotations finish, they pick it up at zero marginal cost (they are being refined anyway). Only docs that are already prose-clean — so the current campaign will never re-touch them — need a dedicated cleanup pass.

Catalog measurements (live, 2026-07-20)

Regeneration price by scope (at ~$0.28/name) Policy + audit change (dev) ~$0 LLM + ~$5–10 smoke batch Marginal, if landed during current run $0 — ~502 remaining names absorb it Targeted cleanup (already-clean docs) ~270–550 names → $75–155 Realistic total marginal (recommended) ~$75–120 Worst case — re-refine every formula doc 2,070 → ~$580 (unnecessary) Bars are proportional to name count. The recommended path lands the policy now (free absorption) then runs one targeted cleanup rotation.
The policy change itself is nearly free; the only real spend is a targeted cleanup of docs the running campaign won't otherwise re-touch. A full catalog re-refine is never necessary.
ScopeNamesEst. costNotes
Policy + audit change (dev)~$0prompt + latex_def_check edit + tests; optional ~$5–10 30-name smoke
Absorbed by the running campaign~502$0remaining selection picks up the new prompt policy for free if landed now
Targeted cleanup (audit:symbol_units)~270 incremental (of ~550 total)$75–155already-prose-clean docs with redundant units; self-pruning rotations
Worst case (full re-refine)~2,070~$580unnecessary — only unit-restating docs need it

Bottom line: the realistic marginal price is ~$75–120 for a clean catalog, dominated by one targeted cleanup rotation — or effectively $0 if the lead is content to let the running campaign + all future refines absorb the policy and leave the ~270 already-clean redundant-unit docs as-is until they are next touched.

§ Decisions

Should a bare unit-in-brackets still satisfy latex_def_check for a non-name math symbol, or must every symbol have an identity/definition word?

May a doc state the primary quantity's own unit in prose at all, given it is already the structured unit field?

Run a dedicated audit:symbol_units cleanup campaign over already-clean redundant-unit docs, or only absorb the policy through the running campaign and future refines?

§ Followups

Run the targeted units cleanup (smoke PASSED)

Landed: latex_def_check identity rule (imas-codex a27beea6); symbol_units_check advisory + audit:symbol_units selector (ee22b1f4); no-units prompt across generate/refine/review seats + shared format partial, Spectrum rule reworded, both narrow exceptions removed (38dfeb97). Smoke PASSED (2026-07-20): 30-name run, 30/30 refined docs unit-free, 10/10 unit-bearing docs stripped to none, 0 errors, $13.04. The new policy produces identity-defined, unit-free docs end-to-end (e.g. ion_diffusivity: tensor definition via name: links, no units). Re-stamp done (2026-07-20): full deterministic-audit re-stamp over all 2,347 accepted docs populated the symbol_units findings; audit:symbol_units now selects 357 docs. Side effect: 43 docs re-quarantined — not new defects but latent deterministic-audit findings (mostly perturbed_*/*_real_part tripping representation/amplitude checks) that the fast-grep path had washed to 'valid'; the campaign will fix them (original campaign grew 85→116). Cleanup running: --campaign audit:symbol_units --campaign-pilot 357 (4 batches, ceiling $130 ≈ $107 at $0.30/name); refines the 357 unit-docs unit-free under the new prompt, self-pruning as units clear. Then: finish the original docs campaign (~116) per f-docs-campaign-rotations.
Project: imas-standard-names / code: imas-codex (branch main)
Section: §2 — symbol-definition policy: prompt + audit
Tier:    sonnet

Context
  The docs-refinement campaign annotates a unit on nearly every LaTeX symbol.
  Units are recoverable (primary quantity → structured unit field; linked
  auxiliary quantity → its own unit via the name: link), so restating them in
  prose is redundant and drift-prone. What is non-redundant is the symbol's
  IDENTITY. The refine seat is partly incentivised to add units because
  latex_def_check accepts a bare unit-in-brackets as the "symbol is defined"
  satisfier. Land this while the running campaign still has ~502 names queued so
  they pick up the new policy at zero marginal cost. Honour the live decisions
  above (unit-satisfier, primary-unit-in-prose) — do not re-litigate them.

State to read  (CODE / FILES)
  imas_codex/standard_names/audits.py — latex_def_check (~line 311), the
    _DEFINITION_WORDS_SINGLE / _DEFINITION_PHRASES sets, and the
    "unit in brackets" branch that currently satisfies the check.
  imas_codex/standard_names/prompts/ — the refine_docs and generate_docs
    prompt templates (symbol/where-clause guidance).
  docs-campaign-before-after.html §2b Example 7 — the evidence of the pattern.

Scope locks / constraints (non-decision)
  - Do NOT halt or alter the running docs campaign; this is forward policy.
  - No name-axis changes; docs-only.
  - Explicit-path commits, no AI trailers, no plan refs in messages.

Done-when
  1. Prompt requires identity/name-link symbol definitions and forbids
     restating derivable units (per the locked decisions); committed + pushed.
  2. latex_def_check no longer accepts a bare unit as the sole satisfier;
     unit tests cover: unit-only symbol FAILS, name-link identity PASSES,
     non-name symbol with a stated unit PASSES (per the unit-satisfier decision).
  3. ~30-name smoke rotation shows the new refines define symbols by identity
     with no redundant units; cost recorded.
  4. Followup written for the cleanup decision (targeted audit:symbol_units
     campaign vs absorb-only) + this followup resolved.