soma.cell

Sunshine — a council cannot vote on what the public never got to discuss

California's Brown Act and its fifty state cousins exist because councils kept adopting ordinances at 11pm on items never noticed on any agenda — courts void such votes, but only years later, after the garage is built. Here the sunshine sequence IS the state machine: `adopted` is reachable only through public notice and an open comment hearing, and soma verify proves it. Each councilmember votes at most once (memory wall), adoption requires a majority of the five-member body, and an adopted ordinance is terminal — no midnight amendment un-adopts it off the record.

A Soma program from the civic part of the verified corpus: 135 lines, cells Ordinance, SunshineTests. It passes soma check with soma 3.2.4.

How it is built

The cell Ordinance declares 8 signals in its face: post_notice() -> Map, open_hearing() -> Map, public_comment(resident: String) -> Map, close_hearing() -> Map, council_vote(member: String, yes: Bool) -> Map, adopt() -> Map, yes_votes() -> Int and phase() -> String; keeps 3 memory slots: voted (Map<String, Int>, persistent and consistent) under the invariant voted >= 0 && voted <= 1, ayes (Map<String, Int>, persistent and consistent) under the invariant ayes >= 0 && ayes <= 1 and comments (Map<String, Int>, persistent and consistent) under the invariant comments >= 0. Its state machine ordinance starts in introduced and allows 6 transitions: introduced -> noticed, noticed -> hearing_open, hearing_open -> hearing_closed, hearing_closed -> adopted, hearing_closed -> tabled and tabled -> noticed. adopted is terminal. Handlers: phase (line 49), post_notice (line 51), open_hearing (line 57), public_comment (line 63), close_hearing (line 72), council_vote (line 78), adopt (line 91) and yes_votes (line 100).

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

Generated from soma describe on this file.

The source

sunshine.cell

// ============================================================================
// SUNSHINE — a council cannot vote on what the public never got to discuss
// ============================================================================
// California's Brown Act and its fifty state cousins exist because councils
// kept adopting ordinances at 11pm on items never noticed on any agenda —
// courts void such votes, but only years later, after the garage is built.
// Here the sunshine sequence IS the state machine: `adopted` is reachable
// only through public notice and an open comment hearing, and soma verify
// proves it. Each councilmember votes at most once (memory wall), adoption
// requires a majority of the five-member body, and an adopted ordinance is
// terminal — no midnight amendment un-adopts it off the record.
// ============================================================================

cell Ordinance {
    face {
        signal post_notice() -> Map
        signal open_hearing() -> Map
        signal public_comment(resident: String) -> Map
        signal close_hearing() -> Map
        signal council_vote(member: String, yes: Bool) -> Map
        signal adopt() -> Map
        signal yes_votes() -> Int
        signal phase() -> String
    }

    memory {
        // member -> ballots on this ordinance. A second vote is unrepresentable.
        voted: Map<String, Int> [persistent, consistent]
        invariant voted >= 0 && voted <= 1

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

        comments: Map<String, Int> [persistent, consistent]
        invariant comments >= 0
    }

    // The only path to adopted runs through the public.
    state ordinance {
        initial: introduced
        introduced -> noticed
        noticed -> hearing_open
        hearing_open -> hearing_closed
        hearing_closed -> adopted
        hearing_closed -> tabled
        tabled -> noticed
    }

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

    on post_notice() {
        let r = try { transition("ordinance", "noticed") }
        if r.error != () { return map("ok", false, "reason", "{r.error}") }
        return map("ok", true)
    }

    on open_hearing() {
        let r = try { transition("ordinance", "hearing_open") }
        if r.error != () { return map("ok", false, "reason", "{r.error}") }
        return map("ok", true)
    }

    on public_comment(resident: String) {
        if phase() != "hearing_open" {
            return map("ok", false, "reason", "hearing is {phase()}")
        }
        let n = comments.get(resident) ?? 0
        comments.set(resident, n + 1)
        return map("ok", true, "resident", resident)
    }

    on close_hearing() {
        let r = try { transition("ordinance", "hearing_closed") }
        if r.error != () { return map("ok", false, "reason", "{r.error}") }
        return map("ok", true)
    }

    on council_vote(member: String, yes: Bool) {
        if phase() != "hearing_closed" {
            return map("ok", false, "reason", "no vote while {phase()}")
        }
        let prior = voted.get(member) ?? 0
        let w = try { voted.set(member, prior + 1) }
        if w.error != () {
            return map("ok", false, "reason", "{member} already voted")
        }
        if yes { ayes.set(member, 1) }
        return map("ok", true, "member", member)
    }

    on adopt() {
        if yes_votes() < 3 {
            return map("ok", false, "reason", "majority needed: {yes_votes()} of 5")
        }
        let r = try { transition("ordinance", "adopted") }
        if r.error != () { return map("ok", false, "reason", "{r.error}") }
        return map("ok", true, "ayes", yes_votes())
    }

    on yes_votes() {
        let names = ayes.keys()
        let n = 0
        for m in names { n = n + (ayes.get(m) ?? 0) }
        return n
    }
}

cell test SunshineTests {
    rules {
        // the midnight shortcut does not exist: introduced -> adopted has no edge
        assert_fails transition("ordinance", "adopted")
        assert_fails transition("ordinance", "hearing_closed")
        assert council_vote("cm1", true).ok == false      // no vote before hearing

        assert post_notice().ok == true
        assert open_hearing().ok == true
        assert public_comment("resident-9").ok == true
        assert council_vote("cm1", true).ok == false      // hearing still open
        assert close_hearing().ok == true
        assert public_comment("late").ok == false         // comment window closed

        assert council_vote("cm1", true).ok == true
        assert council_vote("cm1", true).ok == false      // one vote per member
        assert_fails voted.set("cm1", 2)                  // unrepresentable
        assert council_vote("cm2", true).ok == true
        assert adopt().ok == false                        // 2 ayes is no majority
        assert council_vote("cm3", false).ok == true
        assert council_vote("cm4", true).ok == true
        assert adopt().ok == true                         // 3 of 5
        assert phase() == "adopted"
        assert_fails transition("ordinance", "tabled")    // adopted is forever
        assert_fails transition("ordinance", "noticed")
    }
}

What the toolchain says

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

$ soma check sunshine.cell
✓ All checks passed.
$ soma test sunshine.cell
test SunshineTests ...
  ✓ assert_fails transition("ordinance", "adopted") — raised invalid transition: introduced → adopted. Current state: 'introduced'. Valid targets: [noticed]
  ✓ assert_fails transition("ordinance", "hearing_closed") — raised invalid transition: introduced → hearing_closed. Current state: 'introduced'. Valid targets: [noticed]
  ✓ assert council_vote("cm1", true).ok == false
  ✓ assert post_notice().ok == true
  ✓ assert open_hearing().ok == true
  ✓ assert public_comment("resident-9").ok == true
  ✓ assert council_vote("cm1", true).ok == false
  ✓ assert close_hearing().ok == true
  ✓ assert public_comment("late").ok == false
  ✓ assert council_vote("cm1", true).ok == true
  ✓ assert council_vote("cm1", true).ok == false
  ✓ assert_fails voted.set("cm1", 2) — raised memory invariant violated on 'voted': voted >= 0 && voted <= 1 — rejected write of 2 (key "cm1"); the slot is unchanged
  ✓ assert council_vote("cm2", true).ok == true
  ✓ assert adopt().ok == false
  ✓ assert council_vote("cm3", false).ok == true
  ✓ assert council_vote("cm4", true).ok == true
  ✓ assert adopt().ok == true
  ✓ assert phase() == "adopted"
  ✓ assert_fails transition("ordinance", "tabled") — raised invalid transition: adopted → tabled. Current state: 'adopted'. Valid targets: []
  ✓ assert_fails transition("ordinance", "noticed") — raised invalid transition: adopted → noticed. Current state: 'adopted'. Valid targets: []

20 tests: 20 passed, 0 failed
$ soma verify sunshine.cell
Verifying sunshine.cell...

State machine 'ordinance': 6 states, initial 'introduced'
  States: [adopted, hearing_closed, hearing_open, introduced, noticed, tabled]

  ✓ 6 states, 6 transitions
  ✓ all states reachable from 'introduced'
  ✓ terminal states: [adopted]
  ✓ no deadlocks
  ✓ liveness: every state can eventually reach a terminal state
  ✓ think-isolated: CTL safety properties hold regardless of LLM output (4 handlers, 4 literal transitions, 0 dynamic)
  ✓ termination: all 8 handlers structurally terminate
  ⚠ refinement: declared transition `hearing_closed → tabled` is never reached by any handler — spec may be aspirational or stale
  ✓ refinement: handler `post_notice` ⟶ {noticed}
  ✓ refinement: handler `open_hearing` ⟶ {hearing_open}
  ✓ refinement: handler `close_hearing` ⟶ {hearing_closed}
  ✓ refinement: handler `adopt` ⟶ {adopted}
  · transition sources: `post_notice`, `open_hearing`, `close_hearing`, `adopt` 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)

Ordinance/invariants:

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

14 passed, 2 warnings, 0 failures

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

Builtins used

get_status, map, transition

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