soma.cell

Election audit — a risk-limiting audit that cannot count a ballot twice

Colorado pioneered statewide risk-limiting audits in 2017: pull a random sample of paper ballots, compare to the machine count, escalate to a full hand recount if the sample disagrees. The integrity of the method dies if the same favorable ballot can be inspected twice (padding agreement) or if a "confirmed" audit can be quietly reopened and re-run until it confirms. Both are structural here: each ballot's inspection count is capped at 1 by a memory invariant, escalation to full recount is a real transition the sample-mismatch forces, and `confirmed` is terminal — soma verify proves there is no second bite at the audit.

A Soma program from the civic part of the verified corpus: 117 lines, cells RiskLimitingAudit, AuditTests. It passes soma check with soma 3.2.4.

How it is built

The cell RiskLimitingAudit declares 7 signals in its face: start_sampling() -> Map, inspect_ballot(ballot: String, matches: Bool) -> Map, conclude() -> Map, finish_recount() -> Map, inspected_total() -> Int, mismatch_total() -> Int and phase() -> String; keeps 2 memory slots: inspected (Map<String, Int>, persistent and consistent) under the invariant inspected >= 0 && inspected <= 1 and mismatches (Map<String, Int>, persistent and consistent) under the invariant mismatches >= 0 && mismatches <= 1. Its state machine audit starts in planned and allows 4 transitions: planned -> sampling, sampling -> confirmed, sampling -> full_recount and full_recount -> confirmed. confirmed is terminal. Handlers: phase (line 46), start_sampling (line 48), inspect_ballot (line 54), conclude (line 68), finish_recount (line 84), inspected_total (line 90) and mismatch_total (line 92).

The file also holds 1 test cell (AuditTests) with 18 assertions, run by soma test.

Generated from soma describe on this file.

The source

election_audit.cell

// ============================================================================
// ELECTION AUDIT — a risk-limiting audit that cannot count a ballot twice
// ============================================================================
// Colorado pioneered statewide risk-limiting audits in 2017: pull a random
// sample of paper ballots, compare to the machine count, escalate to a full
// hand recount if the sample disagrees. The integrity of the method dies if
// the same favorable ballot can be inspected twice (padding agreement) or if
// a "confirmed" audit can be quietly reopened and re-run until it confirms.
// Both are structural here: each ballot's inspection count is capped at 1 by
// a memory invariant, escalation to full recount is a real transition the
// sample-mismatch forces, and `confirmed` is terminal — soma verify proves
// there is no second bite at the audit.
// ============================================================================

cell RiskLimitingAudit {
    face {
        signal start_sampling() -> Map
        signal inspect_ballot(ballot: String, matches: Bool) -> Map
        signal conclude() -> Map
        signal finish_recount() -> Map
        signal inspected_total() -> Int
        signal mismatch_total() -> Int
        signal phase() -> String
    }

    memory {
        // ballot -> inspections. Counting one ballot twice is unrepresentable.
        inspected: Map<String, Int> [persistent, consistent]
        invariant inspected >= 0 && inspected <= 1

        mismatches: Map<String, Int> [persistent, consistent]
        invariant mismatches >= 0 && mismatches <= 1
    }

    // confirmed has no exit: an audit that confirmed the outcome is done.
    // The only other road out of sampling is the FULL recount — there is
    // no "quietly drop the bad sample" state.
    state audit {
        initial: planned
        planned -> sampling
        sampling -> confirmed
        sampling -> full_recount
        full_recount -> confirmed
    }

    on phase() { return get_status("audit") }

    on start_sampling() {
        let r = try { transition("audit", "sampling") }
        if r.error != () { return map("ok", false, "reason", "{r.error}") }
        return map("ok", true)
    }

    on inspect_ballot(ballot: String, matches: Bool) {
        if phase() != "sampling" {
            return map("ok", false, "reason", "audit is {phase()}")
        }
        let prior = inspected.get(ballot) ?? 0
        // THE WALL: the same ballot cannot pad the sample twice.
        let w = try { inspected.set(ballot, prior + 1) }
        if w.error != () {
            return map("ok", false, "reason", "ballot {ballot} already inspected")
        }
        if !matches { mismatches.set(ballot, 1) }
        return map("ok", true, "ballot", ballot, "sampled", inspected_total())
    }

    on conclude() {
        if phase() != "sampling" {
            return map("ok", false, "reason", "audit is {phase()}")
        }
        if inspected_total() < 5 {
            return map("ok", false, "reason", "sample too small: {inspected_total()} of 5")
        }
        if mismatch_total() == 0 {
            transition("audit", "confirmed")
            return map("ok", true, "outcome", "confirmed")
        }
        // a disagreeing sample FORCES the full hand recount
        transition("audit", "full_recount")
        return map("ok", true, "outcome", "full_recount")
    }

    on finish_recount() {
        let r = try { transition("audit", "confirmed") }
        if r.error != () { return map("ok", false, "reason", "{r.error}") }
        return map("ok", true, "outcome", "confirmed_by_hand_count")
    }

    on inspected_total() { return len(inspected.keys()) }

    on mismatch_total() { return len(mismatches.keys()) }
}

cell test AuditTests {
    rules {
        assert inspect_ballot("b1", true).ok == false     // no audit started
        assert start_sampling().ok == true
        assert inspect_ballot("b1", true).ok == true
        assert inspect_ballot("b1", true).ok == false     // no double inspection
        assert_fails inspected.set("b1", 2)               // unrepresentable
        assert inspect_ballot("b2", true).ok == true
        assert inspect_ballot("b3", true).ok == true
        assert inspect_ballot("b4", true).ok == true
        assert conclude().ok == false                     // sample below minimum
        assert inspect_ballot("b5", false).ok == true     // a mismatch!
        assert inspected_total() == 5
        assert conclude().outcome == "full_recount"       // escalation is forced
        assert inspect_ballot("b6", true).ok == false     // sampling is over
        assert finish_recount().ok == true
        assert phase() == "confirmed"
        assert_fails transition("audit", "sampling")      // no re-running the audit
        assert_fails transition("audit", "full_recount")
        assert conclude().ok == false                     // and no second verdict
    }
}

What the toolchain says

Run on a fresh copy of the program at build time; the outputs are pasted unedited.

$ soma check election_audit.cell
✓ All checks passed.
$ soma test election_audit.cell
test AuditTests ...
  ✓ assert inspect_ballot("b1", true).ok == false
  ✓ assert start_sampling().ok == true
  ✓ assert inspect_ballot("b1", true).ok == true
  ✓ assert inspect_ballot("b1", true).ok == false
  ✓ assert_fails inspected.set("b1", 2) — raised memory invariant violated on 'inspected': inspected >= 0 && inspected <= 1 — rejected write of 2 (key "b1"); the slot is unchanged
  ✓ assert inspect_ballot("b2", true).ok == true
  ✓ assert inspect_ballot("b3", true).ok == true
  ✓ assert inspect_ballot("b4", true).ok == true
  ✓ assert conclude().ok == false
  ✓ assert inspect_ballot("b5", false).ok == true
  ✓ assert inspected_total() == 5
  ✓ assert conclude().outcome == "full_recount"
  ✓ assert inspect_ballot("b6", true).ok == false
  ✓ assert finish_recount().ok == true
  ✓ assert phase() == "confirmed"
  ✓ assert_fails transition("audit", "sampling") — raised invalid transition: confirmed → sampling. Current state: 'confirmed'. Valid targets: []
  ✓ assert_fails transition("audit", "full_recount") — raised invalid transition: confirmed → full_recount. Current state: 'confirmed'. Valid targets: []
  ✓ assert conclude().ok == false

18 tests: 18 passed, 0 failed
$ soma verify election_audit.cell
Verifying election_audit.cell...

State machine 'audit': 4 states, initial 'planned'
  States: [confirmed, full_recount, planned, sampling]

  ✓ 4 states, 4 transitions
  ✓ all states reachable from 'planned'
  ✓ terminal states: [confirmed]
  ✓ no deadlocks
  ✓ liveness: every state can eventually reach a terminal state
  ✓ think-isolated: CTL safety properties hold regardless of LLM output (3 handlers, 4 literal transitions, 0 dynamic)
  ✓ termination: all 7 handlers structurally terminate
  ✓ refinement: handler `start_sampling` ⟶ {sampling}
  ✓ refinement: handler `conclude` ⟶ {confirmed [if mismatch_total() == 0], full_recount}
  ✓ refinement: handler `finish_recount` ⟶ {confirmed}
  · transition sources: `start_sampling`, `conclude`, `finish_recount` call transition(id, target) without declaring the source — the runtime refuses a move the graph forbids, but the proof does not see where the handler starts; `transition(id, from, to)` names the edge (checked by `soma check`, refused at run time from any other state)

RiskLimitingAudit/invariants:

  ✓ invariant inspected >= 0 — writer 'inspect_ballot' proven by induction (writes prior + 1)
  ⚠ invariant inspected >= 0 && inspected <= 1 — runtime-checked (computed values): inspect_ballot → inspected [inspected <= 1] because `prior + 1` is only known to lie in [1, 2] — narrow it: `require (prior + 1) <= 1 else …`
  ✓ invariant mismatches >= 0 && mismatches <= 1 — writer 'inspect_ballot' proven (writes 1)

12 passed, 1 warnings, 0 failures

  Temporal properties for 'audit':
  ✓ deadlock_free — no deadlocks in any reachable state
Temporal: 1 passed, 0 failed
VERIFY OK

Builtins used

get_status, len, map, transition

Source file: /corpus/civic/election_audit.cell · examples/corpus/civic/election_audit.cell