Payroll run — the payslips must sum EXACTLY to the approved total
Payroll overpayments are a quiet epidemic: a duplicated payslip or a fat-fingered salary slips into a run, the bank file goes out, and the company spends months clawing money back from employees (legally messy, morale-destroying). The wall here: the approved budget is a slot that each payslip DECREMENTS, with `budget >= 0` as an invariant — a payslip that would overspend the approval RAISES at the write. And the run can only commit when the budget hits exactly zero: paid == approved, proven by arithmetic, not by hope. Committed runs are terminal.
A Soma program from the escrow_finance part of the verified corpus: 100 lines, cells Payroll, PayrollTests. It passes soma check with soma 3.2.4.
How it is built
The cell Payroll declares 4 signals in its face: approve_run(run: String, total: Int) -> Map, add_payslip(run: String, employee: String, amount: Int) -> Map, commit_run(run: String) -> Map and unspent(run: String) -> Int; keeps 2 memory slots: budget (Map<String, Int>, persistent and consistent) under the invariant budget >= 0 and slips (Map<String, Int>, persistent and consistent) under the invariant slips >= 0. Its state machine run starts in open_run and allows 3 transitions: open_run -> approved, approved -> committed and approved -> voided. committed and voided are terminal. Handlers: approve_run (line 41), add_payslip (line 48), commit_run (line 63) and unspent (line 72).
The file also holds 1 test cell (PayrollTests) with 14 assertions, run by soma test.
The source
payroll_run.cell
// ============================================================================
// PAYROLL RUN — the payslips must sum EXACTLY to the approved total
// ============================================================================
//
// Payroll overpayments are a quiet epidemic: a duplicated payslip or a
// fat-fingered salary slips into a run, the bank file goes out, and the
// company spends months clawing money back from employees (legally messy,
// morale-destroying). The wall here: the approved budget is a slot that
// each payslip DECREMENTS, with `budget >= 0` as an invariant — a payslip
// that would overspend the approval RAISES at the write. And the run can
// only commit when the budget hits exactly zero: paid == approved, proven
// by arithmetic, not by hope. Committed runs are terminal.
// ============================================================================
cell Payroll {
face {
signal approve_run(run: String, total: Int) -> Map
signal add_payslip(run: String, employee: String, amount: Int) -> Map
signal commit_run(run: String) -> Map
signal unspent(run: String) -> Int
}
memory {
// what's left of the approved total — every payslip draws it down,
// and the invariant makes overspending a rejected write
budget: Map<String, Int> [persistent, consistent]
invariant budget >= 0
slips: Map<String, Int> [persistent, consistent]
invariant slips >= 0
}
// committed is terminal: a run that hit the bank cannot be re-opened
// and quietly padded with one more payslip.
state run {
initial: open_run
open_run -> approved
approved -> committed
approved -> voided
}
on approve_run(run: String, total: Int) {
if total <= 0 { return map("ok", false, "reason", "bad total") }
transition(run, "approved")
budget.set(run, total)
return map("ok", true, "approved", total)
}
on add_payslip(run: String, employee: String, amount: Int) {
if get_status(run) != "approved" {
return map("ok", false, "reason", "run is not open for payslips")
}
if amount <= 0 { return map("ok", false, "reason", "bad amount") }
let k = "{run}:{employee}"
if (slips.get(k) ?? 0) > 0 {
return map("ok", false, "reason", "{employee} already has a payslip")
}
let left = budget.get(run) ?? 0
budget.set(run, left - amount) // RAISES if the payslip overspends
slips.set(k, amount)
return map("ok", true, "employee", employee, "left", left - amount)
}
on commit_run(run: String) {
let left = budget.get(run) ?? -1
if left != 0 {
return map("ok", false, "reason", "paid != approved: {left} unallocated")
}
transition(run, "committed")
return map("ok", true, "state", get_status(run))
}
on unspent(run: String) {
return budget.get(run) ?? 0
}
}
cell test PayrollTests {
rules {
assert approve_run("jan", 9000).ok == true
assert add_payslip("jan", "ada", 4000).ok == true
assert add_payslip("jan", "bob", 3000).ok == true
// THE DUPLICATE PAYSLIP: ada cannot be paid twice in one run
assert add_payslip("jan", "ada", 4000).ok == false
// THE OVERPAYMENT: 2001 > the 2000 left — the write itself raises
assert_fails add_payslip("jan", "cy", 2001)
assert unspent("jan") == 2000
// cannot commit while money is unallocated (paid != approved)
assert commit_run("jan").ok == false
assert add_payslip("jan", "cy", 2000).ok == true
assert unspent("jan") == 0
assert commit_run("jan").ok == true
// committed is terminal: no sneaking in one more payslip
assert add_payslip("jan", "eve", 1).ok == false
assert_fails transition("jan", "approved")
assert_fails transition("jan", "voided")
// the budget slot itself rejects a negative balance
assert_fails budget.set("jan", -5)
}
}What the toolchain says
$ soma check payroll_run.cell
✓ All checks passed.$ soma test payroll_run.cell
test PayrollTests ...
✓ assert approve_run("jan", 9000).ok == true
✓ assert add_payslip("jan", "ada", 4000).ok == true
✓ assert add_payslip("jan", "bob", 3000).ok == true
✓ assert add_payslip("jan", "ada", 4000).ok == false
✓ assert_fails add_payslip("jan", "cy", 2001) — raised memory invariant violated on 'budget': budget >= 0 — rejected write of -1 (key "jan"); the slot is unchanged
✓ assert unspent("jan") == 2000
✓ assert commit_run("jan").ok == false
✓ assert add_payslip("jan", "cy", 2000).ok == true
✓ assert unspent("jan") == 0
✓ assert commit_run("jan").ok == true
✓ assert add_payslip("jan", "eve", 1).ok == false
✓ assert_fails transition("jan", "approved") — raised invalid transition: committed → approved. Current state: 'committed'. Valid targets: []
✓ assert_fails transition("jan", "voided") — raised invalid transition: committed → voided. Current state: 'committed'. Valid targets: []
✓ assert_fails budget.set("jan", -5) — raised memory invariant violated on 'budget': budget >= 0 — rejected write of -5 (key "jan"); the slot is unchanged
14 tests: 14 passed, 0 failed$ soma verify payroll_run.cell
Verifying payroll_run.cell...
State machine 'run': 4 states, initial 'open_run'
States: [approved, committed, open_run, voided]
✓ 4 states, 3 transitions
✓ all states reachable from 'open_run'
✓ terminal states: [committed, voided]
✓ no deadlocks
✓ liveness: every state can eventually reach a terminal state
✓ think-isolated: CTL safety properties hold regardless of LLM output (2 handlers, 2 literal transitions, 0 dynamic)
✓ termination: all 4 handlers structurally terminate
⚠ refinement: declared transition `approved → voided` is never reached by any handler — spec may be aspirational or stale
✓ refinement: handler `approve_run` ⟶ {approved}
✓ refinement: handler `commit_run` ⟶ {committed}
· transition sources: `approve_run`, `commit_run` 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)
Payroll/invariants:
✓ invariant budget >= 0 — writer 'approve_run' proven by induction (writes total)
⚠ invariant budget >= 0 — runtime-checked (computed values): add_payslip → budget
✓ invariant slips >= 0 — writer 'add_payslip' proven by induction (writes amount)
11 passed, 2 warnings, 0 failures
Temporal properties for 'run':
✓ deadlock_free — no deadlocks in any reachable state
Temporal: 1 passed, 0 failed
VERIFY OK