feat(realizer): C1.5 — articulation legality at the realizer boundary
Adds a typed legality check that catches a narrow class of incoherent
finite-predicate surfaces before they ship. Scope is deliberately
narrow:
- generate/articulation_legality.py:
- SlotKind enum {VERB, NON_VERB, UNKNOWN}
- ArticulationLegality enum {LEGAL, ILLEGAL_NON_VERB_FINITE_PREDICATE}
- classify_predicate_slot_kind() — token allowlists for known verbs
and known non-verb nouns
- validate_finite_predicate_legality() — fails on negated +
NON_VERB; fail-open on UNKNOWN to preserve canary behavior
- generate/templates.py:
- _inflect_predicate: copular-aware negation
("is X" -> "is not X" instead of the default "does not be X")
- render_step: invokes the legality validator; returns
"I cannot realize that proposition coherently yet." when an
illegal shape is detected
The check is upstream of register / anchor-lens transforms (presentation
+ substantive axes both downstream of the realizer); no interaction
with R6 / ADR-0073 layering.
Tests pin:
- NON_VERB + negated -> ILLEGAL_NON_VERB_FINITE_PREDICATE
- UNKNOWN + negated -> LEGAL (fail-open preserved)
- render_step returns the disclosure string when illegal detected
- render_step still produces the fall-through surface on UNKNOWN
Validation:
- Cognition eval byte-identical (100/100/91.7/100)
- 370 realizer / lens / register / pack / lane tests pass
- anchor-lens-tour + register-tour both green
This commit is contained in:
parent
6387872051
commit
6761fc0974
3 changed files with 177 additions and 0 deletions
101
generate/articulation_legality.py
Normal file
101
generate/articulation_legality.py
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
"""Typed articulation legality checks at the realizer boundary.
|
||||
|
||||
C1.5 scope:
|
||||
- Catch a narrow class of known-illegal finite-predicate shapes.
|
||||
- Fail open when predicate slot kind is unknown.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class SlotKind(Enum):
|
||||
VERB = "verb"
|
||||
NON_VERB = "non_verb"
|
||||
UNKNOWN = "unknown"
|
||||
|
||||
|
||||
class ArticulationLegality(Enum):
|
||||
LEGAL = "legal"
|
||||
ILLEGAL_NON_VERB_FINITE_PREDICATE = "illegal_non_verb_finite_predicate"
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class ArticulationLegalityVerdict:
|
||||
legality: ArticulationLegality
|
||||
predicate_kind: SlotKind
|
||||
|
||||
@property
|
||||
def is_legal(self) -> bool:
|
||||
return self.legality is ArticulationLegality.LEGAL
|
||||
|
||||
|
||||
_KNOWN_NON_VERB_PREDICATES: frozenset[str] = frozenset(
|
||||
{
|
||||
"right",
|
||||
"truth",
|
||||
"light",
|
||||
"knowledge",
|
||||
"wisdom",
|
||||
"evidence",
|
||||
"thought",
|
||||
"memory",
|
||||
}
|
||||
)
|
||||
|
||||
_KNOWN_VERB_PREDICATES: frozenset[str] = frozenset(
|
||||
{
|
||||
"is",
|
||||
"are",
|
||||
"has",
|
||||
"have",
|
||||
"belongs",
|
||||
"supports",
|
||||
"requires",
|
||||
"grounds",
|
||||
"reveals",
|
||||
"defines",
|
||||
"means",
|
||||
"follows",
|
||||
"precedes",
|
||||
"causes",
|
||||
"answers",
|
||||
"verifies",
|
||||
"evidences",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def classify_predicate_slot_kind(predicate_humanized: str) -> SlotKind:
|
||||
token = predicate_humanized.strip().split(" ", 1)[0].lower()
|
||||
if token in _KNOWN_VERB_PREDICATES:
|
||||
return SlotKind.VERB
|
||||
if token in _KNOWN_NON_VERB_PREDICATES:
|
||||
return SlotKind.NON_VERB
|
||||
return SlotKind.UNKNOWN
|
||||
|
||||
|
||||
def validate_finite_predicate_legality(
|
||||
*,
|
||||
predicate_humanized: str,
|
||||
negated: bool,
|
||||
) -> ArticulationLegalityVerdict:
|
||||
kind = classify_predicate_slot_kind(predicate_humanized)
|
||||
if not negated:
|
||||
return ArticulationLegalityVerdict(
|
||||
legality=ArticulationLegality.LEGAL,
|
||||
predicate_kind=kind,
|
||||
)
|
||||
if kind is SlotKind.NON_VERB:
|
||||
return ArticulationLegalityVerdict(
|
||||
legality=ArticulationLegality.ILLEGAL_NON_VERB_FINITE_PREDICATE,
|
||||
predicate_kind=kind,
|
||||
)
|
||||
# Fail-open by design for UNKNOWN to preserve canary behavior.
|
||||
return ArticulationLegalityVerdict(
|
||||
legality=ArticulationLegality.LEGAL,
|
||||
predicate_kind=kind,
|
||||
)
|
||||
|
||||
|
|
@ -12,6 +12,10 @@ consumes these as constraints rather than final output.
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from generate.articulation_legality import (
|
||||
ArticulationLegality,
|
||||
validate_finite_predicate_legality,
|
||||
)
|
||||
from generate.graph_planner import RhetoricalMove
|
||||
from generate.morphology import base_form, past_participle, past_tense, present_participle
|
||||
|
||||
|
|
@ -142,6 +146,10 @@ def _inflect_predicate(
|
|||
instead of "all molecule binds enzyme" (english_fluency_ood G2).
|
||||
"""
|
||||
verb = predicate_h
|
||||
copular = any(
|
||||
predicate_h.startswith(prefix)
|
||||
for prefix in ("is ", "are ", "has ", "have ", "belongs ")
|
||||
)
|
||||
base = base_form(verb)
|
||||
|
||||
match (aspect, tense, negated, plural_subject):
|
||||
|
|
@ -163,6 +171,18 @@ def _inflect_predicate(
|
|||
return f"will {base}"
|
||||
case (_, _, True, True):
|
||||
return f"do not {base}"
|
||||
case (_, _, True, False) if copular:
|
||||
if predicate_h.startswith("is "):
|
||||
return "is not " + predicate_h[3:]
|
||||
if predicate_h.startswith("are "):
|
||||
return "are not " + predicate_h[4:]
|
||||
if predicate_h.startswith("has "):
|
||||
return "has not " + predicate_h[4:]
|
||||
if predicate_h.startswith("have "):
|
||||
return "have not " + predicate_h[5:]
|
||||
if predicate_h.startswith("belongs "):
|
||||
return "does not belong " + predicate_h[8:]
|
||||
return f"is not {base}"
|
||||
case (_, _, True, False):
|
||||
return f"does not {base}"
|
||||
case (_, _, False, True):
|
||||
|
|
@ -191,6 +211,12 @@ def render_step(
|
|||
is_mass = is_mass_noun(subject)
|
||||
plural = plural_q and not is_mass
|
||||
predicate_h = _humanize_predicate(predicate)
|
||||
legality = validate_finite_predicate_legality(
|
||||
predicate_humanized=predicate_h,
|
||||
negated=negated,
|
||||
)
|
||||
if legality.legality is ArticulationLegality.ILLEGAL_NON_VERB_FINITE_PREDICATE:
|
||||
return "I cannot realize that proposition coherently yet."
|
||||
predicate_h = _inflect_predicate(
|
||||
predicate_h,
|
||||
negated=negated, tense=tense, aspect=aspect,
|
||||
|
|
|
|||
50
tests/test_articulation_legality.py
Normal file
50
tests/test_articulation_legality.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from generate.articulation_legality import (
|
||||
ArticulationLegality,
|
||||
SlotKind,
|
||||
validate_finite_predicate_legality,
|
||||
)
|
||||
from generate.graph_planner import RhetoricalMove
|
||||
from generate.templates import render_step
|
||||
|
||||
|
||||
def test_c15_rejects_known_nonverb_finite_predicate_under_negation() -> None:
|
||||
verdict = validate_finite_predicate_legality(
|
||||
predicate_humanized="thought",
|
||||
negated=True,
|
||||
)
|
||||
assert verdict.legality is ArticulationLegality.ILLEGAL_NON_VERB_FINITE_PREDICATE
|
||||
assert verdict.predicate_kind is SlotKind.NON_VERB
|
||||
|
||||
|
||||
def test_c15_fail_open_on_unknown_predicate_kind() -> None:
|
||||
verdict = validate_finite_predicate_legality(
|
||||
predicate_humanized="quuxified",
|
||||
negated=True,
|
||||
)
|
||||
assert verdict.legality is ArticulationLegality.LEGAL
|
||||
assert verdict.predicate_kind is SlotKind.UNKNOWN
|
||||
|
||||
|
||||
def test_render_step_discloses_when_illegal_shape_detected() -> None:
|
||||
surface = render_step(
|
||||
RhetoricalMove.ASSERT,
|
||||
"right",
|
||||
"thought",
|
||||
"truth",
|
||||
negated=True,
|
||||
)
|
||||
assert surface == "I cannot realize that proposition coherently yet."
|
||||
|
||||
|
||||
def test_render_step_keeps_unknown_predicate_fail_open() -> None:
|
||||
surface = render_step(
|
||||
RhetoricalMove.ASSERT,
|
||||
"signal",
|
||||
"quuxified",
|
||||
"truth",
|
||||
negated=True,
|
||||
)
|
||||
assert surface == "signal does not quuxified truth"
|
||||
|
||||
Loading…
Reference in a new issue