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:
parent
51aad0c2cd
commit
a435411be5
7 changed files with 175 additions and 0 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
)
|
||||
|
|
|
|||
8
language_packs/data/en_core_relations_v2/lexicon.jsonl
Normal file
8
language_packs/data/en_core_relations_v2/lexicon.jsonl
Normal 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"]}
|
||||
13
language_packs/data/en_core_relations_v2/manifest.json
Normal file
13
language_packs/data/en_core_relations_v2/manifest.json
Normal 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"
|
||||
}
|
||||
7
teaching/relations_chains_v2/relations_chains_v2.jsonl
Normal file
7
teaching/relations_chains_v2/relations_chains_v2.jsonl
Normal 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"}
|
||||
140
tests/test_en_core_relations_v2_pack.py
Normal file
140
tests/test_en_core_relations_v2_pack.py
Normal 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
|
||||
Loading…
Reference in a new issue