core/tests/test_relational_finite_verb.py
Shay 0be18ebdb9
feat(reader): add overlaps_event finite-verb reader surface (B3) (#783)
Add a closed finite-verb relational surface to comprehend_relational, narrowly:
declarative <A> overlaps <B>, interrogative Does <A> overlap <B>?, predicate
overlaps_event ONLY (closed finite-verb table, default-off).

Kept SEPARATE from the copula-connective grammar (byte-unchanged): the other
connectives (before/after/during/inside/adjacent/…) still REQUIRE the copula, so
"Monday before Friday." stays a refusal — no connective bypass (adversarial hazard #2).

Fail-closed slot gate: each finite-verb argument slot must be EXACTLY ONE content
token (after article stripping). An enumerated adverb/negation blocklist is unbounded
and leaks — the adversarial audit found "Meeting never overlaps lunch." committed as a
POSITIVE overlaps_event(meeting_never, lunch), plus almost/sometimes/trailing-qualifier/
second-verb compound fabrications in BOTH slots and the query path. The single-token
gate closes the whole COMPOUND-fabrication class (any extra token refuses); the
_FINITE_VERB_MODIFIERS and _CONNECTIVE_TOKENS checks add precise reasons for common
bare-modifier / second-verb slots. Multi-word entities in the finite-verb surface are
deferred (they need a positive content lexicon, which OOV entities preclude). A lone
bare token remains an entity (the reader's universal OOV single-token contract — the
copula and general readers behave identically), not a compound fabrication.

Hazards pinned (adversarial audit hazard #1 + #2): adverb absorption, negation-as-
positive ('never'), interrogative double-verb, trailing qualifier, and the copula-bypass
firewall.

Measurement: migrated ref-009 "Sunrise overlaps dawn." (the documented coverage gap) to
a positive rel-018 — comprehension_relational_predicate 17->18, wrong_total 0, breadth
still 11 (coverage added, not a new domain), baseline digest re-frozen. Refusal floor
rose 9 -> 24 (finite-verb confusers replace the one migrated input). The #775 inference
and #781 transitive lane SHA pins are UNTOUCHED. determine.py is untouched (reader-only):
no answer=False, no FrameVerdict, INV-30 unaffected.

Verified in the worktree: finite-verb + reader + reader-lane + #775 inference lane +
#781 transitive lane + #779 OWA floor + capability baseline/index + INV-30/29/21 + full
smoke — all green. Two read-only adversarial passes: the first found 4 real slot-
fabrication blockers, closed by the single-token gate; a black-box probe of 24
adversarial inputs confirms the compound-fabrication class is fully closed.
2026-06-15 14:56:09 -07:00

196 lines
8.2 KiB
Python

"""Relational reader — the closed finite-verb surface (B3): ``overlaps_event``.
The reader admits ONE finite verb with NO copula: ``<A> overlaps <B>`` (declarative) and
``Does <A> overlap <B>?`` (query). Everything else still requires the copula. Fail-closed:
an adverb-modified overlap, a coordinated/leftover slot, a second verb, a negation, a
non-table verb, or a bare connective-without-copula all REFUSE.
Two adversarial-audit hazards are pinned here:
1. adverb absorption — ``A nearly overlaps B`` must NOT fabricate the entity ``a_nearly``;
2. copula bypass — admitting the finite verb must NOT let the OTHER connectives drop the
copula: ``Monday before Friday.`` must still refuse.
"""
from __future__ import annotations
import pytest
from chat.runtime import ChatRuntime
from generate.determine import Determined, Undetermined, determine
from generate.meaning_graph.reader import Comprehension, Refusal
from generate.meaning_graph.relational import (
comprehend_relational,
load_relational_pack_lemmas,
)
from generate.realize import NotRealized, realize_comprehension
from session.context import SessionContext
_HIGH = 10**9
_PACK = load_relational_pack_lemmas()
@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)
def _read(text: str):
return comprehend_relational(text, _PACK)
# --------------------------------------------------------------------------- #
# Positives — the finite-verb declarative + interrogative read overlaps_event
# --------------------------------------------------------------------------- #
def test_declarative_overlaps_reads() -> None:
comp = _read("Sunrise overlaps dawn.")
assert isinstance(comp, Comprehension), comp
assert not comp.queries and len(comp.meaning_graph.relations) == 1
rel = comp.meaning_graph.relations[0]
assert rel.predicate == "overlaps_event"
assert rel.arguments == ("sunrise", "dawn")
assert rel.negated is False
def test_interrogative_does_overlap_reads_a_query() -> None:
comp = _read("Does Sunrise overlap dawn?")
assert isinstance(comp, Comprehension), comp
assert not comp.meaning_graph.relations and len(comp.queries) == 1
q = comp.queries[0]
assert q.predicate == "overlaps_event" and q.arguments == ("sunrise", "dawn")
assert not q.negated
def test_oov_arguments_in_finite_verb_surface() -> None:
rel = _read("Zorptak overlaps Quxley.").meaning_graph.relations[0]
assert rel.predicate == "overlaps_event" and rel.arguments == ("zorptak", "quxley")
def test_read_then_determine_direct_and_symmetric(vocab_persona) -> None:
# the finite-verb read integrates with determine: direct, and the symmetric converse
# (overlaps_event is pack-declared symmetric) determines via the one-hop rule.
ctx = _ctx(vocab_persona)
realize_comprehension(_read("Sunrise overlaps dawn."), ctx)
direct = determine(_read("Does Sunrise overlap dawn?"), ctx)
assert isinstance(direct, Determined) and direct.answer is True and direct.rule == "direct"
converse = determine(_read("Does dawn overlap Sunrise?"), ctx)
assert isinstance(converse, Determined) and converse.answer is True
assert converse.rule == "symmetric"
# --------------------------------------------------------------------------- #
# Hazard 1 (pinned) — adverb absorption must NOT fabricate an entity
# --------------------------------------------------------------------------- #
def test_adverb_modified_overlap_refuses_no_fabrication(vocab_persona) -> None:
res = _read("Alice nearly overlaps Bob.")
assert isinstance(res, Refusal) and res.reason == "finite_verb_modifier"
# and nothing fabricated is realized — no `alice_nearly` enters memory.
ctx = _ctx(vocab_persona)
told = realize_comprehension(_read("Alice nearly overlaps Bob."), ctx)
assert isinstance(told, NotRealized)
assert isinstance(determine(_read("Does alice_nearly overlap Bob?"), ctx), Undetermined)
# --------------------------------------------------------------------------- #
# Hazard 2 (pinned) — the finite verb must NOT let other connectives bypass copula
# --------------------------------------------------------------------------- #
@pytest.mark.parametrize(
"text",
[
"Monday before Friday.",
"The cat inside the house.",
"The room adjacent the hall.",
],
)
def test_connective_without_copula_still_refuses(text) -> None:
res = _read(text)
assert isinstance(res, Refusal) and res.reason == "no_relational_template"
# --------------------------------------------------------------------------- #
# Mandatory confusers — every malformed finite-verb / non-finite-verb surface refuses
# --------------------------------------------------------------------------- #
@pytest.mark.parametrize(
"text",
[
"Alice overlaps with the team and Bob.", # coordinated/leftover structure
"A overlaps B and C.", # coordination
"A nearly overlaps B.", # adverb
"A completely overlaps B.", # adverb
"Does A nearly overlap B?", # adverb in the query
"A does not overlap B.", # negation
"A borders B.", # finite verb not in the closed table
"A overlaps B overlaps C.", # a second finite verb
"A overlaps B then C.", # sequencing modifier
],
)
def test_finite_verb_confusers_refuse(text) -> None:
res = _read(text)
assert isinstance(res, Refusal), f"{text!r} should refuse but committed {res!r}"
def test_no_finite_verb_confuser_fabricates_a_relation() -> None:
# stronger than "is a Refusal": not a single relation/query is produced for any of them.
for text in (
"Alice overlaps with the team and Bob.",
"A nearly overlaps B.",
"A overlaps B overlaps C.",
"A overlaps B then C.",
"A does not overlap B.",
):
comp = _read(text)
assert isinstance(comp, Refusal), text
# --------------------------------------------------------------------------- #
# Slot-fabrication hazards (adversarial audit) — the single-token slot gate closes
# the UNBOUNDED adverb / negation / qualifier / second-verb class a blocklist cannot.
# --------------------------------------------------------------------------- #
@pytest.mark.parametrize(
"text",
[
"Alice almost overlaps Bob.", # adverb NOT in the enumerated modifier set
"Meeting sometimes overlaps lunch.", # another non-enumerated adverb
"Sprint overlaps almost lunch.", # right-slot adverb
"Sprint overlaps lunch almost.", # trailing right-slot adverb
"Meeting overlaps lunch today.", # trailing temporal qualifier glued into the id
"North station overlaps south station.", # multi-word entities deferred (narrow v1)
"Does A overlap B overlap C?", # interrogative double-verb (base form not in net)
"Does Sprint overlap lunch now?", # interrogative trailing adverb
],
)
def test_unclean_finite_verb_slots_refuse(text) -> None:
# any slot that is not exactly one content token refuses rather than glue extra tokens
# into a fabricated id — this is the backstop the enumerated blocklists cannot provide.
res = _read(text)
assert isinstance(res, Refusal), f"{text!r} should refuse but committed {res!r}"
def test_never_negation_is_not_committed_as_positive(vocab_persona) -> None:
# THE worst class the audit found: 'never' is not a reserved word, so without the
# single-token gate "Meeting never overlaps lunch." was committed as a POSITIVE
# overlaps_event(meeting_never, lunch). It must refuse and hold no positive belief.
res = _read("Meeting never overlaps lunch.")
assert isinstance(res, Refusal)
ctx = _ctx(vocab_persona)
told = realize_comprehension(_read("Meeting never overlaps lunch."), ctx)
assert isinstance(told, NotRealized)
assert isinstance(determine(_read("Does meeting overlap lunch?"), ctx), Undetermined)
assert isinstance(
determine(_read("Does meeting_never overlap lunch?"), ctx), Undetermined
)