core/tests/test_composition_lower_logic.py
Shay 0711988b67 feat(composition): M1 — extract the logic is-a chain into a plan->lower->verify spine (byte-parity)
First phase of the domain-general compositional reading layer (the comprehension
coverage wall is composition — docs/analysis/comprehension-coverage-wall-map-2026-06-14.md).
M1 is a pure PARITY refactor on the flagship logic path, no new capability:

- new generate/composition/ package: LogicChainPlan (plan.py) + lower_logic_chain
  (lower_logic.py), the is-a chain lowering extracted BYTE-IDENTICALLY from
  determine.py::_verify_subsumption.
- _verify_subsumption now builds a plan -> lowers it -> routes through the UNCHANGED
  evaluate_entailment gate. The composer proposes structure; the sound ROBDD gate
  remains the sole wrong=0 firewall.
- only the logic instantiation ships; no math lowering, no join-op vocabulary
  (defer-substrate-vocab-commitment — the chain discriminates on its relational
  predicate, an explicit join-op field lands when a second op dispatches on it).

Verification: byte-identical lowering (3-reviewer adversarial panel: byte-parity PASS
char-for-char, wrong=0 PASS — the instance-of-fallacy refusal mutation-proven
load-bearing); 5 new non-vacuous lowering tests; 28 determine/transitive/consolidation
+ 163-test broad sweep (incl. gsm8k serving + ADR-0218) green; cognition eval 100%.
2026-06-14 15:42:24 -07:00

92 lines
3.1 KiB
Python

"""M1 — the composition spine's logic lowering, in isolation.
`lower_logic_chain` is the extracted (byte-identical) lowering from
`generate/determine/determine.py::_verify_subsumption`. These pin the EXACT
propositional theory it emits (so a wrong extraction is caught) and the
soundness-by-construction refusal of a mislabeled chain. The byte-PARITY of the
end-to-end determine path is covered by the unchanged determine/transitive tests.
"""
from __future__ import annotations
from dataclasses import dataclass
from generate.composition import LogicChainPlan, lower_logic_chain
@dataclass(frozen=True)
class _Fact:
"""Minimal duck-typed relational fact (the lowering reads only these two)."""
relation_predicate: str
relation_arguments: tuple[str, ...]
def test_member_chain_lowers_to_member_subset_theory() -> None:
# "Socrates is a man" (member) ∘ "man ⊆ mortal" (subset) → member(Socrates, mortal).
plan = LogicChainPlan(
predicate="member",
subject="socrates",
target="mortal",
member_fact=_Fact("member", ("socrates", "man")),
subset_path=(_Fact("subset", ("man", "mortal")),),
)
lowered = lower_logic_chain(plan)
assert lowered is not None
premises, query = lowered
# M(soc,man)=x0, S(man,mortal)=x1, sound rule (M∘S→M): (x0 & x1) -> M(soc,mortal)=x2.
assert premises == ("x0", "x1", "(x0 & x1) -> x2")
assert query == "x2"
def test_subset_chain_lowers_to_subset_subset_theory() -> None:
# "a ⊆ b", "b ⊆ c" → subset(a, c) by subset ∘ subset.
plan = LogicChainPlan(
predicate="subset",
subject="a",
target="c",
member_fact=None,
subset_path=(_Fact("subset", ("a", "b")), _Fact("subset", ("b", "c"))),
)
lowered = lower_logic_chain(plan)
assert lowered is not None
premises, query = lowered
# S(a,b)=x0, S(b,c)=x1, rule (S∘S→S): (x0 & x1) -> S(a,c)=x2.
assert premises == ("x0", "x1", "(x0 & x1) -> x2")
assert query == "x2"
def test_mislabeled_member_fact_refuses() -> None:
# A member_fact whose predicate is not "member" must NOT be lowered (it could be
# smuggled in as a subset edge → an unsound member∘member chain).
plan = LogicChainPlan(
predicate="member",
subject="socrates",
target="mortal",
member_fact=_Fact("subset", ("socrates", "man")), # wrong predicate
subset_path=(_Fact("subset", ("man", "mortal")),),
)
assert lower_logic_chain(plan) is None
def test_member_fact_in_subset_path_refuses() -> None:
# A "member" fact laundered into the subset_path must be refused.
plan = LogicChainPlan(
predicate="subset",
subject="a",
target="c",
member_fact=None,
subset_path=(_Fact("subset", ("a", "b")), _Fact("member", ("b", "c"))), # member smuggled in
)
assert lower_logic_chain(plan) is None
def test_wrong_arity_refuses() -> None:
plan = LogicChainPlan(
predicate="subset",
subject="a",
target="c",
member_fact=None,
subset_path=(_Fact("subset", ("a", "b", "extra")),), # arity 3
)
assert lower_logic_chain(plan) is None