Workstream 1 second pack. Closes the ADJ POS gap — prior to this pack
zero adjectives existed in any mounted English content pack, so the
runtime could not emit grounded surfaces for predicative queries like
"What is true?" or "What is important?".
Pack composition (40 ADJ entries):
attitude.truth_value.* (8) true false valid invalid accurate
inaccurate factual sound
attitude.evaluative.* (6) good bad right better worse best
attitude.epistemic.* (10) certain uncertain possible impossible
likely unlikely probable clear obscure
evident
attitude.modal.* (4) necessary sufficient required optional
attitude.importance.* (6) important essential relevant central
primary useful
attitude.scope.* (6) general specific broad narrow universal
particular
Files:
language_packs/data/en_core_attitude_v1/
lexicon.jsonl — 40 entries, SHA-256 checksum-sealed
manifest.json — operational_base / D0 / checksum-verified
chat/pack_resolver.py
Appended to DEFAULT_RESOLVABLE_PACK_IDS after cognition + meta.
core/config.py
Added to RuntimeConfig.input_packs default mount.
tests/test_en_core_attitude_v1_pack.py
11 contract tests: checksum, POS=ADJ uniformity, primary-domain
namespace, no-collision regression gate against all 4 prior packs,
contiguous entry_ids, mounted-by-default, resolver-order invariant,
routing correctness, and cognition+meta resolution unchanged.
Authoring:
Three parallel subagents (1 per cluster) — truth/eval, epistemic/modal,
importance/scope. Strict exemplar + forbidden-lemma list against all
prior packs. Main pass assembled, validated, sealed.
Verification:
Full lane: 2159 passed, 2 skipped, 0 failed (+11 new tests over the
previous 2148 baseline).
Cognition eval byte-identical on both splits:
public 100 / 100 / 91.7 / 100
holdout 100 / 100 / 83.3 / 100
Live composer probes: every ADJ lemma emits a deterministic
pack-grounded surface from en_core_attitude_v1.
139 lines
5 KiB
Python
139 lines
5 KiB
Python
"""chat/pack_resolver.py — ADR-0063 cross-pack surface resolver.
|
|
|
|
The cold-start grounding composers in :mod:`chat.pack_grounding` were
|
|
hardcoded to a single lexicon pack (``en_core_cognition_v1``). That
|
|
asymmetry blocked ``en_core_relations_v1`` from being mounted on the
|
|
default runtime: mounting it would silently widen vault recall and
|
|
intent classification without a corresponding ratified surface
|
|
composer for kinship lemmas.
|
|
|
|
This module supplies the missing abstraction: a *deterministic*,
|
|
*first-match-wins* resolver that maps a lemma to ``(pack_id, semantic_domains)``
|
|
across an ordered tuple of mounted lexicon packs. Surface composers
|
|
consult the resolver instead of a single pack; the surface trust-boundary
|
|
tag follows the *resolving* pack id.
|
|
|
|
Design constraints (CLAUDE.md / Reconstruction-over-storage):
|
|
|
|
- The resolver loads each pack lexicon at most once per process via
|
|
:func:`functools.lru_cache`. Ratified packs are immutable, so caching
|
|
is sound.
|
|
- A pack that fails to load (missing file, unreadable JSON) contributes
|
|
an empty index — callers cannot distinguish "pack absent" from "lemma
|
|
absent in mounted packs". Both produce ``None``.
|
|
- First-match-wins on collision. The orthogonality test in
|
|
``tests/test_en_core_relations_v1_pack.py`` enforces zero overlap
|
|
today; this rule documents the deterministic resolution if a future
|
|
pack pair collides.
|
|
- No mutation of pack data is performed anywhere in this module. All
|
|
return types are tuples/frozensets/dicts of plain strings.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from functools import lru_cache
|
|
from pathlib import Path
|
|
|
|
# Default mounted lexicon-pack ids that ADR-0063 surface composers
|
|
# consult. Order matters: earlier packs win on lemma collision. This
|
|
# tuple is intentionally narrow — it lists only ratified ``en_core_*``
|
|
# *content* packs. Identity/safety/ethics packs are propositional
|
|
# overlays and never carry vocabulary; they are deliberately excluded.
|
|
DEFAULT_RESOLVABLE_PACK_IDS: tuple[str, ...] = (
|
|
"en_core_cognition_v1",
|
|
"en_core_meta_v1",
|
|
"en_core_attitude_v1",
|
|
"en_core_relations_v1",
|
|
"en_core_relations_v2",
|
|
)
|
|
|
|
_PACK_ROOT = Path(__file__).resolve().parent.parent / "language_packs" / "data"
|
|
|
|
|
|
@lru_cache(maxsize=16)
|
|
def _pack_lexicon_for(pack_id: str) -> dict[str, tuple[str, ...]]:
|
|
"""Return ``{lemma_lower: semantic_domains}`` for *pack_id*, cached.
|
|
|
|
Returns an empty dict if the pack's lexicon file is missing,
|
|
unreadable, or contains no entries with ``semantic_domains``.
|
|
Ratified packs are immutable so the cache survives the process
|
|
lifetime.
|
|
"""
|
|
lexicon_path = _PACK_ROOT / pack_id / "lexicon.jsonl"
|
|
if not lexicon_path.exists():
|
|
return {}
|
|
out: dict[str, tuple[str, ...]] = {}
|
|
for line in lexicon_path.read_text(encoding="utf-8").splitlines():
|
|
line = line.strip()
|
|
if not line:
|
|
continue
|
|
try:
|
|
entry = json.loads(line)
|
|
except json.JSONDecodeError:
|
|
continue
|
|
if not isinstance(entry, dict):
|
|
continue
|
|
lemma = entry.get("lemma") or entry.get("surface")
|
|
if not lemma or not isinstance(lemma, str):
|
|
continue
|
|
domains = entry.get("semantic_domains", ())
|
|
if not isinstance(domains, (list, tuple)) or not domains:
|
|
continue
|
|
out[lemma.lower()] = tuple(str(d) for d in domains)
|
|
return out
|
|
|
|
|
|
def resolve_lemma(
|
|
lemma: str,
|
|
pack_ids: tuple[str, ...] = DEFAULT_RESOLVABLE_PACK_IDS,
|
|
) -> tuple[str, tuple[str, ...]] | None:
|
|
"""Return ``(pack_id, semantic_domains)`` for the first pack in
|
|
*pack_ids* whose lexicon contains *lemma*, else ``None``.
|
|
|
|
First-match-wins on collision. ``pack_ids`` defaults to
|
|
:data:`DEFAULT_RESOLVABLE_PACK_IDS`.
|
|
"""
|
|
if not lemma or not isinstance(lemma, str):
|
|
return None
|
|
key = lemma.strip().lower()
|
|
if not key:
|
|
return None
|
|
for pack_id in pack_ids:
|
|
index = _pack_lexicon_for(pack_id)
|
|
domains = index.get(key)
|
|
if domains:
|
|
return (pack_id, domains)
|
|
return None
|
|
|
|
|
|
def is_resolvable(
|
|
lemma: str,
|
|
pack_ids: tuple[str, ...] = DEFAULT_RESOLVABLE_PACK_IDS,
|
|
) -> bool:
|
|
"""Return True iff *lemma* resolves in any of *pack_ids*."""
|
|
return resolve_lemma(lemma, pack_ids) is not None
|
|
|
|
|
|
def mounted_lemmas(
|
|
pack_ids: tuple[str, ...] = DEFAULT_RESOLVABLE_PACK_IDS,
|
|
) -> frozenset[str]:
|
|
"""Return the frozen union of lemmas across *pack_ids*.
|
|
|
|
Used by topic extractors that iterate over user-text tokens and
|
|
need an O(1) "is this token any mounted lemma?" check.
|
|
"""
|
|
out: set[str] = set()
|
|
for pack_id in pack_ids:
|
|
out.update(_pack_lexicon_for(pack_id).keys())
|
|
return frozenset(out)
|
|
|
|
|
|
def clear_resolver_cache() -> None:
|
|
"""Drop the lru_cache for :func:`_pack_lexicon_for`.
|
|
|
|
Test-only escape hatch: enables fixture-based pack mutation
|
|
scenarios. Production code never calls this — ratified packs are
|
|
immutable.
|
|
"""
|
|
_pack_lexicon_for.cache_clear()
|