Consensus Metaphor Design Technical Whitepaper  ·  v1.0  ·  July 2026  ·  reflects fixed release

Vortex Vote

A formal analysis of vortexVote.js — a dependency-free Bun/ES module implementing three threshold-quorum decision procedures (3-4-1/3, 3-6-9, 7-4-?) for gating operations by risk class, from ephemeral feature flags to irreversible infrastructure changes. This edition documents the Result-typed, NSF-compliant release; gaps identified in §6 are marked with their current status.

author: Miguel Ocampo stack: Bun.js · ES Modules · bun:test module: vortexVote.js status: released, Result-typed, NSF-compliant
Fast Path 3-4-1/3 ephemeral, low-latency quorum
Middle Path 3-6-9 balanced redundancy, vortex-stable
Slow Path 7-4-? fail-safe, deterministic hesitation
Abstract

vortexVote.js implements three parameterized threshold-quorum decision procedures over discrete-valued vote arrays: a Fast Path (3-4-1/3) for ephemeral, low-risk operations; a Middle Path (3-6-9) for balanced-redundancy governance decisions; and a Slow Path (7-4-?) for irreversible, emergency-class actions gated behind structural integrity checks and human escalation. Each path is a pure, deterministic, total function — every reachable input produces one of eight terminal outcomes, with no unhandled branch.

This paper treats the module as an object of formal analysis rather than a marketing artifact. We define the input/output model for each path, compare its structure against classical distributed-consensus concepts (unanimity gates, plurality thresholds, Byzantine repeated sampling, veto-weighted rule chains), and identify concrete gaps between the module's documented intent and its current implementation — most notably in the Middle Path's cyclic-audit routine, which is currently deterministic across all rounds rather than exhibiting genuine round-to-round independence.

The conclusion is explicit in the module's own header comment and reaffirmed here: this is a consensus metaphor — a business-rule threshold engine borrowing distributed-systems vocabulary — suitable for gating deploy pipelines, config changes, and human-review triggers, and not a substitute for a fault-tolerant consensus protocol where Byzantine behavior, network partition, or liveness guarantees must be formally proven.

§1 — Model

Formal Model & Notation

Let a vote be an element of the loosely-typed set {accept, reject, true, false, yes}, which the module treats as a coerced boolean via hasQuorum and inline filter predicates. Each path P is a function

Notation
P : V₁ × V₂ × ... × Vₖ  →  Outcome × Details

where each Vᵢ is a fixed-arity vote vector, and Outcome ∈
  { ABORT, COMMIT, ROLLBACK, RECONCILE, HOLD, HUMAN_REVIEW, DELAY_AND_AUDIT, SAFE_COMMIT }

A central structural property holds across all three paths: they are total functions. Every reachable input — including malformed or under-length vote arrays — produces a defined outcome, never an exception or an undefined branch (hasQuorum and allGatesClear both return false on missing or short arrays, rather than throwing). This is a minimal but real correctness guarantee: closer to exhaustiveness in the state-machine sense than to liveness in the CAP-theorem sense, but it does mean the decision procedure cannot stall in an undefined state.

fast · middle · slow
COMMIT / SAFE_COMMIT

Positive terminal outcome — the gated action proceeds.

fast · middle
ABORT

Quorum stage failed outright before any downstream check ran.

fast
ROLLBACK

Quorum passed but stabilizer acceptance fell below threshold.

middle
RECONCILE

Quorum passed but cyclic-audit pass count fell below threshold.

slow
HOLD

Structural gates not all clear — highest-priority block.

slow
HUMAN_REVIEW

Any agent voted ESCALATE — liberal veto, single dissenter wins.

slow
DELAY_AND_AUDIT

Entropy buffer ('?') triggered — deliberate hesitation, independent of vote content.

slow
ABORT

Gates clear, no escalation, no entropy trigger, but no majority yes.

§2 — Fast Path

Two-Stage Threshold Gate (3-4-1/3)

Decision Rule
quorum(primary, k=3)  ∧  |{accept ∈ stabilizers}| ≥ ⌈4·(1/3)⌉

Structurally, this is a conjunction of a unanimity gate and a plurality gate — not a single quorum system but a cascade of two. The first stage, hasQuorum(primary, 3) called with min = length, is effectively unanimity among the 3 primary voters — a strictly stronger requirement than the majority quorum (⌈n/2⌉+1) used in classical systems such as Paxos. The second stage relaxes the bar to ⌈4/3⌉ = 2, i.e., a plurality rather than a majority.

This asymmetry — a strict unanimity gate feeding a comparatively weak downstream check — is the formal expression of the design intent: "fast, but only for low-stakes decisions." The expensive check (unanimous primaries) is cheap to fail early; the cheap check (2-of-4 stabilizers) is what actually governs the commit/rollback boundary.

Implementation note

The threshold Math.ceil(4 * (1/3)) is a roundabout way of writing 2, and it is computed against the literal constant 4, not against stabilizers.length. A caller passing more than 4 stabilizers still gets a threshold of 2 — the arity and the threshold formula are coupled by convention, not by code. Worth an assertion or an explicit stabilizers.length reference if the arity is ever expected to vary.

§3 — Middle Path

Quorum-Gated Repeated Sampling (3-6-9)

Decision Rule
quorum(seeds, k=3)  ∧  passes(auditRounds=9, majority(validators)) ≥ 7

This is the most analytically interesting path. runCyclicAudit re-evaluates the same static validators array across 9 iterations:

JavaScript — runCyclicAudit (excerpt)
for (let i = 0; i < auditCount; i++) {
  const acceptCount = validators.filter(
    (v) => v && (v.vote === "accept" || v.vote === true)
  ).length;
  rounds.push(acceptCount > validators.length / 2);
}

Since validators never mutates between iterations, all 9 rounds are deterministically identical — the result is passes ∈ {0, 9}, never anything in between, under the current implementation. In a genuine cyclic-audit design — repeated sampling under noise, or re-polling live agents whose votes can plausibly flip between rounds — one would expect a distribution over passes ∈ [0, 9]. As written, the function is a majority check dressed as an iterative protocol: the loop currently has no independent source of variance per round.

Current semantics

Single majority evaluation of a static snapshot, repeated 9 times with identical inputs. Output space collapses to two points: full pass (9/9) or full fail (0/9). The "9 rounds" language in the interface is not yet backed by 9 independent observations.

Intended cyclic-audit semantics

Each round re-polls live validators (or samples from a distribution over their possible states), so passes can land anywhere in [0,9] and the 7-of-9 threshold does meaningful work as a noise-tolerance parameter rather than a no-op.

§4 — Slow Path

Priority-Ordered Guard Chain (7-4-?)

Unlike the Fast and Middle paths, the Slow Path is best modeled as an ordered rule list — closer to a firewall ruleset or a cond expression than to a quorum system. The order in which its four checks run is the specification:

slowPath() — decision flow
INPUT: { stateVotes, gateChecks, entropyBufferTriggered }
        │
        ▼
 ┌─────────────────────────┐   fail   ┌────────┐
 │ allGatesClear(4/4)?     ├─────────▶│  HOLD  │   ← safety precondition, unconditional block
 └───────────┬─────────────┘          └────────┘
          pass│
          ▼
 ┌─────────────────────────┐   yes    ┌────────────────┐
 │ any vote === ESCALATE?  ├─────────▶│  HUMAN_REVIEW  │  ← liberal veto: one dissenter wins
 └───────────┬─────────────┘          └────────────────┘
           no│
          ▼
 ┌─────────────────────────┐   yes    ┌───────────────────┐
 │ entropyBufferTriggered? ├─────────▶│  DELAY_AND_AUDIT  │  ← unconditional deliberate delay
 └───────────┬─────────────┘          └───────────────────┘
           no│
          ▼
 ┌──────────────────────────┐   yes    ┌───────────────┐
 │ majorityYes(stateVotes)? ├─────────▶│  SAFE_COMMIT  │
 └───────────┬──────────────┘          └───────────────┘
           no│
          ▼
        ABORT

The priority order encodes a threat model: structural integrity failures are treated as more urgent than a lone dissenter, which is itself more urgent than "just delay," which is itself more urgent than counting votes. The escalation veto is notably stronger than typical Byzantine quorum protections — those usually require ⌊n/3⌋+1 dissenters to block an action, whereas here a single agent voting ESCALATE is sufficient to force human review, regardless of how many other agents voted YES.

Design note

This ordering is a legitimate stance for irreversible actions, but it is currently implicit in code rather than documented as a decision. A reordering — e.g., checking the entropy buffer before the escalation veto — would change real-world behavior nontrivially and should be treated as a breaking change requiring its own review, not a refactor.

§5 — Theory

Relationship to Classical Consensus Theory

Classical consensus protocols (Paxos, Raft, PBFT) are judged against two properties: safety (nothing bad happens — no two conflicting values are ever chosen) and liveness (something good eventually happens, given enough correct, live nodes). vortexVote.js does not model liveness at all — there is no retry, timeout, or leader-election notion anywhere in the module. Its "safety" is really just deterministic evaluation of an already-collected, already-trusted snapshot of votes: it assumes the vote arrays are honest, with no Byzantine fault tolerance. A maliciously constructed {vote: "accept"} object is indistinguishable from a genuine one at this layer.

PathStructureNearest classical analogueKey gap vs. that analogue
fastPath
3-4-1/3
Unanimity gate → plurality gate Two-phase commit (loosely) Threshold hardcoded to arity 4, not relative to stabilizers.length
middlePath
3-6-9
Quorum gate → repeated majority Byzantine repeated sampling Rounds are non-independent; passes is always 0 or 9, never in between
slowPath
7-4-?
Ordered guard chain with veto Rule-based access control / circuit breaker Not a quorum system at all — ordering of checks is the semantics, and is undocumented as a design decision

The honest characterization, consistent with the module's own header comment, is that vortexVote.js is a business-rule threshold engine borrowing consensus vocabulary — well suited to gating deploy pipelines, config changes, or human-review triggers, and not a substitute for a protocol offering formal distributed-systems guarantees.

§6 — Gaps

Known Gaps & Recommendations

  1. GAP 01
    Fast Path threshold is not arity-relative fixed

    Resolved: the commit threshold is now computed as Math.ceil(stabilizers.length * (1/3)) against the actual validated array, not a hardcoded literal.

  2. GAP 02
    Cyclic audit is not independent per round fixed

    Resolved: runCyclicAudit now accepts an optional pollFn(round, validators). Supplying one gives each of the 9 rounds a genuinely independent snapshot, so passes can land anywhere in [0,9]. Without a pollFn, behavior remains deterministic — this was an additive fix, not a breaking change.

  3. GAP 03
    Slow Path guard ordering is implicit documented

    Resolved: the priority order (gates → escalation → entropy → majority) is now stated explicitly in the module's own header comment as a design decision, not left to be inferred from control flow.

  4. GAP 04
    No Byzantine fault tolerance at the vote-object layer by design

    Out of scope for this module by design, and correctly disclosed in the header comment — but worth re-stating at every call site that feeds this module: it validates vote shape, not vote authenticity.

  5. GAP 05
    Malformed input indistinguishable from a legitimate ABORT fixed

    Resolved (post-whitepaper addition): every path now validates input shape, arity, and enum membership at the boundary and returns Err(VortexVoteError) on failure — a distinct channel from the Ok({ outcome: ABORT, ... }) a caller gets from a legitimate quorum or vote-count failure. See §7 for the updated model.

§7 — Error Model

Result Type & Error Model

§1's formal model is updated in the release to reflect a stricter contract:

Notation — updated
P : V₁ × V₂ × ... × Vₖ  →  Result<Decision, VortexVoteError>

Result<T,E>  =  Ok(T)  |  Err(E)
Decision     =  { outcome: Outcome, details: object }

The distinction that matters: Ok({ outcome: ABORT, ... }) is a legitimate business result — the votes were well-formed and a quorum genuinely wasn't reached. Err(VortexVoteError) is a caller error — malformed shape, wrong arity, or an unrecognized enum value. GAP 05 (§6) was exactly this conflation in the pre-release version; the two channels are now structurally distinct, so a consuming system can retry/alert differently for each.

Error codeRaised byMeaning
INVALID_VOTE_ARRAYall three pathsvote array missing, wrong type, or below required arity
INVALID_VOTE_ENTRYfast, middle, slowa vote entry isn't a recognized {vote: accept|reject|true|false} shape
INVALID_GATE_ARRAYslowgateChecks missing, too short, or containing a non-boolean
INVALID_STATE_VALUEslowa stateVotes entry isn't one of the 7 canonical VoteState values
INVALID_PATHrunVotethe requested path isn't fast, middle, or slow
Consequence

Every function in the module is now a total function over a strictly wider domain: it was already total over well-formed input (§1), and is now total over all input, because malformed input routes to a typed Err instead of either throwing unpredictably or silently degrading into a business outcome. No input shape can produce an unhandled exception under normal operation.

§8 — Literature

Terminology, Disambiguation & Adjacent Literature

The module's own vocabulary — "no phase locks," "no silent failure," "fail fast," "total function" — borrows from several distinct bodies of literature: software error-handling patterns, distributed-systems fault models, and (once, loosely) electrical engineering. This section cross-checks each borrowed term against its source definition and states plainly which ones the code actually earns, which are deliberate departures, and which don't apply at all. Recording a negative result here is treated as equally load-bearing as a positive one — the same standard §6 already applies to the module's own claims.

8.1 Patterns the module genuinely implements

ConceptWhere in this moduleStatus
Fail-fast Every path validates shape, arity, and enum membership before any quorum or threshold logic runs (§3 of the source). A malformed call never reaches business logic. confirmed
Defensive programming validateVoteArray, validateGateArray, validateStateVotes — every externally supplied array and enum value is shape-checked at the boundary. confirmed
Exception handling (Result-type substitute) Internally, nothing throws — Ok/Err is used in place of exceptions for control flow. unwrap() is the single, explicitly documented point where an Err becomes a thrown exception, and only at an outermost caller's discretion. confirmed
Undefined behavior — avoided by construction JavaScript has no UB in the C/C++ sense, so this is an analogy, not a literal claim: every reachable input, valid or malformed, maps to exactly one defined branch (§1's totality property). There is no input shape that falls through unhandled. confirmed (by analogy)
Robustness principle (Postel's Law) validateStateVotes rejects an unrecognized value like "yess" with Err(INVALID_STATE_VALUE) instead of liberally coercing it. See callout below. deliberate departure
Graceful degradation RECONCILE, HOLD, and DELAY_AND_AUDIT are non-terminal, non-crashing outcomes a caller can act on, rather than a thrown error or a forced commit. confirmed (loose analogy)
Circuit breaker design pattern allGatesClear() failing routes the Slow Path straight to HOLD, functioning as an open-circuit block on further evaluation. See caveat below. partial analogy
On the robustness-principle departure

Postel's Law ("be liberal in what you accept") is the industry-standard default, but this module inverts it on purpose for stateVotes: GAP 05 (§6) was exactly the failure mode that liberal acceptance produces — a typo'd vote silently degrading into an ordinary ABORT, indistinguishable from a genuine "no." Strict validation was the fix. This is a considered exception to Postel's Law, not an unawareness of it — liberal acceptance is still the right default for most integration boundaries.

On the circuit-breaker analogy

allGatesClear() has no memory across calls: no trip counter, no half-open probe state, no reset timer. Every invocation re-evaluates gateChecks from scratch. It reproduces the pattern's effect (block downstream work when upstream integrity has failed) without its state machine — worth flagging so "circuit breaker" isn't read as a claim of the full pattern.

8.2 Concepts confirmed absent

These were checked against the source and are simply not present — consistent with, not contradicting, the liveness disclaimer already made in §5 (no retry, timeout, or leader election anywhere in the module).

ConceptFinding
Timeout (computing) No deadline or timer is enforced anywhere. entropyBufferTriggered is a caller-supplied boolean the module reacts to — it does not itself run a clock or a wait.
Keepalive No heartbeat or liveness signal exists. The module has no way to distinguish a vote source that is slow from one that is permanently gone.
Futures and promises Every exported function, including a caller-supplied pollFn, is called and used synchronously. A pollFn that genuinely re-polls live agents over a network would need to return a Promise, which the current runCyclicAudit does not await — a real constraint on v1.0, not yet a documented GAP.

8.3 "Phase lock" is a naming metaphor, not a claim

The module header's "NO PHASE LOCKS" principle (and the README's identical language) describes the absence of shared mutable state and cross-call ordering requirements — a software-engineering property. It is not a technical reference to phase-locking, the phase-locked loop, injection locking, or the lock-in amplifier — the electrical-engineering concepts the term is borrowed from. None of this module's functions have an oscillator, a frequency reference, or a signal-synchronization property. Consistent with how the project's "vortex math" (3-6-9) framing is already disclosed as metaphor rather than mechanism (Abstract, §5), no literal EE claim should be read into this naming.

8.4 Implementation footnotes

8.5 Explicitly out of scope — not force-fit

The following were considered and don't describe anything this module does. They're recorded so the absence reads as a checked decision, not an oversight.

Appendix

API Surface & Reference Source

A.1 Exported API

ExportSignatureReturns
fastPath(primaryVotes, stabilizerVotes)Result<{outcome, details}, VortexVoteError>
middlePath(seedVotes, validatorVotes, {passThreshold=7, pollFn}?)Result<{outcome, details}, VortexVoteError>
slowPath({ stateVotes, gateChecks, entropyBufferTriggered })Result<{outcome, details}, VortexVoteError>
runCyclicAudit(validators, auditCount=9, pollFn?){ passes, rounds }
allGatesClear(gateChecks)boolean
majorityYes(stateVotes)boolean
runVote(path, input)dispatches to one of the above
Ok / Err / isOk / isErr / unwrapResult helpers
VortexVoteErrorclasstyped error with .code and .context
ErrorCode / Outcome / VoteState / VotePathfrozen enums

A.2 Fast Path — Reference Source

JavaScript — vortexVote.js
export function fastPath(primaryVotes, stabilizerVotes) {
  const primaryCheck = validateVoteArray(primaryVotes, 3, "primaryVotes");
  if (isErr(primaryCheck)) return primaryCheck;

  const stabilizerCheck = validateVoteArray(stabilizerVotes, 4, "stabilizerVotes");
  if (isErr(stabilizerCheck)) return stabilizerCheck;

  const primary = takeFirst(primaryCheck.value, 3);
  if (!hasQuorum(primary, 3)) {
    return Ok({ outcome: Outcome.ABORT, details: { reason: "no primary quorum" } });
  }

  const stabilizers = takeFirst(stabilizerCheck.value, 4);
  const acceptCount = countAccepts(stabilizers);
  // threshold scales with actual arity — fixes GAP 01
  const threshold = Math.ceil(stabilizers.length * (1 / 3));

  return acceptCount >= threshold
    ? Ok({ outcome: Outcome.COMMIT, details: { acceptCount, threshold } })
    : Ok({ outcome: Outcome.ROLLBACK, details: { acceptCount, threshold } });
}

A.3 Slow Path — Reference Source

JavaScript — vortexVote.js
export function slowPath({ stateVotes, gateChecks, entropyBufferTriggered = false }) {
  const gateCheckResult = validateGateArray(gateChecks);
  if (isErr(gateCheckResult)) return gateCheckResult;

  const stateVoteResult = validateStateVotes(stateVotes);
  if (isErr(stateVoteResult)) return stateVoteResult;

  // ── ordered guard chain — gates → escalation → entropy → majority ──
  if (!allGatesClear(gateCheckResult.value)) {
    return Ok({ outcome: Outcome.HOLD, details: { reason: "structural gates not clear" } });
  }
  if (stateVoteResult.value.some((s) => s === VoteState.ESCALATE)) {
    return Ok({ outcome: Outcome.HUMAN_REVIEW, details: { reason: "escalate vote present" } });
  }
  if (entropyBufferTriggered) {
    return Ok({ outcome: Outcome.DELAY_AND_AUDIT, details: { reason: "entropy buffer triggered ('?')" } });
  }
  if (majorityYes(stateVoteResult.value)) {
    return Ok({ outcome: Outcome.SAFE_COMMIT, details: {} });
  }
  return Ok({ outcome: Outcome.ABORT, details: { reason: "no majority yes" } });
}

References:

[1] Lamport, L. — "The Part-Time Parliament" (Paxos), ACM TOCS, 1998

[2] Ongaro, D. & Ousterhout, J. — "In Search of an Understandable Consensus Algorithm" (Raft), USENIX ATC, 2014

[3] Castro, M. & Liskov, B. — "Practical Byzantine Fault Tolerance", OSDI, 1999

[4] Lynch, N. — Distributed Algorithms, Morgan Kaufmann, 1996

[5] vortexVote.js — reference implementation source, Bun.js module, v1.0 (Miguel Ocampo, 2026)