The proof_chain keystone: a hand-rolled ROBDD canonicalizer mirroring math_symbolic_equivalence one domain over (normalize -> canonical key -> byte-equality -> three-valued verdict; REFUSED preserves wrong=0). - generate/logic_canonical.py: formula -> ROBDD identity under sorted-atom ordering; LogicError/LogicBudgetError refusals; inspectable canonical key. - generate/logic_equivalence.py: EQUIVALENT/NOT_EQUIVALENT/REFUSED wrapper. - tests/test_logic_canonical.py: 33 standalone tests (canonicity laws, discrimination, terminals, determinism, refusals); mutation-verified non-vacuous. - ADR-0201: canonicalizer decision (ROBDD not CNF/DNF; hand-rolled; propositional-only honesty boundary). - ADR-0202: proposition representation contract — single source the canonicalizer and the proof corpus conform to (formula grammar + atom layer binding to ADR-0144 EpistemicNode + honesty boundary). Additive: no existing file touched, zero consumers. Standalone keystone only; binding-graph wiring, acyclicity refusal, and inference rules deferred. smoke: 67 passed.
9.3 KiB
ADR-0202 — Proposition Representation Contract (proof_chain)
Status: Accepted (normative contract — single source for the canonicalizer and the proof corpus) Date: 2026-06-02 Relates to: ADR-0201 (propositional canonicalizer — the formula layer's implementation), ADR-0144 (PropositionGraph epistemic carrier — the atom layer's home), ADR-0143/0142 (recognition outcome / epistemic state taxonomy), ADR-0132 (binding-graph data model — the proof DAG substrate), ADR-0131.3 (bounded-grammar word-problem lane — the eval-case shape this slots into).
Why this document exists
proof_chain introduces propositional formulas to CORE. Two producers must agree
on one representation or they diverge: the canonicalizer
(generate/logic_canonical.py) and the proof corpus authored in parallel.
CORE already carries four "proposition"-named structures — the articulation
Proposition/PropositionGraph (generate/), the ADR-0144 epistemic carrier
(recognition/), and the ADR-0132 symbolic-math binding graph. None is a
truth-functional propositional-logic formula representation. So the formula
language is net-new; but its atoms must ground to the existing epistemic carrier,
not float free — otherwise proof_chain becomes a fifth, disconnected proposition
dialect, the exact fragmentation ADR-0144 was created to resolve.
This contract is that single source. The canonicalizer is authoritative for the
formula grammar; any grammar change updates this doc in the same PR (mirroring
the docs/runtime_contracts.md discipline).
The representation is two layers:
- Atom layer (authoritative, existing) — atoms are declared stable symbol ids
that bind to ADR-0144
EpistemicNode/FeatureBundlecarriers. - Formula layer (net-new — ADR-0201) — truth-functional formulas over those atoms, canonicalized to ROBDD identity.
1. Formula layer — grammar (exact, from generate/logic_canonical.py)
1.1 Tokens
-
Atom: an identifier — first char
[A-Za-z_], subsequent[A-Za-z0-9_]. Atom ids are case-sensitive (P≠p). Reserved keywords (below) are not atoms. -
Constants:
true,false(keywords, case-insensitive). -
Operators — each kind has multiple accepted spellings (ASCII, doubled, unicode, keyword). All spellings of a kind are interchangeable and produce the identical canonical key:
Kind Spellings NOT (unary) not,~,!,¬AND and,&,&&,∧OR or,|,||,∨IMPLIES implies,->,→,⊃IFF iff,<->,↔,≡grouping (…)Keyword operators are matched case-insensitively (
AND=and). Whitespace is insignificant. Any character outside this grammar is a refusal (§3).
1.2 Precedence and associativity
Lowest → highest binding:
IFF < IMPLIES < OR < AND < NOT < atom / ( … )
IMPLIESis right-associative:P -> Q -> R≡P -> (Q -> R).IFF,OR,ANDare left-associative (associativity is semantically irrelevant under ROBDD, but the parse is fixed so errors are crisp).NOTis prefix unary. Parentheses override precedence.
1.3 Grammar (EBNF)
formula = iff ;
iff = implies , { IFF , implies } ;
implies = or , [ IMPLIES , implies ] ; (* right-assoc *)
or = and , { OR , and } ;
and = unary , { AND , unary } ;
unary = NOT , unary | atom ;
atom = ATOM | "true" | "false" | "(" , iff , ")" ;
1.4 Canonical form
A formula is canonicalized to a Reduced Ordered Binary Decision Diagram (ROBDD)
under the sorted-atom variable ordering (the atoms appearing in the formula,
sorted lexicographically). The reduced diagram is serialized to the
canonical_key string. Contract:
- Equivalence = byte-equality of
canonical_key. Two formulas are logically equivalent iff their keys are identical. - Tautology →
"T", contradiction →"F"(every tautology shares the keyTregardless of atoms; likewiseF). - Logically-irrelevant atoms are dropped from the support:
PandP ∧ (Q ∨ ¬Q)produce the same key;Qis not in the result'satoms. - The key is byte-deterministic across processes (structural serialization —
no object ids, no hashing, no dict-order dependence), satisfying the
trace_hashdiscipline. It is human-inspectable, not an opaque digest: e.g.(P→Q)∧(R∨¬S)∧P→0:S?F:T;1:R?T:@0;2:Q?@1:F;3:P?@2:F.
The key is the propositional twin of BoundEquation.rhs_canonical (ADR-0132): when
proof_chain wires to the binding graph, the canonical key occupies rhs_canonical,
the discharged premises occupy dependencies, and the inference rule occupies
operation_kind.
2. Atom layer — declared symbol ids that bind to the epistemic carrier
Atoms are not free-form prose. Each atom is a declared, stable symbol id
(matching the §1.1 atom grammar) that will bind to an ADR-0144 EpistemicNode
carrying a recognized FeatureBundle. A corpus case declares its atoms explicitly.
2.1 Declaration rules
- Atom ids are unique within a case; the same id denotes the same proposition
throughout that case. Recommended convention:
<Letter>[_<slug>], e.g.P_rains,Q_ground_wet. - Every atom declares a human-readable
gloss. - Where an atom maps to a recognizable fact, the case MUST record the intended
FeatureBundlebinding (the feature name→value mapping per ADR-0143/0144), so the corpus is future-compatible with the grounding-half wiring and needs no second pass when atom-grounding lands. The actualEpistemicNode.node_id(teaching_set_id:turn_index) is assigned at recognition time and is therefore not authored into the corpus; the binding resolves by matching the recorded feature mapping. - Atoms that are pure logical variables with no recognizable-fact referent (e.g.
abstract
P/Qin a rule-shape case) recordglossonly andbinding: null. This is allowed and expected for schematic cases.
2.2 Per-case atom block (normative shape)
{
"atoms": {
"P_rains": {
"gloss": "it is raining",
"binding": {
"features": { "agent": "sky", "relation": "is", "state": "raining" }
}
},
"Q_ground_wet": {
"gloss": "the ground is wet",
"binding": { "features": { "agent": "ground", "relation": "is", "state": "wet" } }
},
"R": { "gloss": "an abstract proposition", "binding": null }
},
"premises": ["P_rains -> Q_ground_wet", "P_rains"],
"conclusion": "Q_ground_wet",
"rule": "modus_ponens",
"expected": "provable"
}
features keys are the FeatureBundle feature names (ADR-0143 BoundFeature.name);
values are their bound values. The bundle's canonical sorted-by-name order is
enforced by FeatureBundle.__post_init__ at grounding time — authors need not
pre-sort. premises/conclusion/rule/expected fields compose with the
ADR-0131.3 bounded-grammar case shape; this contract governs only atoms and the
formula strings.
3. Honesty boundary (binding)
- Propositional logic only — finite Boolean atoms. In this regime the ROBDD is
canonical and equivalence is decidable, so the
wrong == 0soundness gate transfers intact. - No predicate / first-order / quantified logic. Equivalence over quantifiers
on infinite domains is undecidable; there is no ROBDD-style canonical form.
Do NOT claim
wrong == 0for quantified reasoning. A formula that requires quantifier reasoning is out of regime and must REFUSE (out_of_decidable_regime), not be silently dropped to a weaker check. Quantifier-free fragments and specific decidable theories are later, separately-scoped work, each with its own honest decidability claim. - Refusal-first, no approximation. The canonicalizer either returns the exact
canonical key or refuses:
- out-of-grammar input →
LogicError→REFUSED; - ROBDD exceeds the node budget →
LogicBudgetError(aLogicErrorsubclass) →REFUSED(canonicalization_budget_exceeded) — refuse rather than churn. Corpus cases that expect refusal must name the typed reason.
- out-of-grammar input →
4. Conformance checklist (corpus authors)
A case conforms to this contract iff:
- every formula uses only the §1 grammar — no invented connectives or spellings;
- every atom referenced in a formula is declared in the case's
atomsblock; - atom ids match the §1.1 atom grammar and are stable within the case;
- every declared atom carries a
gloss; recognizable-fact atoms carry an intendedFeatureBundlebinding, schematic atoms carrybinding: null; - no quantifiers, predicates, or function symbols appear;
- the expected outcome is one of
provable/not_provable/refused(with a typed reason forrefused); - equivalence/identity claims rely on the
canonical_key, never on formula surface string equality.
5. Source-of-truth rule
generate/logic_canonical.py is authoritative for the formula grammar and
canonical form. This document is authoritative for the atom layer and the honesty
boundary. Any change to the formula grammar updates §1 of this doc in the same
PR as the code change. The proof corpus conforms to this doc; it does not extend
the grammar or the atom convention on its own.