core/generate/intent_ratifier.py
Shay 120e9554ab fix(cognition): pre-stage land repairs after L2/PASSTHROUGH excision
Close residual failures from geometric sovereignty hardening without
restoring scalar-L2 or PASSTHROUGH authority:

- Ratify CORRECTION/COMPARISON via vocab-grounded tag and multi-token
  subject anchors so teaching capture and cognition intent accuracy hold.
- Keep observational wave leakage from vetoing teaching while
  identity_wave_gate is off; syntactic override still rejects.
- Surface GoldTetherViolationError as fail-closed tether residual rather
  than aborting lifecycle observation.
- Replace excised legacy identity-eval path with a geometry-blind baseline.
- Align sensorium corridor and lift instrument assertions with sandwich
  unitary close and honest PARITY when baseline already solves.

[Verification]: smoke 176 passed; cognition 122 passed 1 skipped;
teaching 109 passed; claim batch 34 passed
2026-07-20 13:34:59 -07:00

320 lines
11 KiB
Python

"""Field-grounded intent ratification (ADR-0022 §TBD-1).
The rule-based regex classifier in ``generate/intent.py`` is the
*seed*; this module is the *ratifier*. Forward semantic control on
top of a non-geometric classifier would recreate the same gap one
level up — the classifier becomes the oracle the field defers to.
ADR-0022 closes that gap by requiring the field to ratify the seed:
the prompt versor must lie within the seeded intent's admissible
region, or the intent demotes to ``IntentTag.UNKNOWN``.
Design decisions:
* **Smallest deterministic step.** No new classifier model, no
learned ratifier — the existing regex classifier remains the
candidate generator; the field is the gate.
* **No sampling.** Ratification is a CGA-inner-product threshold
check, exact and replayable. Same `(intent, prompt_versor)` →
same verdict byte-for-byte.
* **No new closure invariant.** The ratifier inspects the prompt
versor; it does not normalize, repair, or mutate the field
(CLAUDE.md §Normalization Rules).
* **No new trust surface.** Pure function over typed in-memory
state; no IO, no dynamic import.
The ratifier is wired into ``CognitiveTurnPipeline`` after the
seed classification and before the proposition graph is built; a
demotion routes through the existing UNKNOWN-domain surface path,
preserving honest refusal per ADR-0022 §2.
"""
from __future__ import annotations
import re
from dataclasses import dataclass
from enum import Enum, unique
import numpy as np
from algebra.cga import cga_inner
from generate.admissibility import AdmissibilityRegion, region_from_relation_chain
from generate.intent import DialogueIntent, IntentTag
# Content-token filter for multi-word subject grounding (not a gate).
_SUBJECT_STOPWORDS = frozenset(
{
"a",
"an",
"the",
"it",
"that",
"this",
"those",
"these",
"is",
"are",
"was",
"were",
"be",
"been",
"being",
"s",
"and",
"or",
"to",
"of",
"in",
"on",
"for",
"with",
"as",
"by",
"from",
"at",
"should",
"would",
"could",
"must",
"can",
"will",
}
)
@unique
class RatificationOutcome(Enum):
RATIFIED = "ratified"
DEMOTED = "demoted"
@dataclass(frozen=True, slots=True)
class RatifiedIntent:
"""Result of field ratification of a seeded intent.
``intent`` is the (possibly demoted) intent the downstream
pipeline should use. ``outcome`` records what happened so the
trace and failure surface can name *why* an intent was rejected.
``score`` carries the CGA inner product the verdict was based
on; ``threshold`` records the gate it was checked against.
"""
intent: DialogueIntent
outcome: RatificationOutcome
score: float
threshold: float
seed_tag: IntentTag
def _subject_anchor_tokens(subject: str) -> list[str]:
"""Ground multi-word subjects as whole phrase plus content tokens.
Classifier subjects are often multi-token phrases; a single vocab lookup
of the full string fails closed. Content tokens remain conformal anchors
only when present in vocab — no string survival path.
"""
raw = subject.lower().strip()
if not raw:
return []
tokens = [raw]
for part in re.split(r"[^\w]+", raw, flags=re.UNICODE):
if part and part not in _SUBJECT_STOPWORDS:
tokens.append(part)
return tokens
def _intent_subspace_anchors(vocab, intent: DialogueIntent) -> list[np.ndarray]:
"""Vocab-grounded intent-subspace anchors for conformal argmax scoring.
Anchors are candidate points on the manifold (subject, tag predicates,
relation). The prompt field is scored against these anchors only —
never against a string-derived subject self-inner product.
"""
candidates: list[str] = []
if intent.subject:
candidates.extend(_subject_anchor_tokens(intent.subject))
if intent.secondary_subject:
candidates.extend(_subject_anchor_tokens(intent.secondary_subject))
if intent.object:
candidates.extend(_subject_anchor_tokens(intent.object))
if intent.relation:
candidates.append(intent.relation.strip().lower())
match intent.tag:
case IntentTag.DEFINITION:
candidates.extend(("is", "definition"))
case IntentTag.CAUSE:
candidates.extend(("causes", "because"))
case IntentTag.COMPARISON:
# Pack lexicon uses "compare"; "like"/"compared" may be absent.
candidates.extend(("compare", "like", "unlike", "compared", "contrast"))
case IntentTag.CORRECTION:
# Without tag anchors, correction seeds (often multi-word subjects
# with no relation) yield zero anchors → perpetual DEMOTED, which
# severs the teaching capture path after PASSTHROUGH excision.
candidates.extend(
("no", "wrong", "actually", "correction", "incorrect")
)
case IntentTag.VERIFICATION:
candidates.extend(("is", "true", "verify"))
case IntentTag.RECALL:
candidates.extend(("remember", "recall"))
case _:
pass
anchors: list[np.ndarray] = []
seen: set[str] = set()
for token in candidates:
if not token or token in seen:
continue
seen.add(token)
try:
anchors.append(np.asarray(vocab.get_versor(token), dtype=np.float32))
except (KeyError, AttributeError):
continue
return anchors
def _intent_anchor_versor(vocab, intent: DialogueIntent) -> np.ndarray | None:
"""Return the first vocab-grounded intent-subspace anchor, or ``None``."""
anchors = _intent_subspace_anchors(vocab, intent)
return anchors[0] if anchors else None
#: Default ratification threshold (Finding 3, audit 2026-05-20).
#:
#: Pre-fix the default was ``0.0``, which admitted anything with non-
#: negative projection onto the anchor versor — the field gate was
#: structurally live but semantically transparent (ADR-0022 §TBD-1).
#: Measured against ``core eval cognition`` across all 45 cases (45 =
#: 13 public + 13 dev + 19 holdout), every ratifiable case scored
#: ``cga_inner(prompt, anchor) ≥ 1.10`` after a prime turn primed the
#: field — see ``scripts/calibrate_ratification_threshold.py``. The
#: 0.5 floor is well below that 1.10 minimum (no regression on any
#: passing case) while clearly non-trivially positive (random Cl(4,1)
#: inner products fluctuate around zero, so 0.5 demands genuine
#: correlation with the anchor). Off-corpus / adversarial prompts
#: with weakly-aligned anchors will now demote to ``UNKNOWN`` and
#: route through the honest-refusal surface.
_DEFAULT_RATIFICATION_THRESHOLD: float = 0.5
def ratify_intent(
intent: DialogueIntent,
prompt_versor: np.ndarray,
*,
vocab,
threshold: float = _DEFAULT_RATIFICATION_THRESHOLD,
) -> RatifiedIntent:
"""Ratify a seeded intent against the prompt versor.
The seed classifier produces ``intent`` syntactically. This function
scores **only** the prompt field versor against the intent subspace
anchors in ``vocab`` via conformal argmax:
score = max_i cga_inner(prompt, anchor_i)
No subject self-inner boost, no string-grounded survival path.
Outcomes:
* ``RATIFIED`` — prompt field correlates with the intent subspace
at or above ``threshold``.
* ``DEMOTED`` — field disagrees, seed is already ``UNKNOWN``, or
no grounded anchors exist; intent becomes ``IntentTag.UNKNOWN``.
"""
if intent.tag is IntentTag.UNKNOWN:
return RatifiedIntent(
intent=intent,
outcome=RatificationOutcome.DEMOTED,
score=0.0,
threshold=threshold,
seed_tag=intent.tag,
)
anchors = _intent_subspace_anchors(vocab, intent)
if not anchors:
demoted = DialogueIntent(
tag=IntentTag.UNKNOWN,
subject=intent.subject,
secondary_subject=intent.secondary_subject,
object=intent.object,
relation=intent.relation,
negated=intent.negated,
frame=intent.frame,
)
return RatifiedIntent(
intent=demoted,
outcome=RatificationOutcome.DEMOTED,
score=0.0,
threshold=threshold,
seed_tag=intent.tag,
)
prompt = np.asarray(prompt_versor, dtype=np.float32)
# Argmax over intent subspace only — prompt field vs each anchor.
score = max(float(cga_inner(prompt, a)) for a in anchors)
if score >= threshold:
return RatifiedIntent(
intent=intent,
outcome=RatificationOutcome.RATIFIED,
score=score,
threshold=threshold,
seed_tag=intent.tag,
)
demoted = DialogueIntent(
tag=IntentTag.UNKNOWN,
subject=intent.subject,
secondary_subject=intent.secondary_subject,
object=intent.object,
relation=intent.relation,
negated=intent.negated,
frame=intent.frame,
)
return RatifiedIntent(
intent=demoted,
outcome=RatificationOutcome.DEMOTED,
score=score,
threshold=threshold,
seed_tag=intent.tag,
)
def region_for_intent(
intent: DialogueIntent,
*,
vocab,
label: str | None = None,
) -> AdmissibilityRegion:
"""Build an ``AdmissibilityRegion`` from a (ratified) intent.
The region's relation blade is the outer-product chain of
grounded anchors for the intent's subject, predicate-anchor, and
(when present) relation token. Tokens that are not in the
vocabulary are skipped — they cannot contribute to the blade.
An intent that grounds *no* tokens yields an unconstrained
region; this is the same behavior the propose/realize sites
already accept (region=None) and preserves backwards
compatibility during the ADR-0022 transition window
(§TBD-3).
"""
anchors: list[np.ndarray] = []
candidates: list[str] = []
if intent.subject:
candidates.append(intent.subject.lower())
if intent.relation:
candidates.append(intent.relation.lower())
match intent.tag:
case IntentTag.DEFINITION:
candidates.append("is")
case IntentTag.CAUSE:
candidates.append("causes")
case _:
pass
for token in candidates:
try:
anchors.append(np.asarray(vocab.get_versor(token), dtype=np.float32))
except (KeyError, AttributeError):
continue
if not anchors:
return AdmissibilityRegion(label=label or f"intent[{intent.tag.value}]")
return region_from_relation_chain(
anchors,
label=label or f"intent[{intent.tag.value}]",
)