Whistleblower shield — retaliation against a reporter is unrepresentable
Sherron Watkins warned Ken Lay about Enron's accounting in August 2001; within days, Enron's lawyers were researching "the consequences of terminating" her. That memo is why Sarbanes-Oxley §806 exists: any adverse employment action against a corporate whistleblower — firing, demotion, the quiet bad shift — is unlawful retaliation. Yet OSHA's docket shows it still happens constantly, because the protection lives in a policy document while the termination lives in the HR system. Here they live in the same memory. Once a person files a report they are shielded: the adverse-actions ledger for a shielded employee carries `invariant == 0`, so HR's termination write is not flagged for review — it is REFUSED. And the report itself moves through a machine where `substantiated`/`unfounded` are terminal: a closed finding cannot be quietly re-opened to grind a reporter down.
A Soma program from the governance part of the verified corpus: 119 lines, cells ComplianceOffice, ShieldTests. It passes soma check with soma 3.2.4.
How it is built
The cell ComplianceOffice declares 5 signals in its face: file_report(report: String, reporter: String) -> Map, investigate(report: String) -> Map, close_report(report: String, founded: Bool) -> Map, hr_action(employee: String, action: String) -> Map and strikes(employee: String) -> Int; keeps 3 memory slots: reprisals (Map<String, Int>, persistent and consistent) under the invariant reprisals >= 0 && reprisals <= 0, discipline (Map<String, Int>, persistent and consistent) under the invariant discipline >= 0 and shielded (Map<String, Int>, persistent and consistent) under the invariant shielded >= 1 && shielded <= 1. Its state machine report starts in filed and allows 3 transitions: filed -> investigating, investigating -> substantiated and investigating -> unfounded. substantiated and unfounded are terminal. Handlers: file_report (line 52), investigate (line 58), close_report (line 62), hr_action (line 72) and strikes (line 84).
The file also holds 1 test cell (ShieldTests) with 15 assertions, run by soma test.
The source
whistleblower_shield.cell
// ============================================================================
// WHISTLEBLOWER SHIELD — retaliation against a reporter is unrepresentable
// ============================================================================
//
// Sherron Watkins warned Ken Lay about Enron's accounting in August
// 2001; within days, Enron's lawyers were researching "the consequences
// of terminating" her. That memo is why Sarbanes-Oxley §806 exists: any
// adverse employment action against a corporate whistleblower — firing,
// demotion, the quiet bad shift — is unlawful retaliation. Yet OSHA's
// docket shows it still happens constantly, because the protection lives
// in a policy document while the termination lives in the HR system.
//
// Here they live in the same memory. Once a person files a report they
// are shielded: the adverse-actions ledger for a shielded employee
// carries `invariant == 0`, so HR's termination write is not flagged for
// review — it is REFUSED. And the report itself moves through a machine
// where `substantiated`/`unfounded` are terminal: a closed finding
// cannot be quietly re-opened to grind a reporter down.
// ============================================================================
cell ComplianceOffice {
face {
signal file_report(report: String, reporter: String) -> Map
signal investigate(report: String) -> Map
signal close_report(report: String, founded: Bool) -> Map
signal hr_action(employee: String, action: String) -> Map
signal strikes(employee: String) -> Int
}
memory {
// adverse actions against SHIELDED employees: the only lawful
// count is zero. The firing is a write this slot cannot hold.
reprisals: Map<String, Int> [persistent, consistent]
invariant reprisals >= 0 && reprisals <= 0
// ordinary discipline for everyone else — bounded, but real
discipline: Map<String, Int> [persistent, consistent]
invariant discipline >= 0
shielded: Map<String, Int> [persistent, consistent]
invariant shielded >= 1 && shielded <= 1
}
// filed -> investigating -> substantiated | unfounded (both TERMINAL)
state report {
initial: filed
filed -> investigating
investigating -> substantiated
investigating -> unfounded
}
on file_report(report: String, reporter: String) {
shielded.set(reporter, 1)
transition(report, "investigating")
return map("ok", true, "report", report, "shielded", reporter)
}
on investigate(report: String) {
return map("report", report, "state", get_status(report))
}
on close_report(report: String, founded: Bool) {
let target = "unfounded"
if founded { target = "substantiated" }
let r = try { transition(report, target) }
if r.error != () { return map("ok", false, "reason", "{r.error}") }
return map("ok", true, "finding", target)
}
// ONE door for all adverse actions — and it routes shielded people
// into the ledger that refuses the write.
on hr_action(employee: String, action: String) {
if shielded.get(employee) != () {
let w = try { reprisals.set(employee, (reprisals.get(employee) ?? 0) + 1) }
if w.error != () {
return map("ok", false, "reason",
"SOX 806: '{action}' against {employee} is retaliation; refused")
}
}
discipline.set(employee, (discipline.get(employee) ?? 0) + 1)
return map("ok", true, "employee", employee, "action", action)
}
on strikes(employee: String) {
return discipline.get(employee) ?? 0
}
}
cell test ShieldTests {
rules {
// ordinary discipline works for ordinary employees
assert hr_action("fastow", "formal warning").ok == true
assert strikes("fastow") == 1
// watkins files; the shield goes up with the report
assert file_report("R-2001-08", "watkins").ok == true
assert get_status("R-2001-08") == "investigating"
// THE MEMO'S PLAN: terminate the reporter. The ledger refuses.
assert hr_action("watkins", "termination").ok == false
assert hr_action("watkins", "demotion to basement").ok == false
assert_fails reprisals.set("watkins", 1)
assert strikes("watkins") == 0
// the shield is a fact, not a toggle: writing it off is refused
assert_fails shielded.set("watkins", 0)
// findings close one way or the other, and stay closed
assert close_report("R-2001-08", true).finding == "substantiated"
assert_fails transition("R-2001-08", "investigating")
assert_fails transition("R-2001-08", "unfounded")
// closed report or not, the reporter stays untouchable
assert hr_action("watkins", "bad shifts forever").ok == false
assert strikes("watkins") == 0
assert hr_action("fastow", "second warning").ok == true
}
}What the toolchain says
$ soma check whistleblower_shield.cell
✓ All checks passed.$ soma test whistleblower_shield.cell
test ShieldTests ...
✓ assert hr_action("fastow", "formal warning").ok == true
✓ assert strikes("fastow") == 1
✓ assert file_report("R-2001-08", "watkins").ok == true
✓ assert get_status("R-2001-08") == "investigating"
✓ assert hr_action("watkins", "termination").ok == false
✓ assert hr_action("watkins", "demotion to basement").ok == false
✓ assert_fails reprisals.set("watkins", 1) — raised memory invariant violated on 'reprisals': reprisals >= 0 && reprisals <= 0 — rejected write of 1 (key "watkins"); the slot is unchanged
✓ assert strikes("watkins") == 0
✓ assert_fails shielded.set("watkins", 0) — raised memory invariant violated on 'shielded': shielded >= 1 && shielded <= 1 — rejected write of 0 (key "watkins"); the slot is unchanged
✓ assert close_report("R-2001-08", true).finding == "substantiated"
✓ assert_fails transition("R-2001-08", "investigating") — raised invalid transition: substantiated → investigating. Current state: 'substantiated'. Valid targets: []
✓ assert_fails transition("R-2001-08", "unfounded") — raised invalid transition: substantiated → unfounded. Current state: 'substantiated'. Valid targets: []
✓ assert hr_action("watkins", "bad shifts forever").ok == false
✓ assert strikes("watkins") == 0
✓ assert hr_action("fastow", "second warning").ok == true
15 tests: 15 passed, 0 failed$ soma verify whistleblower_shield.cell
Verifying whistleblower_shield.cell...
State machine 'report': 4 states, initial 'filed'
States: [filed, investigating, substantiated, unfounded]
✓ 4 states, 3 transitions
✓ all states reachable from 'filed'
✓ terminal states: [substantiated, unfounded]
✓ no deadlocks
✓ liveness: every state can eventually reach a terminal state
⚠ dynamic transition targets: handler `close_report` uses a dynamic (computed) transition target — the state these handlers move to is computed at run time (from input or an LLM answer), so the machine's safety is checked at run time for them, not proven
✓ termination: all 5 handlers structurally terminate
⚠ refinement (partial — dynamic targets fall back to runtime check): handler `close_report` calls transition() with a computed target — which edge it takes is checked at run time (invalid_transition), not proven; write the target as a literal state to have it verified
✓ refinement (partial — dynamic targets fall back to runtime check): handler `file_report` ⟶ {investigating}
✓ refinement (partial — dynamic targets fall back to runtime check): handler `close_report` ⟶ <dynamic>
· transition sources: `file_report` calls 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)
ComplianceOffice/invariants:
✓ invariant reprisals >= 0 && reprisals <= 0 — writer 'hr_action' can never commit (reprisals.get(employee) ?? 0) + 1 to 'reprisals': the write is always rejected (and the handler catches it)
✓ invariant discipline >= 0 — writer 'hr_action' proven by induction (writes (discipline.get(employee) ?? 0) + 1)
✓ invariant shielded >= 1 && shielded <= 1 — writer 'file_report' proven (writes 1)
11 passed, 2 warnings, 0 failures
Temporal properties for 'report':
✓ deadlock_free — no deadlocks in any reachable state
Temporal: 1 passed, 0 failed
VERIFY OK