soma.cell

Open meetings — no public decision without public notice, structurally

Every U.S. state has a Sunshine law because councils kept doing the same thing: decide in private, ratify in public. Texas's Open Meetings Act requires 72 hours of posted notice before a governmental body may act; actions taken in an unnoticed meeting are voidable, and courts have voided them — bond issues, firings, rezonings. The dodge du jour is the "walking quorum": a chain of small private chats that adds up to a deliberating majority no citizen ever saw. Here the Act is the machine. A meeting reaches `in_session` ONLY through `noticed`, and the notice ledger physically cannot record a posting shorter than 72 hours — `invariant notice_hours >= 72` makes the midnight 2-hour notice a write the memory refuses. Votes happen only in session; `adjourned` is terminal, so nothing can be slipped into a meeting after the gavel falls.

A Soma program from the governance part of the verified corpus: 118 lines, cells SunshineLaw, SunshineTests. It passes soma check with soma 3.2.4.

How it is built

The cell SunshineLaw declares 5 signals in its face: post_notice(meeting: String, hours_ahead: Int) -> Map, convene(meeting: String) -> Map, vote_item(meeting: String, item: String, ayes: Int, nays: Int) -> Map, adjourn(meeting: String) -> Map and outcome(meeting: String, item: String) -> String; keeps 2 memory slots: notice_hours (Map<String, Int>, persistent and consistent) under the invariant notice_hours >= 72 and decisions (Map<String, String>, persistent and consistent). Its state machine meeting starts in called and allows 3 transitions: called -> noticed, noticed -> in_session and in_session -> adjourned. adjourned is terminal. Handlers: post_notice (line 48), convene (line 63), vote_item (line 69), adjourn (line 80) and outcome (line 86).

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

Generated from soma describe on this file.

The source

open_meetings.cell

// ============================================================================
// OPEN MEETINGS — no public decision without public notice, structurally
// ============================================================================
//
// Every U.S. state has a Sunshine law because councils kept doing the
// same thing: decide in private, ratify in public. Texas's Open Meetings
// Act requires 72 hours of posted notice before a governmental body may
// act; actions taken in an unnoticed meeting are voidable, and courts
// have voided them — bond issues, firings, rezonings. The dodge du jour
// is the "walking quorum": a chain of small private chats that adds up
// to a deliberating majority no citizen ever saw.
//
// Here the Act is the machine. A meeting reaches `in_session` ONLY
// through `noticed`, and the notice ledger physically cannot record a
// posting shorter than 72 hours — `invariant notice_hours >= 72` makes
// the midnight 2-hour notice a write the memory refuses. Votes happen
// only in session; `adjourned` is terminal, so nothing can be slipped
// into a meeting after the gavel falls.
// ============================================================================

cell SunshineLaw {
    face {
        signal post_notice(meeting: String, hours_ahead: Int) -> Map
        signal convene(meeting: String) -> Map
        signal vote_item(meeting: String, item: String, ayes: Int, nays: Int) -> Map
        signal adjourn(meeting: String) -> Map
        signal outcome(meeting: String, item: String) -> String
    }

    memory {
        // hours of advance public notice. The statute's floor IS the
        // type: 71 hours is a value this ledger cannot hold.
        notice_hours: Map<String, Int> [persistent, consistent]
        invariant notice_hours >= 72

        decisions: Map<String, String> [persistent, consistent]
    }

    // called -> noticed -> in_session -> adjourned. No edge skips the
    // notice; no edge leaves adjourned. soma verify proves both.
    state meeting {
        initial: called
        called -> noticed
        noticed -> in_session
        in_session -> adjourned
    }

    on post_notice(meeting: String, hours_ahead: Int) {
        if get_status(meeting) != "called" {
            let s = get_status(meeting)
            return map("ok", false, "reason", "notice must precede everything ({s})")
        }
        // THE WALL: a 2-hour midnight posting is refused by the memory
        let w = try { notice_hours.set(meeting, hours_ahead) }
        if w.error != () {
            return map("ok", false, "reason",
                "{hours_ahead}h notice is below the 72h statutory floor")
        }
        transition(meeting, "noticed")
        return map("ok", true, "meeting", meeting, "hours", hours_ahead)
    }

    on convene(meeting: String) {
        let r = try { transition(meeting, "in_session") }
        if r.error != () { return map("ok", false, "reason", "{r.error}") }
        return map("ok", true, "state", "in_session")
    }

    on vote_item(meeting: String, item: String, ayes: Int, nays: Int) {
        if get_status(meeting) != "in_session" {
            let s = get_status(meeting)
            return map("ok", false, "reason", "no vote outside an open session ({s})")
        }
        let verdict = "failed"
        if ayes > nays { verdict = "passed" }
        decisions.set("{meeting}:{item}", verdict)
        return map("ok", true, "item", item, "verdict", verdict)
    }

    on adjourn(meeting: String) {
        let r = try { transition(meeting, "adjourned") }
        if r.error != () { return map("ok", false, "reason", "{r.error}") }
        return map("ok", true, "state", "adjourned")
    }

    on outcome(meeting: String, item: String) {
        return decisions.get("{meeting}:{item}") ?? "no action"
    }
}

cell test SunshineTests {
    rules {
        // the walking quorum's dream: vote with no meeting noticed at all
        assert vote_item("oct-7", "rezone", 4, 1).ok == false
        assert_fails transition("oct-7", "in_session")     // notice is not skippable

        // the midnight special: 2 hours of "notice" — unrepresentable
        assert post_notice("oct-7", 2).ok == false
        assert_fails notice_hours.set("oct-7", 71)
        assert get_status("oct-7") == "called"

        // lawful notice, lawful session, lawful vote
        assert post_notice("oct-7", 96).ok == true
        assert vote_item("oct-7", "rezone", 4, 1).ok == false   // noticed != in session
        assert convene("oct-7").ok == true
        assert vote_item("oct-7", "rezone", 4, 1).verdict == "passed"
        assert vote_item("oct-7", "bond", 2, 3).verdict == "failed"

        // gavel falls: adjourned is terminal, the record is closed
        assert adjourn("oct-7").ok == true
        assert vote_item("oct-7", "sneaky-item", 5, 0).ok == false
        assert_fails transition("oct-7", "in_session")
        assert_fails transition("oct-7", "called")
        assert outcome("oct-7", "rezone") == "passed"
        assert outcome("oct-7", "sneaky-item") == "no action"
    }
}

What the toolchain says

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

$ soma check open_meetings.cell
✓ All checks passed.
$ soma test open_meetings.cell
test SunshineTests ...
  ✓ assert vote_item("oct-7", "rezone", 4, 1).ok == false
  ✓ assert_fails transition("oct-7", "in_session") — raised invalid transition: called → in_session. Current state: 'called'. Valid targets: [noticed]
  ✓ assert post_notice("oct-7", 2).ok == false
  ✓ assert_fails notice_hours.set("oct-7", 71) — raised memory invariant violated on 'notice_hours': notice_hours >= 72 — rejected write of 71 (key "oct-7"); the slot is unchanged
  ✓ assert get_status("oct-7") == "called"
  ✓ assert post_notice("oct-7", 96).ok == true
  ✓ assert vote_item("oct-7", "rezone", 4, 1).ok == false
  ✓ assert convene("oct-7").ok == true
  ✓ assert vote_item("oct-7", "rezone", 4, 1).verdict == "passed"
  ✓ assert vote_item("oct-7", "bond", 2, 3).verdict == "failed"
  ✓ assert adjourn("oct-7").ok == true
  ✓ assert vote_item("oct-7", "sneaky-item", 5, 0).ok == false
  ✓ assert_fails transition("oct-7", "in_session") — raised invalid transition: adjourned → in_session. Current state: 'adjourned'. Valid targets: []
  ✓ assert_fails transition("oct-7", "called") — raised invalid transition: adjourned → called. Current state: 'adjourned'. Valid targets: []
  ✓ assert outcome("oct-7", "rezone") == "passed"
  ✓ assert outcome("oct-7", "sneaky-item") == "no action"

16 tests: 16 passed, 0 failed
$ soma verify open_meetings.cell
Verifying open_meetings.cell...

State machine 'meeting': 4 states, initial 'called'
  States: [adjourned, called, in_session, noticed]

  ✓ 4 states, 3 transitions
  ✓ all states reachable from 'called'
  ✓ terminal states: [adjourned]
  ✓ 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 5 handlers structurally terminate
  ✓ refinement: handler `post_notice` ⟶ {called → noticed}
  ✓ refinement: handler `convene` ⟶ {in_session}
  ✓ refinement: handler `adjourn` ⟶ {adjourned}
  · transition sources: `convene`, `adjourn` 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)

SunshineLaw/invariants:

  ⚠ invariant notice_hours >= 72 — runtime-checked (computed values): post_notice → notice_hours because `hours_ahead` is a parameter (narrow it: `require hours_ahead >= 72 else …`)

10 passed, 1 warnings, 0 failures

  Temporal properties for 'meeting':
  ✓ 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/open_meetings.cell · examples/corpus/governance/open_meetings.cell