core/tests/test_deduction_serve_e2e.py
Shay ed7c809fd0 feat(deduction-serve): Phase 5 — end-to-end REPL proof, telemetry checks, scope-out docs
Pins the arc's original goal against the ACTUAL serving spine (ChatRuntime,
the driver core chat constructs), not just the composer: a user asks a basic
logic question -> CORE decides -> an articulated, deterministic, telemetered
response comes back. Covers propositional + categorical, flag-off byte-identity
across both bands, honest reader-refusal on out-of-regime input (INV-34), and
the peripheral-systems wiring.

Peripheral systems verified (nothing to build, all automatic): deduction
turns are well-formed served turns (turn_log grows, _context.turn advances)
so ADR-0255 discovery-yield counts them; they carry grounding_source=
'deduction' on ChatResponse + TurnEvent and an observable DispatchAttempt;
they emit no discovery candidates (decided answer, not a would-have-grounded
gap) so they lift the yield denominator without inflating the numerator.

Scope-out doc (docs/research/deduction-serve-arc-completion-and-scope-outs-
2026-07-23.md) records the whole arc + everything deliberately deferred with
rationale: multi-word English reader relaxation (risky, shared reader, its
own pass), multi-step proof recap (Phase-6 core_logos), accrue_realized_
knowledge default, ADR-0246 s3.7 (blocked on research evidence not wiring),
Tier-2 (waits for field reader), Hamiltonian compiler (eval-side), and the
GroundingSource enum registration.

New: tests/test_deduction_serve_e2e.py (5 tests through the real REPL path).

[Verification]: core test --suite deductive 50 passed; smoke 180 passed;
cognition 122 passed/1 skipped.
2026-07-23 13:20:45 -07:00

87 lines
3.4 KiB
Python

"""Deduction-serve arc, Phase 5 — end-to-end workflow through the real REPL path.
The arc's original goal, pinned against the ACTUAL serving spine (``ChatRuntime``,
the driver ``core chat`` constructs) rather than the composer in isolation:
a user asks a basic logic question -> CORE decides -> an articulated,
deterministic, telemetered response comes back.
Also pins the peripheral-systems wiring (Phase 5): deduction turns are
well-formed ``TurnEvent``s tagged ``grounding_source="deduction"``, they advance
the served-turn counter (so ADR-0255 discovery-yield counts them), and they
carry an observable ``DispatchAttempt`` — with the whole thing byte-identical
when the flag is off.
"""
from __future__ import annotations
from chat.runtime import ChatRuntime
from core.config import RuntimeConfig
def _runtime(enabled: bool) -> ChatRuntime:
return ChatRuntime(
config=RuntimeConfig(deduction_serving_enabled=enabled), no_load_state=True,
)
def test_end_to_end_propositional_and_categorical_workflow() -> None:
rt = _runtime(True)
prop = rt.chat("If p then q. p. Therefore q.")
assert prop.grounding_source == "deduction"
assert "Your premises entail: q" in prop.surface
cat = rt.chat(
"All mammals are animals. All whales are mammals. "
"Therefore all whales are animals."
)
assert cat.grounding_source == "deduction"
assert "valid" in cat.surface and "follows" in cat.surface
def test_deduction_turns_are_well_formed_served_turns() -> None:
"""turn_log grows and the served-turn counter advances — so a deduction
conversation is real served traffic (ADR-0255 discovery-yield denominator)."""
rt = _runtime(True)
before = rt._context.turn
rt.chat("p or q. Not p. Therefore q.")
rt.chat("If a then b. If b then c. a. Therefore c.")
assert len(rt.turn_log) == 2
assert rt._context.turn == before + 2
last = rt.turn_log[-1]
assert last.grounding_source == "deduction"
def test_dispatch_trace_records_deduction_commit() -> None:
rt = _runtime(True)
resp = rt.chat("If p then q. p. Therefore q.")
assert resp.dispatch_trace is not None
assert resp.dispatch_trace.selected == "deduction"
committed = [
a for a in resp.dispatch_trace.attempts
if a.source == "deduction" and a.outcome == "admitted"
]
assert committed, "a committed deduction turn must record an admitted DispatchAttempt"
def test_flag_off_is_byte_identical_across_bands() -> None:
"""With the flag off, both a propositional and a categorical argument fall
through to the pre-arc pack-token-gloss surface, unchanged."""
rt = _runtime(False)
for text in [
"If p then q. p. Therefore q.",
"All mammals are animals. All whales are mammals. Therefore all whales are animals.",
]:
resp = rt.chat(text)
assert resp.grounding_source == "pack"
assert "Pack-resident tokens" in resp.surface
def test_out_of_regime_argument_refuses_honestly() -> None:
"""A 'therefore' argument the reader can't parse into either band gets an
honest committed refusal surface, never a fluent-but-ungrounded answer or a
silent fall-through (INV-34 fail-closed)."""
rt = _runtime(True)
resp = rt.chat("If it rains then the ground is wet. It rains. Therefore the ground is wet.")
assert resp.grounding_source == "deduction"
assert "can't parse" in resp.surface # honest reader-refusal disclosure