core/generate/proof_chain/render.py
Shay 82df3c08ae feat(deduction-serve): Phase 1 — deduction turn spine (flag-gated, propositional Band v1)
Wires the verified ROBDD entailment engine (generate/proof_chain,
716/716 wrong=0) into core chat serving. A propositional argument
('P1. P2. ... Therefore C.') is now comprehended, projected, and
decided end-to-end -- the first real working basic-logic workflow:
user question -> CORE decides -> articulated deterministic answer.

New: chat/deduction_surface.py (DEDUCTION composer, sibling to the
existing oov/narrative/example composer pattern), generate/proof_chain/
render.py (deterministic EntailmentTrace -> surface templates, no LLM),
tests/test_deduction_surface.py (15 tests incl. full propositional gold
corpus decided end-to-end across a multi-turn session, wrong=0).

Changed: core/config.py (deduction_serving_enabled flag, default off),
generate/intent.py (IntentTag.DEDUCTION, observability-only -- the core
classify_intent rule table is untouched so flag-off stays byte-identical),
chat/runtime.py (one flag-gated block in the existing pack/teaching/
partial/oov dispatcher).

Design notes (full detail in docs/research/deduction-serve-arc-phase1-
turn-spine-2026-07-23.md): the deduction check runs BEFORE generic intent
classification (never touches classify_intent's rule table) and BEFORE
the empty-vault gate (a live multi-turn probe caught turn-12 silently
falling through once the vault warmed -- pack/teaching/oov are cold-start-
only by design, deduction serving isn't). grounding_source='deduction' is
NOT registered in the closed, Workbench-coupled GroundingSource Literal
(cross-stack TS contract, inert for this arc's core-chat-only consumer,
documented as a deferred follow-up). Band v1 stays propositional-only --
categorical/syllogism arguments commit but honestly decline as
out-of-band; evals.syllogism.oracle is never imported (INV-25 intact).

[Verification]: smoke 180 passed; cognition 122 passed/1 skipped;
test_deduction_surface.py 15 passed; test_dispatch_trace.py +
test_oov_surface.py 26 passed; test_intent*.py 145 passed.
2026-07-23 12:14:28 -07:00

54 lines
2 KiB
Python

"""Deterministic surface rendering for a propositional ``EntailmentTrace``
(deduction-serve arc, Phase 1).
Renders the four ``Entailment`` outcomes into user-facing prose. Deterministic
templates only — no LLM, no synthesis; every visible token is either fixed
prose or a formula string ``to_deductive_logic`` produced directly from the
user's own text. This is the ONLY renderer for propositional-deduction
serving; it must not be confused with ``generate.determine.render`` (which
renders realized-structure DETERMINE answers — a different gear entirely).
"""
from __future__ import annotations
from generate.proof_chain.entail import (
INCONSISTENT_PREMISES,
Entailment,
EntailmentTrace,
)
def render_entailment(
trace: EntailmentTrace, premises: tuple[str, ...], query: str
) -> str:
"""The user-facing surface for a propositional deduction verdict.
Every branch is a fixed template around the literal premise/query
formula strings — no fabricated content. ``trace.outcome`` selects the
template; REFUSED further branches on ``trace.reason`` since an
inconsistent-premises refusal and an out-of-regime refusal warrant
different honest phrasing.
"""
given = "; ".join(premises)
if trace.outcome is Entailment.ENTAILED:
return f"Given: {given}. Your premises entail: {query}."
if trace.outcome is Entailment.REFUTED:
return (
f"Given: {given}. Your premises entail the opposite of "
f"{query} — it cannot hold."
)
if trace.outcome is Entailment.UNKNOWN:
return (
f"Given: {given}. Your premises don't settle whether "
f"{query} — it holds in some cases and fails in others."
)
# REFUSED
if trace.reason == INCONSISTENT_PREMISES:
return (
f"Given: {given}. Those premises are inconsistent — they "
f"can't all be true, so I won't assert anything from them."
)
return f"Given: {given}. I can't evaluate {query} from that as stated."
__all__ = ["render_entailment"]