ADR-0063 closes the ADR-0048/0050/0053/0061 hardcoded-cognition-pack asymmetry. New chat/pack_resolver.py provides resolve_lemma(lemma, pack_ids) → (resolving_pack_id, semantic_domains) across an ordered tuple of mounted lexicon packs (first-match-wins, lru_cache per-pack). Surface composers in chat/pack_grounding.py now consult the resolver instead of a hardcoded en_core_cognition_v1. en_core_relations_v1 joins RuntimeConfig.input_packs defaults; kinship lemmas now ground on the live path: > What is a parent? parent — pack-grounded (en_core_relations_v1): kinship.ascendant.direct; kinship.parent; biology.progenitor. No session evidence yet. Cross-pack comparison (knowledge × parent) renders composite tag (en_core_cognition_v1 × en_core_relations_v1). Cognition lane remains byte-identical: cognition is resolved first and the surface format for cognition lemmas is unchanged. Cognition eval (byte-identical to pre-ADR baseline): public → intent 100% / surface 100% / term 91.7% / closure 100% holdout → intent 100% / surface 100% / term 83.3% / closure 100% Curated lanes green: smoke 67 / cognition 121 / teaching 17 / packs 6 / runtime 19 / algebra 132. New tests: test_pack_resolver.py (28) + test_cross_pack_grounding.py (17). test_en_core_relations_v1_pack.py: default-input-packs guard inverted. test_pack_grounding.py: two stale ADR-0048 tests rewritten (premises invalidated by ADR-0052/0061; now use fully-out-of-pack prompts). chat/teaching_grounding.py UNCHANGED — cognition_chains_v1 corpus stays cognition-only. Cross-pack teaching corpora are the natural ADR-0064.
265 lines
9.9 KiB
Markdown
265 lines
9.9 KiB
Markdown
# ADR-0063 — Cross-pack surface resolver
|
||
|
||
**Status:** Accepted
|
||
**Date:** 2026-05-18
|
||
**Author:** Shay
|
||
**Supersedes:** none (extends ADR-0048 / ADR-0050 / ADR-0052 / ADR-0053 / ADR-0060 / ADR-0061 / ADR-0062)
|
||
**Builds on:** the kinship starter pack `en_core_relations_v1` (commit `f0c57eb`).
|
||
|
||
---
|
||
|
||
## Context
|
||
|
||
ADR-0048 introduced the first pack-grounded surface composer — a
|
||
deterministic cold-start surface for `DEFINITION` / `RECALL` intents
|
||
whose subject lemma is present in the ratified cognition pack. ADR-0050
|
||
extended that to `COMPARISON`. ADR-0053 + ADR-0060 added a `CORRECTION`
|
||
acknowledgement composer (cold-start branch). ADR-0061 added a
|
||
`PROCEDURE` composer. All four composers shared a hardcoded reference
|
||
to a single lexicon pack:
|
||
|
||
```python
|
||
# chat/pack_grounding.py — pre-ADR-0063
|
||
PACK_ID: str = "en_core_cognition_v1"
|
||
```
|
||
|
||
That asymmetry was the rate-limiter on domain expansion. Mounting the
|
||
`en_core_relations_v1` kinship starter pack (already ratified —
|
||
checksum `1a013b46…`) would silently widen the runtime's mounted
|
||
manifold (vault recall + intent classification) without a corresponding
|
||
surface composer for kinship lemmas. The cold-start
|
||
`What is a parent?` prompt would fall through to the universal
|
||
"insufficient grounding" disclosure even though `parent` is in a
|
||
ratified, immutable pack on disk.
|
||
|
||
The previous ADR's memory recorded this explicitly:
|
||
|
||
> Engagement status: OPT-IN ONLY. Pack is NOT in
|
||
> `RuntimeConfig.input_packs` defaults… until cross-pack
|
||
> teaching-grounded composition exists, mounting silently exposes
|
||
> lemmas to vault recall and intent classification without a
|
||
> corresponding ratified surface composer.
|
||
|
||
ADR-0063 is the **one-PR architectural unlock** that closes that gap:
|
||
the surface composers consult an abstract *resolver* across all
|
||
mounted lexicon packs instead of one hardcoded pack, and the relations
|
||
pack joins the default mount.
|
||
|
||
---
|
||
|
||
## Decision
|
||
|
||
### 1. New abstraction: `chat/pack_resolver.py`
|
||
|
||
A small (~140 lines) pure module that maps a lemma to a
|
||
`(resolving_pack_id, semantic_domains)` pair across an ordered tuple of
|
||
mounted lexicon packs:
|
||
|
||
```python
|
||
DEFAULT_RESOLVABLE_PACK_IDS: tuple[str, ...] = (
|
||
"en_core_cognition_v1",
|
||
"en_core_relations_v1",
|
||
)
|
||
|
||
def resolve_lemma(
|
||
lemma: str,
|
||
pack_ids: tuple[str, ...] = DEFAULT_RESOLVABLE_PACK_IDS,
|
||
) -> tuple[str, tuple[str, ...]] | None: ...
|
||
|
||
def is_resolvable(lemma, pack_ids=...) -> bool: ...
|
||
def mounted_lemmas(pack_ids=...) -> frozenset[str]: ...
|
||
```
|
||
|
||
Properties:
|
||
|
||
- **First-match-wins.** Order matters: cognition is listed first so
|
||
that any future cross-pack collision resolves cognition-side,
|
||
preserving cognition-lane byte-identity.
|
||
- **lru_cache per-pack.** Each pack's `lexicon.jsonl` is loaded at most
|
||
once per process (ratified packs are immutable).
|
||
- **No mutation.** Returns tuples, frozensets, plain strings.
|
||
- **Pure.** No I/O outside the cached read; no network; no LLM; no
|
||
approximation.
|
||
|
||
### 2. Surface composers consult the resolver
|
||
|
||
`chat/pack_grounding.py`:
|
||
|
||
- `pack_grounded_surface(lemma)` — surface tag follows the resolving
|
||
pack id. Cognition lemmas keep emitting
|
||
`pack-grounded (en_core_cognition_v1)` byte-identically; kinship
|
||
lemmas emit `pack-grounded (en_core_relations_v1)`.
|
||
- `pack_grounded_comparison_surface(a, b)` — each side resolved
|
||
independently. When both resolve to the same pack, the tag stays
|
||
single (`pack-grounded (en_core_cognition_v1)`). When the two
|
||
resolve to different packs, the tag becomes composite
|
||
(`pack-grounded (en_core_cognition_v1 × en_core_relations_v1)`).
|
||
- `pack_grounded_correction_surface(text)` — anchor pack stays
|
||
cognition (`correction` is a cognition lemma); topic domains come
|
||
from whichever pack resolves the topic lemma. Topic extraction
|
||
scans `mounted_lemmas()` instead of the cognition-only index.
|
||
- `pack_grounded_procedure_surface(subject_text)` — topic extraction
|
||
scans `mounted_lemmas()`; resolving pack id drives the surface tag.
|
||
|
||
`chat/teaching_grounding.py` is **unchanged** in this ADR. The
|
||
reviewed teaching corpus (`cognition_chains_v1.jsonl`) still
|
||
references cognition-pack lemmas only. Cross-pack teaching chains
|
||
(e.g. a `relations_chains_v1.jsonl`) are the natural next ADR.
|
||
|
||
### 3. Backward compatibility
|
||
|
||
- `chat/pack_grounding.py:PACK_ID = "en_core_cognition_v1"` and
|
||
`_pack_index()` (cognition-only) are retained for back-compat with
|
||
consumers in `teaching/contemplation.py`, `teaching/discovery.py`,
|
||
`chat/teaching_grounding.py`, and various tests whose semantics are
|
||
scoped to the cognition pack. They are NOT removed — only the
|
||
composers' internal lookups switch to the resolver.
|
||
- `is_pack_lemma(lemma)` keeps its cognition-only semantics; cross-pack
|
||
residency is `chat.pack_resolver.is_resolvable`.
|
||
|
||
### 4. Default mount adds the relations pack
|
||
|
||
`core/config.py`:
|
||
|
||
```python
|
||
input_packs: tuple[str, ...] = (
|
||
"en_minimal_v1",
|
||
"en_core_cognition_v1",
|
||
"en_core_relations_v1", # added in ADR-0063
|
||
"he_logos_micro_v1",
|
||
"grc_logos_micro_v1",
|
||
)
|
||
```
|
||
|
||
Mounting is now safe because the composers ground kinship lemmas
|
||
deterministically.
|
||
|
||
### 5. The relations-pack default-input test inverts
|
||
|
||
`tests/test_en_core_relations_v1_pack.py`:
|
||
- Before: `test_pack_is_not_in_default_input_packs`
|
||
- After: `test_pack_is_in_default_input_packs`
|
||
|
||
The earlier test was an explicit *guard against premature mounting*.
|
||
It guarded the asymmetry that ADR-0063 closes; its inverse is now the
|
||
correct invariant.
|
||
|
||
---
|
||
|
||
## Consequences
|
||
|
||
### Capability unlocked
|
||
|
||
| Surface composer | Cognition lemmas | Kinship lemmas |
|
||
|---|---|---|
|
||
| `pack_grounded_surface` (DEFINITION / RECALL) | byte-identical | **now grounds** |
|
||
| `pack_grounded_comparison_surface` (COMPARISON) | byte-identical when both cognition | **now grounds** kinship × kinship; composite tag for cross-pack |
|
||
| `pack_grounded_correction_surface` (CORRECTION) | byte-identical when topic ∈ cognition | **now anchors topic** on kinship lemmas |
|
||
| `pack_grounded_procedure_surface` (PROCEDURE) | byte-identical when topic ∈ cognition | **now grounds** kinship topics |
|
||
|
||
### Cognition lane: byte-identical
|
||
|
||
The cognition lane's prompts use only cognition lemmas. The resolver
|
||
picks cognition first, the surface tag stays `en_core_cognition_v1`,
|
||
the surface bytes are identical:
|
||
|
||
```
|
||
core eval cognition → 100/100/91.7/100 (public)
|
||
core eval cognition --split holdout → 100/100/83.3/100 (holdout)
|
||
```
|
||
|
||
Both byte-identical to the pre-ADR baseline.
|
||
|
||
### Live-path grounding on kinship lemmas
|
||
|
||
```text
|
||
$ core chat
|
||
> What is a parent?
|
||
parent — pack-grounded (en_core_relations_v1): kinship.ascendant.direct;
|
||
kinship.parent; biology.progenitor. No session evidence yet.
|
||
grounding_source = pack
|
||
```
|
||
|
||
### Future ADRs unlocked
|
||
|
||
1. **ADR-0064 — Cross-pack teaching chains.** Add
|
||
`relations_chains_v1.jsonl` (reviewed kinship chains) and extend
|
||
`chat/teaching_grounding.py` to consult both corpora. The natural
|
||
next ADR.
|
||
2. **Pronoun + role-filler v2.** `mother`, `father`, `son`, etc. as
|
||
specializations of the v1 kinship primitives — extends
|
||
`en_core_relations_v1` itself.
|
||
3. **Cross-domain triples.** `family causes belonging`,
|
||
`parent grounds identity` — opens after cognition + relations both
|
||
have ratified internal DAGs of reviewed chains.
|
||
|
||
---
|
||
|
||
## Trust boundaries
|
||
|
||
- The resolver does NOT execute pack metadata, validators, or scripts.
|
||
It reads `lexicon.jsonl` only.
|
||
- A pack that fails to load produces an empty index — callers see
|
||
`None`. No exception leaks into the runtime.
|
||
- No mutation site introduced. The resolver is read-only over
|
||
immutable, ratified pack data.
|
||
- Tag tokens emitted in surfaces are always one of:
|
||
`en_core_cognition_v1`, `en_core_relations_v1`, or the composite
|
||
`{a} × {b}` form derived from mounted-pack ids — never from user
|
||
input.
|
||
- `pack_resolver.clear_resolver_cache()` is a test-only escape hatch;
|
||
production paths never call it.
|
||
|
||
---
|
||
|
||
## Verification
|
||
|
||
```
|
||
tests/test_pack_resolver.py 28 passed
|
||
tests/test_cross_pack_grounding.py 17 passed
|
||
tests/test_pack_grounding.py 13 passed
|
||
tests/test_pack_grounded_comparison.py passed
|
||
tests/test_pack_grounded_correction.py passed
|
||
tests/test_correction_topic_lemma.py passed
|
||
tests/test_procedure_surface.py passed
|
||
tests/test_teaching_grounding.py passed
|
||
tests/test_composed_surface.py 11 passed
|
||
tests/test_en_core_relations_v1_pack.py 6 passed (inverted)
|
||
|
||
Lanes:
|
||
core test --suite smoke 67 passed
|
||
core test --suite cognition 121 passed
|
||
core test --suite teaching 17 passed
|
||
core test --suite packs 6 passed
|
||
core test --suite runtime 19 passed
|
||
core test --suite algebra 132 passed
|
||
core test --suite full <pending — see PR>
|
||
|
||
Cognition eval (byte-identical to pre-ADR baseline):
|
||
public split → intent 100% / surface 100% / term 91.7% / closure 100%
|
||
holdout split → intent 100% / surface 100% / term 83.3% / closure 100%
|
||
```
|
||
|
||
The non-negotiable field invariant `versor_condition(F) < 1e-6` is
|
||
unaffected — this ADR is a routing/dispatch change over immutable
|
||
pack data; no algebra, no field operators, no normalization sites
|
||
were touched.
|
||
|
||
---
|
||
|
||
## Files changed
|
||
|
||
```
|
||
chat/pack_resolver.py NEW (~140 lines)
|
||
chat/pack_grounding.py composers use resolver
|
||
core/config.py relations pack joins defaults
|
||
tests/test_pack_resolver.py NEW (28 tests)
|
||
tests/test_cross_pack_grounding.py NEW (17 tests)
|
||
tests/test_pack_grounding.py 2 stale tests rewritten
|
||
tests/test_en_core_relations_v1_pack.py default-input test inverted
|
||
docs/decisions/ADR-0063-cross-pack-surface-resolver.md NEW (this file)
|
||
docs/decisions/README.md ADR-0063 index entry
|
||
```
|
||
|
||
Lines of net change: small. The architectural unlock is in
|
||
`chat/pack_resolver.py`; the rest of the diff is wiring + tests.
|