* 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.
2.6 KiB
relational_transitive / v1 — provenance
Hand-authored gold for the transitive strict-order relational inference DETERMINE capability (mastery-v2 Step-3, Brief B2). Not derived from any corpus; each case is a minimal, fully-grounded scenario authored to exercise one sound transitive closure (or one unsound chain that must refuse).
What this lane measures
determine() may close a declared strict-order predicate transitively over its OWN
realized edges: p(a, b) ∧ p(b, c) ⊨ p(a, c). The admitted predicates are exactly the
four strict orders in generate.meaning_graph.relational.TRANSITIVE_PREDICATES:
less_than,greater_than(numeric comparison)before_event,after_event(event precedence)
Closure is same-predicate only — it never composes inverse/symmetric/other-predicate
edges (that mixing stays one-hop), and it is open-world (asserts only answer=True, never
answer=False). Search-then-verify: BFS reachability proposes a chain; the sound+complete
proof_chain ROBDD verifies the transitive entailment before any assertion.
Files
cases.jsonl— positive coverage. Each{facts, query, edges, query_edge}mustdetermine()toanswer=True,predicate/subject/object == query_edge,rule="transitive". This is the capability-index coverage (domaincomprehension_relational_transitive, breadth 10→11).refusals.jsonl— the wrong=0 bite (Brief-C adversarial confusers). Each must REFUSE (Undetermined); aDeterminedmeans the transitive rule over-fired. Covers: non-transitive predicates (sibling_of,parent_of), non-admitted spatial predicates (left_of), mixed-predicate chains, reflexive cycles, disjoint chains, and inverse+transitive composition (out of scope for this slice).
Independent oracle (INV-25 / INV-27)
evals/relational_transitive/oracle.py::transitively_entails computes the gold from each
case's structured edges by its OWN BFS over its OWN declaration of the transitive set
— importing NO engine module. The lane test cross-checks every case: positives must be
oracle-True, refusals oracle-False. The oracle scopes to the same capability (same-predicate
closure) by design; independence is in the separate authoring, so a divergence (the engine
over- or under-firing relative to the declared capability) surfaces as wrong > 0 or a
broken cross-check.
Determinism
No clock, no sampling, no LLM. The entity ids are the reader's canonical lowercased forms
(Alice → alice, the box → box). The fixture SHAs are pinned in
tests/test_relational_transitive_lane.py so the gold cannot drift silently.