soma.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.

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.

Generated from soma describe on this file.

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

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

$ 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

Builtins used

get_status, map, transition

Source file: /corpus/governance/whistleblower_shield.cell · examples/corpus/governance/whistleblower_shield.cell