content(en_core_relations_v1): +14 kinship/social lemmas + +14 chains, cognition eval byte-identical, ratify-idempotent (#211)

* 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).
This commit is contained in:
Shay 2026-05-23 22:47:53 -07:00 committed by GitHub
parent 0685c87e54
commit 6e072f95be
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 181 additions and 10 deletions

View file

@ -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"]}

View file

@ -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",

View file

@ -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"}

View file

@ -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():

View file

@ -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