fix(cognition): expose hash_surface; point the register-matrix pin at it
*** VERIFICATION PENDING — see the bottom of this message. *** 99 of the 107 full-suite failures are one test, red on clean main since 2026-07-23, and it is guarding the wrong field. WHAT IS NOT WRONG. test_register_matrix_trace_hash_invariant — the load-bearing ADR-0072 truth-path-isolation claim — passes on all 99 registers on pristine main. trace_hash does not vary across the register axis. The invariant holds; the seals and wrong=0 are untouched. WHAT IS. test_register_matrix_canonical_surface_byte_identical asserts on CognitiveTurnResult.surface, on its docstring's claim that this is "the truth-path field compute_trace_hash consumes". That stopped being true when Phase 0 flipped resolve_surface to response-first precedence and introduced SurfaceResolution.hash_surface as the register-invariant capture. Since then pipeline.py folds hash_surface into compute_trace_hash, while result.py documents surface as "final voiced surface (what the user sees)". So the test demanded that registers NOT differ on the served surface — the opposite of what the register axis exists for, and a direct contradiction of test_register_substantive_consumption.py, which pins that they DO differ and is green in smoke. Two tests on main asserting opposite things about the same bytes; the red one is in neither gate, so it stayed red. ROOT CAUSE, and why this is a code fix rather than a test edit: hash_surface was pipeline-local. The invariant was unobservable from a turn result, which is why the test reached for the nearest visible field and drifted. Exposing it makes the contract testable at the seam that owns it: - CognitiveTurnResult.hash_surface — new field, populated from the value the pipeline already computed - evals/cognition CaseResult carries it through - the test asserts byte-identity on hash_surface, with the history recorded so it cannot drift back No stored baseline to regenerate — the fixture computes it live. [Verification]: NOT YET RUN. The register matrix is ~16 min and was still executing when the working tree had to be committed; its output buffers through tail, so no partial signal was available. Committed to preserve the work with an honest label rather than to claim it passes. DO NOT MERGE until `pytest tests/test_cognition_eval_register_matrix.py` is green — expected 0 failed where main has 99, with test_register_matrix_trace_hash_invariant still passing. Result to follow. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FduW6Krm3PPQv3P5iwBYtx
This commit is contained in:
parent
3b2c7608a9
commit
bf17f42834
4 changed files with 49 additions and 12 deletions
|
|
@ -863,6 +863,7 @@ class CognitiveTurnPipeline:
|
||||||
proposition=response.proposition,
|
proposition=response.proposition,
|
||||||
articulation=response.articulation,
|
articulation=response.articulation,
|
||||||
surface=surface,
|
surface=surface,
|
||||||
|
hash_surface=hash_surface,
|
||||||
walk_surface=response.walk_surface,
|
walk_surface=response.walk_surface,
|
||||||
articulation_surface=articulation_surface,
|
articulation_surface=articulation_surface,
|
||||||
dialogue_role=response.dialogue_role,
|
dialogue_role=response.dialogue_role,
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,19 @@ class CognitiveTurnResult:
|
||||||
vault_hits: int
|
vault_hits: int
|
||||||
recall_energy_class: str | None = None
|
recall_energy_class: str | None = None
|
||||||
|
|
||||||
|
#: The register-INVARIANT truth-path bytes — what ``compute_trace_hash``
|
||||||
|
#: actually folds (ADR-0069 inv C, ADR-0077 R6). Distinct from ``surface``,
|
||||||
|
#: which is the served, register-decorated string the user reads.
|
||||||
|
#:
|
||||||
|
#: Exposed 2026-07-25. It was pipeline-local before that, which made the
|
||||||
|
#: truth-path-isolation invariant unobservable from a turn result — so
|
||||||
|
#: ``test_register_matrix_canonical_surface_byte_identical`` asserted it on
|
||||||
|
#: ``surface`` instead, and went red across all 99 registers the moment
|
||||||
|
#: Phase 0 flipped ``resolve_surface`` to response-first precedence and
|
||||||
|
#: moved the invariant here. The contract was never broken; it just could
|
||||||
|
#: not be seen. Empty string ⇒ identical to ``surface`` (no divergence).
|
||||||
|
hash_surface: str = ""
|
||||||
|
|
||||||
# --- intent / graph telemetry ---
|
# --- intent / graph telemetry ---
|
||||||
intent: DialogueIntent | None = None
|
intent: DialogueIntent | None = None
|
||||||
proposition_graph: PropositionGraph | None = None
|
proposition_graph: PropositionGraph | None = None
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,10 @@ class CaseResult:
|
||||||
versor_condition: float
|
versor_condition: float
|
||||||
trace_hash: str
|
trace_hash: str
|
||||||
surface: str
|
surface: str
|
||||||
|
#: The register-invariant truth-path bytes (what compute_trace_hash folds).
|
||||||
|
#: ``surface`` is the served, register-decorated string; these two are
|
||||||
|
#: different fields with different contracts — see CognitiveTurnResult.
|
||||||
|
hash_surface: str = ""
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True)
|
@dataclass(slots=True)
|
||||||
|
|
@ -71,6 +75,7 @@ def _run_case(case: dict[str, Any], pipeline: CognitiveTurnPipeline) -> CaseResu
|
||||||
versor_condition=result.versor_condition,
|
versor_condition=result.versor_condition,
|
||||||
trace_hash=result.trace_hash,
|
trace_hash=result.trace_hash,
|
||||||
surface=result.surface,
|
surface=result.surface,
|
||||||
|
hash_surface=result.hash_surface or result.surface,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -302,24 +302,42 @@ def test_register_matrix_versor_condition_byte_identical(
|
||||||
def test_register_matrix_canonical_surface_byte_identical(
|
def test_register_matrix_canonical_surface_byte_identical(
|
||||||
register_report, baseline_by_id,
|
register_report, baseline_by_id,
|
||||||
):
|
):
|
||||||
"""``CognitiveTurnResult.surface`` is the pre-decoration /
|
"""``hash_surface`` — the truth-path bytes ``compute_trace_hash`` folds —
|
||||||
canonical composer output (the truth-path field
|
must be byte-identical across the register axis.
|
||||||
``compute_trace_hash`` consumes). Substantive register transforms
|
|
||||||
apply downstream on ``turn_log[-1].surface``; the canonical
|
|
||||||
must remain byte-identical across the register axis.
|
|
||||||
|
|
||||||
If this test ever fails for a non-null register, the substantive
|
If this fails for a non-null register, a substantive transform has leaked
|
||||||
transform has leaked into the canonical surface and the
|
into the truth path and the isolation contract is broken.
|
||||||
truth-path-isolation contract is broken.
|
|
||||||
|
ASSERTED ON ``hash_surface``, NOT ``surface`` (corrected 2026-07-25).
|
||||||
|
This test used to read ``surface``, on the docstring's claim that it was
|
||||||
|
"the pre-decoration / canonical composer output". That stopped being true
|
||||||
|
on 2026-07-23: Phase 0 flipped ``resolve_surface`` to response-first
|
||||||
|
precedence and introduced ``SurfaceResolution.hash_surface`` as the
|
||||||
|
register-invariant capture, so ``pipeline.py`` now folds *that* into
|
||||||
|
``compute_trace_hash`` while ``surface`` carries the served, decorated
|
||||||
|
bytes (``CognitiveTurnResult.surface``: "final voiced surface (what the
|
||||||
|
user sees)").
|
||||||
|
|
||||||
|
Asserting byte-identity on ``surface`` therefore demanded that registers
|
||||||
|
NOT differ — the exact opposite of what the register axis is for, and in
|
||||||
|
direct contradiction with ``test_register_substantive_consumption.py``,
|
||||||
|
which pins that they DO differ and is green in smoke. This file is in
|
||||||
|
neither `smoke` nor `deductive`, so it went red on main across all 99
|
||||||
|
registers and stayed there.
|
||||||
|
|
||||||
|
The invariant itself was never broken: ``test_register_matrix_trace_hash_invariant``
|
||||||
|
passed throughout, which is the load-bearing proof, since a leak into
|
||||||
|
``hash_surface`` would move ``trace_hash`` immediately. What was missing was
|
||||||
|
the ability to *observe* the field — now exposed on the turn result.
|
||||||
"""
|
"""
|
||||||
register_id, report = register_report
|
register_id, report = register_report
|
||||||
rows = _diff_rows(
|
rows = _diff_rows(
|
||||||
register_id, baseline_by_id, _by_id(report), "surface",
|
register_id, baseline_by_id, _by_id(report), "hash_surface",
|
||||||
)
|
)
|
||||||
assert not rows, (
|
assert not rows, (
|
||||||
f"CANONICAL SURFACE LEAK under register {register_id!r} — "
|
f"TRUTH-PATH SURFACE LEAK under register {register_id!r} — "
|
||||||
"substantive transforms have escaped into the pre-decoration "
|
"substantive transforms have escaped into hash_surface, the bytes "
|
||||||
"surface that feeds compute_trace_hash.\n" + "\n".join(rows)
|
"compute_trace_hash folds.\n" + "\n".join(rows)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue