core/generate/intent_ratifier.py
Shay e41a14f76c
chore(ratifier): calibrate default ratification threshold 0.0 → 0.5 (#86)
Closes audit Finding 3 (2026-05-20).

Pre-fix ``ratify_intent`` defaulted to ``threshold=0.0``, which admits
anything with non-negative ``cga_inner(prompt, anchor)`` — the field
gate (ADR-0022 §TBD-1) was structurally live but semantically
transparent.  RATIFIED was logged on essentially every turn because
the CGA inner product over conformal space is not sign-symmetric.

Measurement (``scripts/calibrate_ratification_threshold.py``):

  * Runs every cognition eval prompt (45 cases = 13 public + 13 dev +
    19 holdout) through a primed ``CognitiveTurnPipeline``.
  * Captures the actual ``cga_inner(prompt, anchor)`` score from the
    pipeline's own ``_ratify_intent`` via a temporary spy on the
    imported ``ratify_intent`` binding.

Observed distribution:

  * 34 RATIFIED:  min=+1.1039  p10=+1.1039  median=+2.6820  max=+5.7508
  * 11 PASSTHROUGH (no vocab-grounded anchor available; score=0.0)
  *  0 DEMOTED at any threshold ≤ 1.10

Threshold = 0.5 chosen as the calibrated default:

  * Well below the empirical floor of 1.10 — every currently-passing
    case stays RATIFIED, byte-identically.
  * Clearly non-trivially positive — random Cl(4,1) inner products
    fluctuate around zero, so 0.5 demands genuine correlation with
    the anchor rather than passive non-negativity.
  * Leaves headroom for the gate to actually demote weakly-aligned
    off-corpus / adversarial prompts to UNKNOWN and route them
    through the honest-refusal surface.

Verification:

  * ``core eval cognition`` — public 100/100/91.7/100, holdout
    100/100/83.3/100, dev 100/100/78.6/100 — byte-identical to
    MEMORY baselines.
  * ``core test --suite cognition`` — 120/0/1
  * ``core test --suite smoke`` — 67/0
  * ``core test --suite runtime`` — 19/0
  * 2 new tests in ``tests/test_ratification_threshold_default.py``
    pin both the constant and the signature default so a future
    change cannot silently regress to ``0.0``.
2026-05-20 19:59:25 -07:00

230 lines
8.5 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
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
@unique
class RatificationOutcome(Enum):
RATIFIED = "ratified"
DEMOTED = "demoted"
PASSTHROUGH = "passthrough"
@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 _intent_anchor_versor(vocab, intent: DialogueIntent) -> np.ndarray | None:
"""Return a vocab-grounded anchor versor for ``intent`` or ``None``.
The anchor is the prompt-side reference the prompt versor is
compared against. v1 uses the intent's subject token when the
vocab carries it; absent that, the predicate anchor for the
intent tag (e.g. ``is`` for DEFINITION) is the fallback.
Returns ``None`` when no anchor is grounded — that signals
PASSTHROUGH (the ratifier has nothing to check against, so the
seed survives unchanged). PASSTHROUGH is deliberately distinct
from RATIFIED so the trace can audit unratified turns.
"""
if not intent.subject:
return None
candidates: tuple[str, ...] = (intent.subject.lower(),)
if intent.tag is IntentTag.DEFINITION:
candidates = candidates + ("is",)
elif intent.tag is IntentTag.CAUSE:
candidates = candidates + ("causes", "because")
elif intent.tag is IntentTag.TRANSITIVE_QUERY and intent.relation:
candidates = candidates + (intent.relation,)
for token in candidates:
try:
return np.asarray(vocab.get_versor(token), dtype=np.float32)
except (KeyError, AttributeError):
continue
return 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 (``generate.intent.classify_intent``) produced
``intent`` syntactically. This function checks whether the
prompt versor's geometric position is consistent with that
classification — concretely, whether ``cga_inner(prompt, anchor)
≥ threshold`` where ``anchor`` is the vocab-grounded reference
for the seeded intent's subject/relation.
Outcomes:
* ``RATIFIED`` — the seed survives; the field agrees with the
regex.
* ``DEMOTED`` — the field disagrees; the intent is replaced
with ``IntentTag.UNKNOWN`` so the downstream pipeline routes
through the unknown-domain surface (ADR-0022 §2).
* ``PASSTHROUGH`` — no vocab-grounded anchor exists for the
seed; the seed survives unchanged but the trace records
that the field did not ratify it.
The pre-existing ``IntentTag.UNKNOWN`` seed is treated as
PASSTHROUGH (no demotion of an already-unknown intent).
"""
if intent.tag is IntentTag.UNKNOWN:
return RatifiedIntent(
intent=intent,
outcome=RatificationOutcome.PASSTHROUGH,
score=0.0,
threshold=threshold,
seed_tag=intent.tag,
)
anchor = _intent_anchor_versor(vocab, intent)
if anchor is None:
return RatifiedIntent(
intent=intent,
outcome=RatificationOutcome.PASSTHROUGH,
score=0.0,
threshold=threshold,
seed_tag=intent.tag,
)
prompt = np.asarray(prompt_versor, dtype=np.float32)
score = float(cga_inner(prompt, anchor))
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())
if intent.tag is IntentTag.DEFINITION:
candidates.append("is")
elif intent.tag is IntentTag.CAUSE:
candidates.append("causes")
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}]",
)