core/docs/adr/ADR-0201-proposition-canonicalizer.md
Shay 54e6bfc0d0
docs: reorganize docs landscape
Implements the 4-phase documentation reorganization master plan.

- Consolidation: Merged brief/, handoff/, planning/, and decisions/ into briefs/, handoffs/, plans/, and adr/ respectively (101 ADRs relocated)
- Root Cleanup: Relocated HANDOFF-gpt55-*.md and key top-level docs (runtime_contracts.md, etc.) to canonical folders. Added superseded alerts.
- Indices & Navigation: Created docs/README.md navigation document, docs/sessions/README.md index, docs/adr/README.md index
- Note: Also includes prior commit adding ADR-0200+ corpus hygiene governance (ADR-0225, dependency map, backfilled cross-references)
2026-06-30 16:59:36 -07:00

8 KiB
Raw Blame History

ADR-0201 — Propositional Canonicalizer (the proof_chain keystone)

Status: Proposed (Phase 1 of proof_chain; standalone keystone shipped, not yet wired)

Date: 2026-06-02

Relates to: ADR-0131.1.B (math_symbolic_equivalence — the sibling pattern this mirrors), ADR-0132/0133/0134/0135 (binding-graph data model / adapter / admissibility / question-target — the future consumer), the wrong == 0 self-verification doctrine in generate/derivation/verify.py.

Context

CORE has confirmed three things about building proof_chain as a real reasoning primitive (not a declared label):

  1. The ledger "operators" (proof_chain/causal/modal) are classification labels, not executors — proof_chain is green-field.
  2. The wrong == 0 self-check is soundness, not correctness: it fires only when grounded+licensed derivations collapse to one unique canonical conclusion and rivals are checked for agreement. It needs a canonical, comparable conclusion. For arithmetic, exact numeric equality gave that for free.
  3. The ADR-0132 binding graph is already the DAG substrate proof trees need (BoundEquation.dependencies + per-node admissibility + provenance), with a shipped, hand-rolled sibling — math_symbolic_equivalence — that already demonstrates the normalize → canonical-string → byte-equality → three-valued-verdict-with-REFUSED discipline for algebra.

Logic does not get a comparable canonical conclusion for free: two syntactically different formulas can be logically equivalent (P∧Q ≡ Q∧P, ¬¬P ≡ P, P→Q ≡ ¬PQ). Without a canonical form, the uniqueness/disagreement rule cannot fire and proof_chain degrades from sound to merely cautious. This ADR scopes the canonical form — the keystone everything else (rule checkers, the disagreement rule) depends on.

Decision

Canonicalize a propositional formula to a Reduced Ordered Binary Decision Diagram (ROBDD) under a fixed (sorted-atom) variable ordering, and use a canonical string serialization of the reduced diagram as the byte-equality discriminator (the logic analog of Polynomial.to_canonical_string()).

  • ROBDD, not CNF/DNF. For a fixed ordering the ROBDD is canonical — two formulas are logically equivalent iff their reduced diagrams are isomorphic. CNF/DNF are merely normal (standardized shape), not canonical, and have no poly-time equivalence-preserving transform. Free bonuses for later: tautology = the 1-terminal, contradiction = the 0-terminal, f→g valid iff apply(f, ¬g, ∧) = the 0-terminal — so contradiction and proof "conclusion follows" reduce to ROBDD checks.
  • Hand-rolled minimal, no external BDD library (operator-confirmed). Stays in CORE's idiom (the symbolic substrate is entirely hand-rolled), deterministic by construction, fully inspectable, zero opaque dependencies. ~370 LOC: tokenizer + recursive-descent parser + mk/apply/negate + unique table + canonical serialization.
  • wrong == 0 discipline preserved. No approximate path. Out-of-grammar input raises LogicError; a diagram exceeding the node budget raises LogicBudgetError (a LogicError subclass, so callers refusing on LogicError refuse on budget too). Both surface as a REFUSED verdict — refuse rather than guess or churn.

Honesty boundary (stated, not hidden)

  • Propositional logic (finite Boolean variables): canonical and decidable. ROBDD gives a unique form + constant-time equivalence. The full soundness gate transfers. This is the only regime this module claims.
  • Cost caveat: ROBDD size can be exponential in the worst case and is ordering-sensitive. Canonicity is cheap to compare but not always cheap to build. For bounded proof-step propositions (a handful of atoms) this is a non-issue; the node budget refuses on adversarial blowup rather than hanging.
  • Predicate / first-order logic: NOT canonical in general — undecidable. There is no ROBDD-style canonical form for full FOL. We do NOT claim wrong == 0 for quantified reasoning with this machinery. Quantifier-free fragments and specific decidable theories are later, separately-scoped steps, each with their own honest decidability claim.

What shipped in this phase (standalone)

  • generate/logic_canonical.pycanonicalize(formula, *, max_nodes) -> CanonicalProposition{canonical_key, atoms, is_tautology, is_contradiction}; LogicError / LogicBudgetError.
  • generate/logic_equivalence.pycheck_equivalence(a, b) -> EquivalenceVerdict{EQUIVALENT|NOT_EQUIVALENT|REFUSED} (close mirror of math_symbolic_equivalence).
  • tests/test_logic_canonical.py — 33 standalone tests: canonicity laws (commutativity, double-negation, De Morgan, implication rewrite, distributivity, absorption, irrelevant-variable elision), discrimination (non-equivalent → distinct keys), terminal collapse, byte-determinism, operator-spelling parity, and the refusal paths (malformed → LogicError; budget blowup → LogicBudgetError).

Tested in isolation, exactly as the sibling is standalone — proving the keystone holds alone before anything depends on it.

Proof obligation (per CLAUDE.md §Schema-Defined Proof Obligations)

The canonicity tests must be able to meaningfully fail. Verified by mutation: disabling the redundant-node reduction rule (low == high → low) flips P ∧ (Q ¬Q) ≡ P to false, failing test_irrelevant_variable_is_dropped_from_support. The equivalent-pairs and non-equivalent-pairs suites are mutually constraining: a collapse-everything canonicalizer fails discrimination; a no-reduction canonicalizer fails equivalence. The suite is non-vacuous by construction.

Explicitly deferred (NOT in this phase)

  • Binding-graph wiring. proof_chain would be the binding graph's first consumer — there is no existing graph-builder→serving path to join. The integration is net-new wiring, scoped separately. The canonical key is designed to drop into BoundEquation.rhs_canonical (a string field) when that wiring is built.
  • The acyclicity refusal. A cycle in a proof DAG is circular reasoning; the binding graph currently checks referential integrity but not cycles. The circular_dependency refusal is net-new and must land before the structure is load-bearing — not in this standalone phase.
  • Inference rules. No operation_kind logic vocab and no _check_modus_ponens yet. One sound rule (modus_ponens) + the disagreement rule on the canonical key is the next phase, once this keystone is accepted.

Alternatives considered

  • CNF/DNF canonical string — rejected: not canonical (clause/literal ordering is non-unique), and no poly-time equivalence-preserving transform exists.
  • External BDD library (dd / CUDD) — rejected: the only opaque dependency in an otherwise hand-rolled substrate; determinism/trace_hash risk from hash-based node ids and reordering heuristics; CUDD is a C build/footprint cost; and the canonical-string serialization would still need to be hand-controlled for determinism, so the library does not save the load-bearing work.

Governance Cross-Reference (ADR-0225)

This late-corpus ADR is governed by ADR-0225:

  • Safety boundaries: changes must preserve ADR-0027/0028/0029 identity and safety-pack boundaries; no identity, safety, or policy mutation is implied unless explicitly reviewed.
  • Versor closure: runtime field paths must preserve versor_condition(F) < 1e-6; this ADR does not authorize hidden normalization or hot-path drift repair.
  • Reconstruction-over-storage: evidence must remain reconstructive and content-addressed rather than duplicating opaque state.
  • Replay-equivalence: serving, teaching, promotion, or checkpoint changes require a named deterministic replay / byte-equivalence gate.
  • Mutation standing: any durable corpus, pack, policy, or epistemic-status mutation remains reviewed, proposal-only until accepted, or proof-carrying as applicable.