diff --git a/chat/pack_resolver.py b/chat/pack_resolver.py index 455220a9..0785e6e5 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_meta_v1", + "en_core_attitude_v1", "en_core_relations_v1", "en_core_relations_v2", ) diff --git a/core/config.py b/core/config.py index 2c10dfa4..577c9524 100644 --- a/core/config.py +++ b/core/config.py @@ -14,6 +14,7 @@ class RuntimeConfig: "en_minimal_v1", "en_core_cognition_v1", "en_core_meta_v1", + "en_core_attitude_v1", "en_core_relations_v1", "en_core_relations_v2", "he_logos_micro_v1", diff --git a/language_packs/data/en_core_attitude_v1/lexicon.jsonl b/language_packs/data/en_core_attitude_v1/lexicon.jsonl new file mode 100644 index 00000000..d8286fd0 --- /dev/null +++ b/language_packs/data/en_core_attitude_v1/lexicon.jsonl @@ -0,0 +1,40 @@ +{"entry_id":"en-core-attitude-001","surface":"true","lemma":"true","language":"en","pos":"ADJ","semantic_domains":["attitude.truth_value.true","attitude.truth_value","cognition.epistemic"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-002","surface":"false","lemma":"false","language":"en","pos":"ADJ","semantic_domains":["attitude.truth_value.false","attitude.truth_value","cognition.epistemic"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-003","surface":"valid","lemma":"valid","language":"en","pos":"ADJ","semantic_domains":["attitude.truth_value.true","attitude.truth_value","cognition.epistemic"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-004","surface":"invalid","lemma":"invalid","language":"en","pos":"ADJ","semantic_domains":["attitude.truth_value.false","attitude.truth_value","cognition.epistemic"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-005","surface":"accurate","lemma":"accurate","language":"en","pos":"ADJ","semantic_domains":["attitude.truth_value.true","attitude.truth_value","cognition.epistemic"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-006","surface":"inaccurate","lemma":"inaccurate","language":"en","pos":"ADJ","semantic_domains":["attitude.truth_value.false","attitude.truth_value","cognition.epistemic"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-007","surface":"factual","lemma":"factual","language":"en","pos":"ADJ","semantic_domains":["attitude.truth_value.true","attitude.truth_value","cognition.epistemic"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-008","surface":"sound","lemma":"sound","language":"en","pos":"ADJ","semantic_domains":["attitude.truth_value.true","attitude.truth_value","cognition.epistemic"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-009","surface":"good","lemma":"good","language":"en","pos":"ADJ","semantic_domains":["attitude.evaluative.positive","attitude.evaluative","cognition.judgment"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-010","surface":"bad","lemma":"bad","language":"en","pos":"ADJ","semantic_domains":["attitude.evaluative.negative","attitude.evaluative","cognition.judgment"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-011","surface":"right","lemma":"right","language":"en","pos":"ADJ","semantic_domains":["attitude.evaluative.positive","attitude.evaluative","cognition.judgment"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-012","surface":"better","lemma":"better","language":"en","pos":"ADJ","semantic_domains":["attitude.evaluative.positive","attitude.evaluative","cognition.judgment"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-013","surface":"worse","lemma":"worse","language":"en","pos":"ADJ","semantic_domains":["attitude.evaluative.negative","attitude.evaluative","cognition.judgment"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-014","surface":"best","lemma":"best","language":"en","pos":"ADJ","semantic_domains":["attitude.evaluative.positive","attitude.evaluative","cognition.judgment"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-015","surface":"certain","lemma":"certain","language":"en","pos":"ADJ","semantic_domains":["attitude.epistemic.certainty","attitude.epistemic","cognition.epistemic"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-016","surface":"uncertain","lemma":"uncertain","language":"en","pos":"ADJ","semantic_domains":["attitude.epistemic.uncertainty","attitude.epistemic","cognition.epistemic"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-017","surface":"possible","lemma":"possible","language":"en","pos":"ADJ","semantic_domains":["attitude.epistemic.possibility","attitude.epistemic","cognition.modal"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-018","surface":"impossible","lemma":"impossible","language":"en","pos":"ADJ","semantic_domains":["attitude.epistemic.impossibility","attitude.epistemic","cognition.modal"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-019","surface":"likely","lemma":"likely","language":"en","pos":"ADJ","semantic_domains":["attitude.epistemic.possibility","attitude.epistemic","cognition.modal"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-020","surface":"unlikely","lemma":"unlikely","language":"en","pos":"ADJ","semantic_domains":["attitude.epistemic.impossibility","attitude.epistemic","cognition.modal"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-021","surface":"probable","lemma":"probable","language":"en","pos":"ADJ","semantic_domains":["attitude.epistemic.possibility","attitude.epistemic","cognition.modal"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-022","surface":"clear","lemma":"clear","language":"en","pos":"ADJ","semantic_domains":["attitude.epistemic.certainty","attitude.epistemic","cognition.epistemic"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-023","surface":"obscure","lemma":"obscure","language":"en","pos":"ADJ","semantic_domains":["attitude.epistemic.uncertainty","attitude.epistemic","cognition.epistemic"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-024","surface":"evident","lemma":"evident","language":"en","pos":"ADJ","semantic_domains":["attitude.epistemic.certainty","attitude.epistemic","cognition.epistemic"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-025","surface":"necessary","lemma":"necessary","language":"en","pos":"ADJ","semantic_domains":["attitude.modal.necessity","attitude.modal","cognition.modal"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-026","surface":"sufficient","lemma":"sufficient","language":"en","pos":"ADJ","semantic_domains":["attitude.modal.sufficiency","attitude.modal","cognition.modal"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-027","surface":"required","lemma":"required","language":"en","pos":"ADJ","semantic_domains":["attitude.modal.necessity","attitude.modal","cognition.modal"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-028","surface":"optional","lemma":"optional","language":"en","pos":"ADJ","semantic_domains":["attitude.modal.optionality","attitude.modal","cognition.modal"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-029","surface":"important","lemma":"important","language":"en","pos":"ADJ","semantic_domains":["attitude.importance.high","attitude.importance","cognition.judgment"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-030","surface":"essential","lemma":"essential","language":"en","pos":"ADJ","semantic_domains":["attitude.importance.high","attitude.importance","cognition.judgment"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-031","surface":"relevant","lemma":"relevant","language":"en","pos":"ADJ","semantic_domains":["attitude.importance.relevance","attitude.importance","cognition.judgment"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-032","surface":"central","lemma":"central","language":"en","pos":"ADJ","semantic_domains":["attitude.importance.high","attitude.importance","cognition.judgment"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-033","surface":"primary","lemma":"primary","language":"en","pos":"ADJ","semantic_domains":["attitude.importance.high","attitude.importance","cognition.judgment"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-034","surface":"useful","lemma":"useful","language":"en","pos":"ADJ","semantic_domains":["attitude.importance.utility","attitude.importance","cognition.judgment"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-035","surface":"general","lemma":"general","language":"en","pos":"ADJ","semantic_domains":["attitude.scope.general","attitude.scope","cognition.category"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-036","surface":"specific","lemma":"specific","language":"en","pos":"ADJ","semantic_domains":["attitude.scope.specific","attitude.scope","cognition.category"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-037","surface":"broad","lemma":"broad","language":"en","pos":"ADJ","semantic_domains":["attitude.scope.general","attitude.scope","cognition.category"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-038","surface":"narrow","lemma":"narrow","language":"en","pos":"ADJ","semantic_domains":["attitude.scope.specific","attitude.scope","cognition.category"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-039","surface":"universal","lemma":"universal","language":"en","pos":"ADJ","semantic_domains":["attitude.scope.general","attitude.scope","cognition.category"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} +{"entry_id":"en-core-attitude-040","surface":"particular","lemma":"particular","language":"en","pos":"ADJ","semantic_domains":["attitude.scope.specific","attitude.scope","cognition.category"],"morphology_tags":["adjective"],"provenance_ids":["seed:core_attitude_v1"]} diff --git a/language_packs/data/en_core_attitude_v1/manifest.json b/language_packs/data/en_core_attitude_v1/manifest.json new file mode 100644 index 00000000..1e9d80ed --- /dev/null +++ b/language_packs/data/en_core_attitude_v1/manifest.json @@ -0,0 +1,13 @@ +{ + "pack_id": "en_core_attitude_v1", + "language": "en", + "role": "operational_base", + "script": "Latin", + "normalization_policy": "unitize_versor", + "source_manifest": "en_core_attitude_v1.lexicon.jsonl", + "determinism_class": "D0", + "checksum": "11076d46a2b8642e4d2d958a5aa3fc13c42fb53a17d746ec312f27716c237c79", + "version": "1.0.0", + "gate_engaged": true, + "oov_policy": "tagged_fallback" +} diff --git a/tests/test_en_core_attitude_v1_pack.py b/tests/test_en_core_attitude_v1_pack.py new file mode 100644 index 00000000..99b39b22 --- /dev/null +++ b/tests/test_en_core_attitude_v1_pack.py @@ -0,0 +1,155 @@ +"""``en_core_attitude_v1`` — adjective pack tests. + +The attitude pack is the first ADJ-bearing pack in the runtime. It +carries 40 adjectives across three semantic clusters: + + - attitude.truth_value.* — true, false, valid, accurate, ... (8) + - attitude.evaluative.* — good, bad, right, better, ... (6) + - attitude.epistemic.* — certain, possible, likely, ... (10) + - attitude.modal.* — necessary, sufficient, ... (4) + - attitude.importance.* — important, essential, useful, ... (6) + - attitude.scope.* — general, specific, broad, ... (6) + +Contracts pinned: + + - Checksum-verified load. + - 40 entries, all POS=ADJ. + - Every entry's primary semantic_domain begins with ``attitude.``. + - No collision with cognition_v1 / meta_v1 / relations_v1 / relations_v2. + - Mounted by default in ``RuntimeConfig.input_packs``. + - Registered in ``DEFAULT_RESOLVABLE_PACK_IDS`` *after* cognition and + meta (preserves cognition-lane and meta-lane resolution invariants). +""" + +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_attitude_v1" +_PACK_ROOT = Path(__file__).resolve().parent.parent / "language_packs" / "data" / PACK_ID + +EXPECTED_TOTAL = 40 + +EXPECTED_LEMMAS: tuple[str, ...] = ( + "true", "false", "valid", "invalid", "accurate", "inaccurate", "factual", + "sound", "good", "bad", "right", "better", "worse", "best", + "certain", "uncertain", "possible", "impossible", "likely", "unlikely", + "probable", "clear", "obscure", "evident", + "necessary", "sufficient", "required", "optional", + "important", "essential", "relevant", "central", "primary", "useful", + "general", "specific", "broad", "narrow", "universal", "particular", +) + + +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_adjectives() -> None: + entries = _read_lexicon() + assert len(entries) == EXPECTED_TOTAL + for entry in entries: + assert entry["pos"] == "ADJ", entry["entry_id"] + assert entry["morphology_tags"] == ["adjective"], 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_attitude_namespace_primary_domain() -> None: + for entry in _read_lexicon(): + domains = entry["semantic_domains"] + assert isinstance(domains, list) and domains, entry + assert domains[0].startswith("attitude."), ( + f"entry {entry['entry_id']} primary domain {domains[0]!r} " + f"is not in the attitude.* namespace" + ) + + +def test_no_collision_with_prior_packs() -> None: + """Anti-leakage: must not duplicate any lemma in any earlier-mounted + English pack. Re-asserts the authoring-time exclusion.""" + prior_lemmas: set[str] = set() + pack_root = _PACK_ROOT.parent + for pack in ( + "en_core_cognition_v1", + "en_core_meta_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_attitude_v1() -> None: + for entry in _read_lexicon(): + assert entry["provenance_ids"] == ["seed:core_attitude_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-attitude-{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_cognition_and_meta() -> None: + assert PACK_ID in DEFAULT_RESOLVABLE_PACK_IDS + cog_idx = DEFAULT_RESOLVABLE_PACK_IDS.index("en_core_cognition_v1") + meta_idx = DEFAULT_RESOLVABLE_PACK_IDS.index("en_core_meta_v1") + attitude_idx = DEFAULT_RESOLVABLE_PACK_IDS.index(PACK_ID) + assert cog_idx < attitude_idx + assert meta_idx < attitude_idx + + +def test_resolver_routes_attitude_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("attitude.") + + +def test_prior_pack_lemma_resolution_unchanged() -> None: + """Cognition + meta lemma resolution unaffected by attitude pack.""" + for lemma in ("truth", "knowledge", "memory"): + resolved = resolve_lemma(lemma) + assert resolved is not None and resolved[0] == "en_core_cognition_v1" + for lemma in ("doubt", "fact", "self"): + resolved = resolve_lemma(lemma) + assert resolved is not None and resolved[0] == "en_core_meta_v1"