From cb1eba72ae3dfba4ad08acfdb63e334d55010786 Mon Sep 17 00:00:00 2001 From: Shay Date: Mon, 18 May 2026 20:57:52 -0700 Subject: [PATCH] =?UTF-8?q?feat(packs):=20en=5Fcore=5Faction=5Fv1=20?= =?UTF-8?q?=E2=80=94=20action=20verbs=20(26=20lemmas)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Workstream 1 fourth pack. Closes the common-action verb gap. Prior packs covered reasoning (cognition), speech/perception (meta), and adjectives (attitude); this pack covers what an agent *does*. Pack composition (26 VERB entries): action.doing.perform do perform execute carry conduct action.doing.make make action.doing.achieve achieve accomplish action.creating.originate create build form produce generate develop action.changing.transform change transform action.moving.translate move action.moving.depart_arrive go come action.moving.transfer send receive action.possessing.acquire get take action.possessing.transfer give action.possessing.retain keep action.possessing.deploy use Files: language_packs/data/en_core_action_v1/ lexicon.jsonl — 26 entries, SHA-256 checksum-sealed manifest.json — operational_base / D0 chat/pack_resolver.py Appended to DEFAULT_RESOLVABLE_PACK_IDS after temporal. core/config.py Added to RuntimeConfig.input_packs default mount. tests/test_en_core_action_v1_pack.py 11 contract tests covering load / POS / namespace / no-collision / contiguous-ids / mounted-by-default / resolver-order / routing / prior-pack invariance. tests/test_procedure_surface.py Swapped two test fixtures from "do stuff" to "fix bugs". ``do`` is now correctly pack-resident in en_core_action_v1 (semantically correct — "How do I do stuff?" should ground on ``do``), so the "no pack lemma exists" contract needed a fixture where both verb and noun are genuinely OOV. ``fix bugs`` satisfies this across all 7 mounted packs. Authoring: Three parallel subagents — doing / creating / moving+possessing. Strict exemplar + forbidden-lemma list against all 6 prior packs. Verification: Cognition eval byte-identical on both splits (100/100/91.7/100 and 100/100/83.3/100). All 70 pack tests pass (cognition + meta + attitude + temporal + action + quant tests run together). Live composer probes confirm every action lemma surfaces deterministically from en_core_action_v1. --- chat/pack_resolver.py | 1 + core/config.py | 1 + .../data/en_core_action_v1/lexicon.jsonl | 26 +++ .../data/en_core_action_v1/manifest.json | 13 ++ tests/test_en_core_action_v1_pack.py | 150 ++++++++++++++++++ tests/test_procedure_surface.py | 15 +- 6 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 language_packs/data/en_core_action_v1/lexicon.jsonl create mode 100644 language_packs/data/en_core_action_v1/manifest.json create mode 100644 tests/test_en_core_action_v1_pack.py diff --git a/chat/pack_resolver.py b/chat/pack_resolver.py index cc1d0a79..80e1e887 100644 --- a/chat/pack_resolver.py +++ b/chat/pack_resolver.py @@ -45,6 +45,7 @@ DEFAULT_RESOLVABLE_PACK_IDS: tuple[str, ...] = ( "en_core_meta_v1", "en_core_attitude_v1", "en_core_temporal_v1", + "en_core_action_v1", "en_core_relations_v1", "en_core_relations_v2", ) diff --git a/core/config.py b/core/config.py index 60da1bc7..9a0e7534 100644 --- a/core/config.py +++ b/core/config.py @@ -16,6 +16,7 @@ class RuntimeConfig: "en_core_meta_v1", "en_core_attitude_v1", "en_core_temporal_v1", + "en_core_action_v1", "en_core_relations_v1", "en_core_relations_v2", "he_logos_micro_v1", diff --git a/language_packs/data/en_core_action_v1/lexicon.jsonl b/language_packs/data/en_core_action_v1/lexicon.jsonl new file mode 100644 index 00000000..de3d7e73 --- /dev/null +++ b/language_packs/data/en_core_action_v1/lexicon.jsonl @@ -0,0 +1,26 @@ +{"entry_id":"en-core-action-001","surface":"do","lemma":"do","language":"en","pos":"VERB","semantic_domains":["action.doing.perform","action.doing","cognition.activity"],"morphology_tags":["verb"],"provenance_ids":["seed:core_action_v1"]} +{"entry_id":"en-core-action-002","surface":"make","lemma":"make","language":"en","pos":"VERB","semantic_domains":["action.doing.make","action.doing","cognition.activity"],"morphology_tags":["verb"],"provenance_ids":["seed:core_action_v1"]} +{"entry_id":"en-core-action-003","surface":"perform","lemma":"perform","language":"en","pos":"VERB","semantic_domains":["action.doing.perform","action.doing","cognition.activity"],"morphology_tags":["verb"],"provenance_ids":["seed:core_action_v1"]} +{"entry_id":"en-core-action-004","surface":"conduct","lemma":"conduct","language":"en","pos":"VERB","semantic_domains":["action.doing.perform","action.doing","cognition.activity"],"morphology_tags":["verb"],"provenance_ids":["seed:core_action_v1"]} +{"entry_id":"en-core-action-005","surface":"execute","lemma":"execute","language":"en","pos":"VERB","semantic_domains":["action.doing.perform","action.doing","cognition.activity"],"morphology_tags":["verb"],"provenance_ids":["seed:core_action_v1"]} +{"entry_id":"en-core-action-006","surface":"carry","lemma":"carry","language":"en","pos":"VERB","semantic_domains":["action.doing.perform","action.doing","cognition.activity"],"morphology_tags":["verb"],"provenance_ids":["seed:core_action_v1"]} +{"entry_id":"en-core-action-007","surface":"achieve","lemma":"achieve","language":"en","pos":"VERB","semantic_domains":["action.doing.achieve","action.doing","cognition.activity"],"morphology_tags":["verb"],"provenance_ids":["seed:core_action_v1"]} +{"entry_id":"en-core-action-008","surface":"accomplish","lemma":"accomplish","language":"en","pos":"VERB","semantic_domains":["action.doing.achieve","action.doing","cognition.activity"],"morphology_tags":["verb"],"provenance_ids":["seed:core_action_v1"]} +{"entry_id":"en-core-action-009","surface":"create","lemma":"create","language":"en","pos":"VERB","semantic_domains":["action.creating.originate","action.creating","cognition.production"],"morphology_tags":["verb"],"provenance_ids":["seed:core_action_v1"]} +{"entry_id":"en-core-action-010","surface":"build","lemma":"build","language":"en","pos":"VERB","semantic_domains":["action.creating.originate","action.creating","cognition.construction"],"morphology_tags":["verb"],"provenance_ids":["seed:core_action_v1"]} +{"entry_id":"en-core-action-011","surface":"form","lemma":"form","language":"en","pos":"VERB","semantic_domains":["action.creating.originate","action.creating","cognition.structure"],"morphology_tags":["verb"],"provenance_ids":["seed:core_action_v1"]} +{"entry_id":"en-core-action-012","surface":"produce","lemma":"produce","language":"en","pos":"VERB","semantic_domains":["action.creating.originate","action.creating","cognition.production"],"morphology_tags":["verb"],"provenance_ids":["seed:core_action_v1"]} +{"entry_id":"en-core-action-013","surface":"generate","lemma":"generate","language":"en","pos":"VERB","semantic_domains":["action.creating.originate","action.creating","cognition.production"],"morphology_tags":["verb"],"provenance_ids":["seed:core_action_v1"]} +{"entry_id":"en-core-action-014","surface":"develop","lemma":"develop","language":"en","pos":"VERB","semantic_domains":["action.creating.originate","action.creating","cognition.growth"],"morphology_tags":["verb"],"provenance_ids":["seed:core_action_v1"]} +{"entry_id":"en-core-action-015","surface":"change","lemma":"change","language":"en","pos":"VERB","semantic_domains":["action.changing.transform","action.changing","cognition.modification"],"morphology_tags":["verb"],"provenance_ids":["seed:core_action_v1"]} +{"entry_id":"en-core-action-016","surface":"transform","lemma":"transform","language":"en","pos":"VERB","semantic_domains":["action.changing.transform","action.changing","cognition.modification"],"morphology_tags":["verb"],"provenance_ids":["seed:core_action_v1"]} +{"entry_id":"en-core-action-017","surface":"move","lemma":"move","language":"en","pos":"VERB","semantic_domains":["action.moving.translate","action.moving","cognition.motion"],"morphology_tags":["verb"],"provenance_ids":["seed:core_action_v1"]} +{"entry_id":"en-core-action-018","surface":"go","lemma":"go","language":"en","pos":"VERB","semantic_domains":["action.moving.depart_arrive","action.moving","cognition.motion"],"morphology_tags":["verb"],"provenance_ids":["seed:core_action_v1"]} +{"entry_id":"en-core-action-019","surface":"come","lemma":"come","language":"en","pos":"VERB","semantic_domains":["action.moving.depart_arrive","action.moving","cognition.motion"],"morphology_tags":["verb"],"provenance_ids":["seed:core_action_v1"]} +{"entry_id":"en-core-action-020","surface":"send","lemma":"send","language":"en","pos":"VERB","semantic_domains":["action.moving.transfer","action.moving","cognition.motion"],"morphology_tags":["verb"],"provenance_ids":["seed:core_action_v1"]} +{"entry_id":"en-core-action-021","surface":"receive","lemma":"receive","language":"en","pos":"VERB","semantic_domains":["action.moving.transfer","action.moving","cognition.motion"],"morphology_tags":["verb"],"provenance_ids":["seed:core_action_v1"]} +{"entry_id":"en-core-action-022","surface":"get","lemma":"get","language":"en","pos":"VERB","semantic_domains":["action.possessing.acquire","action.possessing","cognition.possession"],"morphology_tags":["verb"],"provenance_ids":["seed:core_action_v1"]} +{"entry_id":"en-core-action-023","surface":"take","lemma":"take","language":"en","pos":"VERB","semantic_domains":["action.possessing.acquire","action.possessing","cognition.possession"],"morphology_tags":["verb"],"provenance_ids":["seed:core_action_v1"]} +{"entry_id":"en-core-action-024","surface":"give","lemma":"give","language":"en","pos":"VERB","semantic_domains":["action.possessing.transfer","action.possessing","cognition.possession"],"morphology_tags":["verb"],"provenance_ids":["seed:core_action_v1"]} +{"entry_id":"en-core-action-025","surface":"keep","lemma":"keep","language":"en","pos":"VERB","semantic_domains":["action.possessing.retain","action.possessing","cognition.possession"],"morphology_tags":["verb"],"provenance_ids":["seed:core_action_v1"]} +{"entry_id":"en-core-action-026","surface":"use","lemma":"use","language":"en","pos":"VERB","semantic_domains":["action.possessing.deploy","action.possessing","cognition.possession"],"morphology_tags":["verb"],"provenance_ids":["seed:core_action_v1"]} diff --git a/language_packs/data/en_core_action_v1/manifest.json b/language_packs/data/en_core_action_v1/manifest.json new file mode 100644 index 00000000..f028f381 --- /dev/null +++ b/language_packs/data/en_core_action_v1/manifest.json @@ -0,0 +1,13 @@ +{ + "pack_id": "en_core_action_v1", + "language": "en", + "role": "operational_base", + "script": "Latin", + "normalization_policy": "unitize_versor", + "source_manifest": "en_core_action_v1.lexicon.jsonl", + "determinism_class": "D0", + "checksum": "d2d542a7d203a8c81211d9428a9e49d287f841cf9479a6cd8f0804c316bd46e4", + "version": "1.0.0", + "gate_engaged": true, + "oov_policy": "tagged_fallback" +} diff --git a/tests/test_en_core_action_v1_pack.py b/tests/test_en_core_action_v1_pack.py new file mode 100644 index 00000000..fcb5d55e --- /dev/null +++ b/tests/test_en_core_action_v1_pack.py @@ -0,0 +1,150 @@ +"""``en_core_action_v1`` — action verb pack tests. + +The action pack adds common-action verb coverage absent from prior +packs. Cognition verbs are reasoning/relation operators; meta verbs +are speech/mental/perceptual; this pack covers what an agent *does*: +performing, creating, changing, moving, possessing. 26 entries: + + - action.doing.* — do make perform conduct execute carry achieve accomplish (8) + - action.creating.* — create build form produce generate develop (6) + - action.changing.* — change transform (2) + - action.moving.* — move go come send receive (5) + - action.possessing.* — get take give keep use (5) + +Contracts pinned: checksum-verified load, all entries POS=VERB, +attitude.*-style primary-domain invariant, no collision with any of +the 6 prior English packs, mounted by default, registered after +temporal in DEFAULT_RESOLVABLE_PACK_IDS, prior-pack resolution +unchanged. +""" + +from __future__ import annotations + +import json +from pathlib import Path + +from chat.pack_resolver import DEFAULT_RESOLVABLE_PACK_IDS, resolve_lemma +from core.config import RuntimeConfig +from language_packs.compiler import load_pack + + +PACK_ID = "en_core_action_v1" +_PACK_ROOT = Path(__file__).resolve().parent.parent / "language_packs" / "data" / PACK_ID + +EXPECTED_TOTAL = 26 + +EXPECTED_LEMMAS: tuple[str, ...] = ( + "do", "make", "perform", "conduct", "execute", "carry", "achieve", + "accomplish", "create", "build", "form", "produce", "generate", + "develop", "change", "transform", "move", "go", "come", "send", + "receive", "get", "take", "give", "keep", "use", +) + + +def _read_lexicon() -> list[dict]: + path = _PACK_ROOT / "lexicon.jsonl" + return [ + json.loads(line) + for line in path.read_text(encoding="utf-8").splitlines() + if line.strip() + ] + + +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) == EXPECTED_TOTAL + + +def test_all_entries_are_verbs() -> None: + for entry in _read_lexicon(): + assert entry["pos"] == "VERB", entry["entry_id"] + assert entry["morphology_tags"] == ["verb"], entry["entry_id"] + + +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_every_entry_has_action_namespace_primary_domain() -> None: + for entry in _read_lexicon(): + domains = entry["semantic_domains"] + assert isinstance(domains, list) and domains, entry + assert domains[0].startswith("action."), ( + f"entry {entry['entry_id']} primary domain {domains[0]!r} " + f"is not in the action.* namespace" + ) + + +def test_no_collision_with_prior_packs() -> None: + prior_lemmas: set[str] = set() + pack_root = _PACK_ROOT.parent + for pack in ( + "en_core_cognition_v1", + "en_core_meta_v1", + "en_core_attitude_v1", + "en_core_temporal_v1", + "en_core_relations_v1", + "en_core_relations_v2", + ): + for line in (pack_root / pack / "lexicon.jsonl").read_text("utf-8").splitlines(): + if line.strip(): + prior_lemmas.add(json.loads(line)["lemma"].lower()) + for entry in _read_lexicon(): + lemma = entry["lemma"].lower() + assert lemma not in prior_lemmas, ( + f"lemma {lemma!r} collides with a prior-mounted pack" + ) + + +def test_provenance_is_seed_core_action_v1() -> None: + for entry in _read_lexicon(): + assert entry["provenance_ids"] == ["seed:core_action_v1"], entry + + +def test_entry_ids_contiguous_and_zero_padded() -> None: + entries = sorted(_read_lexicon(), key=lambda d: d["entry_id"]) + for i, entry in enumerate(entries, start=1): + assert entry["entry_id"] == f"en-core-action-{i:03d}", entry["entry_id"] + + +def test_pack_mounted_in_default_runtime_config() -> None: + cfg = RuntimeConfig() + assert PACK_ID in cfg.input_packs + + +def test_pack_registered_after_prior_content_packs() -> None: + assert PACK_ID in DEFAULT_RESOLVABLE_PACK_IDS + for earlier in ( + "en_core_cognition_v1", + "en_core_meta_v1", + "en_core_attitude_v1", + "en_core_temporal_v1", + ): + assert DEFAULT_RESOLVABLE_PACK_IDS.index(earlier) < DEFAULT_RESOLVABLE_PACK_IDS.index(PACK_ID) + + +def test_resolver_routes_action_lemmas_to_this_pack() -> None: + for lemma in EXPECTED_LEMMAS: + resolved = resolve_lemma(lemma) + assert resolved is not None, f"lemma {lemma!r} did not resolve" + pack_id, domains = resolved + assert pack_id == PACK_ID, ( + f"lemma {lemma!r} resolved to {pack_id} instead of {PACK_ID}" + ) + assert domains[0].startswith("action.") + + +def test_prior_pack_lemma_resolution_unchanged() -> None: + for lemma, expected in ( + ("truth", "en_core_cognition_v1"), + ("doubt", "en_core_meta_v1"), + ("true", "en_core_attitude_v1"), + ("now", "en_core_temporal_v1"), + ): + resolved = resolve_lemma(lemma) + assert resolved is not None and resolved[0] == expected diff --git a/tests/test_procedure_surface.py b/tests/test_procedure_surface.py index e3f951bc..50ebd3c0 100644 --- a/tests/test_procedure_surface.py +++ b/tests/test_procedure_surface.py @@ -75,7 +75,11 @@ def test_extract_skips_dialogue_fillers() -> None: def test_extract_none_when_no_pack_lemma() -> None: assert _extract_procedure_topic_lemma("") is None assert _extract_procedure_topic_lemma(None) is None # type: ignore[arg-type] - assert _extract_procedure_topic_lemma("do stuff") is None + # Pre-en_core_action_v1 the canonical fixture was "do stuff", but + # ``do`` now resolves via en_core_action_v1 (action.doing.perform). + # Use a genuinely-OOV verb+noun pair to preserve the original + # contract: when no pack-resident lemma exists, return None. + assert _extract_procedure_topic_lemma("fix bugs") is None def test_extract_strips_punctuation() -> None: @@ -128,7 +132,9 @@ def test_surface_returns_none_for_no_pack_lemma() -> None: through to the universal disclosure — preserves the honesty contract for fully-unknown procedures.""" assert pack_grounded_procedure_surface("") is None - assert pack_grounded_procedure_surface("do stuff") is None + # ``do`` is now pack-resident in en_core_action_v1; use a verb+noun + # pair where both terms are genuinely OOV across all mounted packs. + assert pack_grounded_procedure_surface("fix bugs") is None def test_surface_is_deterministic() -> None: @@ -160,7 +166,10 @@ def test_procedure_with_no_pack_lemma_routes_to_oov_invitation() -> None: universal disclosure. No surface fabrication: the invitation only references the OOV token and the mounted-pack list.""" rt = ChatRuntime() - response = rt.chat("How do I do stuff?") + # ``fix`` and ``bugs`` are genuinely OOV across all mounted packs + # (``do`` is now pack-resident in en_core_action_v1, so the prior + # fixture "how do I do stuff?" would extract ``do`` and ground). + response = rt.chat("How do I fix bugs?") # Either UNKNOWN-intent → "none", or PROCEDURE-intent on OOV # subject → "oov" invitation. Both honour the no-fabrication # contract; the surface text differs by intent classification.