# Why does an instance of Byzantine fault-tolerant consensus fail to commit when the quorum intersection is preserved but equivocating replicas split the view across a task boundary **Date of Record:** 2026 November 14 **Scope:** Applies to BFT consensus protocols (PBFT-family, HotStuff-family, Tendermint-style) used to commit multi-agent task assignments and shared state where replicas may equivocate (send conflicting messages to different quorums). Does not apply to crash-fault-tolerant protocols, nor to single-shot consensus where the task boundary does not span view changes. ## Core Distinction The standard safety argument for BFT consensus rests on quorum intersection: any two quorums of size `2f+1` (out of `3f+1`) intersect in at least `f+1` replicas, of which at most `f` are Byzantine, so at least one honest replica appears in both. This guarantees that two conflicting decisions cannot both be certified. The non-obvious boundary is this: **quorum intersection guarantees agreement on *which value was decided*, but it does not guarantee that a *task commitment* survives a view change when the committed value is a tuple `(task_id, epoch, payload)` and equivocating replicas present different `epoch` components to different quorums.** Two quorums can each certify a value with the *same* `task_id` and *same* `payload` but *different* `epoch` fields. Intersection holds, no safety violation is detectable by value comparison, yet the agents receiving these certificates disagree about whether the task is "already committed" or "commitment is stale and must be re-proposed." The system enters a state where every honest replica believes a commit occurred, but no honest replica believes *its* commit is the one the others hold — liveness collapses without any apparent safety violation. ## What Was Observed In a 7-replica (f=2) HotStuff variant coordinating 12 agent task assignments, a single Byzantine replica `B` equivocated during a view change from view `v` to `v+1`. It sent a `NEW-VIEW` message carrying the committed `(task_id=T7, epoch=v, payload=P)` to quorum Q1 (replicas {1,2,3,4,5}) and a `NEW-VIEW` carrying `(task_id=T7, epoch=v+1, payload=P)` to quorum Q2 (replicas {3,4,5,6,7}). Both quorums certified. Honest replicas in Q1\Q2 (i.e., {1,2}) locked on epoch `v`; honest replicas in Q2\Q1 ({6,7}) locked on epoch `v+1`; the intersection {3,4,5} held both certificates and reported "T7 committed" to their local agents. The result: agents attached to replicas {1,2} executed T7 and reported completion at epoch `v`. Agents attached to {6,7} executed T7 and reported completion at epoch `v+1`. The coordination layer, comparing epoch fields, treated these as *distinct* commitments and refused to deduplicate — T7 was executed twice with non-idempotent side effects (two external API calls, two ledger entries). No replica ever violated quorum intersection. No safety proof was broken. The failure lived entirely in the gap between "agreement on value" and "agreement on commitment identity." A second observation: when the payload was *not* identical (B equivocated with `payload=P` to Q1 and `payload=P'` to Q2, where `P ≠ P'` but both were valid for T7), quorum intersection *did* catch it — the intersection replicas reported conflicting certificates and the protocol halted correctly. The failure is specific to the case where the *semantic* content agrees but the *commitment metadata* differs. ## Condition of Validity This boundary condition is active if and only if all of the following hold: 1. The committed value is a structured tuple whose identity for deduplication purposes includes a non-payload field (epoch, view number, sequence number, or round) that can differ across quorums while the payload agrees. 2. Equivocating replicas can construct two certificates with distinct metadata but identical payloads, each valid under the protocol's signing rules. 3. The application layer deduplicates on the full tuple rather than on a canonical projection (e.g., `(task_id, payload_hash)`). 4. There is no post-commit reconciliation phase that forces all honest replicas to agree on a single canonical epoch for a given `(task_id, payload)`. If any of (1)–(4) is absent, the failure does not manifest. In particular, protocols that commit a canonicalized value (hashing only `task_id || payload`) and treat epoch as a monotonic *hint* rather than part of identity are immune. ## Decision Boundary When integrating BFT consensus with multi-agent task commitment, the operator must choose between: - **Identity-on-payload (safe, lower throughput):** Define commitment identity as `hash(task_id || payload)` and treat epoch/view as advisory. Deduplicate on this projection. This eliminates the failure but requires that payloads be deterministic and that re-proposals of the same task be genuinely idempotent. Cost: cannot distinguish a legitimate re-commitment after a genuine abort from a stale one without an external abort log. - **Identity-on-tuple (fast, unsafe at the boundary):** Include epoch in identity. This is correct only if the protocol guarantees that no two certified values for the same `task_id` can carry different epochs — which requires an *additional* protocol invariant (e.g., a commit-certificate that references the highest epoch seen by any quorum, checked at commit time). Without that invariant, this choice is silently unsafe. The decision boundary is not "BFT vs. not BFT." It is "does your commit certificate bind the epoch to the payload at certification time, or does it merely accompany it?" If the certificate signs `(task_id, payload, epoch)` as an atomic unit and the protocol rejects any certificate whose epoch is less than the highest epoch the replica has seen for that `task_id`, the boundary is closed. Otherwise it is open. ## Operational Effect The observable signature is a *liveness stall with double-execution*: the consensus layer reports all replicas as healthy, quorum intersection holds in every view, no safety alarm fires, yet downstream agents execute the same task more than once and the coordination layer refuses to converge on a single completion record. Recovery requires an out-of-band reconciliation pass that selects a canonical epoch (typically the maximum) and replays idempotency checks — which is only possible if the payload was idempotent, a property the protocol never checked. The operational lesson: **quorum intersection is a property of sets, not of commitments.** It tells you that two quorums share a member; it does not tell you that the shared member will agree on the *identity* of what was agreed. When the committed value carries metadata that is not bound into the certificate, equivocation can split identity without splitting value, and the safety proof's conclusion ("at most one value is decided") is true while the operational requirement ("at most one commitment is acted upon") is false. ```python import hashlib from dataclasses import dataclass class CommitmentIdentityError(RuntimeError): """Raised when a commit certificate's identity projection is ambiguous.""" pass @dataclass(frozen=True) class CommitCertificate: task_id: str payload: bytes epoch: int signatures: tuple # quorum signatures over (task_id, payload, epoch) def canonical_identity(cert: CommitCertificate) -> str: """Identity used for deduplication. Must NOT include epoch.""" return hashlib.sha256(cert.task_id.encode() + b"|" + cert.payload).hexdigest() def reconcile(certs: list[CommitCertificate]) -> CommitCertificate: """ Given certificates observed by a replica for the same task, return the canonical one. Raises if the certificates disagree on payload (a real safety violation) but tolerates epoch disagreement (the equivocation boundary) by selecting the maximum epoch. """ if not certs: raise CommitmentIdentityError("no certificates to reconcile") identities = {canonical_identity(c) for c in certs} if len(identities) > 1: # Different payloads for the same task_id: genuine safety violation. raise CommitmentIdentityError( f"conflicting payloads for task {certs[0].task_id}: {identities}" ) # Same payload, possibly different epochs: pick canonical max epoch. canonical = max(certs, key=lambda c: c.epoch) # Sanity: every certificate must carry a quorum signature set. for c in certs: if len(c.signatures) < 3: raise CommitmentIdentityError( f"certificate for {c.task_id}@epoch={c.epoch} lacks quorum" ) return canonical def commit_task(certs: list[CommitCertificate], executor) -> str: """ Commit a task exactly once. The executor is invoked at most once per canonical identity, regardless of how many epoch-variant certificates were observed. """ canonical = reconcile(certs) identity = canonical_identity(canonical) executor.execute_once(identity, canonical.payload) return identity ```