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)
8 KiB
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):
- The ledger "operators" (
proof_chain/causal/modal) are classification labels, not executors —proof_chainis green-field. - The
wrong == 0self-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. - 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 thenormalize → canonical-string → byte-equality → three-valued-verdict-with-REFUSEDdiscipline 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 ≡ ¬P∨Q). 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→gvalid iffapply(f, ¬g, ∧)= the 0-terminal — socontradictionand 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 == 0discipline preserved. No approximate path. Out-of-grammar input raisesLogicError; a diagram exceeding the node budget raisesLogicBudgetError(aLogicErrorsubclass, so callers refusing onLogicErrorrefuse on budget too). Both surface as aREFUSEDverdict — 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 == 0for 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.py—canonicalize(formula, *, max_nodes) -> CanonicalProposition{canonical_key, atoms, is_tautology, is_contradiction};LogicError/LogicBudgetError.generate/logic_equivalence.py—check_equivalence(a, b) -> EquivalenceVerdict{EQUIVALENT|NOT_EQUIVALENT|REFUSED}(close mirror ofmath_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_chainwould 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 intoBoundEquation.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_dependencyrefusal is net-new and must land before the structure is load-bearing — not in this standalone phase. - Inference rules. No
operation_kindlogic vocab and no_check_modus_ponensyet. 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_hashrisk 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.