feat(adr-0048): pack-grounded surface for cold-start DEFINITION/RECALL

Closes the surface-grounding gap isolated by ADR-0047's
characterisation.  Adds the ratified cognition pack as a second
grounding source alongside the session vault.

== chat/pack_grounding.py (new) ==

Loads en_core_cognition_v1's lexicon once (cached; immutable pack)
and exposes:

  pack_grounded_surface(lemma) -> str | None

Returns a deterministic, fully pack-sourced surface:

  "{lemma} — pack-grounded ({pack_id}): {d1}; {d2}; {d3}. No session evidence yet."

Every visible atom is the lemma or a verbatim semantic_domains
string from the pack.  No rewording, no synthesis, no LLM.

== chat/runtime.py ==

_stub_response gains optional pack_grounded_surface= parameter.
_maybe_pack_grounded_surface routes to the pack only when all four
hold: gate_source=="empty_vault", output_language=="en",
intent.tag in {DEFINITION, RECALL}, and intent.subject is a pack
lemma.  Safety/ethics refusal still takes priority above this branch.

ChatResponse and TurnEvent gain grounding_source ∈ {vault,pack,none}.
Main walk path tags responses "vault".

== core/cognition/pipeline.py ==

gate_fired detection moved from string equality on the universal
disclosure to provenance:

  gate_fired = response.vault_hits == 0 and response.grounding_source != "vault"

Same intent (suppress realizer template on gate-fired turns),
broader stub-path surface set.

== Characterisation (core eval cognition, 13-case public split) ==

  Metric                  Pre        Post     Δ
  intent_accuracy        100.0%     100.0%    0
  surface_groundedness    15.4%      46.2%   +30.8 pp
  term_capture_rate        0.0%      33.3%   +33.3 pp
  versor_closure_rate    100.0%     100.0%    0

Lift is non-uniform by design: only single-lemma DEFINITION/RECALL
on pack-known English subjects engage.  CAUSE/COMPARISON/VERIFICATION
and multi-word OOV subjects still return the universal disclosure —
fabricating those would violate the no-LLM-fallback doctrine.

== Tests ==

  tests/test_pack_grounding.py                          18 passed
  tests/test_semantic_realizer_integration.py (updated) 1 stub-path test
    pinned to the broader contract: surface is either universal
    disclosure or pack-grounded; never the realizer template.

== Lanes ==

  smoke 67  cognition 121  runtime 19  algebra 132
  teaching 17  packs 6

versor_condition(F) < 1e-6 invariant unaffected (no algebra changes).
This commit is contained in:
Shay 2026-05-18 06:36:10 -07:00
parent 5194a14788
commit c28e107dc7
8 changed files with 633 additions and 14 deletions

120
chat/pack_grounding.py Normal file
View file

@ -0,0 +1,120 @@
"""chat/pack_grounding.py — pack-grounded surface for cold-start DEFINITION
and RECALL intents (ADR-0048).
When the ``UnknownDomainGate`` fires with ``source="empty_vault"`` i.e.
the runtime has no session evidence yet the runtime would otherwise
emit the universal ``_UNKNOWN_DOMAIN_SURFACE`` disclosure on every turn,
including for terms that are explicitly compiled into the ratified
cognition pack.
This module supplies a narrow, auditable alternative: when the input's
intent is ``DEFINITION`` or ``RECALL`` AND the intent's subject lemma is
present in ``en_core_cognition_v1``, return a deterministic surface
composed from the pack lexicon's ``semantic_domains`` for that lemma,
explicitly tagged as pack-sourced.
Design constraints (matching the seven axioms):
- Geometry-first: the pack lookup is by lemma surface, but the
``semantic_domains`` were curated against the same versors the
vocabulary carries; the surface refers only to the lemma and its
curated descriptors no synthesis, no LLM fallback.
- Reconstruction-over-storage: the surface is reconstructed from the
pack at call time; the lexicon is loaded once and cached because
ratified packs are immutable.
- Dual-correction: any lemma not in the pack returns ``None``;
callers fall through to ``_UNKNOWN_DOMAIN_SURFACE`` unchanged.
- Compilation-last: no tensors, no kernels JSONL read and string
formatting only.
- Trust boundary: every surface produced here is explicitly tagged
``pack:en_core_cognition_v1`` so the audit contract distinguishes
pack-grounded surfaces from vault-grounded surfaces and from the
universal disclosure.
"""
from __future__ import annotations
import json
from functools import lru_cache
from pathlib import Path
PACK_ID: str = "en_core_cognition_v1"
_PACK_LEXICON_PATH = (
Path(__file__).resolve().parent.parent
/ "language_packs"
/ "data"
/ PACK_ID
/ "lexicon.jsonl"
)
@lru_cache(maxsize=1)
def _pack_index() -> dict[str, tuple[str, ...]]:
"""Load the cognition pack lexicon once and return ``{lemma: semantic_domains}``.
Ratified packs are immutable; safe to cache for the process lifetime.
Returns an empty dict if the pack is unavailable callers must treat
a missing pack as "no pack-grounded surface available."
"""
if not _PACK_LEXICON_PATH.exists():
return {}
out: dict[str, tuple[str, ...]] = {}
for line in _PACK_LEXICON_PATH.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line:
continue
try:
entry = json.loads(line)
except json.JSONDecodeError:
continue
lemma = entry.get("lemma") or entry.get("surface")
if not lemma:
continue
domains = tuple(entry.get("semantic_domains", ()))
if domains:
out[lemma.lower()] = domains
return out
def pack_grounded_surface(lemma: str) -> str | None:
"""Return a deterministic pack-grounded surface for *lemma*, or ``None``.
The surface format is fixed:
"{lemma} — pack-grounded ({pack_id}): {d1}; {d2}; {d3}. No session evidence yet."
Only the lemma and up to three semantic_domains from the pack are
emitted; both come directly from the ratified pack lexicon, with no
rewording. The trailing disclosure is the constant trust-boundary
label that distinguishes pack-grounded surfaces from vault-grounded
surfaces (which would carry session evidence) and from the universal
"insufficient grounding" disclosure (which carries neither).
Returns ``None`` when:
- the lemma is empty or not a string,
- the pack lexicon file is unavailable,
- the lemma is not present in the pack,
- the pack entry has no ``semantic_domains``.
"""
if not lemma or not isinstance(lemma, str):
return None
key = lemma.strip().lower()
if not key:
return None
index = _pack_index()
domains = index.get(key)
if not domains:
return None
head = "; ".join(domains[:3])
return (
f"{key} — pack-grounded ({PACK_ID}): {head}. "
f"No session evidence yet."
)
def is_pack_lemma(lemma: str) -> bool:
"""Return True iff *lemma* has an entry with ``semantic_domains`` in the pack."""
if not lemma or not isinstance(lemma, str):
return False
return lemma.strip().lower() in _pack_index()

View file

@ -10,6 +10,7 @@ from typing import List
import numpy as np
from algebra.versor import versor_condition
from chat.pack_grounding import pack_grounded_surface, PACK_ID as _COGNITION_PACK_ID
from chat.refusal import (
build_hedge_prefix,
build_refusal_surface,
@ -261,6 +262,13 @@ class ChatResponse:
# (refusal_emitted, hedge_injected). Typed as ``object`` to avoid
# coupling at module-resolution time; downcast at use site.
verdicts: object = None
# ADR-0048 — provenance tag for the surface's grounding. One of:
# "vault" — answer drawn from session vault evidence (main path).
# "pack" — answer drawn from the ratified language pack (cold-start
# fallback for DEFINITION/RECALL on pack-known lemmas).
# "none" — universal "insufficient grounding" disclosure on stub.
# The string is preserved verbatim in TurnEvent for downstream audit.
grounding_source: str = "none"
class ChatRuntime:
@ -514,11 +522,42 @@ class ChatRuntime:
axis_hedges=axis_hedges,
)
def _maybe_pack_grounded_surface(
self, text: str, gate_source: str
) -> str | None:
"""ADR-0048 — return a pack-grounded surface, or None.
Only engages when:
- the gate fired because the session vault is empty,
- the classified intent is DEFINITION or RECALL,
- the intent's subject lemma is in the ratified cognition pack
(``en_core_cognition_v1``).
Any other condition returns None and the caller falls through to
the universal "insufficient grounding" disclosure. English path
only the cognition pack is English-specific; non-English runs
retain the unchanged disclosure.
"""
if gate_source != "empty_vault":
return None
if self.config.output_language != "en":
return None
from generate.intent import IntentTag # local to avoid coupling at import time
from generate.intent_bridge import classify_intent_from_input
intent = classify_intent_from_input(text)
if intent.tag not in (IntentTag.DEFINITION, IntentTag.RECALL):
return None
lemma = (intent.subject or "").strip()
if not lemma:
return None
return pack_grounded_surface(lemma)
def _stub_response(
self,
field_state: FieldState,
*,
tokens: tuple[str, ...] = (),
pack_grounded_surface: str | None = None,
) -> ChatResponse:
zero = np.zeros(field_state.F.shape, dtype=np.float32)
prop = Proposition(
@ -574,8 +613,23 @@ class ChatRuntime:
if refusal_emitted:
response_surface = refusal_surface
self._last_refusal_was_typed = True
elif pack_grounded_surface is not None:
# ADR-0048 — pack-grounded surface for cold-start DEFINITION /
# RECALL on a known pack lemma. Safety/ethics refusal still
# take priority above this branch; the pack surface only
# replaces the universal "insufficient grounding" disclosure
# when no refusal applies.
response_surface = pack_grounded_surface
else:
response_surface = _UNKNOWN_DOMAIN_SURFACE
# ADR-0048 — grounding provenance recorded for both ChatResponse
# and TurnEvent. ``"pack"`` only when we actually emit the
# pack-grounded surface (refusal does not override the source —
# refusal is a remediation tier, not a grounding source).
if pack_grounded_surface is not None and not refusal_emitted:
grounding_source = "pack"
else:
grounding_source = "none"
# ADR-0038 — hedge injection does NOT run on the stub path
# (the unknown-domain marker is already a disclosure surface;
# prepending a hedge would be a confused double-disclosure).
@ -609,6 +663,7 @@ class ChatRuntime:
safety_verdict=safety_verdict,
ethics_verdict=ethics_verdict,
verdicts=verdicts_bundle,
grounding_source=grounding_source,
)
self.turn_log.append(stub_event)
self._emit_turn_event(stub_event)
@ -631,6 +686,7 @@ class ChatRuntime:
safety_verdict=safety_verdict,
ethics_verdict=ethics_verdict,
verdicts=verdicts_bundle,
grounding_source=grounding_source,
)
def chat(self, text: str, max_tokens: int | None = None) -> ChatResponse:
@ -655,14 +711,32 @@ class ChatRuntime:
if gate_decision.fire:
committed = self._context.commit_ingest(filtered)
empty_result = GenerationResult(tokens=(), final_state=committed, vault_hits=0)
# ADR-0048 — pack-grounded fallback for cold-start DEFINITION /
# RECALL on a known pack lemma. Only engages when the gate
# fired because the session vault is empty (``empty_vault``)
# AND the classified intent is DEFINITION or RECALL AND the
# intent's subject lemma is in the ratified cognition pack.
# Any other condition falls through to the universal
# "insufficient grounding" disclosure unchanged.
pack_surface = self._maybe_pack_grounded_surface(
text, gate_decision.source
)
self._context.finalize_turn(
empty_result,
tokens_in=tuple(filtered),
input_versor=committed.F,
dialogue_role="assert",
metadata={"unknown": True, "unknown_source": gate_decision.source},
metadata={
"unknown": True,
"unknown_source": gate_decision.source,
"grounding_source": "pack" if pack_surface else "none",
},
)
return self._stub_response(
committed,
tokens=tuple(filtered),
pack_grounded_surface=pack_surface,
)
return self._stub_response(committed, tokens=tuple(filtered))
field_state = self._context.commit_ingest(filtered)
field_state = self._apply_drive_bias(field_state)
@ -856,6 +930,7 @@ class ChatRuntime:
safety_verdict=safety_verdict,
ethics_verdict=ethics_verdict,
verdicts=verdicts_bundle,
grounding_source="vault",
)
self.turn_log.append(turn_event)
self._emit_turn_event(turn_event)
@ -880,6 +955,7 @@ class ChatRuntime:
safety_verdict=safety_verdict,
ethics_verdict=ethics_verdict,
verdicts=verdicts_bundle,
grounding_source="vault",
)
def _unknown_domain_response(self, field_state: FieldState, filtered: list[str]) -> ChatResponse:

View file

@ -131,16 +131,16 @@ class CognitiveTurnPipeline:
# the better articulation surface from the semantic path.
#
# Exception: when the unknown-domain gate fired, ChatRuntime
# returns the safety stub ("I don't have field coordinates for
# that yet.") and `response.vault_hits == 0`. In that case the
# realizer's fallback surface is template-noise that
# returns either the universal "insufficient grounding" stub
# (ADR-0035) or a pack-grounded surface (ADR-0048) — in both
# cases the realizer's fallback would be template-noise that
# contradicts the gate's honest "no_grounding" signal, so we
# keep the gate's stub user-visible. walk_surface is unaffected
# either way. Addresses calibration gaps.md Finding 2.
from chat.runtime import _UNKNOWN_DOMAIN_SURFACE
# keep ChatRuntime's stub-path surface user-visible. walk_surface
# is unaffected either way. Addresses calibration gaps.md
# Finding 2.
gate_fired = (
response.vault_hits == 0
and response.surface == _UNKNOWN_DOMAIN_SURFACE
and getattr(response, "grounding_source", "vault") != "vault"
)
surface = response.surface
articulation_surface = response.articulation_surface

View file

@ -285,3 +285,7 @@ class TurnEvent:
# Carries refusal_emitted / hedge_injected remediation flags
# alongside the three verdict surfaces.
verdicts: object = None
# ADR-0048 — provenance tag mirroring ChatResponse.grounding_source.
# One of "vault" | "pack" | "none". Preserved verbatim through the
# TurnEvent telemetry stream for downstream audit consumers.
grounding_source: str = "none"

View file

@ -0,0 +1,257 @@
# ADR-0048 — Pack-Grounded Surface for Cold-Start DEFINITION / RECALL
**Status:** Accepted
**Date:** 2026-05-18
**Author:** Shay
---
## Context
[ADR-0047](./ADR-0047-wire-forward-graph-constraint.md) isolated the
load-bearing finding from the cognition lane: with the forward graph
constraint engaged (6/13 cases), `surface_groundedness` and
`term_capture_rate` did **not** move. The candidate-set restriction
upstream of `generate()` was working — the gap lived **downstream of
propagation**.
Investigation traced every cognition case to a single failure mode.
`ChatRuntime.chat()` consults `UnknownDomainGate` on each turn; the
gate measures whether anything in the **session vault** is similar to
the input. A freshly instantiated runtime starts with `len(vault) == 0`,
so the gate fires immediately with `source="empty_vault"` and routes
through `_stub_response`, emitting the universal disclosure:
"I don't know — insufficient grounding for that yet."
This is doctrine-correct refusal — no fabrication. But it elides the
fact that CORE *does* have grounded evidence for `light`, `knowledge`,
`meaning`, `memory`, `truth`, `procedure`, `relation`, and many other
cognition-core lemmas: they are compiled into the **ratified
`en_core_cognition_v1` pack**, each carrying curated `semantic_domains`
(`["cognition.illumination", "logos.core", "perception.clarity",
"meaning.revelation"]` for `light`).
CLAUDE.md is explicit:
> *Semantic Pack Discipline — Prefer compact, curated packs … `en_core_cognition_v1`
> supplies thought vocabulary, operations, and relation predicates.*
Pack evidence is **reviewed/curated memory**, the strongest form of
grounding short of session vault evidence. The gate consulted only
session memory and treated cold-start as universally ungrounded.
---
## Decision
Add a narrow second-source-of-grounding alongside the session vault:
1. **`chat/pack_grounding.py`** (new) — loads `en_core_cognition_v1`'s
lexicon once (cached; ratified packs are immutable) and exposes
`pack_grounded_surface(lemma) -> str | None`. The surface format is
fixed and entirely composed of pack-sourced atoms:
```text
{lemma} — pack-grounded ({pack_id}): {d1}; {d2}; {d3}. No session evidence yet.
```
Every visible token is either the lemma itself or a verbatim
`semantic_domains` string from the pack. No rewording, no LLM,
no synthesis. The trailing disclosure (`No session evidence yet.`)
is a constant trust-boundary label distinguishing pack-grounded
surfaces from vault-grounded surfaces.
2. **`chat/runtime.py`** — extends `_stub_response` with an optional
`pack_grounded_surface: str | None = None` parameter. When the
`UnknownDomainGate` fires, the runtime calls a narrow helper
`_maybe_pack_grounded_surface(text, gate_source)` that returns a
non-`None` surface **only** when:
- `gate_source == "empty_vault"` (not any other gate-firing source),
- `config.output_language == "en"` (pack is English-specific),
- `intent.tag in {DEFINITION, RECALL}` (narrow intent scope),
- `intent.subject` is a known pack lemma.
When all four hold, the stub-path response carries the
pack-grounded surface instead of `_UNKNOWN_DOMAIN_SURFACE`. Safety
and ethics refusal still take priority above this branch (refusal is
a remediation tier, not a grounding source).
3. **`ChatResponse.grounding_source`** and **`TurnEvent.grounding_source`** —
one new field on each, valued in `{"vault", "pack", "none"}`. The
tag is preserved verbatim through the telemetry stream so downstream
audit consumers can distinguish session-evidence answers, pack-
evidence answers, and disclosures.
4. **`core/cognition/pipeline.py`** — `gate_fired` detection is moved
from string equality on `_UNKNOWN_DOMAIN_SURFACE` to provenance:
```python
gate_fired = response.vault_hits == 0 and response.grounding_source != "vault"
```
The realizer's template fallback (`"Truth is defined as ..."`) is
suppressed identically on both stub-path surfaces (universal
disclosure and pack-grounded), preserving the
"calibration gaps Finding 2" contract while accepting the broader
stub-path surface set.
---
## Why this is doctrine-aligned, not a fabrication
CLAUDE.md prohibits *opaque LLM fallbacks, stochastic sampling, hidden
normalisation, hot-path repair, and approximate recall*. Pack-grounded
surfaces are:
- **Not opaque.** Every visible atom is the lemma or a verbatim pack
`semantic_domains` string; the source pack is named in the surface.
- **Not stochastic.** Deterministic JSONL read; identical input
produces byte-identical output (`test_surface_is_deterministic`).
- **Not hidden normalisation.** The pack lookup is a separate source
of grounding, not a normalisation step inside an existing operator.
- **Not hot-path repair.** `UnknownDomainGate` remains correct — the
gate still fires; the change is what the runtime emits *after* the
gate fires, in a narrow, intent-typed, audit-tagged branch.
- **Not approximate recall.** Exact dictionary lookup on the pack
lexicon by lemma. No metric, no neighbourhood, no threshold.
The fundamental architectural move is recognising that **the system has
two grounding sources, not one**: session vault and reviewed pack. The
old code consulted only session vault; this ADR extends the recall step
to also consult the reviewed pack, with provenance preserved end-to-end.
This matches the same trust-boundary discipline ADR-0029 (safety packs)
and ADR-0033 (ethics packs) established for the verdict surfaces:
multiple ratified packs compose into the runtime's evidence basis, each
with its own provenance tag. Identity / safety / ethics packs already
contribute to the manifold and the verdict bundle; this ADR adds the
cognition pack as a corresponding contributor to the *surface*.
---
## Characterisation — `core eval cognition`
A/B run on the 13-case public cognition split, identical
`RuntimeConfig` except for the merge of this ADR:
| Metric | Pre-ADR-0048 | Post-ADR-0048 | Δ |
|---------------------------|--------------|---------------|------------|
| `intent_accuracy` | 100.0 % | 100.0 % | 0 |
| `surface_groundedness` | 15.4 % | **46.2 %** | **+30.8** |
| `term_capture_rate` | 0.0 % | **33.3 %** | **+33.3** |
| `versor_closure_rate` | 100.0 % | 100.0 % | 0 |
| `versor_condition < 1e-6` | preserved | preserved | invariant |
The lift is **not** uniform across cases. ADR-0048 only engages on
single-lemma DEFINITION / RECALL where the subject is in the pack. The
remaining cases (CAUSE / COMPARISON / VERIFICATION / multi-word OOV
subjects) still return the universal disclosure, which is the correct
behaviour — the cognition pack doesn't carry causal explanations or
verification logic, and fabricating those would violate the no-LLM-
fallback doctrine.
Surface examples (post-ADR-0048):
| Prompt | Surface |
|--------|---------|
| `What is light?` | `"light — pack-grounded (en_core_cognition_v1): cognition.illumination; logos.core; perception.clarity. No session evidence yet."` |
| `What is knowledge?` | `"knowledge — pack-grounded (en_core_cognition_v1): cognition.knowledge; epistemic.ground; memory.semantic. No session evidence yet."` |
| `Remember light` | `"light — pack-grounded (en_core_cognition_v1): cognition.illumination; logos.core; perception.clarity. No session evidence yet."` |
| `Why does light exist?` | `"I don't know — insufficient grounding for that yet."` (CAUSE intent — no pack path) |
| `What is a procedure?` | `"I don't know — insufficient grounding for that yet."` (multi-word subject "a procedure" not in pack index) |
---
## Consequences
### What changes
- `ChatRuntime` cold-start DEFINITION / RECALL on a pack-known English
lemma emits a pack-grounded surface instead of the universal
disclosure.
- `ChatResponse` and `TurnEvent` carry a new `grounding_source` field;
downstream audit consumers can filter on `"vault" | "pack" | "none"`.
- `CognitiveTurnPipeline.run` no longer string-matches on the universal
disclosure to detect a gate-fired turn — it uses
`response.grounding_source != "vault"`. Same intent, broader surface
set.
- One test (`test_pipeline_honours_safety_stub_when_gate_fires`)
updated to assert the broader stub-path contract: surface is either
the universal disclosure or a pack-grounded surface, and never the
realizer's `"X is defined as ..."` template.
### What does not change
- `UnknownDomainGate` semantics are unchanged. It still fires on
every empty-vault cold-start turn. Provenance signal preserved
(`gate_decision.source == "empty_vault"`).
- Safety / ethics refusal still takes priority above pack grounding
— refusal is a remediation tier, not a grounding source.
- `_UNKNOWN_DOMAIN_SURFACE` constant retained; non-English, non-pack-
lemma, non-DEFINITION/RECALL paths still return it unchanged.
- `versor_condition(F) < 1e-6` invariant unaffected (no algebra
changes).
- Main walk path's `ChatResponse` carries `grounding_source="vault"`.
### Scope limits
- English path only (`en_core_cognition_v1`). Multilingual cognition
packs would follow the same pattern under a separate ADR.
- DEFINITION + RECALL only. CAUSE / COMPARISON / VERIFICATION /
PROCEDURE intents are out of scope — they need either teaching-store
chains (ADR-0018) or operator-driven inference, not pack lookup.
- Single-lemma subjects only. Multi-word subjects produced by the
intent classifier (`"does light exist"`, `"a procedure"`) bypass the
pack lookup; tightening the intent classifier's subject extraction
is a separate concern and a candidate follow-up ADR.
- Top-3 `semantic_domains` rendered. The pack's deterministic order
in the JSONL determines which three; pack authors can re-order if
needed (a one-line edit to the lexicon).
---
## Cross-References
- [ADR-0018](./ADR-0018-tool-use-scope.md) — `intent_bridge` /
`classify_intent` whose `DialogueIntent.tag` and `subject` this
ADR consults.
- [ADR-0029](./ADR-0029-safety-packs.md) and
[ADR-0033](./ADR-0033-ethics-packs.md) — sibling pack-grounded
contributors to the runtime: this ADR extends that pattern from
*verdict* surfaces to the *answer* surface.
- [ADR-0035](./ADR-0035-turn-loop-verdict-surfacing.md) — stub-path
TurnEvent emission that pack-grounded surfaces preserve.
- [ADR-0046](./ADR-0046-forward-graph-constraint.md) /
[ADR-0047](./ADR-0047-wire-forward-graph-constraint.md) — the
Pillar 1→2→3 wiring whose A/B characterisation isolated the
surface-grounding gap addressed here.
---
## Verification
```
tests/test_pack_grounding.py — 18 tests, all green
tests/test_semantic_realizer_integration.py — gate-fired test updated, all green
Lanes (all green on this branch):
core test --suite smoke 67 passed
core test --suite cognition 121 passed
core test --suite runtime 19 passed
core test --suite algebra 132 passed
core test --suite teaching 17 passed
core test --suite packs 6 passed
core eval cognition (pre → post):
intent_accuracy 100.0% → 100.0% (=)
surface_groundedness 15.4% → 46.2% (+30.8 pp)
term_capture_rate 0.0% → 33.3% (+33.3 pp)
versor_closure_rate 100.0% → 100.0% (=)
```
The non-negotiable field invariant (`versor_condition(F) < 1e-6`) is
preserved: this ADR adds a surface-construction branch on the
existing stub path — no algebra changes, no rotor construction,
no field update.

View file

@ -57,6 +57,7 @@ ADRs record significant architectural decisions: what was decided, why, what alt
| [ADR-0045](ADR-0045-long-context-recall-vs-transformer-baselines.md) | Long-context recall: CORE vs transformer baselines | Accepted (2026-05-17) |
| [ADR-0046](ADR-0046-forward-graph-constraint.md) | PropositionGraph as forward AdmissibilityRegion + industry demos | Accepted (2026-05-18) |
| [ADR-0047](ADR-0047-wire-forward-graph-constraint.md) | Wire forward graph constraint into the chat hot path (opt-in) | Accepted (2026-05-18) |
| [ADR-0048](ADR-0048-pack-grounded-surface.md) | Pack-grounded surface for cold-start DEFINITION / RECALL | Accepted (2026-05-18) |
---

View file

@ -0,0 +1,144 @@
"""ADR-0048 — pack-grounded fallback surface tests.
The contract these tests pin:
- Pack-grounded surfaces engage ONLY when the ``UnknownDomainGate``
fires with ``source="empty_vault"`` AND the intent is DEFINITION
or RECALL AND the subject lemma is in ``en_core_cognition_v1``.
- The surface is composed verbatim from the pack lexicon's
``semantic_domains`` and the lemma no synthesis.
- The audit contract is preserved: ChatResponse and TurnEvent both
carry a ``grounding_source`` provenance tag set to ``"pack"`` on
the pack-grounded path, ``"none"`` on the universal disclosure,
and ``"vault"`` on the main walk path.
- Safety / ethics refusal still takes priority pack-grounded
surfaces never bypass a SafetyVerdict violation.
"""
from __future__ import annotations
import pytest
from chat.pack_grounding import (
PACK_ID,
is_pack_lemma,
pack_grounded_surface,
)
from chat.runtime import ChatRuntime, _UNKNOWN_DOMAIN_SURFACE
# ---------------------------------------------------------------------------
# pack_grounding module — pure-function contracts
# ---------------------------------------------------------------------------
@pytest.mark.parametrize("lemma", ["light", "knowledge", "meaning", "memory", "truth"])
def test_known_pack_lemmas_produce_grounded_surface(lemma: str) -> None:
surface = pack_grounded_surface(lemma)
assert surface is not None
assert lemma in surface
assert PACK_ID in surface
assert "No session evidence yet." in surface
def test_unknown_lemma_returns_none() -> None:
assert pack_grounded_surface("nonexistentwordxyz") is None
@pytest.mark.parametrize("bad", ["", " ", None])
def test_empty_or_invalid_lemma_returns_none(bad) -> None:
assert pack_grounded_surface(bad) is None # type: ignore[arg-type]
def test_is_pack_lemma_round_trips() -> None:
assert is_pack_lemma("light") is True
assert is_pack_lemma("nonexistentwordxyz") is False
assert is_pack_lemma("") is False
def test_surface_is_deterministic() -> None:
"""Same lemma must produce byte-identical surfaces on repeat calls
pack is immutable, no randomness, no synthesis."""
a = pack_grounded_surface("light")
b = pack_grounded_surface("light")
assert a == b
assert a is not None
# ---------------------------------------------------------------------------
# ChatRuntime integration — cold-start path
# ---------------------------------------------------------------------------
def test_cold_start_definition_returns_pack_grounded_surface() -> None:
"""Cold-start DEFINITION on a pack-known lemma routes through the
pack-grounded surface, not the universal disclosure."""
rt = ChatRuntime()
resp = rt.chat("What is light?")
assert "pack-grounded" in resp.surface
assert "light" in resp.surface
assert resp.grounding_source == "pack"
def test_cold_start_recall_returns_pack_grounded_surface() -> None:
"""RECALL intent on a pack-known lemma also engages the pack path."""
rt = ChatRuntime()
resp = rt.chat("Remember light")
assert resp.grounding_source == "pack"
assert "light" in resp.surface
def test_cold_start_unknown_lemma_returns_universal_disclosure() -> None:
"""When the gate fires AND the subject lemma is not in the pack,
we fall through to the universal disclosure unchanged."""
rt = ChatRuntime()
# 'addresses' is in vocab but subject after intent parsing will be
# the multi-word phrase, which won't match the pack lemma index.
resp = rt.chat("How can I correct an error?")
assert resp.surface == _UNKNOWN_DOMAIN_SURFACE
assert resp.grounding_source == "none"
def test_cold_start_non_definition_intent_no_pack_grounding() -> None:
"""CAUSE / COMPARISON / VERIFICATION intents do not engage the
pack path even when their subject is a pack lemma narrow scope
of ADR-0048 is DEFINITION + RECALL only."""
rt = ChatRuntime()
# 'Why does light exist?' is CAUSE intent.
resp = rt.chat("Why does light exist?")
assert resp.grounding_source == "none"
assert resp.surface == _UNKNOWN_DOMAIN_SURFACE
def test_turn_event_carries_grounding_source() -> None:
"""ADR-0048 provenance propagates to TurnEvent for downstream audit."""
rt = ChatRuntime()
rt.chat("What is light?")
last_event = rt.turn_log[-1]
assert getattr(last_event, "grounding_source", None) == "pack"
def test_chat_response_grounding_source_default_for_main_path() -> None:
"""When the walk path runs, the ChatResponse carries
``grounding_source="vault"``. We force the walk by priming the
vault with one turn so the second turn's gate clears."""
rt = ChatRuntime()
rt.chat("light truth") # seed vault with one known-token turn
resp = rt.chat("light truth")
# The second turn may or may not have vault hits depending on
# gate threshold; what we assert is that grounding_source is set
# to one of the documented values.
assert resp.grounding_source in {"vault", "pack", "none"}
def test_pack_grounded_surface_passes_safety_check() -> None:
"""Pack-grounded surfaces preserve the audit contract — safety
and ethics verdicts still surface and refusal still takes
priority above pack grounding when triggered."""
rt = ChatRuntime()
resp = rt.chat("What is light?")
# Safety verdict must be present on every stub-path response
# (ADR-0035). Specific verdict outcome depends on the safety
# pack — we only assert the audit contract holds.
assert resp.safety_verdict is not None
assert resp.ethics_verdict is not None

View file

@ -242,8 +242,17 @@ class TestChatResponseContractStillHolds:
def test_pipeline_honours_safety_stub_when_gate_fires(self) -> None:
"""When the unknown-domain gate fires, the pipeline's surface
is the gate's safety stub — NOT the realizer's fallback
articulation. Closes calibration gaps.md Finding 2."""
is ChatRuntime's stub-path surface — NOT the realizer's
fallback articulation. Closes calibration gaps.md Finding 2.
ADR-0048 broadens the stub-path surface: it may now be either
the universal disclosure (``_UNKNOWN_DOMAIN_SURFACE``) or a
pack-grounded surface for cold-start DEFINITION / RECALL on a
known pack lemma. In both cases ``grounding_source != "vault"``
and the realizer must not override. The articulation_surface
remains the universal disclosure on the stub path because no
real walk produced an articulation.
"""
try:
from chat.runtime import ChatRuntime, _UNKNOWN_DOMAIN_SURFACE
from core.cognition.pipeline import CognitiveTurnPipeline
@ -256,8 +265,16 @@ class TestChatResponseContractStillHolds:
result = pipeline.run("What is truth?")
assert result.vault_hits == 0, "gate-fired turn should have zero vault hits"
assert result.surface == _UNKNOWN_DOMAIN_SURFACE
# Surface is either the universal disclosure or a pack-grounded
# surface — both are valid stub-path surfaces. What we forbid
# is the realizer's "Truth is defined as ..." template surface
# leaking on a gate-fired turn.
is_universal = result.surface == _UNKNOWN_DOMAIN_SURFACE
is_pack_grounded = "pack-grounded" in result.surface
assert is_universal or is_pack_grounded, result.surface
assert "is defined as" not in result.surface
# articulation_surface is always the universal disclosure on
# the stub path — no real walk produced an articulation.
assert result.articulation_surface == _UNKNOWN_DOMAIN_SURFACE
# walk_surface is unaffected by the override decision — it carries
# the realizer's evidence regardless.
# walk_surface is unaffected by the override decision.
assert isinstance(result.walk_surface, str)