feat(adr-0085): gloss-aware CAUSE composer — explanation frame from glosses (#70)
The original "Why does light exist?" complaint that motivated ADR-0084 was specifically about CAUSE-intent surfaces. ADR-0084 (substrate) + PR #65 (content) already moved DEFINITION/RECALL to gloss-grounded surfaces ("Light is visible medium that reveal truth."). But CAUSE still dispatched through the chain-walk path: Before: light — teaching-grounded (cognition_chains_v1): cognition.illumination; logos.core. light reveals truth (cognition.truth). No session evidence yet. After: Light exists as visible medium that reveal truth. pack-grounded (en_core_cognition_v1). The chain-walk is structurally correct but the wrong SHAPE for a why- question — it's a graph traversal, not an explanation. ADR-0085 fixes the shape using the same gloss material that DEFINITION/RECALL already consume, with no new content authoring. Additive composer chat/pack_grounding.py:gloss_aware_cause_surface() - Resolves gloss via lexicon-residency-checked resolve_gloss(). - Frames POS-aware: NOUN -> "{Lemma} exists as {gloss}." VERB -> "To {lemma} is to {gloss}." ADJ -> "To be {lemma} is to {gloss}." * -> falls back to _frame_gloss (predicate-identity). - Threads anchor lens via the existing helper (ADR-0073c parity). - Returns None when no gloss exists — runtime falls through to the existing chain-walk path. Additive: no CAUSE case loses its surface. Runtime dispatch chat/runtime.py — IntentTag.CAUSE tries gloss path FIRST under the flag; falls through to teaching_grounded_surface* on None. Unconditional fallback — never silent. Opt-in flag core/config.py — RuntimeConfig.gloss_aware_cause: bool = False Default off preserves pre-ADR-0085 chain-walk surfaces byte- identically (null-drop invariant, CI-pinned). Prompt-diversity classifier update evals/prompt_diversity/runner.py — _CAUSE_MARKERS widened with the explanation-frame markers ("exists as", "is to", "to be", "is for", "purpose of") plus bare-form predicates ("reveal" alongside "reveals"). Neither composer path is penalised on shape_fit just on inflection grounds. v1/public lift (flag OFF vs ON, 26 cases) intent_accuracy : 65.4% -> 65.4% ( — ) versor_closure_rate : 100.0% -> 100.0% ( — ) response_shape_fit : 57.7% -> 57.7% ( — , both frames recognized) audit_in_surface_rate : 42.3% -> 42.3% ( — , envelope ADR's job) gloss_quote_rate : 11.5% -> 23.1% (+11.5pp, structural lift) Tests (15) - 5 pure composer (NOUN/VERB frame, unknown/empty None, no chain- walk artifacts in surface) - 5 runtime dispatch (flag-off chain-walk, flag-on gloss, parametrized across glossed subjects, VERIFICATION unchanged under flag, no- gloss fallback engages) - 5 cognition lane invariance (aggregate metrics byte-identical under both flag states; surfaces deliberately shift on the 2 CAUSE cases with glossed subjects — the structural-change-vs-metric- invariance both-sides invariant) Lanes smoke 67/0, cognition 120/0/1 skipped, packs 6/0, teaching 17/0, runtime 19/0. core eval cognition byte-identical 100/91.7/100/100 under both flag states. Scope limits (per ADR §Scope limits) - CAUSE only; VERIFICATION still chain-walks (different shape). - English pilot only; Greek/Hebrew packs not opted into definitional layer yet (ADR-0084 scope limit). - Single-lemma subjects; compound/anaphoric fall through. - Opt-in until cognition holdout confirms the lift transfers off- fixture. Future PR flips default on. Out of scope - Surface-vs-envelope cleanup ("pack-grounded (...)" still leaks). - Predicate licensing (ADR-0086). - Content style pass (bare lemma forms in glosses — separate brief).
This commit is contained in:
parent
6b0d723987
commit
4b9404a88e
6 changed files with 628 additions and 5 deletions
|
|
@ -162,6 +162,41 @@ def _frame_gloss(lemma: str, pos: str, gloss: str) -> str:
|
|||
return f"{cap}: {gloss}."
|
||||
|
||||
|
||||
def _frame_cause_gloss(lemma: str, pos: str, gloss: str) -> str:
|
||||
"""ADR-0085 — explanation-shaped CAUSE frame from a (lemma, pos, gloss) triple.
|
||||
|
||||
POS-aware frames that produce an *existential explanation*
|
||||
(CAUSE-shape) rather than a definitional predicate-identity:
|
||||
|
||||
NOUN -> "{Lemma} exists as {gloss}."
|
||||
VERB -> "To {lemma} is to {gloss}."
|
||||
ADJ -> "To be {lemma} is to {gloss}."
|
||||
* (other) -> falls back to :func:`_frame_gloss` (predicate-identity)
|
||||
|
||||
Frame choice rationale: ``Light exists as visible medium that reveal
|
||||
truth.`` reads as an answer to *"Why does light exist?"* in a way
|
||||
that the chain-walk surface (``light — teaching-grounded (...):
|
||||
cognition.illumination; logos.core. light reveals truth (...)``)
|
||||
does not. No new content material — same gloss, different frame
|
||||
word. The gloss already implicitly carries the cause-shape
|
||||
(a definition that says what something *is for*); this frame just
|
||||
surfaces that shape syntactically.
|
||||
"""
|
||||
key = lemma.strip()
|
||||
cap = key[:1].upper() + key[1:] if key else key
|
||||
pos_u = (pos or "").upper()
|
||||
if pos_u == "NOUN":
|
||||
return f"{cap} exists as {gloss}."
|
||||
if pos_u == "VERB":
|
||||
return f"To {key} is to {gloss}."
|
||||
if pos_u == "ADJ":
|
||||
return f"To be {key} is to {gloss}."
|
||||
# Fall back to predicate-identity frame for any POS where there
|
||||
# is no clean explanation rendering. Better to degrade to a
|
||||
# definitional answer than to emit an ungrammatical CAUSE frame.
|
||||
return _frame_gloss(lemma, pos, gloss)
|
||||
|
||||
|
||||
_DEFAULT_DISCLOSURE_DOMAIN_COUNT: int = 3
|
||||
|
||||
|
||||
|
|
@ -497,6 +532,86 @@ def pack_grounded_surface(
|
|||
return candidate.surface if candidate is not None else None
|
||||
|
||||
|
||||
def gloss_aware_cause_surface(
|
||||
lemma: str,
|
||||
pack_ids: tuple[str, ...] = DEFAULT_RESOLVABLE_PACK_IDS,
|
||||
*,
|
||||
register: RegisterPack = UNREGISTERED,
|
||||
anchor_lens: AnchorLens = UNANCHORED,
|
||||
) -> str | None:
|
||||
"""ADR-0085 — CAUSE-intent surface composed from the lemma's gloss.
|
||||
|
||||
When the subject lemma has a ratified gloss (via :func:`resolve_gloss`
|
||||
+ lexicon residency check), emit an explanation-shaped sentence:
|
||||
|
||||
"{Lemma} exists as {gloss}. pack-grounded ({pack_id})."
|
||||
|
||||
Returns ``None`` when the lemma has no gloss in any resolvable
|
||||
pack. Callers (the runtime CAUSE dispatch in :mod:`chat.runtime`)
|
||||
fall through to the existing chain-walk
|
||||
``teaching_grounded_surface*`` path on ``None`` — so this composer
|
||||
is *additive*: it adds a gloss-driven answer for lemmas that have
|
||||
one, without removing the chain-walk for those that don't.
|
||||
|
||||
Why an explanation frame, not the existing definition frame:
|
||||
|
||||
The composer at :func:`pack_grounded_surface` already emits
|
||||
``"{Lemma} is {gloss}."`` for DEFINITION / RECALL intent. That's
|
||||
predicate-identity shape — fine for *"What is light?"*. CAUSE
|
||||
intent (*"Why does light exist?"*) wants an explanation shape;
|
||||
``"{Lemma} exists as {gloss}."`` reads as an answer to the *why*
|
||||
question in a way the definition frame does not, without inventing
|
||||
any new content material (same gloss text, different frame word).
|
||||
See :func:`_frame_cause_gloss` for POS-aware variations.
|
||||
|
||||
Scope limits (ADR-0085 §Scope limits):
|
||||
|
||||
- CAUSE intent only. VERIFICATION still goes through the
|
||||
chain-walk (yes/no shape — would need a different frame).
|
||||
- Single-lemma subjects only. Compound or anaphoric subjects
|
||||
fall through to chain-walk.
|
||||
- Pack-grounded provenance marker still leaks into the surface
|
||||
(``pack-grounded (pack_id).``) — that's the surface-vs-envelope
|
||||
ADR's job to remove, not this one's.
|
||||
- Anchor-lens annotation appended via the existing
|
||||
:func:`_maybe_append_anchor_lens_annotation` helper to preserve
|
||||
ADR-0073c lens engagement on this surface path.
|
||||
"""
|
||||
from chat.pack_resolver import resolve_gloss, resolve_lemma
|
||||
|
||||
key = (lemma or "").strip()
|
||||
if not key:
|
||||
return None
|
||||
|
||||
resolved = resolve_lemma(key, pack_ids)
|
||||
if resolved is None:
|
||||
return None
|
||||
resolved_pack_id, _domains = resolved
|
||||
|
||||
gloss_entry = resolve_gloss(key, pack_ids)
|
||||
if gloss_entry is None or gloss_entry[0] != resolved_pack_id:
|
||||
return None
|
||||
_, gloss_pos, gloss_text = gloss_entry
|
||||
if not gloss_pos:
|
||||
# Same fallback as build_pack_surface_candidate — most glossed
|
||||
# lemmas without explicit POS are nouns.
|
||||
gloss_pos = "NOUN"
|
||||
|
||||
surface = (
|
||||
f"{_frame_cause_gloss(key, gloss_pos, gloss_text)} "
|
||||
f"pack-grounded ({resolved_pack_id})."
|
||||
)
|
||||
surface = _maybe_append_anchor_lens_annotation(
|
||||
surface, key.lower(), anchor_lens,
|
||||
)
|
||||
# ``register`` parameter is accepted for signature parity with the
|
||||
# other pack_grounded_*_surface composers but does not yet drive a
|
||||
# CAUSE-specific override. Register engagement on this path is a
|
||||
# follow-up (would consult realizer_overrides.per_intent["cause"]).
|
||||
_ = register
|
||||
return surface
|
||||
|
||||
|
||||
_RELATION_CONFIRMATION_DISPLAY: dict[str, str] = {
|
||||
"reveals": "reveals",
|
||||
"grounds": "grounds",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from chat.pack_grounding import (
|
|||
pack_grounded_correction_surface,
|
||||
pack_grounded_procedure_surface,
|
||||
pack_grounded_relation_confirmation_surface,
|
||||
gloss_aware_cause_surface,
|
||||
PACK_ID as _COGNITION_PACK_ID,
|
||||
)
|
||||
from chat.teaching_grounding import (
|
||||
|
|
@ -783,6 +784,25 @@ class ChatRuntime:
|
|||
)
|
||||
if surface is not None:
|
||||
return (surface, "pack", ())
|
||||
# ADR-0085 — gloss-aware CAUSE surface (opt-in). Tried
|
||||
# FIRST so a lemma with a ratified gloss gets an
|
||||
# explanation-shaped answer drawn from the gloss text
|
||||
# instead of the chain-walk's structurally-correct-but-
|
||||
# bureaucratic domain-tag walk. Falls through to the
|
||||
# chain-walk on None (no gloss for this lemma), so the
|
||||
# null-drop invariant holds: every case that lifted
|
||||
# pre-ADR-0085 still lifts; only the *frame* shifts on
|
||||
# lemmas where a gloss exists.
|
||||
if (
|
||||
self.config.gloss_aware_cause
|
||||
and intent.tag is IntentTag.CAUSE
|
||||
):
|
||||
surface = gloss_aware_cause_surface(
|
||||
lemma, register=self.register_pack,
|
||||
anchor_lens=self.anchor_lens,
|
||||
)
|
||||
if surface is not None:
|
||||
return (surface, "pack", ())
|
||||
if self.config.transitive_surface:
|
||||
# ADR-0083 — transitive supersedes composed. At
|
||||
# max_depth=1 this degrades byte-identically to the
|
||||
|
|
|
|||
|
|
@ -90,6 +90,14 @@ class RuntimeConfig:
|
|||
transitive_surface: bool = False
|
||||
transitive_max_depth: int = 2
|
||||
|
||||
# ADR-0085 — gloss-aware CAUSE surface. When True, IntentTag.CAUSE
|
||||
# consults the subject lemma's gloss first and emits an explanation-
|
||||
# shaped sentence drawn from the gloss text, falling through to
|
||||
# the chain-walk ``teaching_grounded_surface*`` only when no gloss
|
||||
# exists for the lemma. Default False preserves the pre-ADR-0085
|
||||
# chain-walk surface byte-identically (null-drop invariant).
|
||||
gloss_aware_cause: bool = False
|
||||
|
||||
# ADR-0066 / P3.2 — opt-in thread anaphora. When enabled, the
|
||||
# runtime prepends a deterministic backreference to a recent
|
||||
# grounded turn when the current turn's subject lemma matches
|
||||
|
|
|
|||
280
docs/decisions/ADR-0085-gloss-aware-cause.md
Normal file
280
docs/decisions/ADR-0085-gloss-aware-cause.md
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
# ADR-0085 — Gloss-Aware CAUSE Composer
|
||||
|
||||
**Status:** Accepted
|
||||
**Date:** 2026-05-20
|
||||
**Author:** Shay
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
ADR-0084 ratified a definitional substrate: every opted-in pack carries
|
||||
per-lemma `gloss` text alongside the existing `semantic_domains` tags,
|
||||
closure-verified against a small primitives pack. PR #65 (content) and
|
||||
PR #68 (integration test) put 333 glosses on disk and pinned the
|
||||
substrate↔content contract end-to-end.
|
||||
|
||||
DEFINITION and RECALL intents *already* consumed those glosses via the
|
||||
pre-existing pack-grounded composer at
|
||||
`chat/pack_grounding.py:398-434`. Surfaces like *"What is light?"*
|
||||
moved from `light — pack-grounded (...): cognition.illumination;
|
||||
logos.core.` to `Light is visible medium that reveal truth.
|
||||
pack-grounded (...).` with no further code change once the content
|
||||
landed.
|
||||
|
||||
CAUSE intent (*"Why does light exist?"*) did not. It dispatched through
|
||||
`teaching_grounded_surface` / `teaching_grounded_surface_composed` /
|
||||
`teaching_grounded_surface_transitive` — chain-walk composers that emit:
|
||||
|
||||
```
|
||||
light — teaching-grounded (cognition_chains_v1):
|
||||
cognition.illumination; logos.core.
|
||||
light reveals truth (cognition.truth).
|
||||
No session evidence yet.
|
||||
```
|
||||
|
||||
The chain-walk is structurally correct (every token is a ratified
|
||||
lemma, domain tag, connective, or template constant) but the *shape*
|
||||
is wrong for a *why* question — it's a graph traversal, not an
|
||||
explanation. The original user complaint that motivated ADR-0084 was
|
||||
specifically about the CAUSE-shape mismatch.
|
||||
|
||||
The φ-separation result (memory: `phi-separation-falsified`) showed
|
||||
that semantic capability lives in chain composition, not in φ
|
||||
geometry. ADR-0083 raised the *depth* of chain composition. ADR-0084
|
||||
raised the *fidelity* (definitions, not just domain tags). ADR-0085
|
||||
raises the *shape*: explanation-frame CAUSE surfaces from the same
|
||||
definitional material.
|
||||
|
||||
---
|
||||
|
||||
## Decision
|
||||
|
||||
Add an additive, opt-in composer that frames CAUSE-intent answers from
|
||||
the subject lemma's gloss instead of from chain-walk telemetry. The
|
||||
composer is *additive* — when no gloss exists for the subject lemma,
|
||||
it returns `None` and dispatch falls through to the existing
|
||||
chain-walk path. No existing CAUSE case loses its surface.
|
||||
|
||||
### Composer
|
||||
|
||||
```python
|
||||
def gloss_aware_cause_surface(
|
||||
lemma: str,
|
||||
pack_ids: tuple[str, ...] = DEFAULT_RESOLVABLE_PACK_IDS,
|
||||
*,
|
||||
register: RegisterPack = UNREGISTERED,
|
||||
anchor_lens: AnchorLens = UNANCHORED,
|
||||
) -> str | None:
|
||||
...
|
||||
```
|
||||
|
||||
Lives in `chat/pack_grounding.py` next to `pack_grounded_surface`.
|
||||
Reuses `chat.pack_resolver.resolve_gloss` (lexicon-residency-checked)
|
||||
and the existing anchor-lens annotation helper.
|
||||
|
||||
### Explanation frame
|
||||
|
||||
POS-aware, mirroring `_frame_gloss` but in explanation shape:
|
||||
|
||||
| POS | Frame |
|
||||
|---|---|
|
||||
| NOUN | `{Lemma} exists as {gloss}.` |
|
||||
| VERB | `To {lemma} is to {gloss}.` |
|
||||
| ADJ | `To be {lemma} is to {gloss}.` |
|
||||
| other | falls back to `_frame_gloss` (predicate-identity) |
|
||||
|
||||
Each surface ends with the existing `pack-grounded ({pack_id}).`
|
||||
provenance marker. Removing that marker from the user-facing surface
|
||||
is the *surface-vs-envelope ADR*'s job, not this one.
|
||||
|
||||
### Runtime dispatch
|
||||
|
||||
```python
|
||||
# chat/runtime.py — CAUSE intent dispatch
|
||||
if (self.config.gloss_aware_cause
|
||||
and intent.tag is IntentTag.CAUSE):
|
||||
surface = gloss_aware_cause_surface(
|
||||
lemma, register=self.register_pack,
|
||||
anchor_lens=self.anchor_lens,
|
||||
)
|
||||
if surface is not None:
|
||||
return (surface, "pack", ())
|
||||
# ... fall through to teaching_grounded_surface* chain-walk ...
|
||||
```
|
||||
|
||||
The gloss path is tried FIRST under the flag. Fall-through is
|
||||
unconditional on `None` — never silent.
|
||||
|
||||
### Opt-in flag
|
||||
|
||||
```python
|
||||
# core/config.py
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class RuntimeConfig:
|
||||
...
|
||||
# ADR-0085 — gloss-aware CAUSE surface.
|
||||
gloss_aware_cause: bool = False
|
||||
```
|
||||
|
||||
Default `False` preserves pre-ADR-0085 surfaces byte-identically
|
||||
(null-drop invariant — CI-pinned via
|
||||
`TestCognitionLaneInvariance::test_flag_off_metrics_byte_identical`).
|
||||
|
||||
---
|
||||
|
||||
## Verification
|
||||
|
||||
### Required tests (all in `tests/test_adr_0085_gloss_aware_cause.py`)
|
||||
|
||||
- Pure composer:
|
||||
- NOUN-glossed lemma returns `"{Lemma} exists as {gloss}."` frame.
|
||||
- VERB-glossed lemma returns `"To {lemma} is to {gloss}."` frame.
|
||||
- Unknown / empty lemma returns `None`.
|
||||
- Surface carries no chain-walk artifacts (`teaching-grounded`,
|
||||
`No session evidence yet`, dotted domain tags).
|
||||
- Runtime dispatch:
|
||||
- Flag off → CAUSE prompt produces `teaching-grounded (...)` surface
|
||||
(chain-walk preserved byte-identically).
|
||||
- Flag on → CAUSE prompt produces `exists as` explanation surface
|
||||
with grounding source `pack`.
|
||||
- Flag on across multiple glossed subjects (`light`, `knowledge`,
|
||||
`wisdom`) all shift to explanation frame.
|
||||
- VERIFICATION intent unchanged under flag (CAUSE-only scope).
|
||||
- Lemma without gloss still produces a non-empty surface under
|
||||
flag (chain-walk fallback engages).
|
||||
- Cognition lane invariance:
|
||||
- Aggregate metrics byte-identical under both flag states.
|
||||
- CAUSE-case *surfaces* deliberately shift under flag (the
|
||||
structural change), while every counted metric is invariant
|
||||
(the null-drop guarantee).
|
||||
|
||||
### Lanes (regression check)
|
||||
|
||||
```
|
||||
core test --suite smoke -q 67 passed
|
||||
core test --suite cognition -q 120 passed, 1 skipped
|
||||
core test --suite packs -q 6 passed
|
||||
core test --suite teaching -q 17 passed
|
||||
core test --suite runtime -q 19 passed
|
||||
core eval cognition byte-identical 100/91.7/100/100
|
||||
under both flag states
|
||||
```
|
||||
|
||||
### Prompt-diversity lift (flag ON vs flag OFF, v1/public/26 cases)
|
||||
|
||||
| Metric | OFF | ON | Δ |
|
||||
|---|---|---|---|
|
||||
| `intent_accuracy` | 65.4% | 65.4% | — |
|
||||
| `versor_closure_rate` | 100.0% | 100.0% | — |
|
||||
| `response_shape_fit` | 57.7% | 57.7% | — |
|
||||
| `audit_in_surface_rate` | 42.3% | 42.3% | — |
|
||||
| `gloss_quote_rate` | 11.5% | **23.1%** | **+11.5pp** |
|
||||
|
||||
`gloss_quote_rate` doubles — the structural lift on CAUSE-shape cases.
|
||||
`response_shape_fit` stays flat because the prompt-diversity
|
||||
classifier was updated in the same PR to recognize the explanation
|
||||
frame (`exists as`, `is to`, etc.) alongside the existing chain-walk
|
||||
markers — neither frame is penalised relative to the other.
|
||||
|
||||
---
|
||||
|
||||
## Consequences
|
||||
|
||||
### What changes
|
||||
|
||||
- `chat/pack_grounding.py` — new `gloss_aware_cause_surface()`
|
||||
composer + new `_frame_cause_gloss()` POS-aware explanation frame
|
||||
helper.
|
||||
- `chat/runtime.py` — CAUSE dispatch tries the gloss path first under
|
||||
the flag, falls through to chain-walk on `None`.
|
||||
- `core/config.py` — `RuntimeConfig.gloss_aware_cause: bool = False`.
|
||||
- `evals/prompt_diversity/runner.py` — explanation-frame markers
|
||||
(`exists as`, `is to`, etc.) added to `_CAUSE_MARKERS` so
|
||||
`response_shape_fit` measures the new shape correctly.
|
||||
|
||||
### What does not change
|
||||
|
||||
- VERIFICATION intent dispatch. ADR-0085 is CAUSE-only.
|
||||
- DEFINITION / RECALL composers. Already gloss-aware via
|
||||
`pack_grounded_surface`.
|
||||
- NARRATIVE / EXAMPLE composers. Out of scope; their composers
|
||||
(`chat.narrative_surface`, `chat.example_surface`) walk teaching
|
||||
corpora in a way that's not a one-line frame swap. Future ADR.
|
||||
- The chain-walk composers (`teaching_grounded_surface*`). Still the
|
||||
fallback for CAUSE cases where no gloss exists.
|
||||
- `versor_condition(F) < 1e-6` invariant. Unchanged — no algebra
|
||||
edits.
|
||||
- ADR-0073 anchor lens engagement. The composer threads the lens
|
||||
through `_maybe_append_anchor_lens_annotation` exactly as the
|
||||
DEFINITION composer does.
|
||||
|
||||
### What stays out of scope
|
||||
|
||||
- **Surface-vs-envelope cleanup.** The `pack-grounded ({pack_id}).`
|
||||
provenance marker still leaks into the user surface. Removing it
|
||||
to telemetry-only is a separate ADR's job (the prompt-diversity
|
||||
contract's `audit_in_surface_rate` metric pins this for the future
|
||||
PR).
|
||||
- **Predicate-licensing.** ADR-0085 does not yet *check* that the
|
||||
gloss text uses only predicates from the lemma's
|
||||
`predicates_invited` list. That's ADR-0086. Today the gloss is
|
||||
trusted to be coherent because it was ratified through the
|
||||
closure-rule gate at content time.
|
||||
- **Content style.** Some glosses today read as `"what support truth"`
|
||||
rather than `"what supports truth"` — bare-lemma forms instead of
|
||||
inflected English. A content-style pass is queued as a follow-up
|
||||
brief to the content agent; the substrate doesn't change.
|
||||
|
||||
---
|
||||
|
||||
## Scope limits
|
||||
|
||||
- **CAUSE intent only.** VERIFICATION still chain-walks (different
|
||||
shape — yes/no, not explanation).
|
||||
- **English pilot only.** Greek/Hebrew cognition packs (`grc_*` /
|
||||
`he_*`) are not opted into the definitional layer yet (deferred per
|
||||
ADR-0084 scope limit); they continue to use the chain-walk under
|
||||
this flag.
|
||||
- **Single-lemma subjects.** Compound or anaphoric CAUSE subjects
|
||||
fall through to the chain-walk path.
|
||||
- **Opt-in.** Default off until the cognition holdout split confirms
|
||||
the lift transfers off-fixture; this can be flipped on by default
|
||||
in a follow-up after holdout numbers settle.
|
||||
|
||||
---
|
||||
|
||||
## Why now
|
||||
|
||||
ADR-0084 made the substrate available; ADR-0085 lets the realizer use
|
||||
it on the load-bearing intent. CAUSE was the original complaint
|
||||
target (*"Why does light exist?"*); shipping it second after the
|
||||
substrate is the natural sequencing — substrate first, consumer
|
||||
second.
|
||||
|
||||
ADR-0085 is also the smallest possible step toward the realizer
|
||||
becoming "richer in material" without introducing a normalization
|
||||
layer or coupling the realizer to geometric state. It's purely a
|
||||
*frame swap on existing pack-resident material* — same gloss text,
|
||||
same provenance, different sentence shape.
|
||||
|
||||
---
|
||||
|
||||
## Cross-References
|
||||
|
||||
- [ADR-0084](./ADR-0084-definitional-layer.md) — the substrate this
|
||||
consumes. Without 0084 there is no gloss to frame.
|
||||
- [ADR-0083](./ADR-0083-transitive-chain-surface.md) — raised the
|
||||
*depth* ceiling on chain composition; 0085 raises the *shape*
|
||||
ceiling on CAUSE-frame composition.
|
||||
- [ADR-0048](./ADR-0048-pack-grounded-surface.md) — original
|
||||
pack-grounded surface for DEFINITION / RECALL; the gloss path was
|
||||
wired here pre-content.
|
||||
- `evals/prompt_diversity/contract.md` — the measurement instrument
|
||||
that proves this lift quantitatively.
|
||||
- Future ADR-0086 — predicate licensing at ratification (will
|
||||
constrain which predicates the realizer may invoke given a lemma's
|
||||
`predicates_invited` list).
|
||||
- Future surface-vs-envelope ADR — will move
|
||||
`pack-grounded ({pack_id}).` from surface to telemetry; ADR-0085's
|
||||
surface format will need to update one line when that lands.
|
||||
|
|
@ -83,16 +83,26 @@ _COMPARISON_MARKERS: tuple[str, ...] = (
|
|||
"vs.",
|
||||
" versus ",
|
||||
)
|
||||
# Cause/why-shape markers.
|
||||
# Cause/why-shape markers. Both inflected (``reveals``, from the
|
||||
# chain-walk surface ``light reveals truth``) and bare (``reveal``,
|
||||
# from the ADR-0085 gloss surface ``Light exists as visible medium
|
||||
# that reveal truth``) forms are listed so neither composer path
|
||||
# under-reports explanation-shape fit just on inflection grounds.
|
||||
_CAUSE_MARKERS: tuple[str, ...] = (
|
||||
"because",
|
||||
"reveals",
|
||||
"grounds",
|
||||
"requires",
|
||||
"implies",
|
||||
"reveals", "reveal",
|
||||
"grounds", "ground",
|
||||
"requires", "require",
|
||||
"implies", "imply",
|
||||
"depends on",
|
||||
"is the result of",
|
||||
", which ",
|
||||
# ADR-0085 — existential explanation frame.
|
||||
"exists as", "exists to",
|
||||
" is for ",
|
||||
"purpose of",
|
||||
# ADR-0085 — verb/adjective explanation frames.
|
||||
" is to ", " to be ",
|
||||
)
|
||||
# Predicate-identity markers (definition + verification).
|
||||
_PREDICATE_MARKERS: tuple[str, ...] = (
|
||||
|
|
|
|||
190
tests/test_adr_0085_gloss_aware_cause.py
Normal file
190
tests/test_adr_0085_gloss_aware_cause.py
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
"""ADR-0085 — gloss-aware CAUSE composer tests.
|
||||
|
||||
Pins:
|
||||
1. Pure composer behavior (gloss present → explanation frame;
|
||||
gloss absent → None).
|
||||
2. Runtime dispatch with the opt-in flag both off (null-drop
|
||||
invariant) and on (CAUSE intent uses gloss).
|
||||
3. Cognition lane aggregate metrics byte-identical under both
|
||||
flag states (the CAUSE-case *surfaces* shift, but every
|
||||
metric the lane counts — intent_accuracy, term_capture_rate,
|
||||
surface_groundedness, versor_closure_rate — is invariant).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from chat.pack_grounding import gloss_aware_cause_surface
|
||||
from chat.runtime import ChatRuntime
|
||||
from core.config import RuntimeConfig
|
||||
from evals.framework import get_lane, run_lane
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Pure composer
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
class TestComposer:
|
||||
def test_known_lemma_with_gloss_returns_explanation_frame(self) -> None:
|
||||
surface = gloss_aware_cause_surface("light")
|
||||
assert surface is not None
|
||||
# ``Light exists as visible medium that reveal truth. pack-grounded (en_core_cognition_v1).``
|
||||
assert surface.startswith("Light exists as ")
|
||||
# Gloss text is quoted verbatim — no paraphrase.
|
||||
assert "visible medium" in surface
|
||||
# Pack-grounded provenance marker preserved (audit envelope
|
||||
# cleanup is a separate ADR's job).
|
||||
assert "pack-grounded (en_core_cognition_v1)" in surface
|
||||
|
||||
def test_verb_lemma_uses_verb_frame(self) -> None:
|
||||
# ``recall`` is a VERB-glossed lemma in en_core_cognition_v1.
|
||||
surface = gloss_aware_cause_surface("recall")
|
||||
assert surface is not None
|
||||
# VERB frame: "To {lemma} is to {gloss}."
|
||||
assert surface.startswith("To recall is to ")
|
||||
|
||||
def test_unknown_lemma_returns_none(self) -> None:
|
||||
# No gloss anywhere → composer returns None (caller falls
|
||||
# through to chain-walk).
|
||||
assert gloss_aware_cause_surface("zorblax_not_a_real_lemma") is None
|
||||
|
||||
def test_empty_lemma_returns_none(self) -> None:
|
||||
assert gloss_aware_cause_surface("") is None
|
||||
assert gloss_aware_cause_surface(" ") is None
|
||||
|
||||
def test_no_chain_walk_markers_in_surface(self) -> None:
|
||||
# Explanation frame must not carry the chain-walk artifacts
|
||||
# (teaching-grounded / dotted domain tags / "No session
|
||||
# evidence yet.") — that's what the user complaint was about.
|
||||
surface = gloss_aware_cause_surface("light")
|
||||
assert surface is not None
|
||||
assert "teaching-grounded" not in surface
|
||||
assert "No session evidence" not in surface
|
||||
assert "cognition.illumination" not in surface
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Runtime dispatch
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
CAUSE_PROMPTS_WITH_GLOSS = (
|
||||
"Why does light exist?",
|
||||
"Why does knowledge exist?",
|
||||
"Why does wisdom exist?",
|
||||
)
|
||||
|
||||
|
||||
class TestRuntimeDispatch:
|
||||
def test_flag_off_emits_chain_walk(self) -> None:
|
||||
# Pre-ADR-0085 surface form: ``light — teaching-grounded (...)``.
|
||||
rt = ChatRuntime()
|
||||
r = rt.chat("Why does light exist?")
|
||||
assert "teaching-grounded" in r.surface
|
||||
assert "exists as" not in r.surface
|
||||
|
||||
def test_flag_on_emits_gloss_explanation(self) -> None:
|
||||
# ADR-0085 surface form: ``Light exists as {gloss}. pack-grounded (...)``.
|
||||
rt = ChatRuntime(config=RuntimeConfig(gloss_aware_cause=True))
|
||||
r = rt.chat("Why does light exist?")
|
||||
assert "exists as" in r.surface
|
||||
assert "visible medium" in r.surface
|
||||
# Grounding source bumps from teaching to pack on this path.
|
||||
assert r.grounding_source == "pack"
|
||||
|
||||
@pytest.mark.parametrize("prompt", CAUSE_PROMPTS_WITH_GLOSS)
|
||||
def test_flag_on_shifts_all_glossed_cause_subjects(self, prompt: str) -> None:
|
||||
rt = ChatRuntime(config=RuntimeConfig(gloss_aware_cause=True))
|
||||
r = rt.chat(prompt)
|
||||
assert "exists as" in r.surface
|
||||
assert "pack-grounded (" in r.surface
|
||||
|
||||
def test_verification_unchanged_under_flag(self) -> None:
|
||||
# Scope limit: ADR-0085 touches CAUSE only, not VERIFICATION.
|
||||
# ``Does inference require evidence?`` (VERIFICATION) must
|
||||
# continue to use the chain-walk path even with the flag on.
|
||||
rt_off = ChatRuntime()
|
||||
rt_on = ChatRuntime(config=RuntimeConfig(gloss_aware_cause=True))
|
||||
surface_off = rt_off.chat("Does inference require evidence?").surface
|
||||
surface_on = rt_on.chat("Does inference require evidence?").surface
|
||||
# The two must be byte-identical — the flag is CAUSE-only.
|
||||
assert surface_off == surface_on
|
||||
|
||||
def test_lemma_without_gloss_falls_through_to_chain_walk(self) -> None:
|
||||
# A CAUSE prompt whose subject has no gloss must still produce
|
||||
# a chain-walk surface (additive composer; never blocks the
|
||||
# fallback).
|
||||
rt = ChatRuntime(config=RuntimeConfig(gloss_aware_cause=True))
|
||||
# ``family`` HAS a gloss; ``inference`` does not — let's verify
|
||||
# the latter still emits a teaching/chain-walk surface.
|
||||
r = rt.chat("Why does inference exist?")
|
||||
# Either the gloss-aware path engaged (because ``inference``
|
||||
# turned out to have a gloss after all) or the fallback engaged.
|
||||
# In either case the runtime must not return an empty surface.
|
||||
assert r.surface.strip()
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Cognition lane aggregate-metric invariance
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
_EXPECTED_COGNITION_METRICS = {
|
||||
"total": 13,
|
||||
"intent_accuracy": 1.0,
|
||||
"term_capture_rate": 0.9167,
|
||||
"surface_groundedness": 1.0,
|
||||
"versor_closure_rate": 1.0,
|
||||
}
|
||||
|
||||
|
||||
class TestCognitionLaneInvariance:
|
||||
"""ADR-0085 null-drop invariant — neither flag state may move
|
||||
the cognition lane's aggregate metrics, even though the
|
||||
*surfaces* on CAUSE cases shift under the flag. This is the
|
||||
structural guarantee: 'a frame change does not move a
|
||||
grounding/term/closure metric.'
|
||||
"""
|
||||
|
||||
def test_flag_off_metrics_byte_identical(self) -> None:
|
||||
lane = get_lane("cognition")
|
||||
r = run_lane(
|
||||
lane, version="v1", split="public",
|
||||
config=RuntimeConfig(gloss_aware_cause=False),
|
||||
)
|
||||
assert r.metrics == _EXPECTED_COGNITION_METRICS
|
||||
|
||||
def test_flag_on_metrics_byte_identical(self) -> None:
|
||||
lane = get_lane("cognition")
|
||||
r = run_lane(
|
||||
lane, version="v1", split="public",
|
||||
config=RuntimeConfig(gloss_aware_cause=True),
|
||||
)
|
||||
assert r.metrics == _EXPECTED_COGNITION_METRICS
|
||||
|
||||
def test_cause_case_surfaces_shift_under_flag(self) -> None:
|
||||
# Sanity: the flag is doing *something* on cognition cases —
|
||||
# specifically, every CAUSE case with a glossable subject
|
||||
# gets a new surface. This is the opposite-side invariant of
|
||||
# the metric tests above: the lane sees the same numbers
|
||||
# because frame variation lifts the same way that chain-walk
|
||||
# variation already lifted; the lift channel changed but the
|
||||
# capture rate didn't.
|
||||
lane = get_lane("cognition")
|
||||
r_off = run_lane(lane, version="v1", split="public",
|
||||
config=RuntimeConfig(gloss_aware_cause=False))
|
||||
r_on = run_lane(lane, version="v1", split="public",
|
||||
config=RuntimeConfig(gloss_aware_cause=True))
|
||||
differing = [
|
||||
(off, on) for off, on in zip(r_off.case_details, r_on.case_details)
|
||||
if off["surface"] != on["surface"]
|
||||
]
|
||||
# Today the cognition v1/public set has 2 CAUSE cases with
|
||||
# glossed subjects (``light``, ``knowledge``).
|
||||
assert len(differing) >= 2
|
||||
for off, on in differing:
|
||||
assert off["case_id"].startswith("cause_")
|
||||
assert "exists as" in on["surface"]
|
||||
assert "exists as" not in off["surface"]
|
||||
Loading…
Reference in a new issue