Merge pull request 'fix(cognition): restore register axis on pipeline-served turns + Phase 0 of generalization arc' (#110) from feat/generalization-phase0 into main

Reviewed-on: #110
This commit is contained in:
Joshua Matthew-Catudio Shay 2026-07-24 19:59:07 +00:00
commit 379e1611ac
10 changed files with 582 additions and 28 deletions

View file

@ -36,13 +36,14 @@ is a CI failure (`.github/workflows/lane-shas.yml`).
| --- | --- | --- | --- | --- |
| ADR-0092 | `reviewer_registry` | Reviewer registry schema validates + bootstrap entry self-seals | `evals/reviewer_registry/results/v1_dev.json` | `681a2aab5aa4ffd58cd837ce5673c8b2a9545b570117aec3c02726a12f6876e6` |
| ADR-0093 | `domain_contract_validation` | All ratified packs satisfy the 9 ADR-0091 contract predicates | `evals/domain_contract_validation/results/v1_dev.json` | `98ace04e3f02bbc5a8ad655bb6593c3f1ee64cb67014f1122fe6c3c85f48d22f` |
| ADR-0095 | `miner_loop_closure` | Miner-sourced proposals route through single reviewed teaching path | `evals/miner_loop_closure/results/v1_dev.json` | `9f071733abe7dcacf759f928548ce738fb639af3fd6e4c621a651b306d7e77ce` |
| ADR-0095 | `miner_loop_closure` | Miner-sourced proposals route through single reviewed teaching path | `evals/miner_loop_closure/results/v1_dev.json` | `537094fe21d7e6cfbaf42bfc32b82d669fa9bb05a132d2bc93c72b3ceb7762a6` |
| ADR-0096 | `fabrication_control_summary` | Phantom endpoints / cross-pack non-bridges / sibling collapses refuse | `evals/fabrication_control/results/v1_summary.json` | `01e1b6b711141f2b4a14551d7df3ea482d8d6dd7b364a25c509f4f8d08cda8a8` |
| ADR-0098 | `demo_composition` | Demos compose from shipped modules; no parallel mechanism | `evals/demo_composition/results/v1_dev.json` | `e2ba2314d8768459fb6a8db082a4bbcf4107b5161d869804a4b2a33c3724081a` |
| ADR-0098 | `demo_composition` | Demos compose from shipped modules; no parallel mechanism | `evals/demo_composition/results/v1_dev.json` | `f0611a2ce41721dd40767fc6a83a08470d3c7fd7fc8f1ae8ba003abf8a25ec97` |
| ADR-0099 | `public_demo` | Public showcase runs deterministically under 30s; all claims supported | `evals/public_demo/results/v1_dev.json` | `7d8ba0dbae9287cfe0bf15d231fa78a75abc627121c14900439293e01e1cc1d3` |
| ADR-0104 | `curriculum_loop_closure` | Curriculum-sourced proposals route through single reviewed teaching path | `evals/curriculum_loop_closure/results/v1_dev.json` | `b46d56b2d209172cc3ffaf3776dc8dcfe55093f13587c5cb67372be6dfa23e8d` |
| ADR-0104 | `curriculum_loop_closure` | Curriculum-sourced proposals route through single reviewed teaching path | `evals/curriculum_loop_closure/results/v1_dev.json` | `cb94ca0042d78ec2624129ff6493d52e767b69feea32d2997b85d88f1c0883af` |
| ADR-0131 | `math_teaching_corpus_v1` | Math teaching corpus replays deterministically; all chains pass exit criterion (correct_rate=1.0, wrong=0) | `evals/math_teaching_corpus/v1/report.json` | `eaf160d145da29f9050ede8d58bf111b0f651dd40aeae9201857d0b97e014dd4` |
| ADR-0206 | `deductive_logic_v1` | Propositional entailment scored against an independent truth-table oracle; dev+holdout+external 716/716 correct, wrong=0, refused=0 | `evals/deductive_logic/report.json` | `97a230949016e38d5e3f37a69e4245b320575ee70e5af92ff7607f7b05f74b5f` |
| ADR-0256 | `deduction_serve_v1` | Flag-gated deduction serving decides real English/member/fused arguments end-to-end under earned SERVE licenses; wrong=0 across all splits | `evals/deduction_serve/report.json` | `b530ed99c414a6d1bdd239752b0e1c6c8c03893f402198def29122b9c2a8a8be` |
## Verification

View file

@ -486,6 +486,11 @@ class CognitiveTurnPipeline:
grounding_provenance=grounding_src,
)
surface = resolved.surface
# Truth-path bytes for trace_hash (ADR-0069 inv C): the served
# surface carries register R6/R4 transforms and MUST NOT move the
# hash; hash_surface is the register-invariant canonical-precedence
# capture, kept in lockstep with every served-surface mutation below.
hash_surface = resolved.hash_surface or resolved.surface
articulation_surface = resolved.articulation_surface
authority_source = resolved.authority
@ -518,6 +523,7 @@ class CognitiveTurnPipeline:
if logos_blocks_certified_answer(logos_decision):
refusal = decision_as_coherence_refusal(logos_decision)
surface = refusal.surface_message or refusal.message
hash_surface = surface
articulation_surface = surface
authority_source = "logos_morph_constraint"
except Exception:
@ -667,6 +673,7 @@ class CognitiveTurnPipeline:
# proposal (if any) is added below for FUTURE turns to see.
if self._speculative_subjects and surface and self._should_mark_speculative(text, surface):
surface = _SPECULATIVE_SURFACE_MARKER + surface
hash_surface = _SPECULATIVE_SURFACE_MARKER + hash_surface
articulation_surface = _SPECULATIVE_SURFACE_MARKER + articulation_surface
# 10. TEACHING — correction capture, review, and store
@ -705,9 +712,11 @@ class CognitiveTurnPipeline:
if len(tok) >= 4 and tok not in _SUBJECT_STOPWORDS:
self._forget_speculative_subject(tok)
# Advance turn counter and remember surface for next correction binding
# Advance turn counter and remember surface for next correction binding.
# The truth-path surface (not the served, register-decorated bytes)
# feeds correction binding so register packs cannot perturb teaching.
self._turn_number += 1
self._prior_surface = surface
self._prior_surface = hash_surface
# 11. TRACE — deterministic hash (includes teaching IDs and any
# typed-operator invocation per ADR-0018).
@ -787,7 +796,7 @@ class CognitiveTurnPipeline:
trace_hash = compute_trace_hash(
input_text=text,
filtered_tokens=filtered_tokens,
surface=surface,
surface=hash_surface,
walk_surface=response.walk_surface,
articulation_surface=articulation_surface,
dialogue_role=str(response.dialogue_role),

View file

@ -24,8 +24,6 @@ from typing import TYPE_CHECKING
from core.cognition.fail_closed import (
CoherenceRefusal,
ContractViolation,
FailureClass,
ResidualState,
contract_assessment_none_violation,
open_geometry_refusal,
)
@ -86,6 +84,15 @@ class SurfaceResolution:
refusal: CoherenceRefusal | None = None
contract_violation: ContractViolation | None = None
proof_trace: ProofTrace | None = None
hash_surface: str = ""
"""Truth-path surface for ``trace_hash`` folding (ADR-0069 inv C).
``surface`` carries the *served* bytes (post register R6/R4);
``hash_surface`` carries the register-invariant truth-path bytes
(canonical-precedence base plus the same substrate/fold suffixes).
Empty means the served surface IS the truth-path surface (refusal,
abstention, and legacy callers) the pipeline falls back to
``surface`` when folding the trace."""
def _base_runtime_surface(
@ -95,25 +102,44 @@ def _base_runtime_surface(
response_surface: str,
response_articulation_surface: str,
) -> tuple[str, str, str]:
"""Select the runtime-owned base surface by declared precedence."""
"""Select the runtime-owned base surface by declared precedence.
if canonical_surface:
return canonical_surface, response_articulation_surface, "runtime_canonical"
``response_surface`` is the runtime's final *served* bytes — post
realizer-guard, post substantive register (ADR-0077 R6), post seeded
decoration (ADR-0071 R4) and always wins when present.
``canonical_surface`` / ``pre_decoration_surface`` are truth-path
identity captures the pipeline folds into ``trace_hash``; they are
fallbacks for callers that never sealed a response surface, never a
substitute for served bytes. Preferring canonical here strips the
entire register axis from pipeline-served turns (terse/convivial
stop differing from neutral) while trace_hash stays green the
register-tour claims are the falsifiable contract that catches it.
"""
if response_surface:
return response_surface, response_articulation_surface, "runtime"
if pre_decoration_surface:
return pre_decoration_surface, response_articulation_surface, "runtime_pre_decoration"
return response_surface, response_articulation_surface, "runtime"
if canonical_surface:
return canonical_surface, response_articulation_surface, "runtime_canonical"
return "", response_articulation_surface, "runtime"
def _assessment_residual(
contract_assessment: "ContractAssessment | None",
) -> ResidualState | None:
if contract_assessment is None:
return ResidualState(detail="contract_assessment is None")
return ResidualState(
missing_bindings=tuple(contract_assessment.missing_bindings),
unresolved_hazards=tuple(contract_assessment.unresolved_hazards),
detail=str(contract_assessment.explanation or ""),
)
def _truth_path_base(
*,
canonical_surface: str,
pre_decoration_surface: str,
response_surface: str,
) -> str:
"""Register-invariant base folded into ``trace_hash``.
Canonical-first: the composer's pre-R6 capture is the truth-path
identity field, byte-identical across register packs (ADR-0069
inv C / ADR-0077). Falls through to pre-decoration, then the
response itself for turns that never captured a canonical surface.
"""
return canonical_surface or pre_decoration_surface or response_surface
def _abstention_resolution(
@ -211,9 +237,17 @@ def _grounded_open_hedge_resolution(
response_surface=response_surface,
response_articulation_surface=response_articulation_surface,
)
truth_base = _truth_path_base(
canonical_surface=canonical_surface,
pre_decoration_surface=pre_decoration_surface,
response_surface=response_surface,
)
surface = (
f"{_GROUNDED_OPEN_HEDGE_PREFIX} {base_surface}" if base_surface else base_surface
)
hash_surface = (
f"{_GROUNDED_OPEN_HEDGE_PREFIX} {truth_base}" if truth_base else truth_base
)
articulation = (
f"{_GROUNDED_OPEN_HEDGE_PREFIX} {base_articulation}"
if base_articulation
@ -229,6 +263,7 @@ def _grounded_open_hedge_resolution(
refusal=None,
contract_violation=None,
proof_trace=None,
hash_surface=hash_surface,
)
@ -322,6 +357,11 @@ def resolve_surface(
response_surface=response_surface or "",
response_articulation_surface=response_articulation_surface or "",
)
hash_surface = _truth_path_base(
canonical_surface=canonical_surface or "",
pre_decoration_surface=pre_decoration_surface or "",
response_surface=response_surface or "",
)
# === DUAL-COMPETING SHADOW COHERENCE GATE ===
# Forward and conjugate evaluated as independent competitors; commit
@ -340,6 +380,7 @@ def resolve_surface(
if not gate_blocks and realized_surface and forward_ok and conjugate_ok:
surface = realized_surface
hash_surface = realized_surface
articulation_surface = realized_surface
authority = "substrate_realizer"
elif (
@ -352,12 +393,16 @@ def resolve_surface(
# Transitional shim: geometric coherence holds, but graph not yet
# fully grounded. Never used when conjugate residual fails.
surface = realized_surface
hash_surface = realized_surface
articulation_surface = realized_surface
authority = "realizer"
fold_sources: list[str] = []
if walk_surface:
surface = f"{surface}{walk_surface}" if surface else walk_surface
hash_surface = (
f"{hash_surface}{walk_surface}" if hash_surface else walk_surface
)
articulation_surface = (
f"{articulation_surface}{walk_surface}"
if articulation_surface
@ -367,6 +412,9 @@ def resolve_surface(
if compose_surface:
surface = f"{surface}{compose_surface}" if surface else compose_surface
hash_surface = (
f"{hash_surface}{compose_surface}" if hash_surface else compose_surface
)
articulation_surface = (
f"{articulation_surface}{compose_surface}"
if articulation_surface
@ -392,6 +440,7 @@ def resolve_surface(
refusal=None,
contract_violation=None,
proof_trace=proof,
hash_surface=hash_surface,
)

View file

@ -0,0 +1,52 @@
# Handoff Brief — Tier O (Opus 5, MEDIUM risk)
**Read first:** `docs/plans/generalization-arc-2026-07-24.md` (the plan of
record — §4 gold contract and §6 constraint set are binding), then
`AGENTS.md`. Reference ADRs on demand: 02560259 (band recipe),
0175/0199 (license doctrine), 0251/0252 (math-reader guardrails).
**You own, in order:**
1. **O1 — Band v6-EX (existential `some` witnesses).** Follow the band
recipe exactly as ADR-0258/0259 executed it: reader module in
`generate/proof_chain/`, shape bands in `shape.py`, surface wiring in
`chat/deduction_surface.py`, arena templates ≥720/band in
`evals/deduction_serve/practice/gold.py`, hand-authored lane split,
ledger reseal, tests, ADR, research doc. Gotchas that bit before:
cross-module `_Reject` identity (catch both classes); band-N swallows
band-N+1 test texts (verify the prior band really refuses your
declined cases); surgical single-line pin edits only (never
`verify_lane_shas.py --update`).
2. **O2 — Phase 2 physics serve lane** per the §4 curriculum-entailment
contract. Premises come ONLY from ratified domain chains
(`teaching/domain_chains/physics_chains_v1.jsonl` + pack mounts);
anti-recall probes (gold=UNKNOWN for true-but-untaught) are mandatory
or the lane must not ship. Independent closed-world oracle first,
then the lane. Biology second, only after O3 lands.
3. **O3 — generic consumption bridge**: extract the seal→ratify→
SHA-verified-license→serve-gate pattern (models:
`chat/deduction_serve_license.py`,
`generate/determine/estimation_license.py`) into a reusable ADR-0175
Phase-5 bridge so subject arenas plug in without bespoke wiring.
4. **O4 — math reader: seeding-sentence injection** (standing ruling
#77 — the 75% "no injection" wall). Then compare unblock (4.2).
Hard guardrails: no bespoke-per-case regex growth (ADR-0251); check
every new pattern against
`docs/research/reader-arc-overfit-inventory-2026-07-19.md`; measure
on the frozen tune/measure split; wrong=0 on the full 500 is the
gate, parse-rate gain is the goal.
**Constraints (non-negotiable):** wrong=0; all flags stay default-off —
flips are Shay's ratification only; Forgejo-primary (no `gh`); worktree
per unit off `forgejo-https/main`; fresh venv per worktree
(`rm -rf .venv && uv sync --locked --offline`, then `uv run --no-sync`);
pre-push hook runs smoke+warmed_session (~4.5 min — budget timeouts);
PRs opened by Shay via the compare URL the push prints (push PAT 403s
the PR API); Shay merges — never you; branch+worktree deleted after
merge; no timelines in any doc.
**Your exit checkpoint (O→S):** v6-EX merged; ≥1 subject lane serving
wrong=0 with earned licenses; bridge merged; math increment measured and
documented (a null result is a valid checkpoint). Then update
`BRIEF-S-sonnet-low-tier.md` with actual state, update memory, and stop
for the Sonnet handoff.

View file

@ -0,0 +1,47 @@
# Handoff Brief — Tier S (Sonnet 5, LOW risk)
**Read first:** `docs/plans/generalization-arc-2026-07-24.md` §6
(constraint set is binding) and the Tier-O brief's constraints block —
they apply verbatim. Opus will have updated this brief with actual state
at its exit checkpoint; trust that update over anything stale below.
**You own (independent items, any order unless noted):**
1. **S1 — ratification packet for `deduction_serving_enabled`.**
Evidence collation only — the flip is Shay's decision, never yours.
Contents: 17+-band sealed ledger stats
(`chat/data/deduction_serve_ledger.json`), serve-lane results
(`evals/deduction_serve/report.json`), byte-identical-when-off proof
(cite ADR-0256 + `tests/test_deduction_surface.py` flag tests), blast
radius (exact dispatch point `chat/runtime.py` deduction branch), and
rollback (flip back, zero residue). One markdown doc in
`docs/research/`, PR'd.
2. **S2 — lane re-pins + flake documentation.** Only AFTER the Phase-0.1
drift findings doc exists and says re-pin is safe. Surgical
single-line edits in `scripts/verify_lane_shas.py` — NEVER `--update`
(it rewrites every lane and silently drops erroring lanes' pins).
Document the `public_demo` env-timeout flake where the findings doc
says.
3. **S3 — vocab-trigger instrument.** Implement the measurable test
specified in `docs/handoffs/COMPREHENSION-READER-AUDIT.md`
(§measurable-test, lines ~163166 and ~231238): refusal histogram
split mechanism-vs-coverage + Phase-2-admissions-per-lexicon-batch
counter. CLI or lane runner per the spec; do not invent policy — the
spec decides, you implement.
4. **S4 — HITL proposal-queue CLI.** A `core` CLI surface listing +
reviewing pending proposals from the existing sinks
(`teaching/proposals/`, contemplation/idle sinks). Read + review-state
transitions only; NO ratification automation, NO corpus mutation, NO
flag flips.
5. **S5 — housekeeping.** Promotion sweeps Opus's checkpoint notes call
for; capability-index entries for new subject lanes; docs/memory
updates; dead-code removal only when unambiguous.
**Constraints:** identical to Tier O (worktrees, fresh venv, pre-push
gate, compare-URL PRs, Shay merges, wrong=0, flags stay off, no
timelines). When any item's scope turns out larger than described here —
stop and flag it in the PR/handoff notes rather than improvising.
**Arc close:** all Tier-S PRs pushed; memory updated; note whether the
Phase-5 articulation trigger (multi-step decided content exists) has
fired, for the next arc's scoping.

View file

@ -0,0 +1,238 @@
# Generalization Arc — GSM8K-Level Capability Across Core Subjects
**Date:** 2026-07-24 · **Status:** ACTIVE · **Base:** main @ `5224b5e0` (post ADR-0259 / Band v4-CM)
This is the ratified plan of record for the arc that takes CORE from "one
fully-closed cognitive lifecycle (deduction-serve)" to "the same lifecycle
running across core subjects at GSM8K-exam level" — articulated answers,
problem solving, comprehension, all under wrong=0 discipline.
Execution is **risk-tiered across three operators** (§6): Fable 5 does the
high-risk design + build, Opus 5 the medium tier, Sonnet 5 the low tier.
Handoff briefs live in `docs/handoff/generalization-2026-07/`.
---
## 1. Ground truth at arc start
- Exactly one lifecycle is closed end-to-end: **deduction-serve**
(comprehend → 5 band readers → ROBDD decide → sealed practice arena,
17 bands × 720, θ_SERVE=0.99 → SHA-verified ratified ledger → license
gate → render → register chain). Dark behind
`deduction_serving_enabled=False`, awaiting ratification.
- Cross-subject substrate exists but is idle: 16 unmounted domain seed
packs, 5 domain chain corpora, 6 OOD fluency lanes, five-layer
teaching-order doctrine (`docs/teaching_order.md`), capability index.
- Learning-loop organs exist but are write-only or dark: 19 default-off
flags; proposal/contemplation/promotion paths emit into review sinks
with no runtime consumer. Three real ratified-artifact consumers exist
(deduction ledger, estimation license, recognizer registry) — the
pattern to generalize.
- Math is reader-gated, not solver-gated: compiler holds 50/50 wrong=0 on
dev-1 but the reader parses ~1% of real GSM8K; the ~30-band bet was
falsified; standing ruling #77 selects **seeding-sentence injection**
as the next math move.
- ADR-0246 §3.7 identity calibration is explicitly OFF this critical
path (blocked on §11 research evidence; doctrine order:
capability → practice → calibration → serve).
## 2. Phases
### Phase 0 — Truth-substrate hygiene + first live serve *(Small)*
- **0.1** Root-cause the 3-lane pin drift (`miner_loop_closure`,
`curriculum_loop_closure`, `demo_composition`). Classify: determinism
bug (stop-the-line) vs stale pins vs local/CI byte divergence.
Re-pin surgically only after root-cause. Findings doc in
`docs/research/`.
- **0.2** Ratification packet for `deduction_serving_enabled` (evidence
collation only; the flip is Shay's decision).
**Done when:** drift explained + resolved; packet delivered.
### Phase 1 — Reading generalization: verb predicates + existentials *(Large)*
The band cascade reads only copula sentences; every other subject states
facts as verb sentences. Ordered by leverage:
- **1.1 Band v5-VP — verb predicates**: predicate atoms keyed by
(verb lemma, argument tuple); intransitive + transitive with
named/classed arguments ONLY; closed morphology; typed refusal for
everything else (voice, tense shifts, ditransitives, PP-arguments).
This band gates Phase 2.
- **1.2 Band v6-EX — existential (`some`) witnesses**: completes the
all/no/some square; witness-based lowering.
- Deferred (unchanged from ADR-0259 §5): universal-nested-in-connective
(bound-variable tracking), identity/co-reference, tense.
Recipe per band (proven 3×): reader module → shape bands → arena
templates (≥720/band) → hand-authored lane split (content-disjoint from
practice lexicon) → ledger reseal → tests → ADR → research doc.
**Done when:** verb-predicate and existential arguments decided at SERVE
wrong=0; ledger resealed; promotion sweep of previously-declined cases
done honestly (promotion = *decided correctly*, not *decided favorably*).
### Phase 2 — Port the lifecycle to two non-math subjects *(Medium-Large)*
Physics and biology first (OOD lanes, seed packs, and domain chains
already exist). Implemented strictly to the **curriculum-entailment gold
contract** (§4 — the load-bearing design of this arc):
- Mount subject packs; wire domain chains through the teaching corridor
per the five-layer order.
- Exam-shaped serve lanes: question → read (v5-VP reading) → decide
**from ratified curriculum premises only** → licensed articulated
answer.
- Per-subject band ledgers via the same arena; capability-index
coverage entries; 3-domain anti-overfit panel discipline
(independence must be in the READING).
**Done when:** two subjects serve wrong=0 on hand-authored exam-shaped
lanes with earned licenses, zero subject-specific engine code.
### Phase 3 — Close the discovery→learning loop *(Medium)*
- **3.1** Vocab-expansion trigger as code (the COMPREHENSION-READER-AUDIT
§measurable-test, made a standing instrument: refusal histogram split
mechanism-vs-coverage; admissions-per-lexicon-batch).
- **3.2** HITL proposal-queue surface (review CLI over the existing
sinks).
- **3.3** Generic consumption bridge: extract the seal → ratify →
SHA-verified license → serve-gate pattern (deduction ledger is the
model; estimation license the second instance) into the ADR-0175
Phase-5 bridge so each subject arena plugs in without bespoke wiring.
- **3.4** Feed discovery-yield with real traffic (post 0.2 flip).
**Done when:** a serve-time gap flows discovery → proposal →
ratification → runtime consumption with no manual file surgery.
### Phase 4 — Math problem-solving reader *(Medium-Large, parallel lane)*
Independent of Phases 13 (no shared files; production-line pattern):
- **4.1** Seeding-sentence injection (ruling #77; the 75% "no injection"
wall).
- **4.2** Compare unblock (summation-question reader + inverse compare),
queued behind 4.1 per the same ruling.
- **4.3** q:complex decomposition study (increment-1 band plan §9; 101
cases behind one label).
- Guardrails pinned: `docs/research/reader-arc-overfit-inventory-2026-07-19.md`,
ADR-0251 recalibration (no bespoke-per-case regex growth),
ADR-0252.
**Done when:** real-GSM8K parse floor materially above ~1% with wrong=0
held on the full 500; ceiling revised with evidence.
### Phase 5 — Articulation depth (`generate/` "core_logos") *(trigger-gated)*
Not queued by order. Trigger: Phases 12 produce multi-step decided
content (proof chains, multi-fact answers) — the evidence of where
articulation quality binds that the intelligence-loop plan's Phase 6
deferral was waiting for. Scope the arc from that evidence.
## 3. Dependencies
- Phase 1.1 gates Phase 2. Phase 0 first.
- Phase 3.3 lands before the *second* subject arena (avoid copy-paste
wiring). 3.1/3.2 interleave freely.
- Phase 4 fully parallel. Phase 5 trigger-gated on 12.
- §3.7 identity calibration stays downstream of the whole arc (its
calibration data comes *from* this arc's practice volume).
## 4. Curriculum-entailment gold contract (Phase-2 design, decided here)
This section is the HIGH-risk design, fixed now so the medium tier can
implement without re-litigating epistemology.
1. **Verdict domain** — every exam item resolves to
{entailed, refuted, unknown, declined}, identical to deduction-serve.
UNKNOWN is the honest verdict for untaught facts. No open-world
recall, ever.
2. **Two question shapes** — (a) self-contained arguments (premises in
the text): already owned by bands v2v6; (b) **curriculum-grounded
questions** (the new Phase-2 shape): question text supplies only the
query; the premise set is compiled from the RATIFIED domain chain
corpus for that subject. Gold is a function of (curriculum, question)
— never of case-local hidden text. This is the decoding-not-generating
line in mechanical form.
3. **Premise provenance** — each lane case pins the chain IDs it draws
on; the runner reconstructs premises from the ratified corpus and
MUST fail the case if a pinned chain is absent or unratified.
4. **Independent oracle** — per subject, a closed-world reachability
oracle over the chain corpus (sharing no code with the serving path)
validates every gold verdict; the lane asserts corpus soundness
before any case runs (the `assert_corpus_sound()` pattern).
5. **License granularity** — bands keyed by (subject × relation family ×
chain depth), earned in the ADR-0199 arena: θ_SERVE=0.99, n≥720,
wrong=0, sealed + SHA-verified ledger per subject.
6. **Typed refusals, lifecycle-mapped**`untaught_vocabulary`,
`unratified_chain`, `out_of_curriculum`, `ambiguous_reading`; the
OOV refusal feeds the Phase-3 proposal queue (refusals become
discovery, not dead ends).
7. **Anti-recall probes (mandatory per lane)** — cases whose answer is
true in the world but absent from the curriculum; gold = UNKNOWN.
A lane without these probes cannot prove the system decodes rather
than recalls, and MUST NOT ship.
## 5. Risks
- **HIGH — subject-gold epistemology drift**: mitigated by §4 (contract
fixed before implementation; anti-recall probes mandatory).
- **HIGH — v5-VP scope creep**: argument structure/voice/agreement are a
real jump from copula. Mitigation: two argument shapes only, closed
morphology, typed refusal for the rest — the same discipline that made
v2v4 land clean.
- **MEDIUM — arena scale**: 720/band × growing band count on local
hardware; linear sealing time; watch, don't block.
- **MEDIUM — synthetic-overfit trap**: practice templates and lane cases
stay content-disjoint per subject (standing doctrine).
- **MEDIUM — q:complex may not decompose**: nothing gates on Phase 4.
- **LOW — drift root-cause escalates**: if Phase 0.1 finds a determinism
bug, the arc pauses until it is fixed (stop-the-line).
## 6. Risk-tiered operator assignment
Constraint set for every tier: wrong=0; flags stay default-off (flips
are Shay's ratification only); Forgejo-primary, no GitHub/`gh`; local
smoke + warmed_session gate before every push (pre-push hook); `uv`
always; branch + worktree per unit; no merge automation; one PR per
coherent solution; attribution disabled.
### Tier F — Fable 5 (HIGH risk; this session)
- F1: Phase 0.1 drift root-cause (+ surgical re-pin if benign).
- F2: this plan doc + handoff briefs.
- F3: §4 gold contract (done — it is this document).
- F4: Band v5-VP (Phase 1.1), full recipe, PR.
**Checkpoint F→O:** F1/F2/F4 PRs pushed with compare URLs; memory
updated; briefs current. Opus starts at O1 with no open design
questions.
### Tier O — Opus 5 (MEDIUM risk)
- O1: Band v6-EX existentials (Phase 1.2) — recipe + ADR-0257/0258/0259
as references.
- O2: Phase 2 physics + biology serve lanes per §4 (mount packs, wire
chains, arena, ledgers, lanes). Physics first; biology second only
after the generic bridge (O3) lands.
- O3: Phase 3.3 generic consumption bridge.
- O4: Phase 4.1 seeding-sentence injection, then 4.2; 4.3 as analysis.
Guardrail docs pinned in §2/Phase 4.
**Checkpoint O→S:** v6-EX merged; ≥1 subject lane serving wrong=0 with
earned licenses; bridge merged; math increment measured + documented
(whatever its outcome — a null result is a valid checkpoint).
### Tier S — Sonnet 5 (LOW risk)
- S1: Phase 0.2 ratification packet (evidence collation).
- S2: Post-fix lane re-pins; public_demo flake documentation.
- S3: Phase 3.1 vocab-trigger instrument (spec is pinned in
`docs/handoffs/COMPREHENSION-READER-AUDIT.md` §measurable-test).
- S4: Phase 3.2 HITL proposal-queue CLI surface.
- S5: Promotion sweeps, capability-index entries, docs/memory
housekeeping.
**Arc close:** all three tiers merged; Phase 5 trigger evaluated with
evidence in hand; next arc scoped from it.
## 7. Non-goals (this arc)
- ADR-0246 §3.7 / identity-gate calibration (evidence-blocked).
- `core/ports/` Ring-2/3 consumption (own future flag-gated units).
- Universal-nested-in-connective, co-reference, tense bands.
- Any serving-flag flip without explicit ratification.
- Zig substrate work.

View file

@ -0,0 +1,125 @@
# Lane-Pin Drift Investigation — miner / curriculum / demo_composition
**Date:** 2026-07-24 · **Worktree:** `core-wt-gen` off main @ `5224b5e0`
**Verdict:** two distinct causes; no determinism bug; one **real serving
regression found and fixed** (register axis stripped from pipeline-served
turns).
Context: during the Band v4-CM arc (2026-07-23), a `verify_lane_shas.py
--update` dry run showed three lanes' pins stale. This investigation
root-causes all three before any re-pin (generalization-arc Phase 0.1).
## Determinism check
Each drifted lane was run twice in a fresh worktree (fresh
`uv sync --locked` venv, hermetic `CORE_ENGINE_STATE_DIR`): byte-identical
SHAs across runs for `miner_loop_closure` (`537094fe…`) and
`curriculum_loop_closure` (`cb94ca00…`). **Not a determinism bug.**
## Cause 1 — content-digest widening (miner + curriculum): benign, stale pins
Commit `5c69b741` (2026-07-17, ADR-0244 Phase 5a "§2.7 content-id
semantic rigor") widened `core/contemplation/schema.py::_content_digest`
from 16-hex truncation to the full 64-hex sha256 (birthday-collision
rigor). Both lanes' reports embed `finding_id` / derived `proposal_id`
values from that digest: the fresh `finding_id` is the 64-hex extension
of the exact pinned 16-hex prefix (`4d6b8b5e1d46eb60` →
`4d6b8b5e1d46eb60ea7a…`), and every downstream `proposal_id` cascades.
The commit re-pinned nothing. **Intentional, ADR-tracked, benign → both
pins re-pinned surgically in this branch** (single-line edits; never
`--update`, which rewrites every lane and silently drops erroring
lanes' pins).
## Cause 2 — demo_composition: a REAL serving regression (found via the drift)
The fresh lane report flipped `register-tour` `all_claims_supported`
true→false (plus `composite_supported`, which is `audit ∧ register`).
Isolation showed the register tour's two substantive-differentiation
claims failing while the three invariance claims held: **terse/convivial
registers no longer differed from neutral on pack-grounded definitions**
— the entire register axis (ADR-0077 R6 substantive + ADR-0071 R4
decoration) was missing from pipeline-served surfaces.
Mechanism (three commits, each individually defensible):
1. `ff1dcb25` (2026-05-20, #76) introduced `resolve_surface` with a
`_base_runtime_surface` precedence of **canonical-first** — the
trace-hash identity capture preferred over the runtime's served bytes.
Dormant at first.
2. `e0d1b475` (2026-07-20, #96 fail-closed linguistic governance) routed
pipeline turns through the resolver with `canonical_surface` populated
→ pipeline-served turns began serving the pre-R6 canonical. The
runtime's own `chat()` path stayed correct; `warmed_session`
telemetry-consistency went red (0.9444) because TurnEvent (correct
bytes) disagreed with pipeline (stripped bytes).
3. `5a343b49` (2026-07-22, T13) "fixed" the telemetry red by
back-stamping the pipeline's (stripped) surface onto TurnEvent —
making telemetry consistent with the *regressed* serving. The register
tour's claims — the falsifiable contract for the register axis —
then failed, which is exactly the byte drift the lane pin caught.
Why no gate caught it: the e2e tests that pin this behavior
(`tests/test_register_substantive_consumption.py::test_e2e_terse_*`)
are red on main but are not in the curated smoke suite; the lane-shas CI
job is the only gate that runs the demo lanes, and its failures were
conflated with the known `public_demo` env flake.
### Fix (this branch)
`core/cognition/surface_resolution.py` + `core/cognition/pipeline.py`:
- `_base_runtime_surface` precedence corrected to **response-first**
(served bytes = post-guard, post-R6, post-R4), canonical demoted to
last-resort fallback.
- New `SurfaceResolution.hash_surface`: the truth-path
(canonical-precedence) bytes, kept in lockstep through substrate
overrides, folds, hedge prefix, logos-morph override, and the
speculative marker. `compute_trace_hash` now folds `hash_surface`,
so **trace_hash stays register-invariant (ADR-0069 inv C) while the
served surface carries the register**. `_prior_surface` (correction
binding) also stays on the truth path so register packs cannot perturb
teaching.
- Removed dead `FailureClass` import + `_assessment_residual` (repo-wide
unreferenced).
Verification: register tour **6/6 claims** (both substantive-differs
claims restored AND `all_trace_hashes_identical` held);
`tests/test_surface_resolution.py` (updated precedence tests + 2 new
fallback tests), `test_register_substantive_consumption.py` (previously
red e2e tests now green), `test_grounded_open_hedge_arm.py`,
`test_cognitive_turn_pipeline.py`, `test_warmed_session_lane.py` (T13
consistency) — all green; smoke suite via pre-push gate.
### demo_composition pin disposition (measured)
After the fix, every claims-supported flag in the lane report matches
the committed report again (`all_claims_supported_a/b` true,
`composite_supported` true). The only residual delta vs the 2026-07-14
pin is the two tours' *inner payload* `sha256` values — those payloads
embed per-prompt `trace_hash` values whose format legitimately changed
post-pin (`e7d116c9`, 2026-07-20: Phase-B graph-topology inclusion in
`compute_trace_hash`). Semantics restored, embedded hash format
evolved → re-pinned to `f0611a2c…` with this diff as the audit trail.
## public_demo (not one of the three, for completeness)
Unchanged: its known failure mode is the env-timeout flake
(`all_claims_supported=False` under cold/slow runs), documented in
memory and the lane-shas remediation text. Do not re-pin from a laptop
run; adjudicate on the Act runner.
## Bonus latent break found while re-pinning
`scripts/generate_claims.py` raised on every run since the
`deduction_serve_v1` pin landed (2026-07-23): the pin was added to
`PINNED_SHAS` without the required `_LANE_ADR` entry, so the lane-shas
CI's `generate_claims.py --check` step has been red since then. Fixed
here (ADR-0256 row added; CLAIMS.md regenerated, check green).
## Follow-ups (assigned in the generalization-arc plan)
- Tier S: add `tests/test_register_substantive_consumption.py` e2e
tests to the curated smoke suite so this axis can never silently
regress again (small, mechanical).
- Tier S: document the lane-shas CI expectation that a red lane job is
triaged per-lane, never assumed to be the public_demo flake.

View file

@ -74,6 +74,10 @@ _LANE_ADR: dict[str, tuple[str, str]] = {
"ADR-0206",
"Propositional entailment scored against an independent truth-table oracle; dev+holdout+external 716/716 correct, wrong=0, refused=0",
),
"deduction_serve_v1": (
"ADR-0256",
"Flag-gated deduction serving decides real English/member/fused arguments end-to-end under earned SERVE licenses; wrong=0 across all splits",
),
}

View file

@ -43,11 +43,11 @@ LANE_TIMEOUT_S = int(os.environ.get("CORE_LANE_VERIFY_TIMEOUT_S", "900"))
PINNED_SHAS: dict[str, str] = {
"reviewer_registry": "681a2aab5aa4ffd58cd837ce5673c8b2a9545b570117aec3c02726a12f6876e6",
"miner_loop_closure": "9f071733abe7dcacf759f928548ce738fb639af3fd6e4c621a651b306d7e77ce",
"curriculum_loop_closure": "b46d56b2d209172cc3ffaf3776dc8dcfe55093f13587c5cb67372be6dfa23e8d",
"miner_loop_closure": "537094fe21d7e6cfbaf42bfc32b82d669fa9bb05a132d2bc93c72b3ceb7762a6",
"curriculum_loop_closure": "cb94ca0042d78ec2624129ff6493d52e767b69feea32d2997b85d88f1c0883af",
"domain_contract_validation": "98ace04e3f02bbc5a8ad655bb6593c3f1ee64cb67014f1122fe6c3c85f48d22f",
"fabrication_control_summary": "01e1b6b711141f2b4a14551d7df3ea482d8d6dd7b364a25c509f4f8d08cda8a8",
"demo_composition": "e2ba2314d8768459fb6a8db082a4bbcf4107b5161d869804a4b2a33c3724081a",
"demo_composition": "f0611a2ce41721dd40767fc6a83a08470d3c7fd7fc8f1ae8ba003abf8a25ec97",
"public_demo": "7d8ba0dbae9287cfe0bf15d231fa78a75abc627121c14900439293e01e1cc1d3",
"math_teaching_corpus_v1": "eaf160d145da29f9050ede8d58bf111b0f651dd40aeae9201857d0b97e014dd4",
"deductive_logic_v1": "97a230949016e38d5e3f37a69e4245b320575ee70e5af92ff7607f7b05f74b5f",

View file

@ -32,7 +32,10 @@ def _open_assessment() -> ContractAssessment:
)
def test_runtime_canonical_surface_has_base_precedence() -> None:
def test_runtime_response_surface_has_base_precedence() -> None:
# response_surface is the served bytes (post register R6/R4 —
# ADR-0077/ADR-0071); canonical is the trace-hash identity capture
# and must never displace served bytes when a response exists.
resolved = resolve_surface(
canonical_surface="canonical",
pre_decoration_surface="pre-decoration",
@ -41,13 +44,39 @@ def test_runtime_canonical_surface_has_base_precedence() -> None:
contract_assessment=_closed_assessment(),
)
assert resolved.surface == "canonical"
assert resolved.surface == "runtime"
assert resolved.articulation_surface == "articulation"
assert resolved.authority == "runtime_canonical"
assert resolved.authority == "runtime"
assert resolved.fold_sources == ()
assert resolved.authoritative is True
def test_canonical_surface_is_last_resort_fallback() -> None:
resolved = resolve_surface(
canonical_surface="canonical",
pre_decoration_surface="",
response_surface="",
response_articulation_surface="articulation",
contract_assessment=_closed_assessment(),
)
assert resolved.surface == "canonical"
assert resolved.authority == "runtime_canonical"
def test_pre_decoration_outranks_canonical_when_response_absent() -> None:
resolved = resolve_surface(
canonical_surface="canonical",
pre_decoration_surface="pre-decoration",
response_surface="",
response_articulation_surface="articulation",
contract_assessment=_closed_assessment(),
)
assert resolved.surface == "pre-decoration"
assert resolved.authority == "runtime_pre_decoration"
def test_useful_realizer_requires_conjugate_coherence() -> None:
"""Realizer shim only when conjugate geometric contract is closed."""
resolved = resolve_surface(