Skip to content

ADR-078: Measurement-schema closure pass (Wave 1) — neutral signal names, sample-size exposure, process-aware reaction aggregation

Status: Accepted Date: 2026-05-31 Context: The measurement-schema research (docs/research/foundations/measurement-schema.md + reader's guide) surfaced a set of closure paths ready for engineering work. The research established two relevant disciplines as canonical conventions — Convention 6 (every aggregation carries a sample-size denominator that propagates to consumers) and Principle 7 (a consumer of a "final-state" capture should also read the co-captured "process" information when it would discriminate better) — plus a valuation-neutrality rename of two signals (Swing 3 Closure C). The architectural-independence finding says these closures can ship in any order without cross-coordination. This ADR records the three Wave 1 closures, which are the no-new-migration-risk subset (rename, exposure-layer N-propagation, and a reaction-aggregation refinement on existing infrastructure). The migration-bearing capture features (attention_state, option_tap_trace), mode-stratification (Q4a), and Convention 7 staleness annotation are deferred to Wave 2 and will get their own ADR(s) when that work lands.

At a glance

What it decides: Ships the three no-migration-risk measurement-schema closures (Wave 1) — a consumer-facing rename of two signals, sample-size (N) propagation into agent prose, and process-aware reaction aggregation — all on existing infrastructure with no DB migration or wire-contract change.

  • Display-only rename: E10 → "rationale-choice expression match", E14 → "ideal-actual reference frequency", but internal authenticity keys/columns/enums are retained (with explanatory comments).
  • Convention 6 (N-propagation): low-N claims in the agent_context_block prose now hedge and cite the count ("based on 8 calibration events…") so thin profiles don't read as confident.
  • Principle 7 (A8): post-reveal reaction notes are classified for agreement/contradiction, so a "gets_me + contradictory note" stops being counted as pure agreement — reuses the existing classify-driver SPOF, no new model call.
  • Rename lands first so A8 builds on neutral names; discovery-history artifacts are deliberately not rewritten.
  • Watch: Wave 2 (migration-bearing) becomes higher-priority if paste usage grows.
flowchart LR
    W["Wave 1<br/>(no migration)"] --> R["E10/E14 rename<br/>(consumer-facing only)"]
    R --> N["Convention 6:<br/>N-propagation to prose"]
    N --> A["Principle 7 (A8):<br/>note-aware reaction aggregation"]
    A --> D["Wave 2 deferred:<br/>capture + Q4a + Convention 7"]

Wave 1 closures ship in order (rename first); migration-bearing work defers to Wave 2.

Decision

Implement three coordinated closures:

1. E10 / E14 consumer-facing rename (display-layer only)

Rename two signals to valuation-neutral names everywhere a downstream consumer (agent, predictor LLM, paste-user, reader) sees them — but leave internal storage keys, DB columns, type names, and wire-contract enum values unchanged.

  • "rationale-choice alignment (authenticity)" → "rationale-choice expression match" (E10)
  • "self-aware aspiration gap" → "ideal-actual reference frequency" (E14)

Renamed (consumer-facing): agent-facing one-line prose (distill.ts), the predictor-prompt section header (formatProfileForPrompt in player-model.ts), the ADR-024 signal table + trust-thesis prose, the reader's-guide overview bullet, and any UI labels (none found).

Retained (internal): the authenticity JSONB column / type field / wire enum key and the updateAuthenticity function name. Each retained key gets a one-line comment pointing to this ADR so a developer reading the code understands the name/key drift is intentional.

Not rewritten (history): research-history artifacts that discovered and named the signals — experiment-new-domain-signals.md, measurement-schema-working.md, the cube working doc, and the CHANGELOG — keep their original wording. The canonical measurement-schema.md already carries the authoritative "Formerly …" bridge annotation; that is the single source of truth for the old→new mapping, and rewriting the discovery narrative would destroy the deliberation context.

2. Convention 6 — N-propagation to agent exposure (X5)

Aggregations already carry an N (games_analyzed, and per-signal sample counts). Propagate that N through the distilled AgentProfile and into the agent_context_block prose synthesis: when a signal's N is below a per-signal confidence threshold, the prose marks the claim tentative and cites the count — converting "your calibration suggests overconfidence" into "based on 8 calibration events, your profile suggests overconfidence." N uses games_analyzed per the Decomposition-38 clarification, and per-signal sample counts where the signal carries its own (samples, total_discussions, etc.).

3. A8 — process-aware reaction aggregation (Principle 7)

When recording a C5 post-reveal reaction (gets_me / off_track / interesting), also classify the C6 free-text note (when present) for agreement/contradiction/elaboration, and stratify the resonance counts accordingly — so a "gets_me + contradictory note" is no longer silently counted as pure agreement. Reuses the existing classify-driver-style central-extraction infrastructure (the SPOF classifier) rather than adding a new model dependency.

Rationale

  • Why display-only rename. The rename's entire purpose is consumer framing: the same low-match pattern reads as inauthenticity or epistemic humility depending on context, so the name must not pre-commit either interpretation. That purpose is fully served at the layer consumers see. Internal keys are read only by developers, who are served by a one-line comment. Renaming the authenticity column would force a migration, data backfill, a SCHEMA_VERSION bump, and a coordinated agent-contract cutover — cost with no naming-discipline payoff. The half-measure here aligns exactly with the rename's intent rather than cutting a corner.
  • Why propagate N to prose, not just structured fields. The mainstream paste-user path consumes the prose block, not the structured aggregates. A confident-sounding sentence built on 3 events is the failure mode Convention 6 exists to prevent; the fix has to reach the sentence, not just the JSON.
  • Why classify the note for A8. Principle 7: the reaction enum is the final-state capture; the note is the co-captured process. Treating "gets_me" as agreement while ignoring a contradicting note is precisely the discrimination loss Principle 7 names. The classifier already exists, so this is an algorithm refinement, not a new primitive.

Alternatives Considered

  • Full-depth rename (internal keys too). Rejected for Wave 1 — migration + wire-contract + backfill risk disproportionate to benefit. Left as a possible separate dedicated PR if the team later wants key-level cleanup.
  • N-propagation as a structured field only (no prose change). Rejected — leaves the highest-interpretive-load surface (prose) unguarded.
  • A8: new dedicated note-classification model call. Rejected — the existing classifier covers it; a second call adds cost and a second SPOF.
  • Blanket find-replace of "aspiration gap". Rejected and explicitly guarded against — "aspiration gap" also denotes the M6 measured outcome ("aspiration gap magnitude"), which is out of scope; only the E14 signal sense is renamed.

Discussion

The scoping question that took the most deliberation was rename depth. The instinct toward "rename it everywhere or you get drift" is usually right, but here the drift is between a consumer-facing name and an internal key — two audiences with different needs — and the rename's stated purpose only ever concerned the consumer-facing audience. So the half-measure is not a compromise; it is the correctly-scoped change. The guardrail that made this safe was auditing every aspiration gap / authenticity occurrence by hand rather than sweeping, which is what surfaced the M6-vs-E14 ambiguity and the discovery-history artifacts that must not be rewritten.

The three closures share the classify-driver SPOF as a dependency (the rename touches its consumer-facing prose; A8 reuses its classification path), which is why they ship as one coordinated wave with the rename landing first so A8 builds on neutral names.

Consequences

  • Agents and the predictor LLM see valuation-neutral signal names; internal code keeps authenticity keys with explanatory comments. No wire-contract or DB migration in Wave 1.
  • The agent_context_block prose hedges low-N claims and cites counts, reducing over-assertion on thin profiles.
  • Resonance aggregation distinguishes coherent agreement from reaction/note contradiction.
  • Watch for: if paste-based usage grows, Convention 7 staleness annotation (Wave 2) becomes higher-priority; if the team wants key-level naming cleanup, that is a separate full-depth rename PR.
  • Wave 2 (deferred): attention_state + option_tap_trace capture, Q4a mode-stratification, Convention 7 "as of [date]" — all migration-bearing.

Key files: - src/lib/agent/distill.ts — E10 prose rename; per-signal N surfaced for Convention 6 - src/lib/agent/contract.tsAgentSignalSummary gains sample-size fields (Convention 6) - src/lib/agent/summary.ts — prose prompt + fallback hedge low-N claims (Convention 6) - src/lib/ai/player-model.ts — predictor-prompt header rename; internal-key comments - src/types/database.ts — internal-key comment - src/app/api/results/[id]/reaction/route.ts — A8 note-semantics classification + stratified resonance counts - docs/decisions/024-judgment-layer-signals.md — signal-10 name updated, storage-key note - docs/research/foundations/measurement-schema-readers-guide.md — E14 overview bullet renamed