Closes the consumption-half of the math teaching loop for two of three sub-types per docs/handoff/CONSUMPTION-WIRING-DISPATCH-PACK.md (PR #397). Companion to the doctrinal brief in PR #396. Modules ------- - language_packs/compile_frames.py — byte-deterministic compile of frames/*.jsonl → frames.jsonl (sorted by (frame_category, surface_form)) - language_packs/compile_compositions.py — same shape for compositions/*.jsonl → compositions.jsonl - generate/comprehension/frame_registry.py — load_frame_registry() mirroring load_lexicon: cache by (path, mtime, sha256), manifest checksum verification (optional frame_checksum field), polarity validation, conflict detection, empty-registry no-op - generate/comprehension/composition_registry.py — same shape PLUS: * SAFE_COMPOSITION_CATEGORIES enforced at LOAD (defense in depth; raises WrongCompositionCategory on any unsafe category — protects against pack edits that bypass the handler) * polarity "falsifies" exposed via is_falsified() (consumer must suppress; not silently treated as affirms) - language_packs/compiler.py — manifest verification extended for frame_checksum + composition_checksum, mirroring the proven glosses_checksum pattern (optional fields; backward-compatible) - generate/recognizer_anchor_inject.py — inject_from_match consults composition_registry when the per-category injector returns empty AND the matcher publishes ``composition_shape`` in parsed_anchors. Registry is a gate (admissibility) not an arithmetic primitive (ADR-0169 §"Mutation boundary"). Tests (38 new, all green) ------------------------- tests/test_frame_registry_load.py (11 tests) tests/test_composition_registry_load.py (11 tests) tests/test_composition_consult_in_injector.py ( 6 tests) tests/test_consumption_case_0050_hazard_pin.py( 3 tests, parametrized over allowlist) tests/test_consumption_empty_registry_no_op.py( 4 tests) tests/test_consumption_partition.py ( 3 tests) Registered in core/cli.py "packs" suite. Suite results ------------- core test --suite teaching -q → 93 passed core test --suite runtime -q → 20 passed core test --suite packs -q → 51 passed core eval gsm8k_math --split public → 150/150, wrong=0 Truth-test rows (6-row binding table in dispatch pack): #1 Case 0019 admits ............. PARTIAL — see Scope Boundary below #2 Case 0050 stays refused ....... PASS #3 train_sample 3/47 → ≥4/46 ..... PARTIAL — same as #1 #4 wrong == 0 preserved .......... PASS #5 public split 150/150 .......... PASS #6 Empty-registry no-op .......... PASS Scope Boundary (honest finding) ------------------------------- Rows #1 and #3 (case 0019 admission) require a matcher extension that publishes ``composition_shape`` + a pre-composed CandidateInitial in parsed_anchors. The existing currency_amount / multiplicative_aggregation matchers in generate/recognizer_match.py are detection-only (return empty parsed_anchors). This PR ships the consumption infrastructure correctly but the runtime path remains dormant until a follow-up PR extends the matcher. The dispatch pack's truth test #1/#3 cannot fire without that extension. The wiring is positioned correctly: inject_from_match → consult composition_registry → admit on affirms-with-payload, suppress on falsifies, refuse on absence. A synthetic recognizer match with populated composition_shape + composed_initial DOES admit through the new path (covered by 6 tests in test_composition_consult_in_injector.py). A follow-up brief naming the matcher-extension work is the recommended next step. Anti-regression invariants verified ----------------------------------- - wrong == 0 on core eval gsm8k_math (public 150/150) - case 0050 stays refused (parametrized over allowlist categories) - ADR-0166 — no new eval lanes - ADR-0167 partition — no cognition imports in any new module - Empty-registry runtime byte-identical to today (no-op test) - SAFE_COMPOSITION_CATEGORIES enforced at write AND load - polarity semantics (affirms vs falsifies) honored - engine_state/* never committed
75 lines
2.5 KiB
Python
75 lines
2.5 KiB
Python
"""Partition test — consumption registries are math-domain only.
|
|
|
|
ADR-0167 partition: cognition ``TeachingChainProposal`` flow must not
|
|
see math composition/frame artifacts and vice versa.
|
|
|
|
The consumption registries (FrameRegistry, CompositionRegistry) read
|
|
exclusively from the en_core_math_v1 pack. They do not import from
|
|
cognition modules and cannot be reached via cognition code paths.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import ast
|
|
from pathlib import Path
|
|
|
|
|
|
def _module_imports(path: Path) -> set[str]:
|
|
tree = ast.parse(path.read_text(encoding="utf-8"))
|
|
out: set[str] = set()
|
|
for node in ast.walk(tree):
|
|
if isinstance(node, ast.Import):
|
|
for alias in node.names:
|
|
out.add(alias.name)
|
|
elif isinstance(node, ast.ImportFrom):
|
|
if node.module:
|
|
out.add(node.module)
|
|
return out
|
|
|
|
|
|
def _repo_root() -> Path:
|
|
here = Path(__file__).resolve()
|
|
while here.parent != here and not (here / "pyproject.toml").exists():
|
|
here = here.parent
|
|
return here
|
|
|
|
|
|
def test_frame_registry_imports_no_cognition_modules():
|
|
repo = _repo_root()
|
|
imports = _module_imports(
|
|
repo / "generate" / "comprehension" / "frame_registry.py"
|
|
)
|
|
cognition_taints = {
|
|
i for i in imports if i.startswith("cognition.") or i.startswith("teaching.cognition")
|
|
}
|
|
assert cognition_taints == set(), (
|
|
f"frame_registry imports cognition modules: {cognition_taints}"
|
|
)
|
|
|
|
|
|
def test_composition_registry_imports_no_cognition_modules():
|
|
repo = _repo_root()
|
|
imports = _module_imports(
|
|
repo / "generate" / "comprehension" / "composition_registry.py"
|
|
)
|
|
cognition_taints = {
|
|
i for i in imports if i.startswith("cognition.") or i.startswith("teaching.cognition")
|
|
}
|
|
# Math-side teaching imports are fine (math_composition_ratification);
|
|
# cognition-side teaching imports are not.
|
|
assert cognition_taints == set(), (
|
|
f"composition_registry imports cognition modules: {cognition_taints}"
|
|
)
|
|
|
|
|
|
def test_compile_modules_import_no_cognition():
|
|
repo = _repo_root()
|
|
for mod in ("compile_frames.py", "compile_compositions.py"):
|
|
imports = _module_imports(repo / "language_packs" / mod)
|
|
cognition_taints = {
|
|
i for i in imports
|
|
if i.startswith("cognition.") or i.startswith("teaching.cognition")
|
|
}
|
|
assert cognition_taints == set(), (
|
|
f"{mod} imports cognition modules: {cognition_taints}"
|
|
)
|