feat(packs): en_core_relations_v2 — pronouns + role-fillers (Phase 2.4)

ADR-0065 P2.4.  Eight specialization lemmas, each a typed
specialization of an en_core_relations_v1 primitive:

  mother / father           is-a parent
  daughter / son            is-a child
  sister / brother          is-a sibling
  grandparent / grandchild  is-a ancestor / descendant (1-step)

Strict pack-internal taxonomy under kinship.*:

  mother      → kinship.parent.female
  father      → kinship.parent.male
  daughter    → kinship.child.female
  son         → kinship.child.male
  brother     → kinship.sibling.male
  sister      → kinship.sibling.female
  grandparent → kinship.ascendant.transitive_1step
  grandchild  → kinship.descendant.transitive_1step

Pack ratification:
  - SHA-256 checksum 7d0583f7e6a13ce72a5b0b191786cfc57af31583dc5111b24c3466e89ee70856
  - Orthogonal to en_core_relations_v1 + en_core_cognition_v1 (zero
    lemma collision in either direction)
  - Mounted by default in RuntimeConfig.input_packs + added to the
    cross-pack resolver's DEFAULT_RESOLVABLE_PACK_IDS

Companion corpus relations_chains_v2.jsonl seeds 7 v2-internal
reviewed chains so DEFINITION/CAUSE/VERIFICATION on every v2 lemma
grounds (not just DEFINITION via the pack path):

  cause_mother_precedes_daughter
  cause_father_precedes_son
  cause_grandparent_precedes_grandchild
  cause_daughter_follows_mother
  cause_son_follows_father
  verification_daughter_requires_mother
  verification_son_requires_father

Registered as a third TeachingCorpusSpec alongside cognition and
relations_v1.  Strict pack-internal: every chain's subject AND
object reside in en_core_relations_v2.  Cross-pack chain shapes
(e.g. v2 subject + v1 object) deferred per teaching_order.md §5.

Live verification:
  > What is mother?
    [pack] mother — pack-grounded (en_core_relations_v2):
    kinship.parent.female; kinship.parent; biology.maternal.
  > Why does mother exist?
    [teaching] mother — teaching-grounded (relations_chains_v2):
    mother precedes daughter (kinship.child.female).
  > Does daughter require mother?
    [teaching] daughter requires mother — verification-grounded.

10 pack-contract tests passed.  Curated lanes all green; cognition
eval byte-identical.
This commit is contained in:
Shay 2026-05-18 16:42:02 -07:00
parent 51aad0c2cd
commit a435411be5
7 changed files with 175 additions and 0 deletions

View file

@ -43,6 +43,7 @@ from pathlib import Path
DEFAULT_RESOLVABLE_PACK_IDS: tuple[str, ...] = (
"en_core_cognition_v1",
"en_core_relations_v1",
"en_core_relations_v2",
)
_PACK_ROOT = Path(__file__).resolve().parent.parent / "language_packs" / "data"

View file

@ -112,6 +112,11 @@ TEACHING_CORPORA: tuple[TeachingCorpusSpec, ...] = (
path=_TEACHING_ROOT / "relations_chains" / "relations_chains_v1.jsonl",
pack_id="en_core_relations_v1",
),
TeachingCorpusSpec(
corpus_id="relations_chains_v2",
path=_TEACHING_ROOT / "relations_chains_v2" / "relations_chains_v2.jsonl",
pack_id="en_core_relations_v2",
),
)

View file

@ -14,6 +14,7 @@ class RuntimeConfig:
"en_minimal_v1",
"en_core_cognition_v1",
"en_core_relations_v1",
"en_core_relations_v2",
"he_logos_micro_v1",
"grc_logos_micro_v1",
)

View file

@ -0,0 +1,8 @@
{"entry_id":"en-core-rel2-001","surface":"mother","lemma":"mother","language":"en","pos":"NOUN","semantic_domains":["kinship.parent.female","kinship.parent","biology.maternal"],"morphology_tags":["noun"],"provenance_ids":["seed:core_relations_v2"]}
{"entry_id":"en-core-rel2-002","surface":"father","lemma":"father","language":"en","pos":"NOUN","semantic_domains":["kinship.parent.male","kinship.parent","biology.paternal"],"morphology_tags":["noun"],"provenance_ids":["seed:core_relations_v2"]}
{"entry_id":"en-core-rel2-003","surface":"daughter","lemma":"daughter","language":"en","pos":"NOUN","semantic_domains":["kinship.child.female","kinship.child","biology.female_offspring"],"morphology_tags":["noun"],"provenance_ids":["seed:core_relations_v2"]}
{"entry_id":"en-core-rel2-004","surface":"son","lemma":"son","language":"en","pos":"NOUN","semantic_domains":["kinship.child.male","kinship.child","biology.male_offspring"],"morphology_tags":["noun"],"provenance_ids":["seed:core_relations_v2"]}
{"entry_id":"en-core-rel2-005","surface":"brother","lemma":"brother","language":"en","pos":"NOUN","semantic_domains":["kinship.sibling.male","kinship.sibling","social.peer"],"morphology_tags":["noun"],"provenance_ids":["seed:core_relations_v2"]}
{"entry_id":"en-core-rel2-006","surface":"sister","lemma":"sister","language":"en","pos":"NOUN","semantic_domains":["kinship.sibling.female","kinship.sibling","social.peer"],"morphology_tags":["noun"],"provenance_ids":["seed:core_relations_v2"]}
{"entry_id":"en-core-rel2-007","surface":"grandparent","lemma":"grandparent","language":"en","pos":"NOUN","semantic_domains":["kinship.ascendant.transitive_1step","kinship.elder","lineage.upward"],"morphology_tags":["noun"],"provenance_ids":["seed:core_relations_v2"]}
{"entry_id":"en-core-rel2-008","surface":"grandchild","lemma":"grandchild","language":"en","pos":"NOUN","semantic_domains":["kinship.descendant.transitive_1step","kinship.successor","lineage.downward"],"morphology_tags":["noun"],"provenance_ids":["seed:core_relations_v2"]}

View file

@ -0,0 +1,13 @@
{
"pack_id": "en_core_relations_v2",
"language": "en",
"role": "operational_base",
"script": "Latin",
"normalization_policy": "unitize_versor",
"source_manifest": "en_core_relations_v2.lexicon.jsonl",
"determinism_class": "D0",
"checksum": "7d0583f7e6a13ce72a5b0b191786cfc57af31583dc5111b24c3466e89ee70856",
"version": "1.0.0",
"gate_engaged": true,
"oov_policy": "tagged_fallback"
}

View file

@ -0,0 +1,7 @@
{"chain_id":"cause_mother_precedes_daughter","subject":"mother","intent":"cause","connective":"precedes","object":"daughter","domains_subject_k":2,"domains_object_k":1,"provenance":"adr-0065:reviewed:2026-05-18:relations_v2_seed"}
{"chain_id":"cause_father_precedes_son","subject":"father","intent":"cause","connective":"precedes","object":"son","domains_subject_k":2,"domains_object_k":1,"provenance":"adr-0065:reviewed:2026-05-18:relations_v2_seed"}
{"chain_id":"cause_grandparent_precedes_grandchild","subject":"grandparent","intent":"cause","connective":"precedes","object":"grandchild","domains_subject_k":2,"domains_object_k":1,"provenance":"adr-0065:reviewed:2026-05-18:relations_v2_seed"}
{"chain_id":"cause_daughter_follows_mother","subject":"daughter","intent":"cause","connective":"follows","object":"mother","domains_subject_k":2,"domains_object_k":1,"provenance":"adr-0065:reviewed:2026-05-18:relations_v2_seed"}
{"chain_id":"cause_son_follows_father","subject":"son","intent":"cause","connective":"follows","object":"father","domains_subject_k":2,"domains_object_k":1,"provenance":"adr-0065:reviewed:2026-05-18:relations_v2_seed"}
{"chain_id":"verification_daughter_requires_mother","subject":"daughter","intent":"verification","connective":"requires","object":"mother","domains_subject_k":2,"domains_object_k":1,"provenance":"adr-0065:reviewed:2026-05-18:relations_v2_seed"}
{"chain_id":"verification_son_requires_father","subject":"son","intent":"verification","connective":"requires","object":"father","domains_subject_k":2,"domains_object_k":1,"provenance":"adr-0065:reviewed:2026-05-18:relations_v2_seed"}

View file

@ -0,0 +1,140 @@
"""Phase 2.4 — ``en_core_relations_v2`` (pronouns + role-fillers) tests.
The v2 pack carries 8 specialization lemmas mother/father, son/daughter,
brother/sister, grandparent/grandchild each a typed specialization of
a v1 primitive (mother is-a parent, daughter is-a child, etc.).
The contract these tests pin:
- Checksum-verified load.
- All 8 lemmas present with the expected primary domain.
- No accidental cross-pack collision with v1 OR cognition.
- Pack IS in default ``RuntimeConfig.input_packs`` (mounted on
the live runtime by default paired with v2-internal reviewed
chains so DEFINITION + CAUSE + VERIFICATION all ground).
- The companion corpus ``relations_chains_v2.jsonl`` is registered.
"""
from __future__ import annotations
from language_packs.compiler import load_pack, load_pack_entries
PACK_ID = "en_core_relations_v2"
EXPECTED_LEMMAS: tuple[str, ...] = (
"mother",
"father",
"daughter",
"son",
"brother",
"sister",
"grandparent",
"grandchild",
)
EXPECTED_PRIMARY_DOMAINS: dict[str, str] = {
"mother": "kinship.parent.female",
"father": "kinship.parent.male",
"daughter": "kinship.child.female",
"son": "kinship.child.male",
"brother": "kinship.sibling.male",
"sister": "kinship.sibling.female",
"grandparent": "kinship.ascendant.transitive_1step",
"grandchild": "kinship.descendant.transitive_1step",
}
def test_pack_loads_with_matching_checksum() -> None:
manifest, manifold = load_pack(PACK_ID)
assert manifest.pack_id == PACK_ID
assert len(manifest.checksum) == 64
assert all(c in "0123456789abcdef" for c in manifest.checksum)
assert len(manifold) == len(EXPECTED_LEMMAS)
def test_all_expected_lemmas_present() -> None:
_, manifold = load_pack(PACK_ID)
surfaces = {manifold.get_word_at(i) for i in range(len(manifold))}
assert surfaces == set(EXPECTED_LEMMAS)
def test_each_lemma_carries_expected_primary_domain() -> None:
entries = load_pack_entries(PACK_ID)
by_lemma = {e.lemma: e for e in entries}
for lemma, expected_primary in EXPECTED_PRIMARY_DOMAINS.items():
assert lemma in by_lemma, f"missing lemma: {lemma}"
actual = by_lemma[lemma].semantic_domains
assert actual, f"{lemma!r} has empty semantic_domains"
assert actual[0] == expected_primary, (
f"{lemma!r}: primary domain drifted from {expected_primary!r} "
f"to {actual[0]!r}"
)
def test_every_lemma_has_multiple_semantic_domains() -> None:
entries = load_pack_entries(PACK_ID)
for entry in entries:
assert len(entry.semantic_domains) >= 2, (
f"{entry.lemma!r} has only {len(entry.semantic_domains)} domains"
)
def test_no_lemma_collision_with_v1_or_cognition() -> None:
_, v2_manifold = load_pack(PACK_ID)
_, v1_manifold = load_pack("en_core_relations_v1")
_, cog_manifold = load_pack("en_core_cognition_v1")
v2 = {v2_manifold.get_word_at(i) for i in range(len(v2_manifold))}
v1 = {v1_manifold.get_word_at(i) for i in range(len(v1_manifold))}
cog = {cog_manifold.get_word_at(i) for i in range(len(cog_manifold))}
assert not (v2 & v1), f"v2 collides with v1: {v2 & v1}"
assert not (v2 & cog), f"v2 collides with cognition: {v2 & cog}"
def test_pack_is_in_default_input_packs() -> None:
"""The v2 pack is mounted by default — paired with reviewed
v2-internal chains so DEFINITION/CAUSE/VERIFICATION on v2 lemmas
all ground without falling through to OOV."""
from core.config import RuntimeConfig
assert PACK_ID in RuntimeConfig().input_packs
def test_pack_is_in_resolver_defaults() -> None:
from chat.pack_resolver import DEFAULT_RESOLVABLE_PACK_IDS
assert PACK_ID in DEFAULT_RESOLVABLE_PACK_IDS
def test_v2_corpus_is_registered() -> None:
from chat.teaching_grounding import TEACHING_CORPORA
corpus_ids = {s.corpus_id for s in TEACHING_CORPORA}
assert "relations_chains_v2" in corpus_ids
def test_v2_pack_lemmas_ground_through_resolver() -> None:
"""Every v2 lemma resolves to v2 pack via the cross-pack resolver."""
from chat.pack_resolver import resolve_lemma
for lemma in EXPECTED_LEMMAS:
resolved = resolve_lemma(lemma)
assert resolved is not None, f"{lemma!r} did not resolve"
assert resolved[0] == PACK_ID, (
f"{lemma!r} resolved to {resolved[0]} instead of {PACK_ID}"
)
def test_v2_chains_emit_teaching_grounded_surfaces() -> None:
"""Reviewed v2-internal chains produce teaching-grounded surfaces."""
from chat.teaching_grounding import (
clear_teaching_caches,
teaching_grounded_surface,
)
from generate.intent import IntentTag
clear_teaching_caches()
s = teaching_grounded_surface("mother", IntentTag.CAUSE)
assert s is not None
assert "mother precedes daughter" in s
assert "relations_chains_v2" in s
s2 = teaching_grounded_surface("daughter", IntentTag.VERIFICATION)
assert s2 is not None
assert "daughter requires mother" in s2