feat(cognition): count the swallow — three silent backstops made visible (PR-9, H-11)
Wave 2's PR-9, whose authority is "mechanical PR" and which is gated on no
ruling. Widened by the 2026-07-28 triage from the accrual guard alone to all
three broad `except` blocks on the turn spine.
Each guard is DELIBERATE and stays: accrual is additive and must never crash a
turn; a pack/catalog failure must not take down logos authority; a probe
failure must not touch the served surface. Narrowing them to ImportError/
OSError — as an external assessment proposed — would convert a malformed
decision object into a turn-spine crash, trading silent failure for
served-surface failure. What was wrong is not the tolerance, it is that each
wrote a value INDISTINGUISHABLE FROM ITS HEALTHY CASE:
- a raising read->realize->determine chain left the same None accrual a quiet
turn leaves;
- a logos decision that raised looked exactly like a turn where logos was
never consulted (both: logos_decision_kind == "");
- a crashed geometric probe recorded the same empty neighborhood as a probe
that ran and found nothing — in the block whose entire job is producing the
data that prices the anti-unification roadmap.
Silence, in the one layer whose constitution is "failures are typed, never
silent" (INV-34).
chat/runtime.py _accrual_swallows / _last_accrual_error, read via
accrual_swallow_telemetry(). _last_turn_accrual is
still None on failure, so _maybe_surface_determination
returns byte-identically — ADDITIVE BY CONSTRUCTION,
and pinned as such.
cognition/result.py logos_error on CognitiveTurnResult.
cognition/pipeline.py logos_error recorded; probe_error,
neighbor_scan_errors, graph_anti_unify_error on the
OOV telemetry.
tests/test_accrual_swallow_telemetry.py — 7 pins. The two behavioral ones were
OBSERVED RED against a build carrying the fields with the recording stripped,
so they discriminate rather than merely pass. Two test bugs of mine were fixed
along the way (wrong logos module path; CognitiveTurnResult has required
fields) — the tests were wrong, not the code.
Registered in BOTH TEST_SUITES["smoke"] and smoke.yml on creation, per #136.
Local/CI delta verified unchanged at exactly ten (local 25, CI 15, CI-only 0),
so this settles nothing R-14 has to rule.
Cleanup as found: three dead imports removed from chat/runtime.py
(_COGNITION_PACK_ID, _TEACHING_CORPUS_ID, engine_identity_for_config), each
confirmed to have no re-export dependence first — workbench/readers.py imports
engine_identity_for_config straight from core.engine_identity. ruff 4 -> 1 on
these paths; the survivor is a pre-existing structural E402.
Registers: H-11 CLOSED; PR-9 marked LANDED in the plan.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wcw2pnMBwyvmNyQg4uPEt4
This commit is contained in:
parent
2cfc6ad251
commit
98a6e8b439
8 changed files with 252 additions and 11 deletions
3
.github/workflows/smoke.yml
vendored
3
.github/workflows/smoke.yml
vendored
|
|
@ -61,4 +61,5 @@ jobs:
|
|||
tests/test_audio_*.py \
|
||||
tests/test_pack_measurements_phase2.py \
|
||||
tests/test_register_substantive_consumption.py \
|
||||
tests/test_speculative_subject_lifecycle.py
|
||||
tests/test_speculative_subject_lifecycle.py \
|
||||
tests/test_accrual_swallow_telemetry.py
|
||||
|
|
|
|||
|
|
@ -21,13 +21,11 @@ from chat.pack_grounding import (
|
|||
pack_grounded_relation_confirmation_surface,
|
||||
pack_grounded_unknown_surface,
|
||||
gloss_aware_cause_surface,
|
||||
PACK_ID as _COGNITION_PACK_ID,
|
||||
)
|
||||
from chat.teaching_grounding import (
|
||||
teaching_grounded_surface,
|
||||
teaching_grounded_surface_composed,
|
||||
teaching_grounded_surface_transitive,
|
||||
TEACHING_CORPUS_ID as _TEACHING_CORPUS_ID,
|
||||
)
|
||||
from chat.refusal import (
|
||||
TYPED_REFUSAL_PREFIX,
|
||||
|
|
@ -61,7 +59,6 @@ from engine_state import EngineStateStore
|
|||
from core.engine_identity import (
|
||||
ENGINE_IDENTITY_SCHEME,
|
||||
IdentityReconciliation,
|
||||
engine_identity_for_config,
|
||||
reconcile_loaded_identity,
|
||||
compute_engine_identity,
|
||||
DEFAULT_SAFETY_PACK,
|
||||
|
|
@ -767,6 +764,15 @@ class ChatRuntime:
|
|||
# realized into the held self, or determined over it). Introspectable; the
|
||||
# surface contract is unchanged (slice B-1 records, does not surface).
|
||||
self._last_turn_accrual: TurnAccrual | None = None
|
||||
# PR-9 (H-11) — the accrual guard's visibility. ``_accrue_in_turn``'s
|
||||
# broad ``except`` is a deliberate backstop (accrual is additive and must
|
||||
# never crash a turn), but it wrote the same ``None`` a quiet turn
|
||||
# writes, so a raising read→realize→determine chain was
|
||||
# indistinguishable from nothing-to-accrue. These count and name it.
|
||||
# Observational only: never read by any serving path, never folded into
|
||||
# ``trace_hash``.
|
||||
self._accrual_swallows: int = 0
|
||||
self._last_accrual_error: str | None = None
|
||||
self._relational_pack_lemmas: frozenset[str] | None = None
|
||||
self._engine_state_store: EngineStateStore | None = (
|
||||
None if no_load_state else EngineStateStore(engine_state_path)
|
||||
|
|
@ -1344,6 +1350,20 @@ class ChatRuntime:
|
|||
accrual is off or did not complete. Introspection only — never surfaced."""
|
||||
return self._last_turn_accrual
|
||||
|
||||
def accrual_swallow_telemetry(self) -> tuple[int, str | None]:
|
||||
"""PR-9 (H-11) — ``(swallows, last_error)`` for the accrual backstop.
|
||||
|
||||
``swallows`` counts how many times ``_accrue_in_turn``'s defensive guard
|
||||
absorbed an exception this session; ``last_error`` is the most recent
|
||||
``repr``. Both are zero/None on a healthy session, which is the point:
|
||||
a nonzero count means the read→realize→determine chain raised somewhere,
|
||||
and that fact used to be unrecoverable from a ``None`` accrual.
|
||||
|
||||
Observational only. Nothing on a serving path reads this, and it is
|
||||
never folded into ``trace_hash``.
|
||||
"""
|
||||
return self._accrual_swallows, self._last_accrual_error
|
||||
|
||||
def _accrue_in_turn(self, text: str) -> None:
|
||||
"""Inline realization (Step B): a comprehensible declarative turn accrues a
|
||||
realized fact into the held self (session vault, SPECULATIVE / as-told); a
|
||||
|
|
@ -1393,7 +1413,17 @@ class ChatRuntime:
|
|||
)
|
||||
return
|
||||
self._last_turn_accrual = TurnAccrual(kind="none")
|
||||
except Exception: # additive: accrual must never crash a turn # noqa: BLE001
|
||||
except Exception as exc: # additive: accrual must never crash a turn # noqa: BLE001
|
||||
# PR-9 (H-11) — count the swallow. The backstop stays exactly as it
|
||||
# was: ``_last_turn_accrual`` is still None, so every consumer sees
|
||||
# byte-identical behavior (``_maybe_surface_determination`` returns
|
||||
# the response unchanged on None). What changes is that the guard is
|
||||
# no longer invisible. A None accrual previously had two
|
||||
# indistinguishable causes — "nothing to accrue" and "the chain
|
||||
# raised" — inside the one layer whose constitution is that failures
|
||||
# are typed, never silent (INV-34). These fields separate them.
|
||||
self._accrual_swallows += 1
|
||||
self._last_accrual_error = repr(exc)
|
||||
self._last_turn_accrual = None
|
||||
|
||||
def _accrue_estimate_if_refused(self, comprehension: Any, determination: Any) -> "TurnAccrual":
|
||||
|
|
|
|||
|
|
@ -92,6 +92,15 @@ TEST_SUITES: dict[str, tuple[str, ...]] = {
|
|||
# the same ten files (H-12), so it settles nothing R-14 has to rule.
|
||||
# ~9s, cache-level with two served-surface assertions.
|
||||
"tests/test_speculative_subject_lifecycle.py",
|
||||
# PR-9 (H-11) — the turn spine's three defensive backstops must stay
|
||||
# broad AND stay visible. Each used to write a value indistinguishable
|
||||
# from its healthy case: a raising accrual chain left the same None a
|
||||
# quiet turn leaves, a logos decision that raised looked like one never
|
||||
# consulted, a crashed probe recorded the same empty neighborhood as a
|
||||
# probe that found nothing. Silence in the one layer whose constitution
|
||||
# is "failures are typed, never silent" (INV-34). Registered in both
|
||||
# gates on creation, per #136. ~6s.
|
||||
"tests/test_accrual_swallow_telemetry.py",
|
||||
# ADR governance. An ADR that governs a default-ON flag records a
|
||||
# decision already in force, so leaving it "Proposed" makes the
|
||||
# governance record assert something false about the running system —
|
||||
|
|
|
|||
|
|
@ -542,6 +542,7 @@ class CognitiveTurnPipeline:
|
|||
# certified singular-exclusivity claim against observed plural HE.
|
||||
# English-only turns with no HE surface → no-op (None).
|
||||
logos_decision_kind = ""
|
||||
logos_error = ""
|
||||
logos_decision_reason = ""
|
||||
logos_rule_id = ""
|
||||
logos_constraint_id = ""
|
||||
|
|
@ -568,9 +569,18 @@ class CognitiveTurnPipeline:
|
|||
hash_surface = surface
|
||||
articulation_surface = surface
|
||||
authority_source = "logos_morph_constraint"
|
||||
except Exception:
|
||||
except Exception as exc:
|
||||
# Pack load / catalog failure must not crash the turn spine;
|
||||
# English path continues without morph authority.
|
||||
#
|
||||
# PR-9 (H-11, widened by the 2026-07-28 triage) — the backstop is
|
||||
# kept deliberately broad: narrowing it to ImportError/OSError would
|
||||
# convert a malformed decision object into a turn-spine crash,
|
||||
# trading silent failure for served-surface failure. What it stops
|
||||
# being is invisible. Without this, a logos decision that raised
|
||||
# looked exactly like a turn where logos never fired, so a
|
||||
# constraint failing to block a certified answer left no trace.
|
||||
logos_error = repr(exc)
|
||||
logos_decision = None
|
||||
|
||||
# SUBSTRATE_BYPASS_HAZARD telemetry (data-driven roadmap).
|
||||
|
|
@ -619,7 +629,9 @@ class CognitiveTurnPipeline:
|
|||
if grounding_src == "oov" or has_pending:
|
||||
# Active conformal neighborhood probe (exact cga_inner over vault).
|
||||
probe_performed = False
|
||||
probe_error = ""
|
||||
probe_neighbors: list[dict[str, object]] = []
|
||||
neighbor_scan_errors = 0
|
||||
try:
|
||||
from algebra.backend import cga_inner as _cga_inner
|
||||
|
||||
|
|
@ -636,6 +648,11 @@ class CognitiveTurnPipeline:
|
|||
try:
|
||||
s = float(_cga_inner(F_probe, versor))
|
||||
except Exception:
|
||||
# PR-9 — a shape mismatch on one vault entry skips
|
||||
# that entry, as before; the count makes a
|
||||
# systematically unscannable vault visible instead
|
||||
# of reading as an empty neighborhood.
|
||||
neighbor_scan_errors += 1
|
||||
continue
|
||||
label = str(getattr(entry, "id", "") or getattr(entry, "key", "") or "")
|
||||
scores.append((s, label))
|
||||
|
|
@ -644,12 +661,20 @@ class CognitiveTurnPipeline:
|
|||
{"cga_inner": s, "ref": lab} for s, lab in scores[:5]
|
||||
]
|
||||
probe_performed = True
|
||||
except Exception:
|
||||
except Exception as exc:
|
||||
# PR-9 (H-11) — observability that fails silently is worse than
|
||||
# none: "probe ran, found nothing" and "probe crashed before it
|
||||
# could look" were the same record, and this block's whole job
|
||||
# is producing the data that prices the anti-unification
|
||||
# roadmap. The backstop stays; the reason is now recorded.
|
||||
probe_performed = False
|
||||
probe_error = repr(exc)
|
||||
oov_geometric_context = {
|
||||
"unresolved_topology": effective_graph.get_unresolved_topology() if effective_graph else (),
|
||||
"intent_tag": getattr(intent, "tag", None).value if intent and getattr(intent, "tag", None) else "unknown",
|
||||
"geometric_probe_performed": probe_performed,
|
||||
"probe_error": probe_error,
|
||||
"neighbor_scan_errors": neighbor_scan_errors,
|
||||
"conformal_neighbors": probe_neighbors,
|
||||
"note": "Conformal anti-unification probe: vault neighbors via exact cga_inner.",
|
||||
"node_depths": node_depths,
|
||||
|
|
@ -670,9 +695,13 @@ class CognitiveTurnPipeline:
|
|||
if oov_geometric_context is None:
|
||||
oov_geometric_context = {}
|
||||
oov_geometric_context["graph_anti_unify"] = graph_anti_unify(topo, node_depths)
|
||||
except Exception:
|
||||
# Best-effort telemetry only; anti-unify failure must not affect main path.
|
||||
pass
|
||||
except Exception as exc:
|
||||
# Best-effort telemetry only; anti-unify failure must not affect
|
||||
# main path. PR-9 — but an ABSENT key and a FAILED call are
|
||||
# different facts about the turn, and only one of them is a bug.
|
||||
if oov_geometric_context is None:
|
||||
oov_geometric_context = {}
|
||||
oov_geometric_context["graph_anti_unify_error"] = repr(exc)
|
||||
|
||||
# Capture depths (post-enrich) to attrs for recognize chaining (AC1) + runtime contemplate depth= (real).
|
||||
# Propagation contract (3-lang depth on PropGraph spine): pipeline (after OOV/PropGraph construction)
|
||||
|
|
@ -955,6 +984,7 @@ class CognitiveTurnPipeline:
|
|||
logos_decision_reason=logos_decision_reason,
|
||||
logos_rule_id=logos_rule_id,
|
||||
logos_constraint_id=logos_constraint_id,
|
||||
logos_error=logos_error,
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -230,3 +230,10 @@ class CognitiveTurnResult:
|
|||
logos_decision_reason: str = ""
|
||||
logos_rule_id: str = ""
|
||||
logos_constraint_id: str = ""
|
||||
#: PR-9 (H-11) — ``repr`` of the exception the logos backstop absorbed, or
|
||||
#: "" when it did not fire. The guard stays deliberately broad (narrowing it
|
||||
#: would turn a malformed decision into a turn-spine crash); this is what
|
||||
#: stops it being silent. An empty ``logos_decision_kind`` used to mean
|
||||
#: either "not consulted" or "raised", which are very different facts about
|
||||
#: a turn. Observational; never folded into trace_hash.
|
||||
logos_error: str = ""
|
||||
|
|
|
|||
|
|
@ -90,6 +90,14 @@ Ranked by leverage (cognitive/structural load removed ÷ effort), per the AGENTS
|
|||
**Evidence:** `_accrue_in_turn`'s broad guard converts any exception in the read→realize→determine chain into a no-op accrual with no telemetry (F-10). Defensible as a backstop; invisible as a signal — in the one layer whose constitution is "failures are typed, never silent" (INV-34).
|
||||
**Better home:** count the swallow (a telemetry field on `IdleTickResult`/turn accrual), not a behavior change.
|
||||
**Authority:** mechanical PR.
|
||||
**Status (2026-07-28): CLOSED — PR-9 landed, across all three instances.**
|
||||
- `chat/runtime.py::_accrue_in_turn` — `_accrual_swallows` / `_last_accrual_error`, read via `accrual_swallow_telemetry()`. `_last_turn_accrual` still becomes `None`, so `_maybe_surface_determination` returns byte-identically: **additive by construction**, and pinned as such.
|
||||
- `core/cognition/pipeline.py` logos authority — `logos_error` on `CognitiveTurnResult`. An empty `logos_decision_kind` previously meant *either* "not consulted" *or* "raised"; those are now distinguishable.
|
||||
- The OOV geometric probe — `probe_error`, `neighbor_scan_errors`, and `graph_anti_unify_error`. This block exists to price the anti-unification roadmap, and a crashed probe used to serialize identically to one that ran and found nothing.
|
||||
|
||||
Every guard stays **deliberately broad**. Narrowing them to `ImportError`/`OSError`, as an external assessment proposed, converts a malformed decision object into a turn-spine crash — trading silent failure for served-surface failure, the worse trade. What changed is visibility, not tolerance.
|
||||
|
||||
`tests/test_accrual_swallow_telemetry.py`, 7 pins, **the two behavioral ones observed red** against a build with the fields present but the recording removed — so they discriminate rather than merely pass. Registered in **both** gates on creation (#136); local/CI delta unchanged at ten (local 25, CI 15, CI-only 0).
|
||||
|
||||
## H-12 · The two smoke gates have diverged by ten files, and one comment says they cannot
|
||||
**Verdict:** `strained` — a real asymmetry, *smaller* than it first looks · **Layers:** MV
|
||||
|
|
|
|||
|
|
@ -197,11 +197,13 @@ Formation's six-boundary standard written for `ingest/gate.py` in `runtime_contr
|
|||
### PR-8 · Materialise the typed refusal · **M** · H-3/G-20 — *needs a small ADR (the ADR-0024 chain reserved the seam)*
|
||||
`InnerLoopExhaustion`'s reason/region/rejected-attempt evidence reaches `ChatResponse.refusal_reason` and a minimal honest served surface, instead of `""`. **Verification:** a refusing turn serves a non-empty, typed, replayable refusal, and `trace_hash` behavior is unchanged for non-refusing turns.
|
||||
|
||||
### PR-9 · Count the swallow · **S** · H-11 — *scope widened 2026-07-28*
|
||||
### PR-9 · Count the swallow · **S** · H-11 — **LANDED 2026-07-28**
|
||||
A telemetry field on the turn/idle accrual result when `_accrue_in_turn`'s broad guard fires. **Behavior unchanged by construction** — the backstop stays; it stops being invisible.
|
||||
|
||||
**Widened to the same failure class elsewhere on the spine** (external-assessment triage): the logos-authority block's bare `except Exception` (`core/cognition/pipeline.py:555`) and the three layered OOV-probe swallows (`:605-657`) get the same remedy shape — record `probe_error = repr(e)` in the telemetry, keep the backstop. Observability that fails silently cannot distinguish "probe ran, found nothing" from "probe crashed first," which poisons the very roadmap data the OOV block exists to produce. **Explicitly not the narrow-catch alternative** (catching only `ImportError`/`OSError`): that converts a malformed decision object into a turn-spine crash — trading silent failure for served-surface failure.
|
||||
|
||||
**Landed.** All three instances instrumented; `_last_turn_accrual` still becomes `None` so every consumer is byte-identical (additive by construction, and pinned). `tests/test_accrual_swallow_telemetry.py` — 7 pins, the two behavioral ones **observed red** against a build with the fields present but the recording stripped. Registered in both gates on creation; delta unchanged at ten.
|
||||
|
||||
### PR-10 · Extend declared precedence to the composer arms · **L** · H-4 — *refactor ADR*
|
||||
Lift `core/cognition/surface_resolution.py`'s declared-precedence pattern to arm selection in `chat/runtime.py`. **Do this while one arm is live** — the cost triples after the next three arms. Carries an in-code prospective sabotage note in the `surface_resolution.py` style. **Verification:** byte-identical serving on the full deduction + curriculum + register lanes; this refactor may not change a single served surface.
|
||||
|
||||
|
|
|
|||
154
tests/test_accrual_swallow_telemetry.py
Normal file
154
tests/test_accrual_swallow_telemetry.py
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
"""PR-9 (H-11) — the defensive backstops stop being invisible.
|
||||
|
||||
Three broad ``except`` guards on the turn spine are deliberate and stay:
|
||||
accrual is additive and must never crash a turn; a pack/catalog failure must
|
||||
not take down the logos authority; a probe failure must not affect the served
|
||||
surface. Narrowing them would trade silent failure for served-surface failure,
|
||||
which is the worse trade.
|
||||
|
||||
What was wrong is that each wrote a value indistinguishable from the healthy
|
||||
case. A raising read→realize→determine chain left the same ``None`` accrual a
|
||||
quiet turn leaves. A logos decision that raised looked exactly like a turn
|
||||
where logos was never consulted. A crashed geometric probe recorded the same
|
||||
empty neighborhood as a probe that ran and found nothing — in the block whose
|
||||
whole job is producing the data that prices the anti-unification roadmap.
|
||||
|
||||
These pins hold the guards open *and* visible: behavior byte-identical,
|
||||
cause recorded. INV-34's "failures are typed, never silent" applied to the
|
||||
backstops that were exempt from it.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from chat.runtime import ChatRuntime
|
||||
from core.cognition import CognitiveTurnPipeline
|
||||
from core.cognition.result import CognitiveTurnResult
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def runtime() -> ChatRuntime:
|
||||
return ChatRuntime()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Accrual guard (chat/runtime.py::_accrue_in_turn)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_healthy_session_reports_no_swallows(runtime: ChatRuntime) -> None:
|
||||
swallows, last_error = runtime.accrual_swallow_telemetry()
|
||||
assert swallows == 0
|
||||
assert last_error is None
|
||||
|
||||
|
||||
def test_accrual_guard_counts_and_names_what_it_absorbs(
|
||||
runtime: ChatRuntime, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""A raising chain is absorbed exactly as before — and is now attributable."""
|
||||
runtime._context = object() # get past the early return
|
||||
|
||||
import generate.meaning_graph.reader as reader_mod
|
||||
|
||||
def _boom(*_args: object, **_kwargs: object) -> None:
|
||||
raise RuntimeError("reader exploded")
|
||||
|
||||
monkeypatch.setattr(reader_mod, "comprehend", _boom)
|
||||
|
||||
runtime._accrue_in_turn("light reveals truth")
|
||||
|
||||
swallows, last_error = runtime.accrual_swallow_telemetry()
|
||||
assert swallows == 1, "the guard fired but did not count itself"
|
||||
assert last_error is not None and "reader exploded" in last_error, (
|
||||
"the guard absorbed an exception without naming it"
|
||||
)
|
||||
|
||||
|
||||
def test_accrual_guard_behavior_is_unchanged_by_the_telemetry(
|
||||
runtime: ChatRuntime, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""The backstop still yields a None accrual — consumers see no difference.
|
||||
|
||||
``_maybe_surface_determination`` returns the response untouched on a None
|
||||
accrual, so this is the property that makes PR-9 additive by construction.
|
||||
"""
|
||||
runtime._context = object()
|
||||
|
||||
import generate.meaning_graph.reader as reader_mod
|
||||
|
||||
def _boom(*_args: object, **_kwargs: object) -> None:
|
||||
raise RuntimeError("still exploding")
|
||||
|
||||
monkeypatch.setattr(reader_mod, "comprehend", _boom)
|
||||
runtime._accrue_in_turn("light reveals truth")
|
||||
|
||||
assert runtime.last_turn_accrual() is None
|
||||
|
||||
|
||||
def test_a_quiet_turn_is_distinguishable_from_a_swallowed_one(
|
||||
runtime: ChatRuntime,
|
||||
) -> None:
|
||||
"""The whole point: two causes of a None accrual, now told apart."""
|
||||
runtime._accrue_in_turn("") # nothing to accrue — not a failure
|
||||
|
||||
assert runtime.last_turn_accrual() is None
|
||||
swallows, _ = runtime.accrual_swallow_telemetry()
|
||||
assert swallows == 0, "an empty turn must not read as a swallowed exception"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Pipeline guards (core/cognition/pipeline.py)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_result_carries_a_logos_error_field_defaulting_empty() -> None:
|
||||
"""An un-consulted logos path is "" — only a raise fills it."""
|
||||
import dataclasses
|
||||
|
||||
field = {f.name: f for f in dataclasses.fields(CognitiveTurnResult)}["logos_error"]
|
||||
assert field.default == ""
|
||||
|
||||
|
||||
def test_logos_guard_records_the_exception(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""A logos decision that raises is recorded, not silently treated as absent.
|
||||
|
||||
Without this, ``logos_decision_kind == ""`` meant either "not consulted" or
|
||||
"raised" — and a constraint failing to block a certified answer left no
|
||||
trace anywhere. The pipeline imports the authority *inside* its guarded
|
||||
block, so patching the module attribute is what the running turn resolves.
|
||||
"""
|
||||
from generate.observed_he_morph_v0 import authority as logos_mod
|
||||
|
||||
def _boom(*_args: object, **_kwargs: object) -> None:
|
||||
raise RuntimeError("catalog unavailable")
|
||||
|
||||
monkeypatch.setattr(logos_mod, "evaluate_logos_on_text", _boom)
|
||||
|
||||
pipeline = CognitiveTurnPipeline(runtime=ChatRuntime())
|
||||
result = pipeline.run("what is light?")
|
||||
|
||||
# The turn completed — the backstop did its job, exactly as before.
|
||||
assert isinstance(result.surface, str)
|
||||
# And it named the cause instead of being indistinguishable from silence.
|
||||
assert "catalog unavailable" in result.logos_error
|
||||
assert result.logos_decision_kind == ""
|
||||
|
||||
|
||||
def test_oov_probe_context_exposes_its_failure_channels() -> None:
|
||||
"""When the probe runs, its telemetry carries the error channels.
|
||||
|
||||
A crashed probe and an empty neighborhood must not serialize identically —
|
||||
the roadmap this block feeds is only trustworthy if the two are distinct.
|
||||
"""
|
||||
pipeline = CognitiveTurnPipeline(runtime=ChatRuntime())
|
||||
result = pipeline.run("what is zzqxwv?") # OOV-shaped input
|
||||
|
||||
ctx = result.oov_geometric_context
|
||||
if ctx is not None and "geometric_probe_performed" in ctx:
|
||||
assert "probe_error" in ctx, "probe telemetry lost its failure channel"
|
||||
assert "neighbor_scan_errors" in ctx
|
||||
# A healthy probe reports no error and an integer scan-failure count.
|
||||
assert isinstance(ctx["neighbor_scan_errors"], int)
|
||||
if ctx["geometric_probe_performed"]:
|
||||
assert ctx["probe_error"] == ""
|
||||
Loading…
Reference in a new issue