core/generate/determine/determine.py
Shay aa66047b35 feat(comprehend): relational reader — binary-relation NL→structure
First consumer of en_core_relational_predicates_v1: a fail-closed sibling reader
that maps '<A> is [the] <connective> <B>' onto the pack's closed predicate
vocabulary (parent_of, less_than, left_of, before_event, ...), producing a standard
Comprehension. realize_comprehension consumes it unchanged; determine's guard widens
from the single 'member' predicate to a CLOSED direct-entailment set (member + the
ground binary relational predicates), keeping categorical/propositional predicates
soundly excluded.

Direct entailment only — no transitive/symmetric/rule inference (symmetric-converse
questions are a sound-but-incomplete refusal, never a faked assertion). Only the
predicate is closed-vocabulary; arguments may be OOV. The pack is loaded explicitly,
not default-mounted.

wrong=0 bites: reader refuses non-template/negated/pack-absent surfaces; determine
asserts only on direct entailment and excludes categorical predicates.
2026-06-06 09:32:08 -07:00

133 lines
6.4 KiB
Python

"""DETERMINE (roadmap Step 4) — reason over realized structure → the honest gear.
A question (a query-bearing ``Comprehension``) is answered ONLY from what the held
self has already REALIZED (R0/R1 structural recall) — never from the field, never
from an LLM, never from absence. The verdict is **as-told, never "verified"**: every
realizable record is SPECULATIVE, and ``ADMISSIBLE_AS_EVIDENCE = {COHERENT}``, so a
determination grounded in SPECULATIVE records carries ``basis="as_told"`` — "based on
what I was told (unverified)". Until COHERENT promotion exists (out of scope), D0
produces only ``as_told`` assertions or ``Undetermined`` refusals. No estimation, no
corpus mutation (teaching stays HITL proposal-only).
wrong=0 / soundness (open-world): D0 asserts an answer ONLY when the asked relation
is DIRECTLY entailed by a realized fact. Absence of a fact never refutes it
(open-world), so D0 never asserts an answer from missing knowledge — it refuses
(``Undetermined``). It asserts only ``answer=True`` on a direct hit; it never asserts
False.
Supported predicates are a CLOSED set for which DIRECT entailment is sound — a
realized ground fact ``p(subject, target)`` answers the asked ``p(subject, target)``:
the ``member`` relation (subsumption / "Is X a Y?") and the binary relational
predicates of ``en_core_relational_predicates_v1`` (``parent_of``, ``less_than``,
``left_of``, ``before_event`` …; see ``generate.meaning_graph.relational``). The
categorical (``subset``/``disjoint`` …) and propositional (``implies``/``or`` …)
predicates are deliberately EXCLUDED — their truth is not a stored-pair lookup, so
admitting them would be unsound. Negated questions, symmetric-converse questions, and
any predicate outside the closed set are an honest ``Undetermined`` — D0 ships no
entailment path it cannot exercise (no transitive/symmetric/rule inference).
"""
from __future__ import annotations
from dataclasses import dataclass
from generate.meaning_graph.reader import Comprehension, Refusal
from generate.meaning_graph.relational import RELATIONAL_PREDICATES
from generate.realize import RealizedRecord, recall_realized
from session.context import SessionContext
from teaching.epistemic import ADMISSIBLE_AS_EVIDENCE, EpistemicStatus
#: The CLOSED set of query predicates D0 has a SOUND direct-entailment path for:
#: ``member`` plus the ground binary relational predicates. Direct entailment is
#: "a realized fact ``p(s, t)`` directly answers the asked ``p(s, t)``" — sound for
#: these ground relations, unsound for categorical/propositional predicates (excluded).
_SUPPORTED_PREDICATES = frozenset({"member"}) | RELATIONAL_PREDICATES
@dataclass(frozen=True, slots=True)
class Determined:
"""An answer reasoned from realized structure.
``basis`` is the epistemic standing of the grounding: ``"as_told"`` when the
grounds are SPECULATIVE (candidate memory — the only case today), ``"verified"``
only if every ground is admissible-as-evidence (COHERENT — not yet reachable).
``answer`` is the truth of the asked (possibly negated) question.
"""
answer: bool
basis: str
predicate: str
subject: str
object: str
grounds: tuple[RealizedRecord, ...]
@dataclass(frozen=True, slots=True)
class Undetermined:
"""No honest answer (refusal). ``reason`` is for audit, not control."""
reason: str
def _basis(grounds: tuple[RealizedRecord, ...]) -> str:
"""Carry the grounds' epistemic standing forward — never overclaim "verified"."""
statuses = {EpistemicStatus(g.epistemic_status) for g in grounds}
return "verified" if statuses and statuses <= ADMISSIBLE_AS_EVIDENCE else "as_told"
def determine(
question: Comprehension | Refusal, ctx: SessionContext
) -> Determined | Undetermined:
"""Answer a membership-or-relational question from realized structure, or refuse.
Eligibility: a query-bearing ``Comprehension`` with exactly one binary,
non-negated query whose predicate is in the closed direct-entailment set
(``member`` or a relational pack predicate). The answer is asserted ONLY on direct
structural entailment by a realized fact of the SAME predicate; everything else is
a typed ``Undetermined`` (open-world: absence never asserts a positive answer).
"""
if isinstance(question, Refusal):
return Undetermined("refusal")
if not isinstance(question, Comprehension):
return Undetermined("not_a_comprehension")
if len(question.queries) != 1:
return Undetermined("not_single_query") # a determination answers one question
query = question.queries[0]
if query.predicate not in _SUPPORTED_PREDICATES:
return Undetermined("unsupported_query") # honest: closed direct-entailment set
if len(query.arguments) != 2:
return Undetermined("malformed_query") # member is binary by construction
if query.negated:
# Realized facts are all positive (R0/R1 refuse negated relations), so a
# negated question would only ever be answered from the positive's presence.
# D0 declines it explicitly rather than ship an entailment path the reader
# cannot exercise (it refuses negated membership questions upstream anyway).
return Undetermined("negated_query_unsupported")
predicate = query.predicate
subject, target = query.arguments[0], query.arguments[1]
# Structural recall: the realized facts of this predicate about this subject (R1a).
# Exact, deterministic, versor-collision-irrelevant — never a metric call.
facts = recall_realized(ctx, subject=subject, predicate=predicate)
if not facts:
return Undetermined("ungrounded") # nothing realized about the subject
# Direct entailment: a realized p(subject, target) holds as-told. Open-world:
# facts about the subject that do NOT match the asked target never refute it, so a
# miss is a refusal (``not_entailed``), never an asserted False. Symmetric relations
# (sibling_of, equal_to …) read only the stored direction here — the converse is a
# sound-but-incomplete ``not_entailed``, never a fabricated assertion.
grounding = next((f for f in facts if f.relation_arguments == (subject, target)), None)
if grounding is None:
return Undetermined("not_entailed")
return Determined(
answer=True,
basis=_basis((grounding,)),
predicate=predicate,
subject=subject,
object=target,
grounds=(grounding,),
)