feat(packs): en_core_relations_v1 — kinship starter pack (8 lemmas)
Per teaching_order.md §5 — pick one commercial domain and run the
full 1→4 progression inside it before opening a second. Kinship is
the doctrinally classic starter: tight DAG, well-bounded primitives,
and orthogonal to the cognition pack.
Lemmas (8): parent, child, sibling, family, ancestor, descendant,
spouse, offspring. Each carries ≥2 semantic_domains under a
deterministic taxonomy (kinship.*, lineage.*, biology.*, social.*).
Deliberate exclusions:
- `person` — lives in en_core_cognition_v1; orthogonality test
pins that boundary.
- Specializations (mother/father/son/daughter/grandparent/...) —
derived from v1 primitives; land in v2 after v1 produces
reviewed chains.
- Quantifiers (one/two/many) — separate domain
(en_core_quantification_v1); cross-domain triples come last.
- Verbs of relation (begets/marries/...) — separate composer
work; no relations_chains_v1.jsonl yet.
Engagement is opt-in:
- Pack is NOT in RuntimeConfig.input_packs defaults.
- Programmatic mount via RuntimeConfig(input_packs=(..., "en_core_relations_v1")).
- CLI: core chat --pack en_core_relations_v1 (existing surface).
- Default-not-mounted preserves the cognition lane unchanged
until cross-pack teaching-grounded composition exists.
- language_packs/data/en_core_relations_v1/lexicon.jsonl
— 8 entries, JSONL format matching en_core_cognition_v1.
- language_packs/data/en_core_relations_v1/manifest.json
— pack_id, language, role=operational_base, checksum
(SHA-256 of lexicon bytes per CLAUDE.md pack-discipline),
version 1.0.0, determinism_class D0, oov_policy tagged_fallback.
- tests/test_en_core_relations_v1_pack.py — 6 tests pin:
checksum-match load, lemma roster, per-lemma primary domain,
≥2 domains/lemma (composer headroom), zero collision with
cognition pack (kinship DAG stays orthogonal), pack-not-in-
default-input-packs (opt-in engagement contract).
- docs/curriculum/relations_pack_v1.md — full pack log:
rationale per included/excluded lemma, opt-in engagement path,
4-step ADR roadmap (cross-pack composition → first kinship
chains → pronoun v2 → cross-domain triples).
Mounted-manifold sanity check (en_core_cognition_v1 +
en_core_relations_v1): 93 lemmas combined, no collisions, both
packs' surfaces individually addressable.
Lanes (regression): smoke 67 / packs 6 / algebra 132 / relations-pack 6.
The non-negotiable field invariant (versor_condition < 1e-6) is
unaffected: this is pure pack data + a contract test.
This commit is contained in:
parent
c492014815
commit
f0c57eb32e
4 changed files with 342 additions and 0 deletions
181
docs/curriculum/relations_pack_v1.md
Normal file
181
docs/curriculum/relations_pack_v1.md
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
# Pack Unit — `en_core_relations_v1` (Kinship Starter)
|
||||
|
||||
**Date:** 2026-05-18
|
||||
**Author:** Shay
|
||||
**Pack ID:** `en_core_relations_v1`
|
||||
**Lemma count:** 8 (kinship-only; deliberately tight)
|
||||
**Status:** Ratified (checksum verified). **Not yet mounted** on the default runtime.
|
||||
|
||||
---
|
||||
|
||||
## Why kinship first
|
||||
|
||||
Per [`teaching_order.md`](../teaching_order.md) §5 — "Pick *one*
|
||||
commercial domain and run the full 1→4 progression *inside* that
|
||||
domain before opening a second domain. Cross-domain triples come
|
||||
last and only after both domains have ratified their own internal
|
||||
DAG."
|
||||
|
||||
Kinship is the classic starter domain for relation-curriculum work:
|
||||
|
||||
- Tight DAG: ~10 primitives + transitive closures over them.
|
||||
- Doctrinally well-behaved: "teach `parent_of` before `grandparent_of`,
|
||||
then `ancestor_of`" maps directly to the formation pipeline's
|
||||
prerequisite gates (`ratify.py` G3 — every relation's `head` and
|
||||
`tail` must already be mastered).
|
||||
- Orthogonal to the cognition pack: zero lemma overlap, zero
|
||||
semantic-domain prefix overlap. Two pack DAGs ratify in isolation.
|
||||
|
||||
---
|
||||
|
||||
## What's in v1
|
||||
|
||||
Eight kinship lemmas, each carrying ≥2 semantic_domains in a
|
||||
deterministic taxonomy under `kinship.*`, `lineage.*`,
|
||||
`biology.*`, `social.*`:
|
||||
|
||||
| Lemma | Primary domain | Secondary domains |
|
||||
|---|---|---|
|
||||
| `parent` | `kinship.ascendant.direct` | `kinship.parent`, `biology.progenitor` |
|
||||
| `child` | `kinship.descendant.direct` | `kinship.child`, `biology.offspring` |
|
||||
| `sibling` | `kinship.lateral.direct` | `kinship.sibling`, `social.peer` |
|
||||
| `family` | `kinship.unit` | `social.group.kin`, `kinship.group` |
|
||||
| `ancestor` | `kinship.ascendant.transitive` | `lineage.upward`, `kinship.elder` |
|
||||
| `descendant` | `kinship.descendant.transitive` | `lineage.downward`, `kinship.successor` |
|
||||
| `spouse` | `kinship.partner` | `kinship.lateral.affinal`, `social.marriage` |
|
||||
| `offspring` | `kinship.descendant.direct` | `biology.progeny`, `kinship.child` |
|
||||
|
||||
**Note:** `person` is **not** in this pack. It lives in
|
||||
`en_core_cognition_v1` (which carries it as a cognition primitive
|
||||
covering "the experiencer of cognitive acts"). The orthogonality
|
||||
test pins that boundary; if `person` ever drifts between the two
|
||||
packs, that test fails as a deliberate signal that the domain
|
||||
DAG boundary needs an ADR.
|
||||
|
||||
---
|
||||
|
||||
## What's NOT in v1 (and why)
|
||||
|
||||
### Pronouns + role-fillers
|
||||
|
||||
`mother`, `father`, `son`, `daughter`, `brother`, `sister`, `aunt`,
|
||||
`uncle`, `cousin`, `grandparent`, `grandchild`, `niece`, `nephew`.
|
||||
|
||||
These are **specializations** of the v1 primitives — `mother`
|
||||
is-a `parent` with a gender filler; `grandparent` is `parent of
|
||||
parent` (a composed kinship relation). Following teaching-order
|
||||
doctrine: teach the **atomic** primitives first; specializations
|
||||
land in a v2 only after the v1 DAG has produced reviewed relation
|
||||
chains over the primitives.
|
||||
|
||||
### Quantifiers + ordinals
|
||||
|
||||
`one`, `two`, `many`, `first`, `second`. Useful for kinship
|
||||
statements ("a person has two parents") but a separate domain
|
||||
(`en_core_quantification_v1`) with its own DAG. Cross-domain
|
||||
triples (`one(parent)`, `two(parent)`) come after both domains
|
||||
ratify internally.
|
||||
|
||||
### Verbs of relation
|
||||
|
||||
`begets`, `marries`, `descends-from`. These are predicates, not
|
||||
nouns. The cognition pack carries general predicates already
|
||||
(`reveals`, `grounds`, `requires`); kinship-specific predicates
|
||||
will land alongside the first reviewed kinship chains (a
|
||||
follow-up ADR — there is no `relations_chains_v1.jsonl` yet).
|
||||
|
||||
---
|
||||
|
||||
## Engagement (opt-in only)
|
||||
|
||||
The pack is **not** in the default `RuntimeConfig.input_packs`.
|
||||
Mounting it changes the runtime's mounted manifold (89 → 93
|
||||
lemmas in the cognition+relations combination). Until cross-pack
|
||||
teaching-grounded composition exists, mounting the relations
|
||||
pack would expose lemmas to vault recall and intent classification
|
||||
without a corresponding ratified surface composer for them. That
|
||||
asymmetry would silently lower the deterministic-grounding
|
||||
fraction of any kinship prompt.
|
||||
|
||||
To opt in for development:
|
||||
|
||||
```python
|
||||
from core.config import RuntimeConfig
|
||||
from chat.runtime import ChatRuntime
|
||||
|
||||
cfg = RuntimeConfig(input_packs=RuntimeConfig().input_packs + ("en_core_relations_v1",))
|
||||
rt = ChatRuntime(config=cfg)
|
||||
```
|
||||
|
||||
Programmatic mounting works today. CLI-level support
|
||||
(`core chat --pack en_core_relations_v1`) is already in place via
|
||||
the `--pack` flag (see `core chat --help`).
|
||||
|
||||
---
|
||||
|
||||
## Verification
|
||||
|
||||
```
|
||||
tests/test_en_core_relations_v1_pack.py 6 passed
|
||||
- pack_loads_with_matching_checksum
|
||||
- all_expected_lemmas_present
|
||||
- each_lemma_carries_expected_primary_domain
|
||||
- every_lemma_has_multiple_semantic_domains
|
||||
- no_lemma_collision_with_cognition_pack
|
||||
- pack_is_not_in_default_input_packs
|
||||
|
||||
Lanes (regression):
|
||||
core test --suite smoke 67 passed
|
||||
core test --suite packs 6 passed
|
||||
core test --suite algebra 132 passed
|
||||
```
|
||||
|
||||
The non-negotiable field invariant
|
||||
(`versor_condition(F) < 1e-6`) is unaffected: this is pure pack
|
||||
data + a contract test. No runtime code path changed.
|
||||
|
||||
---
|
||||
|
||||
## Path forward (future ADRs in priority order)
|
||||
|
||||
1. **Cross-pack teaching-grounded composition.**
|
||||
`chat/pack_grounding.py` and `chat/teaching_grounding.py` are
|
||||
currently hardcoded to `en_core_cognition_v1`. A
|
||||
`pack_resolver` abstraction that picks the right pack(s) for a
|
||||
given intent/subject is the natural unlock — composes the
|
||||
relations pack into the live surface path without a separate
|
||||
composer module.
|
||||
|
||||
2. **First kinship reviewed chains** — a
|
||||
`relations_chains_v1.jsonl` or extension of the cognition
|
||||
chains to a `domain` field. Triples like
|
||||
`(parent, verification, is_a, ancestor)`,
|
||||
`(child, verification, is_a, descendant)` form the first
|
||||
ratified kinship DAG.
|
||||
|
||||
3. **Pronoun + role-filler v2.** Once the v1 DAG produces
|
||||
reviewed chains, add `mother`/`father`/`son`/`daughter` as
|
||||
specializations.
|
||||
|
||||
4. **Cross-domain triples.** After both relations v2 AND
|
||||
cognition v1 are mature (and the
|
||||
pack-resolver / cross-pack composer exist), open the cross-
|
||||
domain frontier — e.g., `family causes belonging`,
|
||||
`parent grounds identity`.
|
||||
|
||||
---
|
||||
|
||||
## Cross-References
|
||||
|
||||
- [`teaching_order.md`](../teaching_order.md) — the
|
||||
prerequisite-topological doctrine that scoped this pack.
|
||||
- [Curriculum: cognition saturation v2](cognition_saturation_v2.md)
|
||||
— the sibling cognition-pack saturation that produced the 21
|
||||
chains the cognition lane composes over today.
|
||||
- [ADR-0027](../decisions/ADR-0027-identity-packs.md) — pack
|
||||
loading + ratification surface that this pack consumes
|
||||
unchanged.
|
||||
- [ADR-0062](../decisions/ADR-0062-composed-teaching-grounded-surface.md)
|
||||
— the composed-surface ADR; the relations pack will become
|
||||
the second domain that composer composes over, once cross-pack
|
||||
composition lands.
|
||||
8
language_packs/data/en_core_relations_v1/lexicon.jsonl
Normal file
8
language_packs/data/en_core_relations_v1/lexicon.jsonl
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{"entry_id":"en-core-rel-001","surface":"parent","lemma":"parent","language":"en","pos":"NOUN","semantic_domains":["kinship.ascendant.direct","kinship.parent","biology.progenitor"],"morphology_tags":["noun"],"provenance_ids":["seed:core_relations_v1"]}
|
||||
{"entry_id":"en-core-rel-002","surface":"child","lemma":"child","language":"en","pos":"NOUN","semantic_domains":["kinship.descendant.direct","kinship.child","biology.offspring"],"morphology_tags":["noun"],"provenance_ids":["seed:core_relations_v1"]}
|
||||
{"entry_id":"en-core-rel-003","surface":"sibling","lemma":"sibling","language":"en","pos":"NOUN","semantic_domains":["kinship.lateral.direct","kinship.sibling","social.peer"],"morphology_tags":["noun"],"provenance_ids":["seed:core_relations_v1"]}
|
||||
{"entry_id":"en-core-rel-004","surface":"family","lemma":"family","language":"en","pos":"NOUN","semantic_domains":["kinship.unit","social.group.kin","kinship.group"],"morphology_tags":["noun"],"provenance_ids":["seed:core_relations_v1"]}
|
||||
{"entry_id":"en-core-rel-005","surface":"ancestor","lemma":"ancestor","language":"en","pos":"NOUN","semantic_domains":["kinship.ascendant.transitive","lineage.upward","kinship.elder"],"morphology_tags":["noun"],"provenance_ids":["seed:core_relations_v1"]}
|
||||
{"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"]}
|
||||
13
language_packs/data/en_core_relations_v1/manifest.json
Normal file
13
language_packs/data/en_core_relations_v1/manifest.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"pack_id": "en_core_relations_v1",
|
||||
"language": "en",
|
||||
"role": "operational_base",
|
||||
"script": "Latin",
|
||||
"normalization_policy": "unitize_versor",
|
||||
"source_manifest": "en_core_relations_v1.lexicon.jsonl",
|
||||
"determinism_class": "D0",
|
||||
"checksum": "1a013b464f6a0fad4ef96f9efaf1406c44729643e422c1f76a516099476e61d3",
|
||||
"version": "1.0.0",
|
||||
"gate_engaged": true,
|
||||
"oov_policy": "tagged_fallback"
|
||||
}
|
||||
140
tests/test_en_core_relations_v1_pack.py
Normal file
140
tests/test_en_core_relations_v1_pack.py
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
"""Contract tests for the ``en_core_relations_v1`` kinship starter pack.
|
||||
|
||||
Following the teaching-order doctrine
|
||||
([`docs/teaching_order.md`](../docs/teaching_order.md) §5),
|
||||
domain expansion starts with kinship — a tight, well-bounded
|
||||
domain whose triples exercise every formation gate end-to-end.
|
||||
|
||||
This pack is **not yet mounted** on the default runtime: it is
|
||||
not in ``RuntimeConfig.input_packs`` defaults. Operator opt-in via
|
||||
explicit ``RuntimeConfig(input_packs=(..., "en_core_relations_v1"))``
|
||||
is the engagement path until a follow-up ADR introduces cross-pack
|
||||
composition for teaching-grounded surfaces.
|
||||
|
||||
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 9 kinship lemmas are all present.
|
||||
- Each lemma carries the expected canonical semantic_domains
|
||||
(deterministic taxonomy under ``kinship.*``, ``lineage.*``,
|
||||
``biology.*``, ``social.*``).
|
||||
- No accidental cross-pack lemma collision with
|
||||
``en_core_cognition_v1`` (orthogonal domains; deliberate
|
||||
separation per teaching_order.md).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from language_packs.compiler import load_pack, load_pack_entries
|
||||
|
||||
|
||||
PACK_ID = "en_core_relations_v1"
|
||||
|
||||
EXPECTED_LEMMAS: tuple[str, ...] = (
|
||||
"parent",
|
||||
"child",
|
||||
"sibling",
|
||||
"family",
|
||||
"ancestor",
|
||||
"descendant",
|
||||
"spouse",
|
||||
"offspring",
|
||||
)
|
||||
|
||||
# Note: `person` is intentionally NOT in this pack — it lives in
|
||||
# `en_core_cognition_v1` and the orthogonality test below pins
|
||||
# that boundary.
|
||||
|
||||
EXPECTED_PRIMARY_DOMAINS: dict[str, str] = {
|
||||
"parent": "kinship.ascendant.direct",
|
||||
"child": "kinship.descendant.direct",
|
||||
"sibling": "kinship.lateral.direct",
|
||||
"family": "kinship.unit",
|
||||
"ancestor": "kinship.ascendant.transitive",
|
||||
"descendant": "kinship.descendant.transitive",
|
||||
"spouse": "kinship.partner",
|
||||
"offspring": "kinship.descendant.direct",
|
||||
}
|
||||
|
||||
|
||||
def test_pack_loads_with_matching_checksum() -> None:
|
||||
"""``load_pack`` raises ``ValueError`` on checksum mismatch. A
|
||||
clean load implies manifest.checksum equals SHA-256 of the
|
||||
bytes actually on disk — CLAUDE.md's pack-discipline."""
|
||||
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:
|
||||
"""The first ``semantic_domains`` entry is the load-bearing
|
||||
primary domain — drives mounted-pack resonance grouping."""
|
||||
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} — kinship taxonomy is load-bearing for the "
|
||||
f"first relations curriculum unit; updating requires an ADR."
|
||||
)
|
||||
|
||||
|
||||
def test_every_lemma_has_multiple_semantic_domains() -> None:
|
||||
"""Pack-grounded discipline: every lemma must surface a list of
|
||||
domains so the surface composer can pick top-k. A 1-domain
|
||||
entry would collapse the composer's information surface."""
|
||||
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)} "
|
||||
f"semantic_domains; need ≥2 for surface composition headroom."
|
||||
)
|
||||
|
||||
|
||||
def test_no_lemma_collision_with_cognition_pack() -> None:
|
||||
"""The relations pack and the cognition pack are deliberately
|
||||
orthogonal (teaching_order.md §5 — domain DAGs ratify in
|
||||
isolation before cross-domain triples). Any lemma in both
|
||||
packs would create silent mounting ambiguity at the
|
||||
``_load_mounted_packs_cached`` layer."""
|
||||
_, relations_manifold = load_pack(PACK_ID)
|
||||
_, cognition_manifold = load_pack("en_core_cognition_v1")
|
||||
relations_surfaces = {
|
||||
relations_manifold.get_word_at(i)
|
||||
for i in range(len(relations_manifold))
|
||||
}
|
||||
cognition_surfaces = {
|
||||
cognition_manifold.get_word_at(i)
|
||||
for i in range(len(cognition_manifold))
|
||||
}
|
||||
overlap = relations_surfaces & cognition_surfaces
|
||||
assert not overlap, (
|
||||
f"unexpected lemma collision between en_core_relations_v1 and "
|
||||
f"en_core_cognition_v1: {overlap}. Either the relations pack "
|
||||
f"absorbed a cognition lemma (reject) or the cognition pack "
|
||||
f"grew a kinship lemma (reject) — domain DAGs must stay "
|
||||
f"orthogonal per teaching_order.md §5."
|
||||
)
|
||||
|
||||
|
||||
def test_pack_is_not_in_default_input_packs() -> None:
|
||||
"""Engagement is opt-in. Adding the pack to the default
|
||||
``input_packs`` is a follow-up ADR scope: cross-pack teaching-
|
||||
grounded composition does not exist yet, and silently mounting
|
||||
the pack would change the runtime's recall surface without a
|
||||
corresponding ratification path."""
|
||||
from core.config import RuntimeConfig
|
||||
assert PACK_ID not in RuntimeConfig().input_packs
|
||||
Loading…
Reference in a new issue