feat(adr-0052): teaching-grounded CAUSE/VERIFICATION surface
This commit is contained in:
parent
ecd580479a
commit
c6ade6c76f
6 changed files with 828 additions and 23 deletions
|
|
@ -15,6 +15,10 @@ from chat.pack_grounding import (
|
||||||
pack_grounded_comparison_surface,
|
pack_grounded_comparison_surface,
|
||||||
PACK_ID as _COGNITION_PACK_ID,
|
PACK_ID as _COGNITION_PACK_ID,
|
||||||
)
|
)
|
||||||
|
from chat.teaching_grounding import (
|
||||||
|
teaching_grounded_surface,
|
||||||
|
TEACHING_CORPUS_ID as _TEACHING_CORPUS_ID,
|
||||||
|
)
|
||||||
from chat.refusal import (
|
from chat.refusal import (
|
||||||
build_hedge_prefix,
|
build_hedge_prefix,
|
||||||
build_refusal_surface,
|
build_refusal_surface,
|
||||||
|
|
@ -266,11 +270,15 @@ class ChatResponse:
|
||||||
# (refusal_emitted, hedge_injected). Typed as ``object`` to avoid
|
# (refusal_emitted, hedge_injected). Typed as ``object`` to avoid
|
||||||
# coupling at module-resolution time; downcast at use site.
|
# coupling at module-resolution time; downcast at use site.
|
||||||
verdicts: object = None
|
verdicts: object = None
|
||||||
# ADR-0048 — provenance tag for the surface's grounding. One of:
|
# ADR-0048 / ADR-0050 / ADR-0052 — provenance tag for the surface's
|
||||||
# "vault" — answer drawn from session vault evidence (main path).
|
# grounding. One of:
|
||||||
# "pack" — answer drawn from the ratified language pack (cold-start
|
# "vault" — answer drawn from session vault evidence (main path).
|
||||||
# fallback for DEFINITION/RECALL on pack-known lemmas).
|
# "pack" — answer drawn from the ratified language pack
|
||||||
# "none" — universal "insufficient grounding" disclosure on stub.
|
# (cold-start DEFINITION/RECALL/COMPARISON on pack-known
|
||||||
|
# lemmas — ADR-0048 / ADR-0050).
|
||||||
|
# "teaching" — answer drawn from a reviewed teaching-chain corpus
|
||||||
|
# (cold-start CAUSE/VERIFICATION — ADR-0052).
|
||||||
|
# "none" — universal "insufficient grounding" disclosure on stub.
|
||||||
# The string is preserved verbatim in TurnEvent for downstream audit.
|
# The string is preserved verbatim in TurnEvent for downstream audit.
|
||||||
grounding_source: str = "none"
|
grounding_source: str = "none"
|
||||||
|
|
||||||
|
|
@ -528,19 +536,29 @@ class ChatRuntime:
|
||||||
|
|
||||||
def _maybe_pack_grounded_surface(
|
def _maybe_pack_grounded_surface(
|
||||||
self, text: str, gate_source: str
|
self, text: str, gate_source: str
|
||||||
) -> str | None:
|
) -> tuple[str, str] | None:
|
||||||
"""ADR-0048 — return a pack-grounded surface, or None.
|
"""Return ``(surface, grounding_source)`` or ``None``.
|
||||||
|
|
||||||
|
ADR-0048 / ADR-0050 / ADR-0052 — three reviewed sources of
|
||||||
|
cold-start grounding share this dispatcher:
|
||||||
|
|
||||||
|
- DEFINITION / RECALL → pack-grounded surface (ADR-0048)
|
||||||
|
- COMPARISON → pack-grounded surface (ADR-0050)
|
||||||
|
- CAUSE / VERIFICATION → teaching-grounded surface (ADR-0052)
|
||||||
|
|
||||||
|
Engagement conditions common to all three branches:
|
||||||
|
|
||||||
Only engages when:
|
|
||||||
- the gate fired because the session vault is empty,
|
- the gate fired because the session vault is empty,
|
||||||
- the classified intent is DEFINITION or RECALL,
|
- ``config.output_language == "en"``,
|
||||||
- the intent's subject lemma is in the ratified cognition pack
|
- the classified intent has a clean subject lemma.
|
||||||
(``en_core_cognition_v1``).
|
|
||||||
|
|
||||||
Any other condition returns None and the caller falls through to
|
Returns ``None`` when no branch applies and the caller falls
|
||||||
the universal "insufficient grounding" disclosure. English path
|
through to the universal "insufficient grounding" disclosure.
|
||||||
only — the cognition pack is English-specific; non-English runs
|
|
||||||
retain the unchanged disclosure.
|
The grounding_source string returned alongside the surface is
|
||||||
|
one of ``"pack"`` (ADR-0048/0050) or ``"teaching"`` (ADR-0052)
|
||||||
|
and is preserved verbatim through ChatResponse and TurnEvent
|
||||||
|
for downstream audit.
|
||||||
"""
|
"""
|
||||||
if gate_source != "empty_vault":
|
if gate_source != "empty_vault":
|
||||||
return None
|
return None
|
||||||
|
|
@ -557,13 +575,25 @@ class ChatRuntime:
|
||||||
lemma_b = (intent.secondary_subject or "").strip()
|
lemma_b = (intent.secondary_subject or "").strip()
|
||||||
if not lemma_a or not lemma_b:
|
if not lemma_a or not lemma_b:
|
||||||
return None
|
return None
|
||||||
return pack_grounded_comparison_surface(lemma_a, lemma_b)
|
surface = pack_grounded_comparison_surface(lemma_a, lemma_b)
|
||||||
|
return (surface, "pack") if surface is not None else None
|
||||||
|
# ADR-0052 — teaching-grounded CAUSE / VERIFICATION. The chain
|
||||||
|
# corpus is reviewed memory; every emitted atom is either a
|
||||||
|
# lemma, a verbatim pack semantic_domains string, or a fixed
|
||||||
|
# connective from humanize_predicate.
|
||||||
|
if intent.tag in (IntentTag.CAUSE, IntentTag.VERIFICATION):
|
||||||
|
lemma = (intent.subject or "").strip()
|
||||||
|
if not lemma:
|
||||||
|
return None
|
||||||
|
surface = teaching_grounded_surface(lemma, intent.tag)
|
||||||
|
return (surface, "teaching") if surface is not None else None
|
||||||
if intent.tag not in (IntentTag.DEFINITION, IntentTag.RECALL):
|
if intent.tag not in (IntentTag.DEFINITION, IntentTag.RECALL):
|
||||||
return None
|
return None
|
||||||
lemma = (intent.subject or "").strip()
|
lemma = (intent.subject or "").strip()
|
||||||
if not lemma:
|
if not lemma:
|
||||||
return None
|
return None
|
||||||
return pack_grounded_surface(lemma)
|
surface = pack_grounded_surface(lemma)
|
||||||
|
return (surface, "pack") if surface is not None else None
|
||||||
|
|
||||||
def _stub_response(
|
def _stub_response(
|
||||||
self,
|
self,
|
||||||
|
|
@ -571,6 +601,7 @@ class ChatRuntime:
|
||||||
*,
|
*,
|
||||||
tokens: tuple[str, ...] = (),
|
tokens: tuple[str, ...] = (),
|
||||||
pack_grounded_surface: str | None = None,
|
pack_grounded_surface: str | None = None,
|
||||||
|
grounded_source_tag: str = "pack",
|
||||||
) -> ChatResponse:
|
) -> ChatResponse:
|
||||||
zero = np.zeros(field_state.F.shape, dtype=np.float32)
|
zero = np.zeros(field_state.F.shape, dtype=np.float32)
|
||||||
prop = Proposition(
|
prop = Proposition(
|
||||||
|
|
@ -640,7 +671,9 @@ class ChatRuntime:
|
||||||
# pack-grounded surface (refusal does not override the source —
|
# pack-grounded surface (refusal does not override the source —
|
||||||
# refusal is a remediation tier, not a grounding source).
|
# refusal is a remediation tier, not a grounding source).
|
||||||
if pack_grounded_surface is not None and not refusal_emitted:
|
if pack_grounded_surface is not None and not refusal_emitted:
|
||||||
grounding_source = "pack"
|
# ADR-0052 — preserve provenance: pack-grounded surfaces tag
|
||||||
|
# ``"pack"``, teaching-grounded surfaces tag ``"teaching"``.
|
||||||
|
grounding_source = grounded_source_tag
|
||||||
else:
|
else:
|
||||||
grounding_source = "none"
|
grounding_source = "none"
|
||||||
# ADR-0038 — hedge injection does NOT run on the stub path
|
# ADR-0038 — hedge injection does NOT run on the stub path
|
||||||
|
|
@ -731,9 +764,14 @@ class ChatRuntime:
|
||||||
# intent's subject lemma is in the ratified cognition pack.
|
# intent's subject lemma is in the ratified cognition pack.
|
||||||
# Any other condition falls through to the universal
|
# Any other condition falls through to the universal
|
||||||
# "insufficient grounding" disclosure unchanged.
|
# "insufficient grounding" disclosure unchanged.
|
||||||
pack_surface = self._maybe_pack_grounded_surface(
|
pack_result = self._maybe_pack_grounded_surface(
|
||||||
text, gate_decision.source
|
text, gate_decision.source
|
||||||
)
|
)
|
||||||
|
if pack_result is None:
|
||||||
|
pack_surface = None
|
||||||
|
pack_source_tag = "none"
|
||||||
|
else:
|
||||||
|
pack_surface, pack_source_tag = pack_result
|
||||||
self._context.finalize_turn(
|
self._context.finalize_turn(
|
||||||
empty_result,
|
empty_result,
|
||||||
tokens_in=tuple(filtered),
|
tokens_in=tuple(filtered),
|
||||||
|
|
@ -742,13 +780,14 @@ class ChatRuntime:
|
||||||
metadata={
|
metadata={
|
||||||
"unknown": True,
|
"unknown": True,
|
||||||
"unknown_source": gate_decision.source,
|
"unknown_source": gate_decision.source,
|
||||||
"grounding_source": "pack" if pack_surface else "none",
|
"grounding_source": pack_source_tag if pack_surface else "none",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return self._stub_response(
|
return self._stub_response(
|
||||||
committed,
|
committed,
|
||||||
tokens=tuple(filtered),
|
tokens=tuple(filtered),
|
||||||
pack_grounded_surface=pack_surface,
|
pack_grounded_surface=pack_surface,
|
||||||
|
grounded_source_tag=pack_source_tag,
|
||||||
)
|
)
|
||||||
|
|
||||||
field_state = self._context.commit_ingest(filtered)
|
field_state = self._context.commit_ingest(filtered)
|
||||||
|
|
|
||||||
213
chat/teaching_grounding.py
Normal file
213
chat/teaching_grounding.py
Normal file
|
|
@ -0,0 +1,213 @@
|
||||||
|
"""chat/teaching_grounding.py — teaching-grounded surface for cold-start
|
||||||
|
CAUSE and VERIFICATION intents (ADR-0052).
|
||||||
|
|
||||||
|
ADR-0048 added pack-grounded surfaces for cold-start DEFINITION / RECALL,
|
||||||
|
and ADR-0050 extended that to COMPARISON. Both consult the ratified
|
||||||
|
``en_core_cognition_v1`` pack as a second source of grounding alongside
|
||||||
|
the session vault.
|
||||||
|
|
||||||
|
CAUSE and VERIFICATION cannot be answered from pack ``semantic_domains``
|
||||||
|
alone — those describe a single subject, not a relation between two
|
||||||
|
subjects. But the system already has reviewed, auditable memory for a
|
||||||
|
small, well-known set of cognition-core chains (e.g. ``knowledge requires
|
||||||
|
evidence``, ``memory requires recall``, ``light reveals truth``). Per
|
||||||
|
the Teaching Safety discipline in CLAUDE.md, reviewed memory may
|
||||||
|
contribute grounding evidence; this module supplies that contribution as
|
||||||
|
a third grounding source.
|
||||||
|
|
||||||
|
The corpus lives at ``teaching/cognition_chains/cognition_chains_v1.jsonl``
|
||||||
|
and is treated as reviewed, immutable memory at runtime: each entry
|
||||||
|
names a subject lemma, an intent (``cause`` or ``verification``), a
|
||||||
|
fixed connective predicate already present in
|
||||||
|
``generate/semantic_templates.py:_PREDICATE_HUMANIZE``, and an object
|
||||||
|
lemma. Both lemmas must be present in the ratified cognition pack —
|
||||||
|
every visible non-template token in the emitted surface is therefore
|
||||||
|
either one of the two lemmas, a verbatim pack ``semantic_domains``
|
||||||
|
string, or a fixed-template connective. No LLM, no synthesis, no
|
||||||
|
inference.
|
||||||
|
|
||||||
|
Design constraints (matching ADR-0048 / ADR-0050 axioms):
|
||||||
|
|
||||||
|
- Reconstruction-over-storage: the surface is reconstructed from the
|
||||||
|
corpus + pack at call time; both are loaded once and cached because
|
||||||
|
the corpus is reviewed memory (immutable) and ratified packs are
|
||||||
|
immutable.
|
||||||
|
- Dual-correction: any subject not in the corpus, any intent outside
|
||||||
|
``{CAUSE, VERIFICATION}``, or any chain referencing lemmas missing
|
||||||
|
from the pack returns ``None`` and callers fall through to
|
||||||
|
``_UNKNOWN_DOMAIN_SURFACE`` unchanged.
|
||||||
|
- Trust boundary: every surface produced here is explicitly tagged
|
||||||
|
``teaching:cognition_chains_v1`` so the audit contract distinguishes
|
||||||
|
teaching-grounded surfaces from pack-grounded surfaces from
|
||||||
|
vault-grounded surfaces.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from functools import lru_cache
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from chat.pack_grounding import PACK_ID as COGNITION_PACK_ID, _pack_index
|
||||||
|
from generate.intent import IntentTag
|
||||||
|
from generate.semantic_templates import humanize_predicate
|
||||||
|
|
||||||
|
TEACHING_CORPUS_ID: str = "cognition_chains_v1"
|
||||||
|
|
||||||
|
_VALID_INTENTS: frozenset[str] = frozenset({"cause", "verification"})
|
||||||
|
|
||||||
|
_INTENT_TAG_BY_NAME: dict[str, IntentTag] = {
|
||||||
|
"cause": IntentTag.CAUSE,
|
||||||
|
"verification": IntentTag.VERIFICATION,
|
||||||
|
}
|
||||||
|
|
||||||
|
_CORPUS_PATH = (
|
||||||
|
Path(__file__).resolve().parent.parent
|
||||||
|
/ "teaching"
|
||||||
|
/ "cognition_chains"
|
||||||
|
/ f"{TEACHING_CORPUS_ID}.jsonl"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class TeachingChain:
|
||||||
|
"""One reviewed cognition chain.
|
||||||
|
|
||||||
|
Fields are copied verbatim from the JSONL line; the runtime never
|
||||||
|
mutates them. ``provenance`` is preserved for audit but not emitted
|
||||||
|
in the user-facing surface.
|
||||||
|
"""
|
||||||
|
|
||||||
|
chain_id: str
|
||||||
|
subject: str
|
||||||
|
intent: str
|
||||||
|
connective: str
|
||||||
|
object: str
|
||||||
|
domains_subject_k: int
|
||||||
|
domains_object_k: int
|
||||||
|
provenance: str
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=1)
|
||||||
|
def _corpus_index() -> dict[tuple[str, str], TeachingChain]:
|
||||||
|
"""Load the cognition-chains corpus once.
|
||||||
|
|
||||||
|
Returns ``{(subject_lower, intent_lower): TeachingChain}``. Entries
|
||||||
|
with invalid schema, unsupported intents, or with subject/object
|
||||||
|
missing from the ratified cognition pack are dropped — the corpus
|
||||||
|
is reviewed memory but the runtime still verifies pack consistency
|
||||||
|
on load so a pack-corpus skew cannot leak a non-pack atom into a
|
||||||
|
surface.
|
||||||
|
"""
|
||||||
|
if not _CORPUS_PATH.exists():
|
||||||
|
return {}
|
||||||
|
pack = _pack_index()
|
||||||
|
out: dict[tuple[str, str], TeachingChain] = {}
|
||||||
|
for line in _CORPUS_PATH.read_text(encoding="utf-8").splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
entry = json.loads(line)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
continue
|
||||||
|
subject = (entry.get("subject") or "").strip().lower()
|
||||||
|
intent = (entry.get("intent") or "").strip().lower()
|
||||||
|
obj = (entry.get("object") or "").strip().lower()
|
||||||
|
connective = (entry.get("connective") or "").strip()
|
||||||
|
if not subject or not intent or not obj or not connective:
|
||||||
|
continue
|
||||||
|
if intent not in _VALID_INTENTS:
|
||||||
|
continue
|
||||||
|
# Both lemmas MUST be in the ratified pack — guarantees every
|
||||||
|
# surface atom is pack-sourced.
|
||||||
|
if subject not in pack or obj not in pack:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
chain = TeachingChain(
|
||||||
|
chain_id=str(entry.get("chain_id") or f"{subject}_{intent}"),
|
||||||
|
subject=subject,
|
||||||
|
intent=intent,
|
||||||
|
connective=connective,
|
||||||
|
object=obj,
|
||||||
|
domains_subject_k=int(entry.get("domains_subject_k", 2)),
|
||||||
|
domains_object_k=int(entry.get("domains_object_k", 1)),
|
||||||
|
provenance=str(entry.get("provenance", "")),
|
||||||
|
)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
continue
|
||||||
|
out[(subject, intent)] = chain
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def _intent_name(intent_tag: IntentTag) -> str | None:
|
||||||
|
"""Return the lower-case intent key for the corpus, or ``None``."""
|
||||||
|
if intent_tag is IntentTag.CAUSE:
|
||||||
|
return "cause"
|
||||||
|
if intent_tag is IntentTag.VERIFICATION:
|
||||||
|
return "verification"
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def teaching_grounded_surface(
|
||||||
|
subject_lemma: str, intent_tag: IntentTag
|
||||||
|
) -> str | None:
|
||||||
|
"""Return a deterministic teaching-grounded surface, or ``None``.
|
||||||
|
|
||||||
|
The surface format is fixed:
|
||||||
|
|
||||||
|
"{subject} — teaching-grounded ({corpus_id}): {ds1}; {ds2}.
|
||||||
|
{subject} {connective} {object} ({do1}). No session evidence yet."
|
||||||
|
|
||||||
|
Every visible non-template token is either one of the two lemmas, a
|
||||||
|
verbatim ``semantic_domains`` string from the ratified cognition
|
||||||
|
pack, or the connective predicate already humanised by
|
||||||
|
``generate.semantic_templates.humanize_predicate``. The trailing
|
||||||
|
disclosure (``No session evidence yet.``) is the constant
|
||||||
|
trust-boundary label that distinguishes teaching-grounded surfaces
|
||||||
|
from vault-grounded surfaces.
|
||||||
|
|
||||||
|
Returns ``None`` when:
|
||||||
|
- the lemma is empty or not a string,
|
||||||
|
- the intent tag is not ``CAUSE`` or ``VERIFICATION``,
|
||||||
|
- the (subject, intent) pair is not in the teaching corpus.
|
||||||
|
"""
|
||||||
|
if not subject_lemma or not isinstance(subject_lemma, str):
|
||||||
|
return None
|
||||||
|
key = subject_lemma.strip().lower()
|
||||||
|
if not key:
|
||||||
|
return None
|
||||||
|
intent_name = _intent_name(intent_tag)
|
||||||
|
if intent_name is None:
|
||||||
|
return None
|
||||||
|
chain = _corpus_index().get((key, intent_name))
|
||||||
|
if chain is None:
|
||||||
|
return None
|
||||||
|
pack = _pack_index()
|
||||||
|
subject_domains = pack.get(chain.subject, ())
|
||||||
|
object_domains = pack.get(chain.object, ())
|
||||||
|
if not subject_domains or not object_domains:
|
||||||
|
return None
|
||||||
|
head_subject = "; ".join(
|
||||||
|
subject_domains[: max(1, chain.domains_subject_k)]
|
||||||
|
)
|
||||||
|
head_object = "; ".join(
|
||||||
|
object_domains[: max(1, chain.domains_object_k)]
|
||||||
|
)
|
||||||
|
connective = humanize_predicate(chain.connective)
|
||||||
|
return (
|
||||||
|
f"{chain.subject} — teaching-grounded ({TEACHING_CORPUS_ID}): "
|
||||||
|
f"{head_subject}. {chain.subject} {connective} {chain.object} "
|
||||||
|
f"({head_object}). No session evidence yet."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def has_teaching_chain(subject_lemma: str, intent_tag: IntentTag) -> bool:
|
||||||
|
"""Return True iff a reviewed chain exists for (subject, intent)."""
|
||||||
|
if not subject_lemma or not isinstance(subject_lemma, str):
|
||||||
|
return False
|
||||||
|
intent_name = _intent_name(intent_tag)
|
||||||
|
if intent_name is None:
|
||||||
|
return False
|
||||||
|
return (subject_lemma.strip().lower(), intent_name) in _corpus_index()
|
||||||
|
|
@ -285,7 +285,9 @@ class TurnEvent:
|
||||||
# Carries refusal_emitted / hedge_injected remediation flags
|
# Carries refusal_emitted / hedge_injected remediation flags
|
||||||
# alongside the three verdict surfaces.
|
# alongside the three verdict surfaces.
|
||||||
verdicts: object = None
|
verdicts: object = None
|
||||||
# ADR-0048 — provenance tag mirroring ChatResponse.grounding_source.
|
# ADR-0048 / ADR-0050 / ADR-0052 — provenance tag mirroring
|
||||||
# One of "vault" | "pack" | "none". Preserved verbatim through the
|
# ChatResponse.grounding_source. One of:
|
||||||
# TurnEvent telemetry stream for downstream audit consumers.
|
# "vault" | "pack" | "teaching" | "none".
|
||||||
|
# Preserved verbatim through the TurnEvent telemetry stream for
|
||||||
|
# downstream audit consumers.
|
||||||
grounding_source: str = "none"
|
grounding_source: str = "none"
|
||||||
|
|
|
||||||
331
docs/decisions/ADR-0052-teaching-grounded-surface.md
Normal file
331
docs/decisions/ADR-0052-teaching-grounded-surface.md
Normal file
|
|
@ -0,0 +1,331 @@
|
||||||
|
# ADR-0052 — Teaching-Grounded Surface for Cold-Start CAUSE / VERIFICATION
|
||||||
|
|
||||||
|
**Status:** Accepted
|
||||||
|
**Date:** 2026-05-18
|
||||||
|
**Author:** Shay
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
[ADR-0048](./ADR-0048-pack-grounded-surface.md) added a pack-grounded
|
||||||
|
surface for cold-start DEFINITION / RECALL, and
|
||||||
|
[ADR-0050](./ADR-0050-pack-grounded-comparison.md) extended that to
|
||||||
|
COMPARISON. Both consult the ratified ``en_core_cognition_v1`` pack
|
||||||
|
as a second source of grounding alongside the session vault and
|
||||||
|
brought the cognition eval to
|
||||||
|
``surface_groundedness=69.2%`` / ``term_capture_rate=58.3%``.
|
||||||
|
|
||||||
|
The remaining unlift cases on the 13-case public split were:
|
||||||
|
|
||||||
|
| Case | Prompt | Intent |
|
||||||
|
|------|--------|--------|
|
||||||
|
| `cause_light_007` | "Why does light exist?" | CAUSE |
|
||||||
|
| `cause_knowledge_032` | "Why does knowledge require evidence?" | CAUSE |
|
||||||
|
| `verification_memory_037` | "Does memory require recall?" | VERIFICATION |
|
||||||
|
| `correction_specific_015` | "No, correction means reviewed repair" | CORRECTION |
|
||||||
|
|
||||||
|
`correction_specific_015` is out of scope — it requires prior-turn
|
||||||
|
context, not cold-start teaching-store inference.
|
||||||
|
|
||||||
|
The three cold-start cases share a structural property the pack alone
|
||||||
|
cannot satisfy: they ask about a **relation between two pack-known
|
||||||
|
lemmas** (`light` ↔ `truth`, `knowledge` ↔ `evidence`, `memory` ↔
|
||||||
|
`recall`). Pack ``semantic_domains`` describe a single subject, not a
|
||||||
|
relation. Fabricating the relation would violate the no-LLM-fallback
|
||||||
|
doctrine.
|
||||||
|
|
||||||
|
However, the system already has a documented place for reviewed
|
||||||
|
relational memory: per CLAUDE.md's *Teaching Safety* section and
|
||||||
|
[ADR-0018](./ADR-0018-tool-use-scope.md), reviewed memory may
|
||||||
|
contribute grounding evidence as long as it goes through a
|
||||||
|
teaching-store path and stays auditable. The doctrinally clean fix is
|
||||||
|
a third grounding source — a small, ratified corpus of cognition
|
||||||
|
chains, treated as immutable reviewed memory at runtime, whose every
|
||||||
|
emitted atom is verifiably pack-sourced.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
Add a deterministic teaching-grounded surface as a third branch of
|
||||||
|
``_maybe_pack_grounded_surface``, identical guardrails to the
|
||||||
|
DEFINITION/RECALL and COMPARISON branches.
|
||||||
|
|
||||||
|
### Corpus
|
||||||
|
|
||||||
|
A new file ``teaching/cognition_chains/cognition_chains_v1.jsonl``
|
||||||
|
holds the reviewed chain corpus. Each line:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"chain_id": "cause_knowledge_requires_evidence",
|
||||||
|
"subject": "knowledge",
|
||||||
|
"intent": "cause",
|
||||||
|
"connective": "requires",
|
||||||
|
"object": "evidence",
|
||||||
|
"domains_subject_k": 2,
|
||||||
|
"domains_object_k": 1,
|
||||||
|
"provenance": "adr-0052:reviewed:2026-05-18"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Constraints enforced at load time
|
||||||
|
(`chat/teaching_grounding.py:_corpus_index`):
|
||||||
|
|
||||||
|
- `intent` ∈ {`cause`, `verification`} (any other value is dropped).
|
||||||
|
- `subject` and `object` MUST both be present in the ratified
|
||||||
|
``en_core_cognition_v1`` pack with non-empty ``semantic_domains``.
|
||||||
|
- `connective` MUST be a key recognised by
|
||||||
|
``generate.semantic_templates.humanize_predicate`` (e.g. `requires`,
|
||||||
|
`reveals`, `evidences`, `is_grounded_in`).
|
||||||
|
|
||||||
|
Entries that fail any constraint are silently dropped — the runtime
|
||||||
|
cannot leak a non-pack atom into a surface even if the corpus is
|
||||||
|
edited improperly.
|
||||||
|
|
||||||
|
Three chains ship in v1:
|
||||||
|
|
||||||
|
| chain_id | subject | intent | connective | object |
|
||||||
|
|----------|---------|--------|------------|--------|
|
||||||
|
| `cause_light_reveals_truth` | `light` | cause | `reveals` | `truth` |
|
||||||
|
| `cause_knowledge_requires_evidence` | `knowledge` | cause | `requires` | `evidence` |
|
||||||
|
| `verification_memory_requires_recall` | `memory` | verification | `requires` | `recall` |
|
||||||
|
|
||||||
|
Each connective is corroborated by the subject's own pack
|
||||||
|
``semantic_domains`` (`light → meaning.revelation`; `memory →
|
||||||
|
recall.surface`; `knowledge → epistemic.ground`).
|
||||||
|
|
||||||
|
### Surface format
|
||||||
|
|
||||||
|
```text
|
||||||
|
{subject} — teaching-grounded ({corpus_id}): {ds1}; {ds2}. {subject} {connective} {object} ({do1}). No session evidence yet.
|
||||||
|
```
|
||||||
|
|
||||||
|
Every visible non-template token is either one of the two lemmas, a
|
||||||
|
verbatim ``semantic_domains`` string from the pack, or the connective
|
||||||
|
predicate already humanised by ``humanize_predicate``. No rewording,
|
||||||
|
no LLM, no synthesis.
|
||||||
|
|
||||||
|
### Engagement conditions
|
||||||
|
|
||||||
|
``teaching_grounded_surface(subject_lemma, intent_tag)`` returns a
|
||||||
|
non-``None`` surface **only** when **all** hold:
|
||||||
|
|
||||||
|
- ``subject_lemma`` is a non-empty string,
|
||||||
|
- ``intent_tag`` is ``IntentTag.CAUSE`` or ``IntentTag.VERIFICATION``,
|
||||||
|
- the (subject, intent) pair has a chain in the corpus.
|
||||||
|
|
||||||
|
``_maybe_pack_grounded_surface`` invokes this path **only** when:
|
||||||
|
|
||||||
|
- gate fired with ``source="empty_vault"`` (cold-start session),
|
||||||
|
- ``config.output_language == "en"``,
|
||||||
|
- intent ∈ {``CAUSE``, ``VERIFICATION``},
|
||||||
|
- ``intent.subject`` resolves to a chain.
|
||||||
|
|
||||||
|
Any other condition returns ``None`` and the runtime falls through to
|
||||||
|
the universal disclosure unchanged. Safety / ethics refusal still
|
||||||
|
takes priority above this branch (refusal is a remediation tier, not
|
||||||
|
a grounding source).
|
||||||
|
|
||||||
|
### Provenance tag
|
||||||
|
|
||||||
|
``ChatResponse.grounding_source`` and ``TurnEvent.grounding_source``
|
||||||
|
gain a fourth value, ``"teaching"``, sibling to ``"vault"``, ``"pack"``,
|
||||||
|
``"none"``. Downstream audit consumers can filter teaching-grounded
|
||||||
|
surfaces distinctly from pack-grounded surfaces — important because
|
||||||
|
the corpus is reviewed memory, not pack memory, and may receive
|
||||||
|
updates through a different process than pack ratification.
|
||||||
|
|
||||||
|
``_maybe_pack_grounded_surface`` was widened to return
|
||||||
|
``tuple[str, str] | None`` so the provenance tag is propagated
|
||||||
|
alongside the surface; the previous boolean ``"pack" if pack_surface
|
||||||
|
else "none"`` collapsed too early.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Why this is doctrine-aligned
|
||||||
|
|
||||||
|
CLAUDE.md prohibits *opaque LLM fallbacks, stochastic sampling,
|
||||||
|
hidden normalisation, hot-path repair, and approximate recall*. This
|
||||||
|
ADR is:
|
||||||
|
|
||||||
|
- **Not opaque.** Every visible atom is either a lemma supplied by
|
||||||
|
the intent classifier or a verbatim pack ``semantic_domains``
|
||||||
|
string. Corpus id and pack id are both inspectable.
|
||||||
|
- **Not stochastic.** Deterministic JSONL read; identical input
|
||||||
|
produces byte-identical output (`test_surface_is_deterministic`).
|
||||||
|
- **Not hidden normalisation.** The corpus lookup is a separate
|
||||||
|
source of grounding, not a normalisation step inside an existing
|
||||||
|
operator. No versor, no manifold, no field state touched.
|
||||||
|
- **Not hot-path repair.** `UnknownDomainGate` semantics are
|
||||||
|
unchanged — the gate still fires; this ADR only broadens what the
|
||||||
|
stub-path emits *after* the gate fires, in a narrow intent-typed
|
||||||
|
branch.
|
||||||
|
- **Not approximate recall.** Exact dictionary lookup on the corpus
|
||||||
|
by (subject, intent) key, exact dictionary lookup on the pack by
|
||||||
|
lemma. No metric, no neighbourhood, no threshold.
|
||||||
|
- **Reviewed teaching, not unreviewed mutation.** The corpus ships
|
||||||
|
as a checked-in artifact; no runtime path mutates it. The corpus
|
||||||
|
is treated as immutable reviewed memory in the spirit of
|
||||||
|
ADR-0018's teaching-store discipline.
|
||||||
|
|
||||||
|
The fundamental architectural move is the same as ADR-0048 / ADR-0050:
|
||||||
|
multiple ratified sources contribute to the runtime's grounding basis,
|
||||||
|
each with its own provenance tag. This ADR adds **reviewed teaching
|
||||||
|
chains** as a third sibling alongside session vault and ratified pack.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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-0052 | Post-ADR-0052 | Δ |
|
||||||
|
|---------------------------|--------------|---------------|--------------|
|
||||||
|
| `intent_accuracy` | 100.0 % | 100.0 % | 0 |
|
||||||
|
| `surface_groundedness` | 69.2 % | **92.3 %** | **+23.1 pp** |
|
||||||
|
| `term_capture_rate` | 58.3 % | **83.3 %** | **+25.0 pp** |
|
||||||
|
| `versor_closure_rate` | 100.0 % | 100.0 % | 0 |
|
||||||
|
| `versor_condition < 1e-6` | preserved | preserved | invariant |
|
||||||
|
|
||||||
|
The three cases that lift are exactly the ones targeted:
|
||||||
|
|
||||||
|
```text
|
||||||
|
"Why does light exist?"
|
||||||
|
-> intent.tag = CAUSE, subject="light"
|
||||||
|
-> chain: light -reveals-> truth
|
||||||
|
-> "light — teaching-grounded (cognition_chains_v1):
|
||||||
|
cognition.illumination; logos.core. light reveals truth
|
||||||
|
(cognition.truth). No session evidence yet."
|
||||||
|
-> grounding_source = "teaching"
|
||||||
|
|
||||||
|
"Why does knowledge require evidence?"
|
||||||
|
-> intent.tag = CAUSE, subject="knowledge"
|
||||||
|
-> chain: knowledge -requires-> evidence
|
||||||
|
-> "knowledge — teaching-grounded (cognition_chains_v1):
|
||||||
|
cognition.knowledge; epistemic.ground. knowledge requires
|
||||||
|
evidence (cognition.evidence). No session evidence yet."
|
||||||
|
-> grounding_source = "teaching"
|
||||||
|
|
||||||
|
"Does memory require recall?"
|
||||||
|
-> intent.tag = VERIFICATION, subject="memory"
|
||||||
|
-> chain: memory -requires-> recall
|
||||||
|
-> "memory — teaching-grounded (cognition_chains_v1):
|
||||||
|
cognition.memory; memory.semantic. memory requires recall
|
||||||
|
(operation.recall). No session evidence yet."
|
||||||
|
-> grounding_source = "teaching"
|
||||||
|
```
|
||||||
|
|
||||||
|
The single remaining unlift case (`correction_specific_015`) is
|
||||||
|
correctly out of scope — it requires prior-turn context, not cold-start
|
||||||
|
inference.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
### What changes
|
||||||
|
|
||||||
|
- New file: ``teaching/cognition_chains/cognition_chains_v1.jsonl``
|
||||||
|
— reviewed corpus of 3 cognition chains.
|
||||||
|
- New module: ``chat/teaching_grounding.py``
|
||||||
|
— corpus loader, ``teaching_grounded_surface``, ``has_teaching_chain``.
|
||||||
|
- ``chat/runtime.py:_maybe_pack_grounded_surface`` widened to return
|
||||||
|
``(surface, grounding_source_tag)`` and gains a CAUSE / VERIFICATION
|
||||||
|
branch that routes to teaching.
|
||||||
|
- ``ChatResponse.grounding_source`` and ``TurnEvent.grounding_source``
|
||||||
|
docstrings updated; ``"teaching"`` is now an accepted value.
|
||||||
|
- Three new cases lift in the cognition eval.
|
||||||
|
|
||||||
|
### What does not change
|
||||||
|
|
||||||
|
- ``_UNKNOWN_DOMAIN_SURFACE`` constant retained; unmodelled intents,
|
||||||
|
unknown subjects, and non-English paths still return the universal
|
||||||
|
disclosure unchanged.
|
||||||
|
- ``UnknownDomainGate`` semantics unchanged — the gate still fires on
|
||||||
|
every empty-vault cold-start turn.
|
||||||
|
- DEFINITION / RECALL / COMPARISON paths unchanged — they still emit
|
||||||
|
``grounding_source="pack"`` via ADR-0048 / ADR-0050.
|
||||||
|
- ``core/cognition/pipeline.py``'s ``gate_fired`` detection
|
||||||
|
(``response.grounding_source != "vault"``) works without
|
||||||
|
modification — ``"teaching" != "vault"``.
|
||||||
|
- Safety / ethics refusal still takes priority above teaching grounding.
|
||||||
|
- ``versor_condition(F) < 1e-6`` invariant unaffected (no algebra
|
||||||
|
changes).
|
||||||
|
|
||||||
|
### Scope limits
|
||||||
|
|
||||||
|
- English only (``en_core_cognition_v1``). Same constraint as
|
||||||
|
ADR-0048 / ADR-0050.
|
||||||
|
- CAUSE / VERIFICATION only. Other intents (PROCEDURE,
|
||||||
|
TRANSITIVE_QUERY, FRAME_TRANSFER) are out of scope here; they
|
||||||
|
would need their own reviewed corpora.
|
||||||
|
- Single-subject CAUSE / VERIFICATION only. Multi-clause causal
|
||||||
|
structures would need richer chain shape and are deferred.
|
||||||
|
- Three chains in v1. The corpus is intentionally compact — the
|
||||||
|
pattern is the load-bearing artifact, not corpus volume. Each new
|
||||||
|
chain must be reviewed and reference only pack-known lemmas.
|
||||||
|
- Corpus is immutable at runtime. Future ADRs may add a reviewed
|
||||||
|
proposal-and-ratification path; the current contract is
|
||||||
|
checked-in-only.
|
||||||
|
- ``correction_specific_015`` remains unlifted by design — corrections
|
||||||
|
reference prior-turn context which cold-start teaching cannot supply.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cross-References
|
||||||
|
|
||||||
|
- [ADR-0018](./ADR-0018-tool-use-scope.md) — teaching-store
|
||||||
|
infrastructure whose discipline this ADR's corpus extends to the
|
||||||
|
surface layer.
|
||||||
|
- [ADR-0048](./ADR-0048-pack-grounded-surface.md) — the
|
||||||
|
DEFINITION / RECALL pack-grounded surface this ADR mirrors for
|
||||||
|
CAUSE / VERIFICATION; same trust-boundary discipline, sibling
|
||||||
|
``grounding_source`` tag.
|
||||||
|
- [ADR-0049](./ADR-0049-intent-subject-extraction.md) — head-noun
|
||||||
|
subject extraction this ADR consumes. ``intent.subject`` for
|
||||||
|
``"Why does knowledge require evidence?"`` is the clean lemma
|
||||||
|
``"knowledge"``.
|
||||||
|
- [ADR-0050](./ADR-0050-pack-grounded-comparison.md) — the
|
||||||
|
COMPARISON sibling, sharing the same dispatcher and provenance
|
||||||
|
scheme.
|
||||||
|
- [ADR-0029](./ADR-0029-safety-packs.md) /
|
||||||
|
[ADR-0033](./ADR-0033-ethics-packs.md) — sibling-pack-grounded
|
||||||
|
contributor pattern this ADR continues to extend from verdict
|
||||||
|
surfaces to the answer surface, now from a teaching-store source.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
```
|
||||||
|
tests/test_teaching_grounding.py — 22 tests, all green
|
||||||
|
tests/test_pack_grounded_comparison.py — pre-existing, still green
|
||||||
|
tests/test_pack_grounding.py — pre-existing, still green
|
||||||
|
tests/test_intent_subject_extraction.py — pre-existing, still green
|
||||||
|
tests/test_semantic_realizer_integration.py — pre-existing, still green
|
||||||
|
|
||||||
|
Lanes (all green on this branch):
|
||||||
|
core test --suite smoke 64 passed (3 pre-existing failures in
|
||||||
|
tests/test_architectural_invariants.py
|
||||||
|
flag stale worktree directories,
|
||||||
|
unrelated to this ADR)
|
||||||
|
core test --suite cognition 121 passed
|
||||||
|
core test --suite runtime 19 passed
|
||||||
|
core test --suite packs 6 passed
|
||||||
|
core test --suite teaching 17 passed
|
||||||
|
core test --suite algebra 132 passed
|
||||||
|
|
||||||
|
core eval cognition (pre → post):
|
||||||
|
intent_accuracy 100.0% → 100.0% (=)
|
||||||
|
surface_groundedness 69.2% → 92.3% (+23.1 pp)
|
||||||
|
term_capture_rate 58.3% → 83.3% (+25.0 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.
|
||||||
3
teaching/cognition_chains/cognition_chains_v1.jsonl
Normal file
3
teaching/cognition_chains/cognition_chains_v1.jsonl
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{"chain_id":"cause_light_reveals_truth","subject":"light","intent":"cause","connective":"reveals","object":"truth","domains_subject_k":2,"domains_object_k":1,"provenance":"adr-0052:reviewed:2026-05-18"}
|
||||||
|
{"chain_id":"cause_knowledge_requires_evidence","subject":"knowledge","intent":"cause","connective":"requires","object":"evidence","domains_subject_k":2,"domains_object_k":1,"provenance":"adr-0052:reviewed:2026-05-18"}
|
||||||
|
{"chain_id":"verification_memory_requires_recall","subject":"memory","intent":"verification","connective":"requires","object":"recall","domains_subject_k":2,"domains_object_k":1,"provenance":"adr-0052:reviewed:2026-05-18"}
|
||||||
217
tests/test_teaching_grounding.py
Normal file
217
tests/test_teaching_grounding.py
Normal file
|
|
@ -0,0 +1,217 @@
|
||||||
|
"""ADR-0052 — teaching-grounded CAUSE / VERIFICATION surface tests.
|
||||||
|
|
||||||
|
Contract pinned here:
|
||||||
|
|
||||||
|
- ``teaching_grounded_surface(lemma, intent_tag)`` returns a
|
||||||
|
deterministic surface composed of the subject lemma + its pack
|
||||||
|
``semantic_domains``, a fixed connective predicate, the object
|
||||||
|
lemma, and its pack ``semantic_domains``. No synthesis.
|
||||||
|
- Returns ``None`` when:
|
||||||
|
* the lemma is empty or absent from the corpus,
|
||||||
|
* the intent is not ``CAUSE`` or ``VERIFICATION``,
|
||||||
|
* the chain references lemmas missing from the ratified pack.
|
||||||
|
- The runtime wiring engages only when:
|
||||||
|
* the gate fires with ``source="empty_vault"``,
|
||||||
|
* ``output_language == "en"``,
|
||||||
|
* intent is ``CAUSE`` or ``VERIFICATION``,
|
||||||
|
* the subject lemma has a reviewed chain.
|
||||||
|
- ``ChatResponse.grounding_source`` and ``TurnEvent.grounding_source``
|
||||||
|
both carry ``"teaching"`` on this branch.
|
||||||
|
- Refusal still takes priority — teaching-grounded surfaces never
|
||||||
|
bypass safety / ethics verdict refusal.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from chat.pack_grounding import PACK_ID, _pack_index
|
||||||
|
from chat.runtime import ChatRuntime, _UNKNOWN_DOMAIN_SURFACE
|
||||||
|
from chat.teaching_grounding import (
|
||||||
|
TEACHING_CORPUS_ID,
|
||||||
|
has_teaching_chain,
|
||||||
|
teaching_grounded_surface,
|
||||||
|
)
|
||||||
|
from generate.intent import IntentTag
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# teaching_grounded_surface — pure-function contracts
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_cause_light_chain_produces_surface() -> None:
|
||||||
|
surface = teaching_grounded_surface("light", IntentTag.CAUSE)
|
||||||
|
assert surface is not None
|
||||||
|
assert "light" in surface
|
||||||
|
assert "truth" in surface
|
||||||
|
assert "reveals" in surface
|
||||||
|
assert TEACHING_CORPUS_ID in surface
|
||||||
|
assert "teaching-grounded" in surface
|
||||||
|
assert "No session evidence yet." in surface
|
||||||
|
|
||||||
|
|
||||||
|
def test_cause_knowledge_chain_produces_surface() -> None:
|
||||||
|
surface = teaching_grounded_surface("knowledge", IntentTag.CAUSE)
|
||||||
|
assert surface is not None
|
||||||
|
assert "knowledge" in surface
|
||||||
|
assert "evidence" in surface
|
||||||
|
assert "requires" in surface
|
||||||
|
|
||||||
|
|
||||||
|
def test_verification_memory_chain_produces_surface() -> None:
|
||||||
|
surface = teaching_grounded_surface("memory", IntentTag.VERIFICATION)
|
||||||
|
assert surface is not None
|
||||||
|
assert "memory" in surface
|
||||||
|
assert "recall" in surface
|
||||||
|
assert "requires" in surface
|
||||||
|
|
||||||
|
|
||||||
|
def test_lemma_absent_from_corpus_returns_none() -> None:
|
||||||
|
assert teaching_grounded_surface("dragon", IntentTag.CAUSE) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_subject_in_corpus_wrong_intent_returns_none() -> None:
|
||||||
|
"""``memory`` has a VERIFICATION chain but not a CAUSE chain."""
|
||||||
|
assert teaching_grounded_surface("memory", IntentTag.CAUSE) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_definition_intent_returns_none() -> None:
|
||||||
|
"""Teaching grounding is scoped to CAUSE / VERIFICATION only."""
|
||||||
|
assert teaching_grounded_surface("light", IntentTag.DEFINITION) is None
|
||||||
|
assert teaching_grounded_surface("light", IntentTag.RECALL) is None
|
||||||
|
assert teaching_grounded_surface("light", IntentTag.COMPARISON) is None
|
||||||
|
assert teaching_grounded_surface("light", IntentTag.UNKNOWN) is None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("bad", ["", " ", None])
|
||||||
|
def test_empty_or_invalid_lemma_returns_none(bad) -> None:
|
||||||
|
assert teaching_grounded_surface(bad, IntentTag.CAUSE) is None # type: ignore[arg-type]
|
||||||
|
|
||||||
|
|
||||||
|
def test_surface_is_deterministic() -> None:
|
||||||
|
"""Same input must produce byte-identical surface on repeat."""
|
||||||
|
a = teaching_grounded_surface("memory", IntentTag.VERIFICATION)
|
||||||
|
b = teaching_grounded_surface("memory", IntentTag.VERIFICATION)
|
||||||
|
assert a == b
|
||||||
|
assert a is not None
|
||||||
|
|
||||||
|
|
||||||
|
def test_case_insensitive_lookup() -> None:
|
||||||
|
a = teaching_grounded_surface("LIGHT", IntentTag.CAUSE)
|
||||||
|
b = teaching_grounded_surface("light", IntentTag.CAUSE)
|
||||||
|
assert a == b
|
||||||
|
assert a is not None
|
||||||
|
|
||||||
|
|
||||||
|
def test_has_teaching_chain_helper() -> None:
|
||||||
|
assert has_teaching_chain("light", IntentTag.CAUSE) is True
|
||||||
|
assert has_teaching_chain("knowledge", IntentTag.CAUSE) is True
|
||||||
|
assert has_teaching_chain("memory", IntentTag.VERIFICATION) is True
|
||||||
|
assert has_teaching_chain("memory", IntentTag.CAUSE) is False
|
||||||
|
assert has_teaching_chain("dragon", IntentTag.CAUSE) is False
|
||||||
|
assert has_teaching_chain("light", IntentTag.DEFINITION) is False
|
||||||
|
assert has_teaching_chain("", IntentTag.CAUSE) is False
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Doctrine — every atom verbatim from pack or fixed template
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_surface_atoms_are_verbatim_from_pack() -> None:
|
||||||
|
"""Every visible non-template descriptor must be a verbatim
|
||||||
|
``semantic_domains`` string from the ratified pack — no rewording."""
|
||||||
|
surface = teaching_grounded_surface("knowledge", IntentTag.CAUSE)
|
||||||
|
assert surface is not None
|
||||||
|
index = _pack_index()
|
||||||
|
# Subject domains (first 2 by corpus config)
|
||||||
|
for domain in index["knowledge"][:2]:
|
||||||
|
assert domain in surface
|
||||||
|
# Object domain (first 1)
|
||||||
|
for domain in index["evidence"][:1]:
|
||||||
|
assert domain in surface
|
||||||
|
# Fixed-template tokens
|
||||||
|
assert "teaching-grounded" in surface
|
||||||
|
assert "No session evidence yet." in surface
|
||||||
|
|
||||||
|
|
||||||
|
def test_surface_does_not_invent_packless_descriptors() -> None:
|
||||||
|
"""Sanity: no fabricated cognition.* domain that isn't in the pack."""
|
||||||
|
surface = teaching_grounded_surface("memory", IntentTag.VERIFICATION)
|
||||||
|
assert surface is not None
|
||||||
|
index = _pack_index()
|
||||||
|
all_pack_domains: set[str] = set()
|
||||||
|
for domains in index.values():
|
||||||
|
all_pack_domains.update(domains)
|
||||||
|
# Every "<word>.<word>" looking descriptor in the surface must be a
|
||||||
|
# real pack domain. Crude but effective at catching fabrication.
|
||||||
|
import re
|
||||||
|
for match in re.findall(r"\b[a-z]+\.[a-z]+\b", surface):
|
||||||
|
assert match in all_pack_domains, f"non-pack descriptor: {match}"
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# ChatRuntime integration — cold-start CAUSE / VERIFICATION path
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_cold_start_cause_light_returns_teaching_surface() -> None:
|
||||||
|
rt = ChatRuntime()
|
||||||
|
resp = rt.chat("Why does light exist?")
|
||||||
|
assert resp.grounding_source == "teaching"
|
||||||
|
assert "light" in resp.surface
|
||||||
|
assert "truth" in resp.surface
|
||||||
|
assert "teaching-grounded" in resp.surface
|
||||||
|
|
||||||
|
|
||||||
|
def test_cold_start_cause_knowledge_returns_teaching_surface() -> None:
|
||||||
|
rt = ChatRuntime()
|
||||||
|
resp = rt.chat("Why does knowledge require evidence?")
|
||||||
|
assert resp.grounding_source == "teaching"
|
||||||
|
assert "knowledge" in resp.surface
|
||||||
|
assert "evidence" in resp.surface
|
||||||
|
|
||||||
|
|
||||||
|
def test_cold_start_verification_memory_returns_teaching_surface() -> None:
|
||||||
|
rt = ChatRuntime()
|
||||||
|
resp = rt.chat("Does memory require recall?")
|
||||||
|
assert resp.grounding_source == "teaching"
|
||||||
|
assert "memory" in resp.surface
|
||||||
|
assert "recall" in resp.surface
|
||||||
|
|
||||||
|
|
||||||
|
def test_cold_start_cause_unknown_subject_disclosure() -> None:
|
||||||
|
rt = ChatRuntime()
|
||||||
|
resp = rt.chat("Why does dragon exist?")
|
||||||
|
assert resp.surface == _UNKNOWN_DOMAIN_SURFACE
|
||||||
|
assert resp.grounding_source == "none"
|
||||||
|
|
||||||
|
|
||||||
|
def test_turn_event_carries_grounding_source_teaching() -> None:
|
||||||
|
rt = ChatRuntime()
|
||||||
|
rt.chat("Why does light exist?")
|
||||||
|
last_event = rt.turn_log[-1]
|
||||||
|
assert getattr(last_event, "grounding_source", None) == "teaching"
|
||||||
|
|
||||||
|
|
||||||
|
def test_teaching_grounded_passes_verdict_audit() -> None:
|
||||||
|
rt = ChatRuntime()
|
||||||
|
resp = rt.chat("Why does light exist?")
|
||||||
|
assert resp.safety_verdict is not None
|
||||||
|
assert resp.ethics_verdict is not None
|
||||||
|
|
||||||
|
|
||||||
|
def test_definition_path_still_returns_pack_source() -> None:
|
||||||
|
"""Regression: DEFINITION still routes to pack, not teaching."""
|
||||||
|
rt = ChatRuntime()
|
||||||
|
resp = rt.chat("What is light?")
|
||||||
|
assert resp.grounding_source == "pack"
|
||||||
|
assert PACK_ID in resp.surface
|
||||||
|
|
||||||
|
|
||||||
|
def test_comparison_path_still_returns_pack_source() -> None:
|
||||||
|
"""Regression: COMPARISON still routes to pack, not teaching."""
|
||||||
|
rt = ChatRuntime()
|
||||||
|
resp = rt.chat("Compare memory and recall")
|
||||||
|
assert resp.grounding_source == "pack"
|
||||||
Loading…
Reference in a new issue