From a435411be510152b6b0e3ad481e8e8401e8e0fbb Mon Sep 17 00:00:00 2001 From: Shay Date: Mon, 18 May 2026 16:42:02 -0700 Subject: [PATCH] =?UTF-8?q?feat(packs):=20en=5Fcore=5Frelations=5Fv2=20?= =?UTF-8?q?=E2=80=94=20pronouns=20+=20role-fillers=20(Phase=202.4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- chat/pack_resolver.py | 1 + chat/teaching_grounding.py | 5 + core/config.py | 1 + .../data/en_core_relations_v2/lexicon.jsonl | 8 + .../data/en_core_relations_v2/manifest.json | 13 ++ .../relations_chains_v2.jsonl | 7 + tests/test_en_core_relations_v2_pack.py | 140 ++++++++++++++++++ 7 files changed, 175 insertions(+) create mode 100644 language_packs/data/en_core_relations_v2/lexicon.jsonl create mode 100644 language_packs/data/en_core_relations_v2/manifest.json create mode 100644 teaching/relations_chains_v2/relations_chains_v2.jsonl create mode 100644 tests/test_en_core_relations_v2_pack.py diff --git a/chat/pack_resolver.py b/chat/pack_resolver.py index 9bf59233..a5e2e62c 100644 --- a/chat/pack_resolver.py +++ b/chat/pack_resolver.py @@ -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" diff --git a/chat/teaching_grounding.py b/chat/teaching_grounding.py index cffb4021..474982e1 100644 --- a/chat/teaching_grounding.py +++ b/chat/teaching_grounding.py @@ -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", + ), ) diff --git a/core/config.py b/core/config.py index aa0b4368..1c58f839 100644 --- a/core/config.py +++ b/core/config.py @@ -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", ) diff --git a/language_packs/data/en_core_relations_v2/lexicon.jsonl b/language_packs/data/en_core_relations_v2/lexicon.jsonl new file mode 100644 index 00000000..0307e3f8 --- /dev/null +++ b/language_packs/data/en_core_relations_v2/lexicon.jsonl @@ -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"]} diff --git a/language_packs/data/en_core_relations_v2/manifest.json b/language_packs/data/en_core_relations_v2/manifest.json new file mode 100644 index 00000000..652caf21 --- /dev/null +++ b/language_packs/data/en_core_relations_v2/manifest.json @@ -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" +} diff --git a/teaching/relations_chains_v2/relations_chains_v2.jsonl b/teaching/relations_chains_v2/relations_chains_v2.jsonl new file mode 100644 index 00000000..358dadf8 --- /dev/null +++ b/teaching/relations_chains_v2/relations_chains_v2.jsonl @@ -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"} diff --git a/tests/test_en_core_relations_v2_pack.py b/tests/test_en_core_relations_v2_pack.py new file mode 100644 index 00000000..5653fba4 --- /dev/null +++ b/tests/test_en_core_relations_v2_pack.py @@ -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