soma.cell

Foia — the statutory clock cannot be silently ignored

5 U.S.C. § 552 gives an agency 20 working days to answer a FOIA request. In practice requests sit for years — agencies just keep them "processing" and courts call the silence a constructive denial (journalists sued the FBI over requests pending for a decade). The dodge works because nothing FORCES the escalation. Here the day counter has a hard ceiling: day 21 cannot be written for a request still in processing — the refused write trips the escalation transition instead. Overdue requests land on the commissioner's desk structurally, and `fulfilled` is terminal: an answered request cannot be quietly re-opened to dodge reporting.

A Soma program from the civic part of the verified corpus: 99 lines, cells Foia, FoiaTests. It passes soma check with soma 3.2.4.

How it is built

The cell Foia declares 6 signals in its face: assign_officer(id: String) -> Map, log_day(id: String) -> Map, simulate_days(id: String, n: Int) -> String, fulfill(id: String) -> Map, days_open(id: String) -> Int and request_state(id: String) -> String; keeps 1 memory slot: days (Map<String, Int>, persistent and consistent) under the invariant days >= 0 && days <= 20. Its state machine request starts in received and allows 4 transitions: received -> processing, processing -> fulfilled, processing -> overdue and overdue -> fulfilled. fulfilled is terminal. Handlers: request_state (line 41), assign_officer (line 43), log_day (line 50), simulate_days (line 65), fulfill (line 70) and days_open (line 76).

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

Generated from soma describe on this file.

The source

foia.cell

// ============================================================================
// FOIA — the statutory clock cannot be silently ignored
// ============================================================================
// 5 U.S.C. § 552 gives an agency 20 working days to answer a FOIA request.
// In practice requests sit for years — agencies just keep them "processing"
// and courts call the silence a constructive denial (journalists sued the
// FBI over requests pending for a decade). The dodge works because nothing
// FORCES the escalation. Here the day counter has a hard ceiling: day 21
// cannot be written for a request still in processing — the refused write
// trips the escalation transition instead. Overdue requests land on the
// commissioner's desk structurally, and `fulfilled` is terminal: an answered
// request cannot be quietly re-opened to dodge reporting.
// ============================================================================

cell Foia {
    face {
        signal assign_officer(id: String) -> Map
        signal log_day(id: String) -> Map
        signal simulate_days(id: String, n: Int) -> String
        signal fulfill(id: String) -> Map
        signal days_open(id: String) -> Int
        signal request_state(id: String) -> String
    }

    memory {
        // request -> working days in processing. Day 21 is unrepresentable:
        // the write refuses, and the refusal IS the escalation trigger.
        days: Map<String, Int> [persistent, consistent]
        invariant days >= 0 && days <= 20
    }

    // fulfilled is terminal: an answered request is answered forever.
    state request {
        initial: received
        received -> processing
        processing -> fulfilled
        processing -> overdue
        overdue -> fulfilled
    }

    on request_state(id: String) { return get_status(id) }

    on assign_officer(id: String) {
        let r = try { transition(id, "processing") }
        if r.error != () { return map("ok", false, "reason", "{r.error}") }
        days.set(id, 0)
        return map("ok", true, "request", id)
    }

    on log_day(id: String) {
        if request_state(id) != "processing" {
            return map("ok", false, "reason", "request is {request_state(id)}")
        }
        let d = days.get(id) ?? 0
        let w = try { days.set(id, d + 1) }
        if w.error != () {
            // The statute, enforced by the memory: day 21 does not exist
            // for an unanswered request. Escalate instead.
            transition(id, "overdue")
            return map("ok", true, "escalated", true, "day", d)
        }
        return map("ok", true, "escalated", false, "day", d + 1)
    }

    on simulate_days(id: String, n: Int) {
        for i in range(0, n) { log_day(id) }
        return request_state(id)
    }

    on fulfill(id: String) {
        let r = try { transition(id, "fulfilled") }
        if r.error != () { return map("ok", false, "reason", "{r.error}") }
        return map("ok", true, "after_days", days_open(id))
    }

    on days_open(id: String) { return days.get(id) ?? 0 }
}

cell test FoiaTests {
    rules {
        assert fulfill("r1").ok == false                  // can't answer the unassigned
        assert assign_officer("r1").ok == true
        assert simulate_days("r1", 20) == "processing"    // 20 days: still lawful
        assert days_open("r1") == 20
        assert_fails days.set("r1", 21)                   // day 21: unrepresentable
        assert simulate_days("r1", 1) == "overdue"        // the clock escalates
        assert log_day("r1").ok == false                  // overdue clock is stopped
        assert fulfill("r1").ok == true                   // commissioner closes it
        assert_fails transition("r1", "processing")       // no quiet re-opening
        assert_fails transition("r1", "overdue")

        // a fast request never touches escalation
        assert assign_officer("r2").ok == true
        assert simulate_days("r2", 5) == "processing"
        assert fulfill("r2").ok == true
        assert request_state("r2") == "fulfilled"
    }
}

What the toolchain says

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

$ soma check foia.cell
✓ All checks passed.
$ soma test foia.cell
test FoiaTests ...
  ✓ assert fulfill("r1").ok == false
  ✓ assert assign_officer("r1").ok == true
  ✓ assert simulate_days("r1", 20) == "processing"
  ✓ assert days_open("r1") == 20
  ✓ assert_fails days.set("r1", 21) — raised memory invariant violated on 'days': days >= 0 && days <= 20 — rejected write of 21 (key "r1"); the slot is unchanged
  ✓ assert simulate_days("r1", 1) == "overdue"
  ✓ assert log_day("r1").ok == false
  ✓ assert fulfill("r1").ok == true
  ✓ assert_fails transition("r1", "processing") — raised invalid transition: fulfilled → processing. Current state: 'fulfilled'. Valid targets: []
  ✓ assert_fails transition("r1", "overdue") — raised invalid transition: fulfilled → overdue. Current state: 'fulfilled'. Valid targets: []
  ✓ assert assign_officer("r2").ok == true
  ✓ assert simulate_days("r2", 5) == "processing"
  ✓ assert fulfill("r2").ok == true
  ✓ assert request_state("r2") == "fulfilled"

14 tests: 14 passed, 0 failed
$ soma verify foia.cell
Verifying foia.cell...

State machine 'request': 4 states, initial 'received'
  States: [fulfilled, overdue, processing, received]

  ✓ 4 states, 4 transitions
  ✓ all states reachable from 'received'
  ✓ terminal states: [fulfilled]
  ✓ 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 6 handlers structurally terminate
  ✓ refinement: handler `assign_officer` ⟶ {processing}
  ✓ refinement: handler `log_day` ⟶ {overdue [if w.error != ()]}
  ✓ refinement: handler `fulfill` ⟶ {fulfilled}
  · transition sources: `assign_officer`, `log_day`, `fulfill` 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)

Foia/invariants:

  ✓ invariant days >= 0 && days <= 20 — writer 'assign_officer' proven (writes 0)
  ✓ invariant days >= 0 — writer 'log_day' proven by induction (writes d + 1)
  ⚠ invariant days >= 0 && days <= 20 — runtime-checked (computed values): log_day → days [days <= 20] because `d + 1` is only known to lie in [1, 21] — narrow it: `require (d + 1) <= 20 else …`

12 passed, 1 warnings, 0 failures

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

Builtins used

get_status, map, range, transition

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