P3.1.2 — Quorum Computation — Audit

Step 1 of the 5-step chain (CLAUDE.md §6). Phase 3 θ Wave 2 — the BFT quorum primitive every downstream θ slice consumes (finality SM, view-change, equivocation, governance).

§1. Surface inventory at base SHA e63a8bcf

Path Exists? Role
src/domains/consensus/ Yes (P3.1.1, e63a8bcf) Existing θ domain directory; only houses messages.ts today
src/domains/consensus/messages.ts Yes (P3.1.1) REUSEVote type, hashMessage() for canonical-tuple grouping
src/domains/consensus/quorum.ts No — to create Pure BFT-quorum module
src/__tests__/domains/consensus/quorum.test.ts No — to create Worked-table fixture + property tests + n=1 single-arbiter
src/__tests__/domains/consensus/messages.test.ts Yes (P3.1.1) Reference for module-corpus pattern
src/domains/rules/canonical.ts Yes (κ P1.5.4) Transitive — messages.ts already wraps it
src/domains/rules/integer-math.ts Yes (κ P1.1.1) Reference for bigint-only convention
src/__tests__/domains/rules/determinism.test.ts Yes (κ P1.1.2) Reference for property-test pattern (random sampling over bigint domain)

No source path collides with the new file. The audit confirms a clean greenfield slice: zero edits to existing modules. P3.1.1’s exported Vote interface is consumed via type-only import.

§2. Spec inventory

2.1 Quorum formula (consensus.md §Quorum formula §20–40)

quorum = floor(2n / 3) + 1
f      = floor((n - 1) / 3)

The arithmetic is integer (BPS / bigint). For any n:

quorum + f = n + 1 - (n mod 3 == 0 ? 1 : 0)

This is the honest-majority invariant any two quorums must intersect in.

2.2 Worked table (consensus.md §Quorum math §31–38)

n f quorum Notes
4 1 3 Minimum viable BFT
7 2 5 Comfortable small set
10 3 7 Phase-5+ fork checkpoint (matches ι 7-of-10)
13 4 9 Scaled arbiter committee

These four rows are the fixture corpus. Each row must round-trip through both quorumThreshold(n) and maxFaulty(n).

2.3 Single-arbiter clause (consensus.md §Phase 0 posture §193–198)

The runtime accepts θ-shaped APIs but always returns “trivially finalized” because n = 1.

Hence:

  • quorumThreshold(1n) === 1n
  • maxFaulty(1n) === 0n
  • hasQuorum([oneSignedVote], 1n) === true

The single-arbiter clause is the Phase-0 posture; the runtime never exits this state until the multi-model milestone (ADR-005).

2.4 Honest-majority intersection property

For any n ≥ 4 and any two quorum-sized sets A, B drawn from {1,…,n}, |A ∩ B| ≥ 1 — at least one honest arbiter is in both quorums. This follows from |A| + |B| > n because 2 * quorumThreshold(n) > n for all n ≥ 1.

2.5 Equivocation primitive

Per consensus.md §Equivocation §122-147:

An arbiter that signs two distinct tuples at the same finality level in the same round is equivocating.

detectDoubleVote(arbiter_id, round_id, votes) returns the list of conflicting Vote PAIRS so that P3.5.1 can construct one EquivocationProof per pair via P3.1.1’s buildEquivocationProof().

Distinction:

  • Same tuple twice → benign retry, returns []
  • Different (merkle_root, rule_version_hash) → equivocation, returns the pair [voteA, voteB]

Grouping is by the canonical hash of the (round_id, merkle_root, rule_version_hash) 3-tuple, NOT by object identity (per gotcha line 523-528 of the prompt). We reuse P3.1.1’s hashMessage(vote) indirectly by building a small derived key ${round_id}|${merkle_root.hex}|${rule_version_hash.hex} since hashMessage covers the entire Vote (including signature, sender, clock) and we need to group by the SUB-tuple.

§3. Vote-shape sanity check (against P3.1.1)

P3.1.1 exports:

export interface Vote extends VoteTuple {
  readonly sender_id: string;
  readonly vote_type: VoteType;        // 'ACCEPT' | 'REJECT' | 'ABSTAIN'
  readonly timestamp_logical: bigint;
  readonly signature: Buffer;
}

export interface VoteTuple {
  readonly round_id: bigint;
  readonly merkle_root: Buffer;
  readonly rule_version_hash: Buffer;
}
  • round_id: bigint — direct
  • merkle_root: Buffer.toString('hex') for the group key
  • rule_version_hash: Buffer.toString('hex') for the group key
  • sender_id: string — used as the arbiter identity in detectDoubleVote

Signature is NOT part of the group key — two Votes with identical 3-tuple-plus-signature are still benign retries (the same signed bytes). Two Votes with identical 3-tuple but different signatures are not equivocation either — equivocation requires DIFFERENT TUPLES from the same arbiter in the same round.

No surprises in the Vote shape — proceeding to Step 2.

§4. Files to create

  • src/domains/consensus/quorum.ts (Step 4 commit feat)
  • src/__tests__/domains/consensus/quorum.test.ts (Step 4 commit feat)

§5. Files NOT touched

  • src/domains/consensus/messages.ts — type-only import; no edit
  • All other existing modules — out of scope

§6. Step 1 conclusion

Greenfield slice. P3.1.1’s Vote shape is compatible with all acceptance criteria. Worked table + single-arbiter clause + honest- majority invariant + equivocation primitive are all spec-grounded. Proceeding to Step 2 (contract).


Back to top

Colibri — documentation-first MCP runtime. Apache 2.0 + Commons Clause.

This site uses Just the Docs, a documentation theme for Jekyll.