Campaign finance — the contribution limit lives in the ledger, not the law
In 2014 Dinesh D'Souza was convicted for funneling money past the federal per-donor limit through straw donors he reimbursed. Straw schemes work because the limit is checked at filing time, months after the money moved. Here the per-donor cap ($2,900, the 2022-cycle federal limit) is a memory invariant: the cumulative-total write for any donor name above the cap is refused at the instant of donation. The reporting lifecycle is one-way — once the committee files, `reported` is terminal: soma verify proves the books cannot be re-opened to massage totals.
A Soma program from the civic part of the verified corpus: 106 lines, cells Committee, CommitteeTests. It passes soma check with soma 3.2.4.
How it is built
The cell Committee declares 7 signals in its face: open_books() -> Map, contribute(donor: String, amount: Int) -> Map, close_books() -> Map, file_report() -> Map, donor_total(donor: String) -> Int, raised_total() -> Int and phase() -> String; keeps 1 memory slot: donated (Map<String, Int>, persistent and consistent) under the invariant donated >= 0 && donated <= 2900. Its state machine cycle starts in registered and allows 3 transitions: registered -> raising, raising -> closed and closed -> reported. reported is terminal. Handlers: phase (line 39), open_books (line 41), contribute (line 47), close_books (line 63), file_report (line 69), donor_total (line 75) and raised_total (line 77).
The file also holds 1 test cell (CommitteeTests) with 17 assertions, run by soma test.
The source
campaign_finance.cell
// ============================================================================
// CAMPAIGN FINANCE — the contribution limit lives in the ledger, not the law
// ============================================================================
// In 2014 Dinesh D'Souza was convicted for funneling money past the federal
// per-donor limit through straw donors he reimbursed. Straw schemes work
// because the limit is checked at filing time, months after the money moved.
// Here the per-donor cap ($2,900, the 2022-cycle federal limit) is a memory
// invariant: the cumulative-total write for any donor name above the cap is
// refused at the instant of donation. The reporting lifecycle is one-way —
// once the committee files, `reported` is terminal: soma verify proves the
// books cannot be re-opened to massage totals.
// ============================================================================
cell Committee {
face {
signal open_books() -> Map
signal contribute(donor: String, amount: Int) -> Map
signal close_books() -> Map
signal file_report() -> Map
signal donor_total(donor: String) -> Int
signal raised_total() -> Int
signal phase() -> String
}
memory {
// donor -> cumulative dollars this cycle. $2,901 is unrepresentable.
donated: Map<String, Int> [persistent, consistent]
invariant donated >= 0 && donated <= 2900
}
// reported is terminal: filed books are filed forever.
state cycle {
initial: registered
registered -> raising
raising -> closed
closed -> reported
}
on phase() { return get_status("cycle") }
on open_books() {
let r = try { transition("cycle", "raising") }
if r.error != () { return map("ok", false, "reason", "{r.error}") }
return map("ok", true)
}
on contribute(donor: String, amount: Int) {
if phase() != "raising" {
return map("ok", false, "reason", "books are {phase()}")
}
if amount <= 0 { return map("ok", false, "reason", "amount must be positive") }
let sofar = donated.get(donor) ?? 0
// THE WALL: past $2,900 the ledger refuses, at donation time,
// not at filing time.
let w = try { donated.set(donor, sofar + amount) }
if w.error != () {
return map("ok", false, "reason",
"limit: {donor} at {sofar}, +{amount} exceeds 2900")
}
return map("ok", true, "donor", donor, "total", sofar + amount)
}
on close_books() {
let r = try { transition("cycle", "closed") }
if r.error != () { return map("ok", false, "reason", "{r.error}") }
return map("ok", true, "raised", raised_total())
}
on file_report() {
let r = try { transition("cycle", "reported") }
if r.error != () { return map("ok", false, "reason", "{r.error}") }
return map("ok", true, "raised", raised_total())
}
on donor_total(donor: String) { return donated.get(donor) ?? 0 }
on raised_total() {
let names = donated.keys()
let n = 0
for d in names { n = n + (donated.get(d) ?? 0) }
return n
}
}
cell test CommitteeTests {
rules {
assert contribute("ada", 100).ok == false // books not open yet
assert open_books().ok == true
assert contribute("ada", 2000).ok == true
assert contribute("ada", 1000).ok == false // 3000 > 2900: refused
assert donor_total("ada") == 2000 // ledger untouched
assert contribute("ada", 900).ok == true // exactly the cap: fine
assert donor_total("ada") == 2900
assert contribute("ada", 1).ok == false // one dollar over: never
assert_fails donated.set("ada", 2901) // unrepresentable
assert_fails donated.set("bob", -10) // no laundering negatives
assert contribute("bob", 500).ok == true
assert raised_total() == 3400
assert close_books().ok == true
assert contribute("late", 5).ok == false // books are closed
assert file_report().ok == true
assert_fails transition("cycle", "raising") // filed is forever
assert_fails transition("cycle", "closed")
}
}What the toolchain says
$ soma check campaign_finance.cell
✓ All checks passed.$ soma test campaign_finance.cell
test CommitteeTests ...
✓ assert contribute("ada", 100).ok == false
✓ assert open_books().ok == true
✓ assert contribute("ada", 2000).ok == true
✓ assert contribute("ada", 1000).ok == false
✓ assert donor_total("ada") == 2000
✓ assert contribute("ada", 900).ok == true
✓ assert donor_total("ada") == 2900
✓ assert contribute("ada", 1).ok == false
✓ assert_fails donated.set("ada", 2901) — raised memory invariant violated on 'donated': donated >= 0 && donated <= 2900 — rejected write of 2901 (key "ada"); the slot is unchanged
✓ assert_fails donated.set("bob", -10) — raised memory invariant violated on 'donated': donated >= 0 && donated <= 2900 — rejected write of -10 (key "bob"); the slot is unchanged
✓ assert contribute("bob", 500).ok == true
✓ assert raised_total() == 3400
✓ assert close_books().ok == true
✓ assert contribute("late", 5).ok == false
✓ assert file_report().ok == true
✓ assert_fails transition("cycle", "raising") — raised invalid transition: reported → raising. Current state: 'reported'. Valid targets: []
✓ assert_fails transition("cycle", "closed") — raised invalid transition: reported → closed. Current state: 'reported'. Valid targets: []
17 tests: 17 passed, 0 failed$ soma verify campaign_finance.cell
Verifying campaign_finance.cell...
State machine 'cycle': 4 states, initial 'registered'
States: [closed, raising, registered, reported]
✓ 4 states, 3 transitions
✓ all states reachable from 'registered'
✓ terminal states: [reported]
✓ no deadlocks
✓ liveness: every state can eventually reach a terminal state
✓ think-isolated: CTL safety properties hold regardless of LLM output (3 handlers, 3 literal transitions, 0 dynamic)
✓ termination: all 7 handlers structurally terminate
✓ refinement: handler `open_books` ⟶ {raising}
✓ refinement: handler `close_books` ⟶ {closed}
✓ refinement: handler `file_report` ⟶ {reported}
· transition sources: `open_books`, `close_books`, `file_report` 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)
Committee/invariants:
✓ invariant donated >= 0 — writer 'contribute' proven by induction (writes sofar + amount)
⚠ invariant donated >= 0 && donated <= 2900 — runtime-checked (computed values): contribute → donated [donated <= 2900] because `sofar + amount` is only known to lie in [1, ∞) — narrow it: `require (sofar + amount) <= 2900 else …`
11 passed, 1 warnings, 0 failures
Temporal properties for 'cycle':
✓ deadlock_free — no deadlocks in any reachable state
Temporal: 1 passed, 0 failed
VERIFY OK