* feat(determine): add transitive strict-order relational inference Add sound transitive closure for declared strict-order relational predicates — less_than, greater_than, before_event, after_event, and ONLY these. A query p(a, c) determines True when a same-predicate chain p(a, b), p(b, c), ... over the predicate's OWN realized edges is found (BFS reachability) and the proof_chain ROBDD verifies the transitive entailment (search-then-verify, through the UNCHANGED evaluate_entailment via a new lower_transitive_chain). Mirrors _determine_subsumption. Scope / firewall: - TRANSITIVE_PREDICATES is closed and default-off; sibling_of, parent_of, left_of/right_of, inside_of, during_event, overlaps_event are EXCLUDED. - Same-predicate edges only — no transitive-through-inverse, no cross-predicate (triple firewall: recall filter + lowering re-reject + ROBDD re-verify). - Open-world: asserts only answer=True via the shared _relational_determined surface (rule="transitive"), so INV-30 stays at exactly 3 Determined sites and no answer=False is added. One-hop inverse/symmetric (#775) is unchanged. FrameVerdict / closed-world (ADR-0222) is untouched. wrong=0 bite (unit + lane + independent oracle): non-transitive-predicate chains, non-admitted spatial chains, mixed-predicate chains, disjoint chains, reflexive cycles, and inverse+transitive composition all REFUSE. Measurement: new evals/relational_transitive lane with an INDEPENDENT BFS transitive-closure oracle (imports no engine module; INV-25/27), cross-checked both directions (positives oracle-True, confusers oracle-False). Capability-index breadth 10 -> 11, wrong_total 0, deterministic digest re-frozen. Cross-PR reconcile: migrated the one-hop confuser rinf-ref-001 (a less_than chain, which now correctly determines transitively) to a B2 positive; repurposed its unit test to a non-transitive parent_of chain; corrected a pre-existing stale breadth==9 assertion (#775 added a 10th domain but never updated test_capability_index). Verified in the worktree: determine + transitive/one-hop lanes + capability baseline/index + ProofWriter-OWA floor + INV-30/29/21/02 + full smoke — all green. A 3-skeptic read-only adversarial review found no wrong=0 leak. * test(determine): B2 hardening — TRANSITIVE_PREDICATES pin + lower_transitive defensive + budget Add the required B2 hardening tests before merge: - test_transitive_predicates_closed_and_excludes: the table is EXACTLY {less_than, greater_than, before_event, after_event}, every member is in RELATIONAL_PREDICATES, and every deliberately-excluded predicate (sibling_of, spouse_of, parent_of, child_of, left_of, right_of, inside_of, during_event, overlaps_event) is explicitly absent. (Closes the gap where relational.py's comment claimed this test existed but it did not.) - tests/test_composition_lower_transitive.py: direct defensive tests on lower_transitive_chain — empty path, mislabeled (cross-predicate) edge, wrong arity, non-contiguous path, and path-not-reaching-target all lower to None (refuse); plus exact 2-hop and 3-hop theory pins. - test_edge_budget_exhaustion_refuses: above _TRANSITIVE_EDGE_BUDGET the transitive search declines (a safe coverage refusal), never proves.
142 lines
5.1 KiB
Python
142 lines
5.1 KiB
Python
"""Per-lane adapters — normalize each independent-gold lane to a DomainResult.
|
|
|
|
These are thin COUNT extractors, not capability logic: each calls a lane's own
|
|
self-loading runner and reads its correct/wrong/refused counts. A lane that fails
|
|
to run is recorded as ``not_covered`` (no silent drop), never faked.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
from evals.capability_index.index import DomainResult
|
|
|
|
|
|
def _counts(report: dict) -> tuple[int, int, int]:
|
|
c = report.get("counts", report)
|
|
return int(c["correct"]), int(c["wrong"]), int(c["refused"])
|
|
|
|
|
|
def deductive_logic_result() -> DomainResult:
|
|
from evals.deductive_logic.runner import build_combined_report
|
|
|
|
agg = build_combined_report()["aggregate"] # {n, correct, wrong, refused}
|
|
return DomainResult(
|
|
"deductive_logic", int(agg["correct"]), int(agg["wrong"]), int(agg["refused"])
|
|
)
|
|
|
|
|
|
def relational_metric_result() -> DomainResult:
|
|
from evals.relational_metric.runner import run
|
|
|
|
r = run()
|
|
return DomainResult(
|
|
"relational_metric", int(r["correct"]), int(r["wrong"]), int(r["refused"])
|
|
)
|
|
|
|
|
|
def dimensional_result() -> DomainResult:
|
|
from evals.dimensional.runner import _ROOT, _load, build_report
|
|
|
|
correct, wrong, refused = _counts(build_report(_load(_ROOT / "v1" / "cases.jsonl")))
|
|
return DomainResult("dimensional", correct, wrong, refused)
|
|
|
|
|
|
def comprehension_set_membership_result() -> DomainResult:
|
|
from evals.comprehension.set_membership_runner import run
|
|
|
|
c, w, r = _counts(run())
|
|
return DomainResult("comprehension_set_membership", c, w, r)
|
|
|
|
|
|
def comprehension_syllogism_result() -> DomainResult:
|
|
from evals.comprehension.syllogism_runner import run
|
|
|
|
c, w, r = _counts(run())
|
|
return DomainResult("comprehension_syllogism", c, w, r)
|
|
|
|
|
|
def comprehension_total_ordering_result() -> DomainResult:
|
|
from evals.comprehension.total_ordering_runner import run
|
|
|
|
c, w, r = _counts(run())
|
|
return DomainResult("comprehension_total_ordering", c, w, r)
|
|
|
|
|
|
def comprehension_propositional_result() -> DomainResult:
|
|
from evals.comprehension.propositional_runner import run
|
|
|
|
c, w, r = _counts(run())
|
|
return DomainResult("comprehension_propositional", c, w, r)
|
|
|
|
|
|
def comprehension_relational_metric_result() -> DomainResult:
|
|
from evals.comprehension.relational_metric_runner import run
|
|
|
|
c, w, r = _counts(run())
|
|
return DomainResult("comprehension_relational_metric", c, w, r)
|
|
|
|
|
|
def comprehension_relational_predicate_result() -> DomainResult:
|
|
from evals.comprehension.relational_predicate_runner import run
|
|
|
|
c, w, r = _counts(run())
|
|
return DomainResult("comprehension_relational_predicate", c, w, r)
|
|
|
|
|
|
def comprehension_relational_inference_result() -> DomainResult:
|
|
from evals.comprehension.relational_inference_runner import run
|
|
|
|
c, w, r = _counts(run())
|
|
return DomainResult("comprehension_relational_inference", c, w, r)
|
|
|
|
|
|
def comprehension_relational_transitive_result() -> DomainResult:
|
|
from evals.relational_transitive.runner import run
|
|
|
|
c, w, r = _counts(run())
|
|
return DomainResult("comprehension_relational_transitive", c, w, r)
|
|
|
|
|
|
#: The reasoning domains currently composed into the index (self-loading lanes).
|
|
#: The six ``comprehension_*`` lanes score the GENERAL comprehension reader; the
|
|
#: relational_metric one reads arithmetic prose into the binding-graph quantity
|
|
#: substrate (admissibility-checked) and projects to the arithmetic oracle, and the
|
|
#: relational_predicate one (#596) reads binary-relation prose into pack-named
|
|
#: predicates, the relational_inference one DETERMINES one-hop inverse/symmetric
|
|
#: entailments (mastery-v2 Step 3), and the relational_transitive one DETERMINES
|
|
#: same-predicate transitive closure over the declared strict orders (mastery-v2 Step-3
|
|
#: Brief B2) — so the index measures comprehension breadth across categorical, ordering,
|
|
#: propositional, quantitative, relational-reading, one-hop relational-inference, AND
|
|
#: transitive relational-inference reasoning.
|
|
ADAPTERS = (
|
|
deductive_logic_result,
|
|
relational_metric_result,
|
|
dimensional_result,
|
|
comprehension_set_membership_result,
|
|
comprehension_syllogism_result,
|
|
comprehension_total_ordering_result,
|
|
comprehension_propositional_result,
|
|
comprehension_relational_metric_result,
|
|
comprehension_relational_predicate_result,
|
|
comprehension_relational_inference_result,
|
|
comprehension_relational_transitive_result,
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class Collection:
|
|
results: tuple[DomainResult, ...]
|
|
not_covered: tuple[tuple[str, str], ...] # (adapter_name, error) — no silent drop
|
|
|
|
|
|
def collect_domain_results() -> Collection:
|
|
"""Run every adapter; surface any that fail rather than dropping them."""
|
|
results: list[DomainResult] = []
|
|
not_covered: list[tuple[str, str]] = []
|
|
for adapter in ADAPTERS:
|
|
try:
|
|
results.append(adapter())
|
|
except Exception as exc: # noqa: BLE001 — surfacing is the contract
|
|
not_covered.append((adapter.__name__, repr(exc)))
|
|
return Collection(results=tuple(results), not_covered=tuple(not_covered))
|