core/evals/deductive_logic/contract.md
Shay a447dce5d1 feat: ratify independent-gold invariant (INV-25) + SHA-pin deductive lane
INV-25 makes the GSM8K lesson structural: no capability claim is valid unless
its gold is computed by a procedure sharing no code with the system under test.
Three meaningfully-failing checks (proven able to fail): 25a oracle imports no
SUT module (AST); 25b every committed deductive gold is reproduced by the
independent oracle AND matched by the engine; 25c an unsound engine disagrees
on committed cases.

SHA-pin the deductive lane (deductive_logic_v1, dev+holdout+external 716/716,
wrong=0, refused=0) via a deterministic --report writer + run_as_module (the
lane dir's local generate.py shadows the package in script mode) + CLAIMS regen.

Fix drift the review surfaced: contract.md + pivot doc claimed an 8,000/7,340
fuzz and an 'external mirror' -> corrected to the real gated 3,000-case fuzz
(2,796 definite) and 'hand-authored, NOT a published-benchmark mirror'; pivot
doc GSM8K holdout 0->5 (all 5 are R1 reconstruction; composer scored 0).

Validated: smoke 78, deductive 23, proof 29, invariants 44, lane-sha+claims 9.
2026-06-04 15:56:28 -07:00

5.3 KiB

deductive-logic eval lane (ADR-0206)

What it measures — and why it is honest

CORE's deterministic propositional entailment operator (generate.proof_chain.entail.evaluate_entailment): given premise formulas and a query formula, does the premise set entail, refute, or leave undetermined the query — or is the input outside the decidable regime?

This is the inference operator evals/symbolic_logic/gaps.md asked for ("no operator that takes A→B, B→C and returns A→C") and ADR-0205 deferred. It is built directly on the ADR-0201 ROBDD canonicalizer, so it is sound and complete for propositional logic, not single-step:

premises ⊨ query iff (⋀ premises) → query is a tautology (an exact ROBDD check).

Unlike the GSM8K lanes, capability here can show real correct numbers because the answer is checkable: this is the terrain CORE was built for (exact, verifiable, deterministic), not the multi-step natural-language arithmetic GSM8K is engineered to make stochastic.

The numbers (2026-06-04)

Set n correct wrong refused of which non-trivial (entailed + refuted)
dev 200 200 0 0 74
holdout v1 500 500 0 0 227 (117 entailed + 110 refuted)
external (hand-authored, illustrative) v1 16 16 0 0 12
fuzz (seed 424242, engine vs oracle) 2,796 2,796 0 0 1,241

(The fuzz row is the deterministic test_engine_matches_independent_oracle_fuzz gate: 3,000 random formulas, 2,796 of them in-regime/definite, 0 engine-vs-oracle disagreements; a complementary 4,000-case fuzz exercises inconsistent-premise refusal. The property holds far more broadly than the committed gate — these are the numbers CI actually pins, not a headline count.)

100% correct with wrong = 0 and refused = 0 on committed in-regime cases, and the correct answers include hundreds of non-trivial multi-hop entailments and refutations — not just unknown pass-throughs.

Why the gold is trustworthy (the GSM8K lesson applied)

The gold is not computed by the engine under test. It comes from an independent truth-table decision procedure (oracle.py): a separate parser + brute-force model enumeration over all 2^k assignments, sharing no code with the ROBDD engine. Two independently-coded sound procedures agreeing on every committed case plus a deterministic 3,000-case fuzz (2,796 definite, zero disagreements) is real soundness evidence — exactly the property the GSM8K composer could never establish (it could not tell its 2 right answers from its 87 wrong ones). A single engine/oracle disagreement fails the test suite. A refusal on a committed in-regime case is also a lane failure; refusal-boundary cases are tested separately.

Honesty boundary (load-bearing — do not overclaim)

  • Propositional only. Atoms are opaque Boolean variables. Quantified / predicate input (forall x. P(x), rough(x)) is out of regime and refuses (typed, by design — ADR-0201.1), never guessed. A finite-entity problem grounded to per-(entity,predicate) atoms is propositional and in scope; an ungrounded first-order rule is not.
  • This is given formulas, not natural language. The operator decides logic; it does not yet read English word-problems into logic. NL→formula grounding is a separate, later layer (and the place the GSM8K rake lives — kept out of scope here deliberately).
  • wrong = 0 is the floor, not a bragging point. It is structural (an exact tautology check refuses rather than approximates). The capability claim is the coverage with wrong=0: hundreds of correct non-trivial deductions on held-out data, independently verified.

Outcomes (closed vocabulary)

outcome meaning
entailed (⋀P) → Q is a tautology — Q holds in every model of the premises
refuted (⋀P) → ¬Q is a tautology — Q fails in every model
unknown neither — Q is true in some premise-models and false in others
refused premises inconsistent (no model) or malformed / out-of-regime

Run

PYTHONPATH=. .venv/bin/python -m evals.deductive_logic.runner    # dev + holdout + external; exits 1 unless every in-regime case is correct
PYTHONPATH=. .venv/bin/python -m evals.deductive_logic.generate  # regenerate committed cases (deterministic)
PYTHONPATH=. .venv/bin/python -m pytest tests/test_deductive_logic_entail.py -q

Data layout

evals/deductive_logic/
  contract.md
  oracle.py            # independent truth-table gold (no shared code with the engine)
  generate.py          # deterministic, seeded; gold from oracle
  runner.py            # engine vs gold; correct/wrong/refused + class breakdown
  dev/cases.jsonl      # seed 20260604, 200 cases
  holdout/v1/cases.jsonl  # seed 70260604, 500 cases (disjoint)
  external/v1/cases.jsonl # frozen hand-authored propositional cases (illustrative; NOT a published-benchmark mirror)
  refusal/v1/cases.jsonl  # refusal-boundary cases, tested separately