core/tests/test_pack_resolver.py
Shay 9f83b27a7c feat(adr-0063): cross-pack surface resolver — kinship lemmas ground on live path
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.
2026-05-18 15:00:58 -07:00

167 lines
6 KiB
Python

"""ADR-0063 — cross-pack surface resolver tests.
The contract these tests pin:
- :func:`chat.pack_resolver.resolve_lemma` returns the first
``(pack_id, semantic_domains)`` whose lexicon contains the lemma.
- Cognition lemmas resolve to ``en_core_cognition_v1``; kinship
lemmas resolve to ``en_core_relations_v1``; absent lemmas return
``None``.
- First-match-wins on order.
- The lru_cache survives repeat calls without re-reading disk.
- :func:`mounted_lemmas` is the union of lemma keys across the
mounted packs in deterministic order.
"""
from __future__ import annotations
import pytest
from chat.pack_resolver import (
DEFAULT_RESOLVABLE_PACK_IDS,
_pack_lexicon_for,
clear_resolver_cache,
is_resolvable,
mounted_lemmas,
resolve_lemma,
)
# ---------------------------------------------------------------------------
# resolve_lemma — first-match-wins across mounted packs
# ---------------------------------------------------------------------------
@pytest.mark.parametrize(
"lemma",
["light", "knowledge", "meaning", "memory", "truth", "thought", "concept"],
)
def test_cognition_lemma_resolves_to_cognition_pack(lemma: str) -> None:
resolved = resolve_lemma(lemma)
assert resolved is not None, f"{lemma!r} did not resolve"
pack_id, domains = resolved
assert pack_id == "en_core_cognition_v1"
assert isinstance(domains, tuple)
assert domains, "resolved pack must surface non-empty semantic_domains"
@pytest.mark.parametrize(
"lemma",
["parent", "child", "sibling", "family", "ancestor",
"descendant", "spouse", "offspring"],
)
def test_kinship_lemma_resolves_to_relations_pack(lemma: str) -> None:
resolved = resolve_lemma(lemma)
assert resolved is not None, f"{lemma!r} did not resolve"
pack_id, domains = resolved
assert pack_id == "en_core_relations_v1"
assert isinstance(domains, tuple)
assert domains
def test_unknown_lemma_returns_none() -> None:
assert resolve_lemma("nonexistent_lemma_xyz") is None
@pytest.mark.parametrize("bad", ["", " ", None])
def test_empty_or_invalid_lemma_returns_none(bad) -> None:
assert resolve_lemma(bad) is None # type: ignore[arg-type]
def test_resolver_normalizes_case_and_whitespace() -> None:
a = resolve_lemma("Knowledge")
b = resolve_lemma(" knowledge ")
c = resolve_lemma("knowledge")
assert a == b == c
assert a is not None and a[0] == "en_core_cognition_v1"
def test_resolver_is_first_match_wins() -> None:
"""When the same lemma appears in two mounted packs, the earlier
pack in *pack_ids* wins. Today no lemma collision exists between
cognition and relations (orthogonality test enforces this); this
test reverses the order to verify the resolution rule itself."""
reversed_order = (
"en_core_relations_v1",
"en_core_cognition_v1",
)
# ``parent`` exists only in relations; ``knowledge`` only in
# cognition — order swap should not change which pack carries
# them, only their resolution order.
parent = resolve_lemma("parent", pack_ids=reversed_order)
knowledge = resolve_lemma("knowledge", pack_ids=reversed_order)
assert parent is not None and parent[0] == "en_core_relations_v1"
assert knowledge is not None and knowledge[0] == "en_core_cognition_v1"
def test_pack_ids_default_contains_both_packs() -> None:
assert "en_core_cognition_v1" in DEFAULT_RESOLVABLE_PACK_IDS
assert "en_core_relations_v1" in DEFAULT_RESOLVABLE_PACK_IDS
# Cognition first — first-match-wins favours cognition on any
# future cross-pack lemma collision.
assert DEFAULT_RESOLVABLE_PACK_IDS.index("en_core_cognition_v1") < \
DEFAULT_RESOLVABLE_PACK_IDS.index("en_core_relations_v1")
# ---------------------------------------------------------------------------
# is_resolvable — boolean shortcut
# ---------------------------------------------------------------------------
def test_is_resolvable_round_trips() -> None:
assert is_resolvable("light") is True
assert is_resolvable("parent") is True
assert is_resolvable("nonexistent_lemma_xyz") is False
assert is_resolvable("") is False
# ---------------------------------------------------------------------------
# mounted_lemmas — union view used by topic extractors
# ---------------------------------------------------------------------------
def test_mounted_lemmas_unions_both_packs() -> None:
union = mounted_lemmas()
assert "knowledge" in union
assert "parent" in union
assert "nonexistent_lemma_xyz" not in union
# Frozen so callers cannot mutate the cached union accidentally.
assert isinstance(union, frozenset)
def test_mounted_lemmas_respects_explicit_pack_ids() -> None:
cognition_only = mounted_lemmas(pack_ids=("en_core_cognition_v1",))
assert "knowledge" in cognition_only
assert "parent" not in cognition_only, (
"cognition-only view leaked a kinship lemma — pack residency "
"boundary broken"
)
# ---------------------------------------------------------------------------
# Caching contract
# ---------------------------------------------------------------------------
def test_pack_lexicon_is_cached() -> None:
"""Two calls for the same pack must return the same dict identity
(lru_cache returns the cached object, not a copy)."""
a = _pack_lexicon_for("en_core_cognition_v1")
b = _pack_lexicon_for("en_core_cognition_v1")
assert a is b
def test_clear_resolver_cache_is_safe() -> None:
"""The test-only escape hatch must not crash when called twice."""
clear_resolver_cache()
clear_resolver_cache()
# And the resolver still works afterwards.
assert resolve_lemma("knowledge") is not None
def test_missing_pack_returns_empty_index() -> None:
"""A pack id with no on-disk lexicon must yield an empty dict and
callers see ``None`` from :func:`resolve_lemma` — no exception."""
empty = _pack_lexicon_for("nonexistent_pack_id_zzz_v0")
assert empty == {}
assert resolve_lemma("knowledge", pack_ids=("nonexistent_pack_id_zzz_v0",)) is None