feat(determine): D0 — reason over realized structure → assert (as-told) / refuse
The honest gear (roadmap Step 4). `determine(question: Comprehension | Refusal, ctx)`
answers a membership question ("Is X a Y?") ONLY from what the held self has already
REALIZED (R1a structural recall) — never from the field, an LLM, or absence.
Honesty (from the design review): 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)", NEVER "verified".
Until COHERENT promotion exists (out of scope), D0 produces only as-told assertions or
typed `Undetermined` refusals. No estimation; no corpus mutation (teaching stays HITL
proposal-only).
Soundness (open-world, wrong=0): D0 asserts an answer ONLY on DIRECT structural
entailment by a realized fact; absence never refutes (so it never asserts a positive
answer from missing knowledge — it refuses). It asserts only `answer=True` on a direct
hit; it never asserts False. Negated questions and non-`member` queries are an explicit
`Undetermined` — D0 ships no entailment path the reader cannot exercise.
Tests (test_determine_d0.py, 8) BITE: present-but-non-entailing (a record about the
subject exists but not the asked relation) MUST refuse; the same question against a
fresh context with no realized fact flips Determined→Undetermined (the verdict is
entailment, not mere presence); the negated + unsupported-query refusals are exercised
via hand-built queries (the reader refuses them upstream, so a comprehend-based test
would pass vacuously). Independent of #593: reasons over R0/R1 `member` facts only.
This commit is contained in:
parent
ba50f3933b
commit
bf9ad77fda
3 changed files with 271 additions and 0 deletions
5
generate/determine/__init__.py
Normal file
5
generate/determine/__init__.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
"""DETERMINE — reason over realized structure → assert (as-told) / refuse (roadmap Step 4)."""
|
||||
|
||||
from generate.determine.determine import Determined, Undetermined, determine
|
||||
|
||||
__all__ = ["Determined", "Undetermined", "determine"]
|
||||
119
generate/determine/determine.py
Normal file
119
generate/determine/determine.py
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
"""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.
|
||||
|
||||
Slice D0 supports the realized, non-negated ``member`` relation (subsumption /
|
||||
"Is X a Y?"). Negated questions and other query predicates are an honest
|
||||
``Undetermined`` until their realized form + entailment predicate land — D0 ships no
|
||||
entailment path it cannot exercise.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from generate.meaning_graph.reader import Comprehension, Refusal
|
||||
from generate.realize import RealizedRecord, recall_realized
|
||||
from session.context import SessionContext
|
||||
from teaching.epistemic import ADMISSIBLE_AS_EVIDENCE, EpistemicStatus
|
||||
|
||||
#: The only realized relation D0 can reason over in this slice.
|
||||
_SUPPORTED_PREDICATE = "member"
|
||||
|
||||
|
||||
@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 question from realized structure, or refuse.
|
||||
|
||||
Eligibility: a query-bearing ``Comprehension`` with exactly one ``member`` query.
|
||||
The answer is asserted ONLY on direct structural entailment by a realized
|
||||
``member`` fact; 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 != _SUPPORTED_PREDICATE:
|
||||
return Undetermined("unsupported_query") # honest: only `member` in D0
|
||||
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")
|
||||
|
||||
subject, target = query.arguments[0], query.arguments[1]
|
||||
|
||||
# Structural recall: the realized member facts about this subject (R1a). Exact,
|
||||
# deterministic, versor-collision-irrelevant — never a metric call.
|
||||
facts = recall_realized(ctx, subject=subject, predicate=_SUPPORTED_PREDICATE)
|
||||
if not facts:
|
||||
return Undetermined("ungrounded") # nothing realized about the subject
|
||||
|
||||
# Direct entailment: a realized member(subject, target) holds as-told. Open-world:
|
||||
# member 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.
|
||||
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=_SUPPORTED_PREDICATE,
|
||||
subject=subject,
|
||||
object=target,
|
||||
grounds=(grounding,),
|
||||
)
|
||||
147
tests/test_determine_d0.py
Normal file
147
tests/test_determine_d0.py
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
"""DETERMINE slice D0 — answer a membership question from realized structure, or refuse.
|
||||
|
||||
The pinned tests BITE: D0 asserts only on direct entailment by a realized fact and
|
||||
carries ``basis="as_told"`` (never "verified", since realized facts are SPECULATIVE);
|
||||
a present-but-non-entailing question (a record about the subject exists, but not the
|
||||
asked relation) must REFUSE, and removing the grounding flips Determined→Undetermined.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from chat.runtime import ChatRuntime
|
||||
from generate.determine import Determined, Undetermined, determine
|
||||
from generate.meaning_graph.reader import comprehend
|
||||
from generate.realize import realize_comprehension
|
||||
from session.context import SessionContext
|
||||
|
||||
_HIGH_INTERVAL = 10**9
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vocab_persona():
|
||||
rt = ChatRuntime(no_load_state=True)
|
||||
return rt._context.vocab, rt._context.persona
|
||||
|
||||
|
||||
def _ctx(vocab_persona) -> SessionContext:
|
||||
vocab, persona = vocab_persona
|
||||
return SessionContext(vocab=vocab, persona=persona, vault_reproject_interval=_HIGH_INTERVAL)
|
||||
|
||||
|
||||
def _tell(text: str, ctx: SessionContext):
|
||||
return realize_comprehension(comprehend(text), ctx)
|
||||
|
||||
|
||||
def _ask(text: str, ctx: SessionContext):
|
||||
return determine(comprehend(text), ctx)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Refuse the ungrounded — never assert from absence (open-world, wrong=0)
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
def test_ask_before_realizing_is_undetermined(vocab_persona) -> None:
|
||||
ctx = _ctx(vocab_persona)
|
||||
res = _ask("Is truth a concept?", ctx)
|
||||
assert isinstance(res, Undetermined) and res.reason == "ungrounded"
|
||||
|
||||
|
||||
def test_non_question_is_undetermined(vocab_persona) -> None:
|
||||
ctx = _ctx(vocab_persona)
|
||||
_tell("Truth is a concept.", ctx)
|
||||
# a declarative (no query) is intake, not a question to determine
|
||||
assert isinstance(_ask("Truth is a concept.", ctx), Undetermined)
|
||||
# a Refusal question
|
||||
assert isinstance(determine(comprehend("The weather is nice today."), ctx), Undetermined)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Assert (as-told) on direct entailment by a realized fact
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
def test_realized_fact_answers_its_own_question_as_told(vocab_persona) -> None:
|
||||
ctx = _ctx(vocab_persona)
|
||||
_tell("Truth is a concept.", ctx)
|
||||
res = _ask("Is truth a concept?", ctx)
|
||||
assert isinstance(res, Determined)
|
||||
assert res.answer is True
|
||||
assert res.basis == "as_told" # SPECULATIVE -> as-told, NEVER "verified"
|
||||
assert res.predicate == "member" and res.subject == "truth" and res.object == "concept"
|
||||
assert len(res.grounds) == 1
|
||||
assert res.grounds[0].relation_arguments == ("truth", "concept")
|
||||
|
||||
|
||||
def test_negated_query_is_refused(vocab_persona) -> None:
|
||||
# The reader refuses negated membership questions upstream, so determine() can
|
||||
# only meet a negated `member` query via a hand-built comprehension. D0 declines
|
||||
# it explicitly (ships no entailment path the reader cannot exercise).
|
||||
from generate.meaning_graph.model import MeaningGraph, MeaningSpan
|
||||
from generate.meaning_graph.reader import Comprehension, Query
|
||||
|
||||
ctx = _ctx(vocab_persona)
|
||||
_tell("Truth is a concept.", ctx) # positive member(truth, concept) realized
|
||||
span = MeaningSpan(source_id="input", start=0, end=5, text="dummy")
|
||||
negated = Comprehension(
|
||||
meaning_graph=MeaningGraph(),
|
||||
queries=(Query("member", ("truth", "concept"), span, negated=True),),
|
||||
)
|
||||
res = determine(negated, ctx)
|
||||
assert isinstance(res, Undetermined) and res.reason == "negated_query_unsupported"
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Present-but-non-entailing — a record about the subject exists, but not the
|
||||
# asked relation. MUST refuse (the discriminant bites: not "a record exists").
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
def test_present_but_non_entailing_is_refused(vocab_persona) -> None:
|
||||
ctx = _ctx(vocab_persona)
|
||||
_tell("Truth is a concept.", ctx) # member(truth, concept) realized
|
||||
res = _ask("Is truth a number?", ctx) # member(truth, number) NOT realized
|
||||
assert isinstance(res, Undetermined) and res.reason == "not_entailed"
|
||||
|
||||
|
||||
def test_other_subject_does_not_ground(vocab_persona) -> None:
|
||||
ctx = _ctx(vocab_persona)
|
||||
_tell("Knowledge is a concept.", ctx)
|
||||
# nothing realized about `truth` -> ungrounded, not a stray match on `concept`
|
||||
assert isinstance(_ask("Is truth a concept?", ctx), Undetermined)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Mutation bite — the verdict is entailment, not "a record exists"
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
def test_verdict_is_entailment_not_mere_presence(vocab_persona) -> None:
|
||||
# With the grounding present -> Determined; the SAME question against a fresh
|
||||
# context with NO realized facts -> Undetermined. Proves the Determined verdict
|
||||
# is carried by the realized fact, not by the question alone.
|
||||
grounded = _ctx(vocab_persona)
|
||||
_tell("Truth is a concept.", grounded)
|
||||
assert isinstance(_ask("Is truth a concept?", grounded), Determined)
|
||||
|
||||
empty = _ctx(vocab_persona)
|
||||
assert isinstance(_ask("Is truth a concept?", empty), Undetermined)
|
||||
|
||||
|
||||
def test_unsupported_query_predicate_is_refused(vocab_persona) -> None:
|
||||
# A non-`member` query (e.g. subset/compare) is an honest refusal in D0 — built
|
||||
# by hand so the branch bites regardless of which predicates the reader emits.
|
||||
from generate.meaning_graph.model import MeaningGraph, MeaningSpan
|
||||
from generate.meaning_graph.reader import Comprehension, Query
|
||||
|
||||
ctx = _ctx(vocab_persona)
|
||||
_tell("Truth is a concept.", ctx)
|
||||
span = MeaningSpan(source_id="input", start=0, end=5, text="dummy")
|
||||
subset_q = Comprehension(
|
||||
meaning_graph=MeaningGraph(),
|
||||
queries=(Query("subset", ("concept", "thought"), span),),
|
||||
)
|
||||
res = determine(subset_q, ctx)
|
||||
assert isinstance(res, Undetermined) and res.reason == "unsupported_query"
|
||||
Loading…
Reference in a new issue