feat(adr-0060): correction acknowledgement carries corrected-topic lemma

ADR-0053's cold-start CORRECTION surface was topic-blind: a user who
said "Actually, truth requires evidence" got a response referencing
`correction` but never `truth`.  The holdout case correction_truth_040
expected `term=['truth']` and missed — one of the architectural gaps
surfaced by the epistemology v1 curriculum unit.

ADR-0060 closes that gap by weaving the first pack-resident topical
lemma from the utterance into a fixed-template extension:

  correction received — pack-grounded ({pack_id}):
  {correction_domains}. Noted topic: {lemma} ({lemma_domains}).
  No prior turn in this session to correct yet.

Selection rule (deterministic, left-to-right token order):
  - skip stopwords: `correction`, `correct`, `be`, `have`
  - pick first pack-resident lemma
  - if none found → ADR-0053 topic-less template byte-identically

Trust-boundary invariants preserved:
  - Every visible non-template token is still lemma / pack-domain / template
  - Deterministic: same text → same bytes
  - Backward compatible: existing 15 ADR-0053 tests pass byte-identically
  - "No prior turn in this session to correct yet." trust label kept

Cognition lane lift:
  public  : intent 100% / surface 100% / term 91.7% / versor 100%   (unchanged)
  holdout : intent 100% / surface 94.7% / term 75.0%→79.2% / versor 100%

The +4.2pp matches the single-case fix exactly (correction_truth_040).
Remaining 3 holdout misses (procedure_define_010, unknown_spirit_041,
unknown_word_018) are out of scope for this ADR.

- chat/pack_grounding.py — `_extract_correction_topic_lemma` helper +
  optional `text` parameter on `pack_grounded_correction_surface`.
- chat/runtime.py — single-line call-site change to pass `text` through.
- tests/test_correction_topic_lemma.py — 14 new tests pin:
  extraction (first lemma / skips correction / skips fillers / None on
  empty / strips punctuation / case-insensitive); surface (contains
  corrected lemma / contains topic domains / degrades to ADR-0053
  byte-identically / preserves trust label / deterministic / correct
  pack_id); end-to-end (correction_truth_040 emits 'truth' / no-pack-
  lemma still grounds).

Why text-level extraction, not intent.subject:
  `intent.subject` after ADR-0049 head-noun extraction returns
  ", truth requires evidence" for the test prompt — the CORRECTION
  intent's subject-extractor preserves the post-marker tail.  Parsing
  the raw text at the surface layer is cleaner; isolates the fix;
  doesn't perturb upstream classification logic.

Lanes (regression): smoke 67 / cognition 121 / teaching 17 /
correction tests 29 (15 ADR-0053 backward-compat + 14 ADR-0060 new) —
all green.

The non-negotiable field invariant (versor_condition < 1e-6) is
unaffected: this ADR changes surface composition only.
This commit is contained in:
Shay 2026-05-18 14:14:27 -07:00
parent 2acf71f024
commit c9e858c266
6 changed files with 652 additions and 8 deletions

View file

@ -120,8 +120,51 @@ def is_pack_lemma(lemma: str) -> bool:
return lemma.strip().lower() in _pack_index()
def pack_grounded_correction_surface() -> str | None:
"""ADR-0053 — cold-start CORRECTION acknowledgement.
_CORRECTION_TOPIC_STOPWORDS: frozenset[str] = frozenset({
# The meta-cognition lemma itself — we never echo it as the topic
# because it's already the subject of the acknowledgement template.
"correction",
"correct",
# Common dialogue markers / fillers that classify as pack lemmas
# but don't carry topical signal in a correction utterance.
"be",
"have",
})
def _extract_correction_topic_lemma(text: str) -> str | None:
"""Return the first pack-resident, topical lemma in *text*, or None.
Deterministic: tokens are processed in left-to-right utterance
order; the first token that is pack-resident AND not in the
correction-stopword set wins. Stopwords filter out the meta-
cognition lemma itself (``correction``) and dialogue fillers
(``be``, ``have``) that classify as pack lemmas but carry no
topical signal.
Used by ``pack_grounded_correction_surface`` to weave the
corrected claim's subject into the acknowledgement template.
"""
if not text or not isinstance(text, str):
return None
index = _pack_index()
# Tokenize: lowercase, strip surrounding punctuation, skip empties.
raw = text.lower()
# Replace common punctuation with whitespace; preserve word boundaries.
for ch in ",.;:!?\"'()[]{}":
raw = raw.replace(ch, " ")
for token in raw.split():
if not token:
continue
if token in _CORRECTION_TOPIC_STOPWORDS:
continue
if token in index:
return token
return None
def pack_grounded_correction_surface(text: str | None = None) -> str | None:
"""ADR-0053 + ADR-0060 — cold-start CORRECTION acknowledgement.
A CORRECTION intent (``"No, that's wrong"``, ``"Actually, X means Y"``)
is meta-cognitive: it claims the previous turn was incorrect. On a
@ -131,13 +174,23 @@ def pack_grounded_correction_surface() -> str | None:
acknowledge receipt and state explicitly that no prior turn exists
in this session.
Surface format (fixed template, all atoms pack-sourced):
Surface format (fixed templates, all atoms pack-sourced):
"correction received — pack-grounded ({pack_id}): {d1}; {d2}; {d3}.
No prior turn in this session to correct yet."
- **Without topic** (text=None or no pack-resident lemma found):
"correction received — pack-grounded ({pack_id}): {d1}; {d2}; {d3}.
No prior turn in this session to correct yet."
- **With topic** (text supplied AND pack lemma found):
"correction received — pack-grounded ({pack_id}): {d1}; {d2}; {d3}.
Noted topic: {lemma} ({td1}; {td2}).
No prior turn in this session to correct yet."
Every visible non-template token is either the lemma ``correction``,
the corrected-topic lemma, or a verbatim ``semantic_domains`` string
from the ratified pack. No inference; no rewording.
Every visible non-template token is either the lemma ``correction``
or a verbatim ``semantic_domains`` string from the ratified pack.
The trailing disclosure (``No prior turn in this session to correct
yet.``) is the constant trust-boundary label distinguishing this
cold-start acknowledgement from the post-correction teaching
@ -153,6 +206,21 @@ def pack_grounded_correction_surface() -> str | None:
if not domains:
return None
head = "; ".join(domains[:3])
topic_lemma = _extract_correction_topic_lemma(text) if text else None
if topic_lemma is not None:
topic_domains = index.get(topic_lemma, ())
topic_head = "; ".join(topic_domains[:2]) if topic_domains else ""
if topic_head:
return (
f"correction received — pack-grounded ({PACK_ID}): {head}. "
f"Noted topic: {topic_lemma} ({topic_head}). "
f"No prior turn in this session to correct yet."
)
return (
f"correction received — pack-grounded ({PACK_ID}): {head}. "
f"Noted topic: {topic_lemma}. "
f"No prior turn in this session to correct yet."
)
return (
f"correction received — pack-grounded ({PACK_ID}): {head}. "
f"No prior turn in this session to correct yet."

View file

@ -676,7 +676,12 @@ class ChatRuntime:
# post-correction reviewed-teaching path (``teaching/correction.py``)
# engages only once a prior turn exists in the session.
if intent.tag is IntentTag.CORRECTION:
surface = pack_grounded_correction_surface()
# ADR-0060 — pass the raw text so the acknowledgement can
# weave the corrected claim's first pack-resident topical
# lemma into the surface. Backward compatible: with no
# topical lemma present, the surface degrades to the
# ADR-0053 topic-less template.
surface = pack_grounded_correction_surface(text)
return (surface, "pack") if surface is not None else None
if intent.tag not in (IntentTag.DEFINITION, IntentTag.RECALL):
return None

View file

@ -0,0 +1,202 @@
# ADR-0060 — CORRECTION Acknowledgement Carries the Corrected-Topic Lemma
**Status:** Accepted
**Date:** 2026-05-18
**Author:** Shay
---
## Context
[ADR-0053](./ADR-0053-cognition-lane-closure.md) introduced
`pack_grounded_correction_surface()` — the cold-start CORRECTION
acknowledgement. When a user begins a session with a meta-cognitive
correction utterance (`"No, that's wrong"`, `"Actually, X means Y"`),
there is no prior turn to apply the correction to, so the runtime
emits a deterministic pack-grounded surface stating that fact:
```
correction received — pack-grounded (en_core_cognition_v1):
cognition.correction; teaching.review; dialogue.repair.
No prior turn in this session to correct yet.
```
This surface was honest but **topic-blind**: a user who said
`"Actually, truth requires evidence"` got a response that referenced
`correction` but never `truth`. The holdout case
`correction_truth_040` expected `["truth"]` in `expected_terms` and
missed — contributing one of the 5 term-capture misses on the
holdout split.
The first curriculum unit
([epistemology v1](../curriculum/epistemology_v1.md), `2acf71f`)
closed one corpus-fixable miss (`verification_wisdom_036`). Three of
the remaining four were architectural; `correction_truth_040` was the
cleanest to address.
---
## Decision
Extend `pack_grounded_correction_surface` to accept an optional
`text: str | None` argument. When supplied, the surface composer
extracts the **first pack-resident topical lemma** from the utterance
(left-to-right token order, excluding the meta-cognition lemma
`correction` itself and dialogue fillers `be` / `have`) and weaves it
into a fixed template:
```
correction received — pack-grounded ({pack_id}):
{correction_domains}. Noted topic: {lemma} ({lemma_domains}).
No prior turn in this session to correct yet.
```
When no topical lemma is found (or `text` is `None`), the surface
degrades to the ADR-0053 topic-less template byte-identically.
### Trust-boundary invariants preserved
- **Every visible non-template token** is still either the lemma
`correction`, the topical lemma, or a verbatim `semantic_domains`
string from the ratified pack. No inference, no rewording.
- **Determinism.** Same `text` → same surface bytes. The selector is
left-to-right token order; no scoring, no NLP heuristic, no LLM.
- **Backward compatibility.**
`pack_grounded_correction_surface()` with no argument returns the
ADR-0053 template byte-identically. Existing 15 tests in
`tests/test_pack_grounded_correction.py` continue to pass.
- **The "No prior turn in this session to correct yet."
trust-boundary label** — distinguishing this cold-start surface
from the post-correction teaching-repair path
(`teaching/correction.py`) — is preserved in both variants.
### Stopword selection
Two stopword classes are excluded from topic-lemma selection:
1. **The meta-cognition lemma itself** (`correction`, `correct`) —
echoing it as the topic would be circular; it's already the
subject of the acknowledgement template.
2. **Dialogue fillers** (`be`, `have`) — pack-resident lemmas that
classify but carry no topical signal in a correction utterance.
This stopword set is deliberately tiny. Expanding it requires an
amendment to this ADR — pack-resident lemmas that survive both the
"have semantic_domains" gate and the natural-language flow of a
correction utterance are real topic candidates by default.
### Token normalization
Tokens are lowercased and stripped of attached punctuation
(`,.;:!?"'()[]{}`) before pack-lookup. This handles common surface
forms like `"truth."`, `'truth'`, `truth,` without requiring a full
tokenizer.
---
## Why text-level extraction, not intent.subject
`intent.subject` after ADR-0049 head-noun extraction returns
`", truth requires evidence"` for the prompt
`"Actually, truth requires evidence"` — the correction intent's
subject extractor preserves the post-marker tail rather than
extracting a single head noun. Using `intent.subject` would require
either:
1. Extending ADR-0049's head-noun normalization to the CORRECTION
intent — substantial change to upstream classification logic.
2. Re-parsing `intent.subject` at the surface composer — equivalent
work to parsing the raw text.
Parsing the raw text at the surface layer is cleaner: it isolates
the fix, doesn't perturb upstream classification, and lets the
correction acknowledgement own its own topic-extraction policy.
---
## Verification
```
tests/test_correction_topic_lemma.py 14 passed
- extraction: first lemma / skips correction / skips fillers /
None on empty / strips punctuation / case-insensitive
- surface: contains corrected lemma / contains topic domains /
degrades to ADR-0053 / preserves trust label / deterministic /
correct pack_id
- end-to-end: correction_truth_040 emits 'truth' / no-pack-lemma
still grounds
tests/test_pack_grounded_correction.py 15 passed (ADR-0053; backward compat)
Lanes (regression check):
core test --suite smoke 67 passed
core test --suite cognition 121 passed
core test --suite teaching 17 passed
```
### Cognition lane lift
| Split | Metric | Pre-ADR-0060 | Post-ADR-0060 |
|---|---|---|---|
| **public** | intent / surface / term / versor | 100 / 100 / 91.7 / 100 | **100 / 100 / 91.7 / 100** (unchanged) |
| **holdout** | intent / surface / term / versor | 100 / 94.7 / 75.0 / 100 | **100 / 94.7 / 79.2 / 100** (+4.2pp term_capture) |
The +4.2pp matches the single-case fix: `correction_truth_040` now
captures `truth`. The remaining three holdout misses
(`procedure_define_010`, `unknown_spirit_041`, `unknown_word_018`)
are out of scope for this ADR.
---
## Consequences
### What changes
- `chat/pack_grounding.py``_extract_correction_topic_lemma`
helper and an optional `text` parameter on
`pack_grounded_correction_surface`.
- `chat/runtime.py` — call site passes `text` through. Single line.
### What does not change
- ADR-0053's contract for the no-text path: byte-identical surface.
- Refusal priority: a `SafetyVerdict` violation still pre-empts the
acknowledgement (per ADR-0036).
- The pack-grounded discipline: zero LLM-generated tokens in the
surface; every visible word is either lemma, pack-domain, or
fixed template constant.
- `versor_condition(F) < 1e-6` invariant: untouched — this ADR
only changes surface composition.
---
## Scope limits
- **Single topical lemma per surface.** A correction utterance
containing multiple pack-resident lemmas (`"Actually, truth
requires evidence"`) currently surfaces only the first
(`truth`). Extending to "Noted topics: truth, evidence" is a
reasonable follow-up but expands the trust surface (more
tokens, more template branches) and was deferred.
- **English path only.** The stopword set and tokenizer are
English-specific. Non-English correction utterances will not
match pack lemmas and will degrade to the topic-less template —
no behaviour change vs ADR-0053.
- **No subject-claim propagation to corpus.** The corrected claim
(`truth requires evidence`) is acknowledged as a topic, not as
a candidate teaching chain. Promoting corrections into the
corpus is the `teaching/correction.py` repair path, which
engages only once a prior turn exists.
---
## Cross-References
- [ADR-0053](./ADR-0053-cognition-lane-closure.md) — the
CORRECTION acknowledgement contract this ADR extends.
- [ADR-0049](./ADR-0049-intent-subject-extraction.md) — the
head-noun extraction whose contract this ADR consciously did
not extend.
- [Curriculum: epistemology v1](../curriculum/epistemology_v1.md)
— the first unit that surfaced this gap as one of the
architectural misses.

View file

@ -69,6 +69,7 @@ ADRs record significant architectural decisions: what was decided, why, what alt
| [ADR-0057](ADR-0057-teaching-chain-proposal-review.md) | Teaching-chain proposal + review + replay-equivalence gate (Phase C2): the only path to active-corpus extension; eligibility predicate; auto-reject on metric regression; operator accept/reject/withdraw; append-only proposal log | **Accepted** (2026-05-18) |
| [ADR-0058](ADR-0058-forward-graph-constraint-status.md) | `forward_graph_constraint` remains opt-in default-`False`; no identity pack flips it on; ADR-0047 null-lift on cognition lane promoted to CI-enforced invariant (regression test); identity-pack→`RuntimeConfig` composition deferred until at least one such preference shows lift | **Accepted** (2026-05-18) |
| [ADR-0059](ADR-0059-correction-pass-telemetry.md) | `ChatRuntime.correct()` emits a discriminated `"type": "correction"` JSONL event to the existing telemetry sink with `target_turn`, `records_count`, `turn_idxs_affected`, `max_delta_norm`, `mean_delta_norm`, SHA-256 correction-versor digest, pack ids — no raw versor coordinates; deterministic; no-op without sink | **Accepted** (2026-05-18) |
| [ADR-0060](ADR-0060-correction-acknowledgment-topic-lemma.md) | CORRECTION acknowledgement surface weaves the first pack-resident topical lemma from the utterance (left-to-right, excluding `correction` itself and `be`/`have` fillers) into a fixed template; backward-compatible with ADR-0053 (no-arg path byte-identical); closes `correction_truth_040` holdout miss; holdout `term_capture_rate` 75.0% → 79.2% | **Accepted** (2026-05-18) |
---

View file

@ -0,0 +1,205 @@
{
"cases": [
{
"case_id": "cause_truth_031",
"category": "cause",
"intent_correct": true,
"surface": "truth — teaching-grounded (cognition_chains_v1): cognition.truth; logos.core. truth grounds knowledge (cognition.knowledge). No session evidence yet.",
"surface_contains_pass": true,
"trace_hash": "cf91517f1d2288cec1c4c27ea8a36ef3db8a51ff6538e8b4ba88c45080930142",
"versor_closure": true,
"versor_condition": 1.08e-07
},
{
"case_id": "cause_wisdom_009",
"category": "cause",
"intent_correct": true,
"surface": "wisdom — teaching-grounded (cognition_chains_v1): cognition.wisdom; epistemic.judgment. wisdom orders judgment (cognition.judgment). No session evidence yet.",
"surface_contains_pass": true,
"trace_hash": "363848c63bec301b25d2a9eaa0c5784c5b040d619d4217e1df15ee946ebfe4d4",
"versor_closure": true,
"versor_condition": 1.94e-07
},
{
"case_id": "comparison_reason_cause_028",
"category": "comparison",
"intent_correct": true,
"surface": "reason (cognition.reason; logic.cause) contrasts with cause (relation.causal; predicate.cause) — pack-grounded (en_core_cognition_v1). No session evidence yet.",
"surface_contains_pass": true,
"trace_hash": "5953b999066ca280920998eeb8ae8d32e2cbb77e8e84b499d033490cad73f926",
"versor_closure": true,
"versor_condition": 3.9e-08
},
{
"case_id": "comparison_truth_light_005",
"category": "comparison",
"intent_correct": true,
"surface": "truth (cognition.truth; logos.core) contrasts with light (cognition.illumination; logos.core) — pack-grounded (en_core_cognition_v1). No session evidence yet.",
"surface_contains_pass": true,
"trace_hash": "0339b114eaf83b3f3825bcf69d3c1fe71003864ccdfb409069d729f5fc6bb3f3",
"versor_closure": true,
"versor_condition": 6.6e-08
},
{
"case_id": "comparison_word_meaning_006",
"category": "comparison",
"intent_correct": true,
"surface": "word (cognition.language.word; logos.core) contrasts with meaning (cognition.meaning; semantics) — pack-grounded (en_core_cognition_v1). No session evidence yet.",
"surface_contains_pass": true,
"trace_hash": "8049248aa33af5dd2e506a9e641d5dbdb6501c0f9a4d601a2a540e47c38c0e8e",
"versor_closure": true,
"versor_condition": 6.2e-08
},
{
"case_id": "correction_truth_040",
"category": "correction",
"intent_correct": true,
"surface": "correction received — pack-grounded (en_core_cognition_v1): cognition.correction; teaching.review; dialogue.repair. No prior turn in this session to correct yet.",
"surface_contains_pass": true,
"trace_hash": "49d37322c5df9698f32ff44dc569e57389f7728f999021da8c26d4ff3f13b869",
"versor_closure": true,
"versor_condition": 1.7e-07
},
{
"case_id": "definition_thought_043",
"category": "definition",
"intent_correct": true,
"surface": "thought — pack-grounded (en_core_cognition_v1): cognition.thought; logos.internal; reason.process. No session evidence yet.",
"surface_contains_pass": true,
"trace_hash": "4580f35ba98e57f4bce9110b2ea01fc552d56b483b59c84bc83fb8c837ea0710",
"versor_closure": true,
"versor_condition": 2.95e-07
},
{
"case_id": "definition_truth_001",
"category": "definition",
"intent_correct": true,
"surface": "truth — pack-grounded (en_core_cognition_v1): cognition.truth; logos.core; epistemic.ground. No session evidence yet.",
"surface_contains_pass": true,
"trace_hash": "3d53af19b1b8f3664081d2bfdbf6a01afb210e9ed2e10765fedf92b39650c511",
"versor_closure": true,
"versor_condition": 1.05e-07
},
{
"case_id": "definition_understanding_045",
"category": "definition",
"intent_correct": true,
"surface": "understanding — pack-grounded (en_core_cognition_v1): cognition.understanding; epistemic.grasp; meaning.comprehension. No session evidence yet.",
"surface_contains_pass": true,
"trace_hash": "fbb2d48eff6fdaf3550a2fff214f1678c642d31ec247e876d0382b55fe8faa00",
"versor_closure": true,
"versor_condition": 4.7e-08
},
{
"case_id": "definition_verification_024",
"category": "definition",
"intent_correct": true,
"surface": "verification — pack-grounded (en_core_cognition_v1): cognition.verification; epistemic.check; truth.test. No session evidence yet.",
"surface_contains_pass": true,
"trace_hash": "f2d7973bfda57b03a7254dac73e4c5ed1e32a6222bea392f2475377b6a20efeb",
"versor_closure": true,
"versor_condition": 1.01e-07
},
{
"case_id": "definition_wisdom_020",
"category": "definition",
"intent_correct": true,
"surface": "wisdom — pack-grounded (en_core_cognition_v1): cognition.wisdom; epistemic.judgment; reason.order. No session evidence yet.",
"surface_contains_pass": true,
"trace_hash": "eec38987c51de5cee3cbf722f35c36fa56969666b94f24a60968d6321ca00356",
"versor_closure": true,
"versor_condition": 2e-08
},
{
"case_id": "procedure_define_010",
"category": "procedure",
"intent_correct": true,
"surface": "I don't know — insufficient grounding for that yet.",
"surface_contains_pass": false,
"trace_hash": "1aba4270212b74e04dd86bed0a8fa7c0e0ada638e1d50e5987c3db6744c641b7",
"versor_closure": true,
"versor_condition": 1.1e-08
},
{
"case_id": "procedure_verify_034",
"category": "procedure",
"intent_correct": true,
"surface": "I don't know — insufficient grounding for that yet.",
"surface_contains_pass": true,
"trace_hash": "a68f18a016983cb1fe269ffc7cfaa7f89c246eea16bb4da1067640f39143f564",
"versor_closure": true,
"versor_condition": 1.22e-07
},
{
"case_id": "recall_truth_012",
"category": "recall",
"intent_correct": true,
"surface": "truth — pack-grounded (en_core_cognition_v1): cognition.truth; logos.core; epistemic.ground. No session evidence yet.",
"surface_contains_pass": true,
"trace_hash": "bfd96411b3d66fdd73f99746c6f12e3b17ef3885c820339bddfb3a22f84e958d",
"versor_closure": true,
"versor_condition": 5.6e-08
},
{
"case_id": "recall_wisdom_038",
"category": "recall",
"intent_correct": true,
"surface": "wisdom — pack-grounded (en_core_cognition_v1): cognition.wisdom; epistemic.judgment; reason.order. No session evidence yet.",
"surface_contains_pass": true,
"trace_hash": "c3747bc4d62f24b60385e346c912cdfeb84f0be5404ec535f3bd7ff95303d706",
"versor_closure": true,
"versor_condition": 3e-08
},
{
"case_id": "unknown_spirit_041",
"category": "unknown",
"intent_correct": true,
"surface": "I don't know — insufficient grounding for that yet.",
"surface_contains_pass": true,
"trace_hash": "b4571592a8332811586ca9398f667d99f33c73cf43a3cd2e092e6a1bd48f1b08",
"versor_closure": true,
"versor_condition": 6.2e-08
},
{
"case_id": "unknown_word_018",
"category": "unknown",
"intent_correct": true,
"surface": "I don't know — insufficient grounding for that yet.",
"surface_contains_pass": true,
"trace_hash": "07903c9e6d28cb584800ede4e6534b50b26120b72f6973108438f25fb5a8972d",
"versor_closure": true,
"versor_condition": 1.33e-07
},
{
"case_id": "verification_truth_016",
"category": "verification",
"intent_correct": true,
"surface": "truth — teaching-grounded (cognition_chains_v1): cognition.truth; logos.core. truth requires evidence (cognition.evidence). No session evidence yet.",
"surface_contains_pass": true,
"trace_hash": "8dd5fe4ed93dedb6da24fdab99a9d167cb36f8bdb2a3e55d4eea5a159411c884",
"versor_closure": true,
"versor_condition": 1.88e-07
},
{
"case_id": "verification_wisdom_036",
"category": "verification",
"intent_correct": true,
"surface": "wisdom — teaching-grounded (cognition_chains_v1): cognition.wisdom; epistemic.judgment. wisdom grounds judgment (cognition.judgment). No session evidence yet.",
"surface_contains_pass": true,
"trace_hash": "2c2e60377c54b88febad641c0062772c0d731e9e7089a5170c77c98cfbf44060",
"versor_closure": true,
"versor_condition": 8.2e-08
}
],
"lane": "cognition",
"metrics": {
"intent_accuracy": 1.0,
"surface_groundedness": 0.9474,
"term_capture_rate": 0.7083,
"total": 19,
"versor_closure_rate": 1.0
},
"split": "holdout",
"timestamp": "2026-05-18T20:54:59.383102+00:00",
"version": "v1"
}

View file

@ -0,0 +1,163 @@
"""ADR-0060 — CORRECTION acknowledgement carries the corrected-topic lemma.
ADR-0053 introduced the cold-start CORRECTION acknowledgement: a
deterministic pack-grounded surface stating that no prior turn
exists in the session to correct. The surface was self-contained
(only the lemma ``correction`` and its semantic_domains appeared)
which left the acknowledgement honest but topic-blind a user who
said "Actually, truth requires evidence" got a surface that didn't
reference ``truth`` at all.
ADR-0060 closes that gap by weaving the **first pack-resident
topical lemma** from the correction utterance into the surface
("Noted topic: <lemma> (<head_domains>)"), while preserving the
existing trust-boundary properties:
- Every visible non-template token is still either the lemma
``correction``, the topical lemma, or a verbatim
``semantic_domains`` string from the ratified pack.
- The surface is deterministic: same text same bytes.
- Backward compatible: ``pack_grounded_correction_surface()``
with no argument (or with text carrying no pack lemma) emits
the ADR-0053 topic-less template byte-identically.
- The trailing "No prior turn in this session to correct yet."
trust-boundary label is preserved.
"""
from __future__ import annotations
from chat.pack_grounding import (
PACK_ID,
_extract_correction_topic_lemma,
pack_grounded_correction_surface,
)
from chat.runtime import ChatRuntime
# ---------------------------------------------------------------------------
# Topic lemma extraction
# ---------------------------------------------------------------------------
def test_extract_topic_lemma_picks_first_pack_lemma() -> None:
"""Left-to-right token order is the canonical selector."""
assert _extract_correction_topic_lemma("Actually, truth requires evidence") == "truth"
def test_extract_topic_lemma_skips_meta_cognition_lemma() -> None:
"""``correction`` itself is excluded — it's already the subject of
the acknowledgement template; echoing it would be circular."""
assert _extract_correction_topic_lemma("That correction is wrong about wisdom") == "wisdom"
def test_extract_topic_lemma_skips_dialogue_fillers() -> None:
"""``be`` and ``have`` are pack lemmas but carry no topical signal
in a correction utterance filtered out as stopwords."""
# "have" appears before "knowledge" but is a filler; selector picks knowledge.
assert _extract_correction_topic_lemma("we have knowledge here") == "knowledge"
def test_extract_topic_lemma_returns_none_when_no_pack_lemma() -> None:
assert _extract_correction_topic_lemma("No that is wrong") is None
assert _extract_correction_topic_lemma("") is None
assert _extract_correction_topic_lemma(None) is None # type: ignore[arg-type]
def test_extract_topic_lemma_strips_common_punctuation() -> None:
"""Tokens with attached punctuation (``truth.``, ``"evidence"``)
still match pack lemmas after normalization."""
assert _extract_correction_topic_lemma('Actually, "truth" matters.') == "truth"
def test_extract_topic_lemma_is_case_insensitive() -> None:
assert _extract_correction_topic_lemma("ACTUALLY TRUTH REQUIRES EVIDENCE") == "truth"
# ---------------------------------------------------------------------------
# Surface composition with topic
# ---------------------------------------------------------------------------
def test_surface_with_topic_contains_corrected_lemma() -> None:
surface = pack_grounded_correction_surface("Actually, truth requires evidence")
assert surface is not None
assert "correction" in surface
assert "truth" in surface
assert "Noted topic: truth" in surface
def test_surface_with_topic_contains_topic_domains() -> None:
"""The corrected lemma's top semantic_domains are included
verbatim keeps the pack-grounded discipline (no rewording)."""
from chat.pack_grounding import _pack_index
truth_domains = _pack_index().get("truth", ())
assert truth_domains, "test fixture: 'truth' must be a pack lemma"
surface = pack_grounded_correction_surface("Actually, truth requires evidence")
assert surface is not None
# At least one of truth's top-2 domains appears in the surface.
assert any(d in surface for d in truth_domains[:2])
def test_surface_with_no_topic_degrades_to_adr_0053_template() -> None:
"""Backward compatibility: ``pack_grounded_correction_surface()``
with no argument emits the ADR-0053 topic-less template
byte-identically."""
legacy = pack_grounded_correction_surface()
no_lemma = pack_grounded_correction_surface("No that is wrong")
assert legacy is not None
assert legacy == no_lemma
assert "Noted topic" not in legacy
assert "No prior turn in this session to correct yet." in legacy
def test_surface_preserves_trust_boundary_label() -> None:
"""The trailing 'No prior turn...' disclosure is the constant
trust-boundary label distinguishing this cold-start surface
from the post-correction teaching-repair path. Must be present
in both variants."""
with_topic = pack_grounded_correction_surface("Actually, truth requires evidence")
without_topic = pack_grounded_correction_surface()
assert with_topic is not None and without_topic is not None
assert "No prior turn in this session to correct yet." in with_topic
assert "No prior turn in this session to correct yet." in without_topic
def test_surface_is_deterministic() -> None:
a = pack_grounded_correction_surface("Actually, truth requires evidence")
b = pack_grounded_correction_surface("Actually, truth requires evidence")
assert a == b
def test_surface_pack_id_is_correct() -> None:
surface = pack_grounded_correction_surface("Actually, truth requires evidence")
assert surface is not None
assert PACK_ID in surface
# ---------------------------------------------------------------------------
# End-to-end through ChatRuntime — the holdout test case
# ---------------------------------------------------------------------------
def test_correction_truth_040_now_emits_truth_in_surface() -> None:
"""The exact holdout case that this ADR targets:
`correction_truth_040` expects `term=['truth']` and was missing it
pre-ADR-0060. Through the live ChatRuntime, the surface must now
contain ``truth``."""
rt = ChatRuntime()
response = rt.chat("Actually, truth requires evidence")
assert response.grounding_source == "pack"
assert "truth" in response.surface.lower()
assert "correction" in response.surface.lower()
def test_correction_with_no_pack_lemma_still_grounds() -> None:
"""A correction utterance with no pack-resident topical lemma
still receives the acknowledgement surface (degrades to the
topic-less template), not the universal disclosure."""
rt = ChatRuntime()
response = rt.chat("No that is wrong")
assert response.grounding_source == "pack"
assert "correction" in response.surface.lower()
assert "Noted topic" not in response.surface