Closes W-015 wiring debt. Per Sonnet's investigation (PR #252,
verdict (c)): _slerp_toward interpolates on S^31 but the versor
manifold (Spin sub-group in Cl(4,1)) is a proper subset. Slerp's
geodesic doesn't stay on the manifold, producing systematic
off-manifold state that the post-hoc unitize_versor was repairing.
Fix replaces _slerp_toward with the proper rotor-geodesic path:
R = word_transition_rotor(field_state.F, anchor_field)
R_step = rotor_power(R, _ANCHOR_PULL_ALPHA)
pulled_F = versor_apply(R_step, field_state.F)
rotor_power stays on the manifold by construction (same principle
as generate/stream.py:220). versor_apply closes via algebra/
versor.py — an already-sanctioned site. The unsanctioned
unitize_versor call in _anchor_pull and the entire _slerp_toward
function are removed.
CLAUDE.md normalization-site discipline is now restored:
session/context.py:_anchor_pull no longer performs normalization.
Changes:
- session/context.py: import rotor_power + word_transition_rotor,
remove _slerp_toward (34 lines), rewrite _anchor_pull to use
rotor-geodesic (15 lines net change).
- tests/test_session_coherence.py: new test pins the manifold
invariant — after anchor pull, versor_condition stays < 1e-6
without any unitize call (32 lines).
Intentional lane re-pins (audit-trail per #229 discipline):
- demo_composition: 403be13b → 3a3d09f3 (anchor pull now produces
correct on-manifold fields; demo output shifts as expected).
- public_demo: acd51d0c → 888ddd0d (same cause).
CLAIMS.md regenerated to reflect new pins (per #239 lesson).
Verification:
- tests/test_session_coherence.py: 3 passed
- core test --suite smoke: 67 passed
- scripts/verify_lane_shas.py: 7/7 match (post-re-pin)
- Manifold invariant test pinned: anchor pull preserves
versor_condition < 1e-6 by construction (no repair).
Investigation source: PR #252 (Sonnet). 4,138-sample bimodal
distribution confirmed _slerp_toward as the sole drift source.
Three lane SHA pins drifted because intentional surface/serialization
changes shipped without re-running scripts/verify_lane_shas.py --update.
Bisect attributing the drift:
- demo_composition + public_demo broke at 5cad0a4 (#118 ADR-0110
mathematics_logic → expert_demo) — the demos enumerate the expert set.
- demo_composition drifted a second time at ab4c7cb (#220 Phase 3
state tagging spine) — additional epistemic fields shifted the surface.
- domain_contract_validation broke at a45eab1 (#219 Phase 2 epistemic
bug repairs) — normative/epistemic field shape changed.
The in-tree canonical report for fabrication_control_summary was also
stale vs. its (correct) pin; refreshed here for byte-alignment.
After this commit: 7/7 lanes match pinned SHAs; verify_lane_shas.py
runs green locally and in CI.
Followup (separate PR): hook/template guard so future PRs that touch
core/cognition/result.py, chat/runtime.py, or capability registries
re-run --update before merge.
DemoCommand Protocol + thin adapters retrofit shipped tours to a
typed composition contract. Composability becomes a structural
property: the ADR-0099 showcase will consume DemoResult through one
stable type rather than special-casing each tour. No demo behavior
changes — adapters wrap underlying run_tour() entry points.
- new core/demos/ package:
- contract.py: frozen Claim / DemoResult dataclasses, runtime-checkable
DemoCommand Protocol, canonical_json() sanctioned serializer
(sorted keys, 2-space indent, trailing newline), CLAIM_CONTRACT_VERSION
- audit_tour_adapter.py: AuditTourDemo (5 claims from ADR-0042 scenes
1-4: identity_pack_swaps_visible, safety_typed_refusal,
ethics_opt_in_deployment_fires, ethics_default_silent,
replay_byte_identical)
- tour_adapters.py: shared pattern for register/anchor-lens/orthogonality
tours; _extract_claims walks the dict tree for *_supported booleans
and builds Claim objects in deterministic sorted order
- global-state-mutation detector (ADR-0098 invariant #2):
capture_state() snapshots a load-bearing subset of process state
(CORE_* env vars + module identities for chat.telemetry,
chat.runtime, language_packs.compiler);
verify_no_global_state_mutation() ignores None→id transitions
(benign lazy import) and only flags env-var changes or module
identity rebindings
- new evals/demo_composition/ lane (ADR-0098 invariant proving):
- 6 cases asserting byte-equality + no-state-mutation across the
three fast adapters (audit-tour, register-tour, orthogonality-tour)
- composition_read_only: confirms two adapter results compose into
a composite claim set without mutating either
- stateful_fixture_rejected: negative control — a deliberately
stateful adapter MUST trigger divergence detection
- anchor-lens-tour adapter is exercised by tests, not the lane,
to keep wall time bounded
- byte-identical across runs (sha256 27d838241bf3..)
- 26 unit tests covering Claim/DemoResult validation, canonical_json
determinism, state-mutation detector (including the lazy-import
benign case), Protocol conformance (isinstance check + claim
contract version) for all four adapters, seed-rejection per
adapter (all current adapters are fully deterministic), and an
audit-tour integration smoke verifying 5 claims + byte-equality +
no state mutation across two consecutive runs
- smoke 67/67, cognition eval byte-identical 100/100/100/100, all
five lanes byte-identical (reviewer_registry 681a2aab..,
miner_loop_closure 9f071733.., domain_contract_validation f9c06cde..,
fabrication_control summary 01e1b6b7.., demo_composition 27d83824..)