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.
54 lines
2 KiB
Python
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"]
|