From 6e072f95be822ee0ac1410fb0f05ed55fdc33030 Mon Sep 17 00:00:00 2001 From: Shay Date: Sat, 23 May 2026 22:47:53 -0700 Subject: [PATCH] content(en_core_relations_v1): +14 kinship/social lemmas + +14 chains, cognition eval byte-identical, ratify-idempotent (#211) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * content(packs): update relations checksum * revert transient relations manifest checksum * content(packs): extend relations lexicon additively * content(teaching): extend relations chains additively * content(packs): ratify relations manifest checksum * test(packs): accept additive relations lemma extension * test(packs): add relations v1 extension regressions * fix(tests): align relations extension lemma set * content(packs): add relations mastery report * content(packs): drop unused .mastery_report.json sidecar Language packs do not consume mastery reports — the pattern is from identity packs (packs/identity/) and has no consumer in language_packs/ loader.py or compiler.py. The added sidecar's self-seal hash also did not validate against sha256(json.dumps(body, sort_keys=True, separators=(',', ':'))). Drop the file. The actual ratification surface for this pack is the manifest.json lexicon_checksum, which still matches lexicon.jsonl bytes (verified). --- .../data/en_core_relations_v1/lexicon.jsonl | 14 +++ .../data/en_core_relations_v1/manifest.json | 2 +- .../relations_chains_v1.jsonl | 14 +++ tests/test_en_core_relations_v1_pack.py | 49 ++++++-- tests/test_relations_pack_v1_extensions.py | 112 ++++++++++++++++++ 5 files changed, 181 insertions(+), 10 deletions(-) create mode 100644 tests/test_relations_pack_v1_extensions.py diff --git a/language_packs/data/en_core_relations_v1/lexicon.jsonl b/language_packs/data/en_core_relations_v1/lexicon.jsonl index 50b06c8b..2745287f 100644 --- a/language_packs/data/en_core_relations_v1/lexicon.jsonl +++ b/language_packs/data/en_core_relations_v1/lexicon.jsonl @@ -6,3 +6,17 @@ {"entry_id":"en-core-rel-006","surface":"descendant","lemma":"descendant","language":"en","pos":"NOUN","semantic_domains":["kinship.descendant.transitive","lineage.downward","kinship.successor"],"morphology_tags":["noun"],"provenance_ids":["seed:core_relations_v1"]} {"entry_id":"en-core-rel-007","surface":"spouse","lemma":"spouse","language":"en","pos":"NOUN","semantic_domains":["kinship.partner","kinship.lateral.affinal","social.marriage"],"morphology_tags":["noun"],"provenance_ids":["seed:core_relations_v1"]} {"entry_id":"en-core-rel-008","surface":"offspring","lemma":"offspring","language":"en","pos":"NOUN","semantic_domains":["kinship.descendant.direct","biology.progeny","kinship.child"],"morphology_tags":["noun"],"provenance_ids":["seed:core_relations_v1"]} +{"entry_id":"en-core-rel-009","surface":"advisor","lemma":"advisor","language":"en","pos":"NOUN","semantic_domains":["professional.guidance","social.counsel","relation.developmental"],"morphology_tags":["noun"],"provenance_ids":["relations_extension_v1:reviewed:2026-05-23"]} +{"entry_id":"en-core-rel-010","surface":"apprentice","lemma":"apprentice","language":"en","pos":"NOUN","semantic_domains":["professional.learner","social.student","relation.developmental"],"morphology_tags":["noun"],"provenance_ids":["relations_extension_v1:reviewed:2026-05-23"]} +{"entry_id":"en-core-rel-011","surface":"caregiver","lemma":"caregiver","language":"en","pos":"NOUN","semantic_domains":["social.care.role","kinship.support","relation.protective"],"morphology_tags":["noun"],"provenance_ids":["relations_extension_v1:reviewed:2026-05-23"]} +{"entry_id":"en-core-rel-012","surface":"colleague","lemma":"colleague","language":"en","pos":"NOUN","semantic_domains":["professional.peer","social.workplace","relation.cooperative"],"morphology_tags":["noun"],"provenance_ids":["relations_extension_v1:reviewed:2026-05-23"]} +{"entry_id":"en-core-rel-013","surface":"cousin","lemma":"cousin","language":"en","pos":"NOUN","semantic_domains":["kinship.lateral.extended","family.role","social.peer"],"morphology_tags":["noun"],"provenance_ids":["relations_extension_v1:reviewed:2026-05-23"]} +{"entry_id":"en-core-rel-014","surface":"elder","lemma":"elder","language":"en","pos":"NOUN","semantic_domains":["kinship.ascendant.social","social.seniority","relation.guidance"],"morphology_tags":["noun"],"provenance_ids":["relations_extension_v1:reviewed:2026-05-23"]} +{"entry_id":"en-core-rel-015","surface":"friend","lemma":"friend","language":"en","pos":"NOUN","semantic_domains":["social.bond.peer","social.trust","relation.voluntary"],"morphology_tags":["noun"],"provenance_ids":["relations_extension_v1:reviewed:2026-05-23"]} +{"entry_id":"en-core-rel-016","surface":"guardian","lemma":"guardian","language":"en","pos":"NOUN","semantic_domains":["social.protection.role","kinship.care","relation.responsible"],"morphology_tags":["noun"],"provenance_ids":["relations_extension_v1:reviewed:2026-05-23"]} +{"entry_id":"en-core-rel-017","surface":"manager","lemma":"manager","language":"en","pos":"NOUN","semantic_domains":["professional.authority","social.organization","relation.supervisory"],"morphology_tags":["noun"],"provenance_ids":["relations_extension_v1:reviewed:2026-05-23"]} +{"entry_id":"en-core-rel-018","surface":"mentor","lemma":"mentor","language":"en","pos":"NOUN","semantic_domains":["professional.guidance","social.teacher","relation.developmental"],"morphology_tags":["noun"],"provenance_ids":["relations_extension_v1:reviewed:2026-05-23"]} +{"entry_id":"en-core-rel-019","surface":"neighbor","lemma":"neighbor","language":"en","pos":"NOUN","semantic_domains":["social.proximity.local","social.community","relation.adjacent"],"morphology_tags":["noun"],"provenance_ids":["relations_extension_v1:reviewed:2026-05-23"]} +{"entry_id":"en-core-rel-020","surface":"relative","lemma":"relative","language":"en","pos":"NOUN","semantic_domains":["kinship.member.extended","family.role","relation.kin"],"morphology_tags":["noun"],"provenance_ids":["relations_extension_v1:reviewed:2026-05-23"]} +{"entry_id":"en-core-rel-021","surface":"supervisor","lemma":"supervisor","language":"en","pos":"NOUN","semantic_domains":["professional.authority","social.organization","relation.supervisory"],"morphology_tags":["noun"],"provenance_ids":["relations_extension_v1:reviewed:2026-05-23"]} +{"entry_id":"en-core-rel-022","surface":"teammate","lemma":"teammate","language":"en","pos":"NOUN","semantic_domains":["professional.team.peer","social.cooperation","relation.shared_goal"],"morphology_tags":["noun"],"provenance_ids":["relations_extension_v1:reviewed:2026-05-23"]} diff --git a/language_packs/data/en_core_relations_v1/manifest.json b/language_packs/data/en_core_relations_v1/manifest.json index 1bfcd39b..7a324199 100644 --- a/language_packs/data/en_core_relations_v1/manifest.json +++ b/language_packs/data/en_core_relations_v1/manifest.json @@ -6,7 +6,7 @@ "normalization_policy": "unitize_versor", "source_manifest": "en_core_relations_v1.lexicon.jsonl", "determinism_class": "D0", - "checksum": "1a013b464f6a0fad4ef96f9efaf1406c44729643e422c1f76a516099476e61d3", + "checksum": "57859a47bab8d62fc4b8b2089eca2da707c4943ca8ac70af578b5b76d5751109", "version": "1.0.0", "gate_engaged": true, "oov_policy": "tagged_fallback", diff --git a/teaching/relations_chains/relations_chains_v1.jsonl b/teaching/relations_chains/relations_chains_v1.jsonl index ab2babfd..2daef954 100644 --- a/teaching/relations_chains/relations_chains_v1.jsonl +++ b/teaching/relations_chains/relations_chains_v1.jsonl @@ -5,3 +5,17 @@ {"chain_id":"cause_family_grounds_parent","subject":"family","intent":"cause","connective":"grounds","object":"parent","domains_subject_k":2,"domains_object_k":1,"provenance":"adr-0064:reviewed:2026-05-18:relations_seed_v1"} {"chain_id":"verification_child_requires_parent","subject":"child","intent":"verification","connective":"requires","object":"parent","domains_subject_k":2,"domains_object_k":1,"provenance":"adr-0064:reviewed:2026-05-18:relations_seed_v1"} {"chain_id":"verification_descendant_requires_ancestor","subject":"descendant","intent":"verification","connective":"requires","object":"ancestor","domains_subject_k":2,"domains_object_k":1,"provenance":"adr-0064:reviewed:2026-05-18:relations_seed_v1"} +{"chain_id":"cause_advisor_precedes_apprentice","subject":"advisor","intent":"cause","connective":"precedes","object":"apprentice","domains_subject_k":2,"domains_object_k":1,"provenance":"relations_extension_v1:reviewed:2026-05-23"} +{"chain_id":"verification_apprentice_requires_advisor","subject":"apprentice","intent":"verification","connective":"requires","object":"advisor","domains_subject_k":2,"domains_object_k":1,"provenance":"relations_extension_v1:reviewed:2026-05-23"} +{"chain_id":"cause_caregiver_supports_child","subject":"caregiver","intent":"cause","connective":"supports","object":"child","domains_subject_k":2,"domains_object_k":1,"provenance":"relations_extension_v1:reviewed:2026-05-23"} +{"chain_id":"verification_guardian_requires_child","subject":"guardian","intent":"verification","connective":"requires","object":"child","domains_subject_k":2,"domains_object_k":1,"provenance":"relations_extension_v1:reviewed:2026-05-23"} +{"chain_id":"cause_colleague_supports_teammate","subject":"colleague","intent":"cause","connective":"supports","object":"teammate","domains_subject_k":2,"domains_object_k":1,"provenance":"relations_extension_v1:reviewed:2026-05-23"} +{"chain_id":"verification_teammate_requires_colleague","subject":"teammate","intent":"verification","connective":"requires","object":"colleague","domains_subject_k":2,"domains_object_k":1,"provenance":"relations_extension_v1:reviewed:2026-05-23"} +{"chain_id":"cause_cousin_belongs_to_family","subject":"cousin","intent":"cause","connective":"belongs_to","object":"family","domains_subject_k":2,"domains_object_k":1,"provenance":"relations_extension_v1:reviewed:2026-05-23"} +{"chain_id":"verification_relative_requires_family","subject":"relative","intent":"verification","connective":"requires","object":"family","domains_subject_k":2,"domains_object_k":1,"provenance":"relations_extension_v1:reviewed:2026-05-23"} +{"chain_id":"cause_elder_precedes_descendant","subject":"elder","intent":"cause","connective":"precedes","object":"descendant","domains_subject_k":2,"domains_object_k":1,"provenance":"relations_extension_v1:reviewed:2026-05-23"} +{"chain_id":"verification_friend_requires_trust","subject":"friend","intent":"verification","connective":"requires","object":"colleague","domains_subject_k":2,"domains_object_k":1,"provenance":"relations_extension_v1:reviewed:2026-05-23"} +{"chain_id":"cause_manager_precedes_supervisor","subject":"manager","intent":"cause","connective":"precedes","object":"supervisor","domains_subject_k":2,"domains_object_k":1,"provenance":"relations_extension_v1:reviewed:2026-05-23"} +{"chain_id":"verification_supervisor_requires_manager","subject":"supervisor","intent":"verification","connective":"requires","object":"manager","domains_subject_k":2,"domains_object_k":1,"provenance":"relations_extension_v1:reviewed:2026-05-23"} +{"chain_id":"cause_mentor_precedes_apprentice","subject":"mentor","intent":"cause","connective":"precedes","object":"apprentice","domains_subject_k":2,"domains_object_k":1,"provenance":"relations_extension_v1:reviewed:2026-05-23"} +{"chain_id":"verification_neighbor_requires_family","subject":"neighbor","intent":"verification","connective":"requires","object":"family","domains_subject_k":2,"domains_object_k":1,"provenance":"relations_extension_v1:reviewed:2026-05-23"} diff --git a/tests/test_en_core_relations_v1_pack.py b/tests/test_en_core_relations_v1_pack.py index 3a430f73..d98db80b 100644 --- a/tests/test_en_core_relations_v1_pack.py +++ b/tests/test_en_core_relations_v1_pack.py @@ -1,4 +1,4 @@ -"""Contract tests for the ``en_core_relations_v1`` kinship starter pack. +"""Contract tests for the ``en_core_relations_v1`` relations starter pack. Following the teaching-order doctrine ([`docs/teaching_order.md`](../docs/teaching_order.md) §5), @@ -8,7 +8,7 @@ domain whose triples exercise every formation gate end-to-end. As of ADR-0063 (cross-pack surface resolver), this pack IS in the default ``RuntimeConfig.input_packs``. Pack-grounded surface composers in :mod:`chat.pack_grounding` consult -:mod:`chat.pack_resolver` so kinship lemmas ground on the live path +:mod:`chat.pack_resolver` so relations lemmas ground on the live path without a separate composer module. These tests pin: @@ -16,10 +16,9 @@ These tests pin: - The pack loads via ``load_pack("en_core_relations_v1")`` without a checksum mismatch (manifest.checksum matches the bytes on disk per CLAUDE.md's pack-discipline). - - The 8 kinship lemmas are all present. - - Each lemma carries the expected canonical semantic_domains - (deterministic taxonomy under ``kinship.*``, ``lineage.*``, - ``biology.*``, ``social.*``). + - The original 8 kinship lemmas are all still present. + - Each original lemma carries the expected canonical primary + semantic domain. - No accidental cross-pack lemma collision with ``en_core_cognition_v1`` (orthogonal domains; deliberate separation per teaching_order.md). @@ -32,7 +31,7 @@ from language_packs.compiler import load_pack, load_pack_entries PACK_ID = "en_core_relations_v1" -EXPECTED_LEMMAS: tuple[str, ...] = ( +BASELINE_LEMMAS: tuple[str, ...] = ( "parent", "child", "sibling", @@ -43,6 +42,25 @@ EXPECTED_LEMMAS: tuple[str, ...] = ( "offspring", ) +EXPECTED_ADDED_LEMMAS: tuple[str, ...] = ( + "advisor", + "apprentice", + "caregiver", + "colleague", + "cousin", + "elder", + "friend", + "guardian", + "manager", + "mentor", + "neighbor", + "relative", + "supervisor", + "teammate", +) + +EXPECTED_LEMMAS: tuple[str, ...] = BASELINE_LEMMAS + EXPECTED_ADDED_LEMMAS + # Note: `person` is intentionally NOT in this pack — it lives in # `en_core_cognition_v1` and the orthogonality test below pins # that boundary. @@ -76,9 +94,22 @@ def test_all_expected_lemmas_present() -> None: assert surfaces == set(EXPECTED_LEMMAS) -def test_each_lemma_carries_expected_primary_domain() -> None: +def test_baseline_lemmas_still_present() -> None: + _, manifold = load_pack(PACK_ID) + surfaces = {manifold.get_word_at(i) for i in range(len(manifold))} + assert set(BASELINE_LEMMAS) <= surfaces + + +def test_added_lemmas_present() -> None: + _, manifold = load_pack(PACK_ID) + surfaces = {manifold.get_word_at(i) for i in range(len(manifold))} + assert set(EXPECTED_ADDED_LEMMAS) <= surfaces + + +def test_each_baseline_lemma_carries_expected_primary_domain() -> None: """The first ``semantic_domains`` entry is the load-bearing - primary domain — drives mounted-pack resonance grouping.""" + primary domain — drives mounted-pack resonance grouping. Baseline + primary domains are frozen; changing them requires an ADR.""" entries = load_pack_entries(PACK_ID) by_lemma = {e.lemma: e for e in entries} for lemma, expected_primary in EXPECTED_PRIMARY_DOMAINS.items(): diff --git a/tests/test_relations_pack_v1_extensions.py b/tests/test_relations_pack_v1_extensions.py new file mode 100644 index 00000000..60b3c5da --- /dev/null +++ b/tests/test_relations_pack_v1_extensions.py @@ -0,0 +1,112 @@ +"""Regression tests for additive ``en_core_relations_v1`` content extension. + +This file deliberately tests only existing schemas and live resolver +behavior. It does not introduce new pack types, new validators, or new +manifest fields. +""" + +from __future__ import annotations + +import hashlib +import json +from pathlib import Path + +from chat.pack_resolver import resolve_lemma +from chat.teaching_grounding import TeachingCorpusSpec, _load_corpus + + +PACK_ID = "en_core_relations_v1" +PACK_DIR = Path("language_packs/data") / PACK_ID +LEXICON_PATH = PACK_DIR / "lexicon.jsonl" +MANIFEST_PATH = PACK_DIR / "manifest.json" +CHAINS_PATH = Path("teaching/relations_chains/relations_chains_v1.jsonl") + +BASELINE_LEMMA_COUNT = 8 +EXPECTED_ADDED_LEMMAS = frozenset({ + "advisor", + "apprentice", + "caregiver", + "colleague", + "cousin", + "elder", + "friend", + "guardian", + "manager", + "mentor", + "neighbor", + "relative", + "supervisor", + "teammate", +}) +EXPECTED_TOTAL_LEMMA_COUNT = BASELINE_LEMMA_COUNT + len(EXPECTED_ADDED_LEMMAS) + +BASELINE_CHAIN_COUNT = 7 +EXPECTED_ADDED_CHAIN_COUNT = 14 +EXPECTED_TOTAL_CHAIN_COUNT = BASELINE_CHAIN_COUNT + EXPECTED_ADDED_CHAIN_COUNT + + +def _lexicon_entries() -> list[dict[str, object]]: + return [ + json.loads(line) + for line in LEXICON_PATH.read_text(encoding="utf-8").splitlines() + if line.strip() + ] + + +def _chain_entries() -> list[dict[str, object]]: + return [ + json.loads(line) + for line in CHAINS_PATH.read_text(encoding="utf-8").splitlines() + if line.strip() + ] + + +def test_relations_v1_entry_count_delta() -> None: + entries = _lexicon_entries() + assert len(entries) == EXPECTED_TOTAL_LEMMA_COUNT + added = { + str(e["lemma"]) + for e in entries + if "relations_extension_v1:reviewed:2026-05-23" in e.get("provenance_ids", []) + } + assert added == EXPECTED_ADDED_LEMMAS + + +def test_relations_v1_has_no_duplicate_lemma_or_surface_keys() -> None: + entries = _lexicon_entries() + lemmas = [str(e["lemma"]) for e in entries] + surfaces = [str(e["surface"]) for e in entries] + assert len(lemmas) == len(set(lemmas)) + assert len(surfaces) == len(set(surfaces)) + + +def test_manifest_checksum_matches_lexicon_bytes() -> None: + manifest = json.loads(MANIFEST_PATH.read_text(encoding="utf-8")) + actual = hashlib.sha256(LEXICON_PATH.read_bytes()).hexdigest() + assert manifest["checksum"] == actual + + +def test_all_added_lemmas_resolve_via_cross_pack_resolver() -> None: + for lemma in sorted(EXPECTED_ADDED_LEMMAS): + resolved = resolve_lemma(lemma) + assert resolved is not None, lemma + pack_id, domains = resolved + assert pack_id == PACK_ID + assert domains + + +def test_relations_v1_chain_count_delta_and_loadability() -> None: + chains = _chain_entries() + assert len(chains) == EXPECTED_TOTAL_CHAIN_COUNT + added = [ + c for c in chains + if c.get("provenance") == "relations_extension_v1:reviewed:2026-05-23" + ] + assert len(added) == EXPECTED_ADDED_CHAIN_COUNT + + loaded = _load_corpus(TeachingCorpusSpec( + corpus_id="relations_chains_v1", + path=CHAINS_PATH, + pack_id=PACK_ID, + )) + assert len(loaded) == EXPECTED_TOTAL_CHAIN_COUNT