core/docs/research/lane-drift-investigation-2026-07-24.md
Shay f95ac26ec0 fix(cognition): restore register axis on pipeline-served turns + Phase 0 of generalization arc
Plan of record for the generalization arc lands here
(docs/plans/generalization-arc-2026-07-24.md) with the risk-tiered
handoff pack (docs/handoff/generalization-2026-07/); Phase 0.1 executed.

Root-caused the three stale lane pins (docs/research/lane-drift-
investigation-2026-07-24.md):
- miner/curriculum: 5c69b741 (ADR-0244 §2.7) widened content digests
  16→64 hex; intentional, pins were simply never updated. Re-pinned
  surgically; SHAs triple-run deterministic.
- demo_composition: a REAL serving regression. #76's canonical-first
  base precedence in resolve_surface, activated when #96 routed pipeline
  turns through the resolver, served the pre-R6 canonical — the register
  axis (ADR-0077 R6 substantive + ADR-0071 R4 decoration) was silently
  stripped from every pipeline-served turn. T13's back-stamp then made
  telemetry mirror the stripped bytes; register-tour claims went red and
  the e2e tests pinning the axis (outside smoke) were failing on main.

Fix: response-first base precedence (served bytes = post-guard, post-R6,
post-R4) + new SurfaceResolution.hash_surface carrying the truth-path
canonical-precedence bytes through substrate overrides, folds, hedge,
logos-morph, and the speculative marker. compute_trace_hash folds
hash_surface, so the register axis varies the served surface ONLY and
trace_hash stays register-invariant (ADR-0069 inv C). _prior_surface
(correction binding) stays on the truth path. Dead FailureClass import
and _assessment_residual removed.

Also: generate_claims.py had raised on every run since the
deduction_serve_v1 pin landed without a _LANE_ADR entry — mapping added
(ADR-0256), CLAIMS.md regenerated, --check green.

[Verification]: register tour 6/6 claims (substantive-differs restored,
all_trace_hashes_identical held); 88 tests green in-worktree
(test_surface_resolution incl. 2 new fallback tests,
test_register_substantive_consumption incl. previously-red e2e tests,
test_grounded_open_hedge_arm, test_cognitive_turn_pipeline,
test_warmed_session_lane 10/10 T13 consistency); miner/curriculum lane
SHAs byte-identical across three runs; deduction_serve + deductive_logic
lane reports byte-identical to their unchanged pins; smoke suite via
pre-push gate.
2026-07-24 12:28:32 -07:00

6.3 KiB

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 (4d6b8b5e1d46eb604d6b8b5e1d46eb60ea7a…), 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.