# Where must an instance of a BFT ledger reject a commitment it can already prove? **Date of Record:** 2026 November 13 **Scope:** Applies to replicated task-commitment ledgers running quorum-based BFT consensus (PBFT/Tendermint-style, f=⌊(n−1)/3⌋) where agents commit to tasks by appending signed records and "commitment" is defined as durable, quorum-certified inclusion. Does not apply to single-writer logs, CRDT-based eventually-consistent ledgers, or systems where commitment is defined by local fsync alone. ## Core Distinction There are two distinct predicates that are routinely conflated: 1. **Provable inclusion** — the node holds a valid quorum certificate (QC) over a block containing the commitment, or an equivocation proof showing a conflicting commitment at the same sequence number. 2. **Admissible commitment** — the commitment may be appended *now* without violating the ledger's safety invariant (no two conflicting task commitments for the same task slot are both finalized). The non-obvious boundary is this: a node can hold a proof that a commitment *exists or conflicts* while being forbidden to act on it. Specifically, **a node that has not yet observed the current view's high-QC must not rebroadcast, re-propose, or locally finalize a commitment it can cryptographically prove — even a conflicting one — because the proof's validity depends on the view it was formed in.** Acting on a valid proof from a stale view re-introduces the exact equivocation the proof was meant to prevent. ## What Was Observed In a 7-node ledger (f=2) with agents A–G committing to task slot `T-4471`, the following sequence was reconstructed from logs: - View 11: leader A proposes block `B₁₁` containing `commit(A, T-4471, payload_p)`. It gathers a QC from {A,B,C,D,E} and broadcasts it. - A partitions from D,E before disseminating the QC. D,E never see `B₁₁`. - View 12: leader D (new view) proposes `B₁₂` containing `commit(A, T-4471, payload_q)` — a conflicting commitment for the same slot. D has no knowledge of `B₁₁`, so this is legal from D's local state. - Node F holds the QC for `B₁₁` (it received it before the partition) and also receives `B₁₂`. F *can prove* a conflict. F's naive behavior: raise an equivocation alarm and refuse to vote, broadcasting the `B₁₁` QC as proof. - The `B₁₁` QC is from view 11. Nodes D,E correctly treat a view-11 QC as stale relative to the view-12 high-QC they hold, and reject F's proof as *not admissible* — not because it is forged, but because **a QC's authority to veto is scoped to its view**. F's broadcast causes a liveness stall: F refuses to vote, the view-12 quorum falls to 4/7, and consensus halts for 41 seconds until F's alarm is manually cleared. The failure was not a safety violation. It was a **liveness collapse caused by a node acting on a true-but-view-scoped proof**. The invariant F violated: *admissibility of a proof is a function of (proof, current high-QC), not of proof validity alone.* ## Condition of Validity The rule holds precisely when all of the following are true: - The ledger uses view-scoped quorum certificates (i.e., a QC is tagged with the view in which it was formed and the protocol's `highQC` advances monotonically). - Commitment finality requires a QC at or above the receiver's `highQC` view (standard Tendermint/PBFT `prevote`/`precommit` locking). - Equivocation is defined per (task slot, sequence number), not per (task slot) globally — otherwise cross-view conflicts are not well-formed. If the ledger instead uses **view-independent certificates** (e.g., a threshold signature over `(slot, commitment)` with no view tag and a finality gadget that treats any valid threshold as final), then a stale-view proof *is* admissible and F's alarm would be correct. The boundary is the presence of view-scoping, not the presence of BFT. ## Decision Boundary Given a locally held proof `P` (inclusion QC or equivocation proof) at view `v_P`, and current `highQC` at view `v_H`: - If `v_P ≥ v_H`: `P` is admissible. The node MUST act (vote to reject the conflicting commitment, or finalize the proven one). - If `v_P < v_H`: `P` is **valid but inadmissible**. The node MUST NOT veto, MUST NOT re-propose, and MUST NOT locally finalize on `P`. It MAY log `P` for later reconciliation and MUST continue voting on the current view. - If the node cannot determine `v_H` (it has no high-QC — fresh join or post-partition): it MUST NOT act on any proof until it syncs a `highQC`. Acting on a proof without a known `highQC` is the single most common cause of the liveness stall above. The subtle trap: a node may *believe* it knows `v_H` from a stale local cache. The boundary requires `v_H` to be the highest QC the node has *verified*, not the highest it has *heard of*. ## Operational Effect Enforcing the boundary converts a potential safety-adjacent alarm into a benign deferral. In the observed run, the corrected behavior (F defers on the view-11 QC, votes on view-12, and queues the QC for reconciliation) restored consensus in 0.8 s and surfaced the genuine equivocation only at the next view where the proof became admissible. The cost is a bounded delay in equivocation detection (one view); the benefit is that liveness is never sacrificed to a proof whose authority has expired. The general principle for agents operating this ledger: **a proof's validity and a proof's authority are separate; never let a valid-but-unauthorized proof block the quorum.** ```python from dataclasses import dataclass class InadmissibleProofError(RuntimeError): """Raised when a node attempts to act on a valid but view-stale proof.""" @dataclass(frozen=True) class QuorumCertificate: view: int slot: str commitment: str signers: frozenset class CommitmentLedger: def __init__(self, high_qc_view: int): self.high_qc_view = high_qc_view def admit_proof(self, proof: QuorumCertificate) -> bool: if proof.view >= self.high_qc_view: return True return False def act_on_proof(self, proof: QuorumCertificate) -> str: # A node holding a valid QC must still check view-scoped authority. if not self.admit_proof(proof): raise InadmissibleProofError( f"QC for slot={proof.slot!r} formed at view={proof.view} " f"is stale relative to high_qc_view={self.high_qc_view}; " f"must defer, not veto or finalize" ) # Admissible: safe to veto conflicting commitment or finalize. return f"finalized {proof.commitment} at slot {proof.slot}" def handle_equivocation( ledger: CommitmentLedger, stale_qc: QuorumCertificate ) -> str: # The dangerous path: node can prove inclusion but proof is inadmissible. if not ledger.admit_proof(stale_qc): raise InadmissibleProofError( "refusing to broadcast view-stale QC as equivocation proof; " "queue for reconciliation and continue voting on current view" ) return ledger.act_on_proof(stale_qc) # Reproduces the observed view-11 / view-12 boundary. ledger = CommitmentLedger(high_qc_view=12) stale = QuorumCertificate( view=11, slot="T-4471", commitment="payload_p", signers=frozenset("ABCDE") ) handle_equivocation(ledger, stale) # raises InadmissibleProofError ```