Extends ADR-0022 with inspection/telemetry surfaces that turn the
forward-semantic-control claim from "mechanism exists" into "mechanism
is causally load-bearing, isolated, and replayable."
Changes (zero runtime semantics change beyond a pipeline bug fix):
- AdmissibilityTraceStep + GenerationResult.admissibility_trace —
per-transition record of region label, candidates before/after,
selected destination, and the typed AdmissibilityVerdict.
- ChatResponse + CognitiveTurnResult expose admissibility_trace,
admissibility_trace_hash, ratification_outcome,
region_was_unconstrained.
- hash_admissibility_trace + compute_trace_hash fold the new fields
only when they carry non-default values, so pre-ADR-0023 turn
hashes remain byte-preserved.
- Same-path ablation leg in evals/forward_semantic_control/runner.py:
generate(..., region=None) vs generate(..., region=R) on the same
runtime/vocab/field/persona/prompt — isolates the region as cause.
- Lane expansion: 8 dev cases across 4 relation axes (cause, means,
precedes, part_of) including 2 adversarial distractor cases.
- Lane metrics now report region_only_constrained_rate /
region_only_gap / ratified_rate / demoted_rate / passthrough_rate /
passthrough_on_scored.
- Bug fix surfaced by the new accounting: _ratify_intent looked up
runtime.vocab (always None) instead of runtime.session.vocab —
every production turn was silently PASSTHROUGH. Fixed; ratifier
now actually gates intent classification.
- tests/test_admissibility_trace.py: hash determinism +
pre-ADR-0023 byte-preservation tests.
Lane evidence (dev, 8 cases):
- constrained_pass_rate=0.80, causality_gap=0.80
- region_only_gap=1.00 (5/5 with region, 0/5 without — same path)
- ratified_rate=1.00, passthrough_on_scored=false
- overall_pass=true
Bench: 9.41s / 20 turns (~470ms/turn), well inside the +5% budget.
Full pytest: 922 passed, 1 pre-existing failure
(test_language_pack_cache, unrelated to ADR-0023).
Resolves all 5 TBDs and closes all 8 acceptance gates for ADR-0022.
TBD-1 (intent oracle): regex seed + field ratification —
generate/intent_ratifier.py. RATIFIED / DEMOTED / PASSTHROUGH
outcomes; DEMOTED routes through honest refusal.
TBD-2 (region intersection algebra): generate/admissibility.py.
Token-set composition via sorted set intersection; blade composition
via outer product with zero-blade as neutral element; rotor
composition via sandwich conjugation routed through
algebra.backend.versor_apply (Rust parity preserved by construction).
Empty intersections preserved — no silent relaxation.
Wiring: propose() and generate() accept an AdmissibilityRegion
(default None preserves legacy behavior); pipeline ratifies intent
at step 1b.i before graph construction.
Eval lane: evals/forward_semantic_control/ — both legs run against
CognitiveTurnPipeline (constrained) vs bare ChatRuntime.chat()
(unconstrained baseline). Dev (3 cases) and public/v1 (1 case) both
report overall_pass=true, causality_gap=1.0, coincidence_rate=0.0.
Chain-endpoint probe surfaces 'delta' only under forward semantic
control.
Bench cost (30 turns): -2.8% wall-clock (within +5% budget the ADR
set for the ratification gate on every turn). 138x cheaper than
Sonnet 4.5; main was 142x.
Tests: 33 new (25 admissibility + 8 ratifier). Full suite 912/913
pass — the single failure is pre-existing pack-size drift on main,
unrelated.
Categorizes every production vault.recall() callsite as RECOGNITION,
EVIDENCE_TELEMETRY, or EVIDENCE_USER_FACING. Adds INV-24 architectural
invariant (TestINV24VaultRecallRegistry, 3 tests) that forces any new
callsite to declare its role and requires EVIDENCE_USER_FACING sites to
pass min_status=COHERENT.
Audit findings:
- chat/runtime.py:330 → RECOGNITION (gate decision input)
- vault/decompose.py:121 → RECOGNITION (grade-decomposed gate fallback)
- generate/stream.py:147 → EVIDENCE_TELEMETRY (walk_surface per runtime contract)
- No EVIDENCE_USER_FACING sites exist today — user-facing surface comes from
pack-grounded realize(proposition, vocab), not vault.recall.
Why this closes Leak C: the write-side fix already stamps SPECULATIVE on
self-stored propositions; the read-side audit confirms no inference path
treats them as ratified evidence. If a future change routes the
generation walk into the user-facing surface, INV-24 forces the
recategorization to be explicit.
CLAIMS.md Tier 4.5 Leak C row now CLOSED. docs/truth_seeking_schema.md
§Leak C updated with full audit categorization.
Verified: smoke (67), cognition (121), runtime (19), all architectural
invariants (40) — green.
Three issues in the drift-fix landing (922bddc) addressed:
1. algebra/rotor.py: add rotor_power(R, alpha) — slerp on the rotor manifold
via the rotor's exp/log decomposition. Handles both rotation planes
(cos/sin) and boost planes (cosh/sinh); falls back to identity for
non-simple bivectors or null cases.
2. generate/stream.py: the score-weighted vault recall previously did
`weight*V + (1-weight)*np.eye(V.shape[0])`. Two bugs:
- np.eye produced a 32x32 matrix for a 1D multivector, crashing
versor_apply with a broadcasting error (2 cognition tests failing
on main).
- The linear blend produced multivectors with versor_condition up to
2.2e-2, violating the non-negotiable 1e-6 invariant declared in
CLAUDE.md. Now uses rotor_power(V, weight) which stays on the
manifold by construction (versor_condition <= 1.1e-16).
3. session/context.py: respond() now re-binds result.final_state to
self.state after finalize_turn's anchor pull, restoring the
"respond returns the same object that was vaulted" contract
(test_engine_loop_proof regression).
Verification:
- 41 new tests in tests/test_rotor_power.py covering closure preservation,
alpha=0/1 boundaries, half-angle composition, and word-transition rotors.
- Empirical multi-turn versor_condition stays at machine epsilon with
anchor pull, max 9.4e-7 without (under threshold either way after fix).
- Full suite: 609 passed, 4 skipped, 0 failed.
1. session/context.py — dialogue blade accumulation is now magnitude-preserving
via EMA (α=0.15). Running blade grows stronger each turn a concept is
confirmed rather than resetting to unit magnitude on every record_dialogue().
2. generate/stream.py — vault recall transitions are now score-weighted.
Each recalled rotor is scaled by softmax(scores)[i] before application so
high-confidence vault hits dominate and stale low-score entries barely move
the field.
3. session/context.py — anchor pull added after _hemisphere_consistent_field().
A mild α=0.05 slerp toward _anchor_field is applied at finalize_turn() to
provide continuous conjugate correction against angular drift within the
hemisphere. Unitized before writing back to state.
- Fix running_dialogue_blade grade explosion: replace outer_product
accumulation (which pushed past grade-5 in Cl(4,1), silently zeroing
the blade from turn 3 onward) with CGA-inner-oriented blade tracking
that preserves grade-2 across arbitrary turn counts.
- Add versor_condition guard at session composition boundary: cross-turn
field composition via versor_apply now fails closed (threshold 1e-2,
matching algebra construction residue tolerance) instead of silently
propagating degraded fields into vault and generation.
- Replace VaultStore list with deque(maxlen=max_entries): eliminates
O(N) list.pop(0) on every bounded eviction; deque auto-evicts in O(1).
- Replace O(N) vocab scan in generate/stream.py stop_nodes construction
with O(1) try/except index lookup per stop token.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- remove normalization and unitization calls from generation path
- skip invalid recalled fields instead of repairing them in generation
- punctuate selected articulation surfaces
- stabilize assertive dialogue roles
- anchor proposition slots to live field
- preserve session anchor orientation for coherence
- restore articulation surface as ChatResponse.surface while retaining walk_surface telemetry
- calibrate moderate E2 energy boundary
- reclose generated field states after propagation and recall
- restore pytest-safe REPL parsing and field_walk helper
- anchor proposition predicate selection to prompt field
- make vault exact self-recall deterministic
- align chat telemetry regression with restored surface contract
- calibrate identity threshold and per-axis telemetry
- keep walk surfaces visible when identity flags are telemetry
- report real vault recall hits through generation/runtime logs
- record selected surface in TurnEvent
- fix async chat persona reference
- add regression coverage for chat telemetry
agenerate() skipped _recall_state() entirely, meaning async streaming
responses were disconnected from session memory. This patch brings
agenerate() to full parity with the synchronous path:
- Accepts vault and recall_top_k parameters (default 3, matching generate())
- Calls _recall_state(_voiced_state(current, persona), vault, recall_top_k)
at each step before nearest-node selection
- Does not add stop_nodes or salience (those remain sync-only for now;
the core correctness gap is vault recall)
The async return value is still token-by-token via yield. Callers that
want final_state should use the synchronous path or wrap in a collector.
Add RuntimeConfig with English default output policy, wire output language through runtime/frame selection/generation/CLI, preserve language metadata in mounted manifolds, and add runtime/CLI policy tests.
- field/state.py: FieldState is now frozen+slotted; constructor copies and
enforces float32 shape (32,); advance() updated to pass raw arrays.
np.ndarray inside frozen dataclass is ref-frozen — copy() at construction
is the explicit contract boundary.
- generate/result.py: NEW — GenerationResult frozen dataclass carrying
tokens + final_state. Async variant yields tokens and exposes final_state
on completion.
- generate/stream.py: generate() now returns GenerationResult, not list[str].
vocab.edge_rotor() call replaced with:
A = vocab.get_versor_at(current.node)
B = vocab.get_versor_at(word_idx)
V = word_transition_rotor(A, B)
agenerate() updated to yield tokens and surface final_state.
- vocab/manifold.py: added get_versor_at(idx) and get_word_at(idx) indexed
accessors. VocabManifold stores points; algebra constructs operators.
normalize_to_versor() call-site in docstring clarified: callers must call
unitize_versor() (algebra construction primitive) before add(), not
normalize_to_versor() directly.
- algebra/versor.py: unitize_versor() added as the explicit construction-time
primitive. normalize_to_versor() kept but marked internal/gate-only.
Distinction encoded in docstrings and __all__.
- persona/motor.py + ingest/gate.py: SessionContext.respond() is not yet in
the repo as a separate file; gate.py docstring updated to reflect the
three-tier normalization doctrine:
unitize_versor() — algebra construction only
inject() — gate, once per raw input
normalization — forbidden in propagate/generate/vault recall