Files
bikoret/azazel_processor
Scott KarpandClaude Opus 4.7 c3267c59bf azazel_processor: commit 5 — orchestrator state machine (properties 5, 7, 10)
Pass 4 / Finding F, commit 5 of 10 from BUILD_AZAZEL.md. Largest
commit so far; pulls together vidui + shaliach + goral + stages and
adds the state machine + in-flight persistence.

THE STATE MACHINE (six checkpoints, persisted):

  STAGED       → AzazelStaged envelope tagged; payload held by shaliach
  CONFESSED    → vidui carried into the staged value
  ROUTED       → routing committed (always ERETZ_GEZERAH); authorized_by
                 + shaliach attribution recorded
  DESTROYED    → payload references cleared from shaliach;
                 gc.collect() called to release; payload becomes
                 unreachable from any orchestrator-visible code path
  ACKNOWLEDGED → out-of-band ack callback invoked (commit 9 wires
                 Chatzotzrot.signal(:moadim) here)
  RECORDED     → AzazelRecord persisted via callback (commit 7 wires
                 the cross-language L3 file-watcher bridge here)

After RECORDED completes, the in-flight checkpoint file is shredded —
the disposal is now permanent in L3, the in-flight artifact must not
linger.

PROPERTY COVERAGE:

  Property 5 (provisioned infrastructure / sukkot):
    Each checkpoint persists state to <in_flight_dir>/<case_ref>.json
    via write-then-rename (atomic against crash mid-write). list_in_flight
    returns partial disposals so resume tooling can pick them up.
    The Mishkan analog: the sukkot along the wilderness route where
    the ish itti can rest if the disposal halts.

  Property 7 (authorized routing):
    AzazelRecord.authorized_by carries the kohen_gadol identifier;
    AzazelRecord.shaliach carries the ish itti identifier. The
    routing destination is the ERETZ_GEZERAH sentinel (property 12).
    The routing decision is OWNED and traceable; the destination
    is not.

  Property 10 (hard termination):
    The DESTROYED checkpoint clears handler._payload_refs and calls
    gc.collect(). The AzazelRecord contains the vidui — what was
    disposed — but NOT the disposed payload itself. The to_storage_dict
    serialization explicitly excludes payload-shaped fields.

CREATED:

  src/azazel_processor/azazel_record.py    ~95 lines
    AzazelRecord                           — frozen dataclass; fields:
      case_ref, vidui, routing, authorized_by, shaliach,
      triggering_call, goral_outcome_id, disposed_at
      .to_storage_dict() — JSON-safe serialization for L3 + in-flight

  src/azazel_processor/azazel.py           ~225 lines
    AzazelInput                            — what is being disposed
    dispatch_for_disposal(input, *, handler, confess_with,
                          authorized_by, in_flight_dir,
                          acknowledger, recorder, goral_outcome_id)
                                           — the single legitimate
                                             entry point; walks the
                                             six checkpoints
    list_in_flight(in_flight_dir)          — surfaces partial disposals
    DEFAULT_IN_FLIGHT_DIR                  — /tmp/shorashim/azazel/in_flight

  test/test_azazel.py                      21 tests covering:
    HAPPY PATH:
    - dispatch returns AzazelRecord
    - record carries authorization + shaliach attribution
    - record routes to ERETZ_GEZERAH
    - record carries the full vidui

    PROPERTY 10 CI INVARIANTS:
    - record does not carry payload (by attribute name)
    - serialized record does not contain the payload's marker string
    - STRUCTURAL: AzazelRecord dataclass field set has no
      payload-shaped names (catches future regression)
    - STRUCTURAL: no recover/undispose/restore/rehydrate/retrieve
      function in azazel_processor public surface (catches
      future regression)
    - payload references cleared from shaliach at DESTROYED

    PROPERTY 5 CHECKPOINT TESTS:
    - in-flight file shredded after RECORDED
    - checkpoints persist incrementally (verified by inspecting the
      file at ack/rec callback fire-time)
    - list_in_flight returns partial disposals
    - list_in_flight empty when no disposals
    - persistence is atomic (file is valid JSON at every visit)

    ROLE GATE (property 4 still enforced):
    - dispatch refuses inactive handler
    - dispatch refuses non-IshItti

    FILENAME SAFETY:
    - case_ref with slash → ValueError
    - case_ref with NUL → ValueError

    AzazelRecord VALIDATION:
    - authorized_by required
    - shaliach required
    - to_storage_dict omits payload
    - frozen (cannot mutate)

DEFAULTS:

  acknowledger and recorder default to no-op callables. This is
  deliberate for commit 5 — the state machine works end-to-end without
  external side effects, and commits 7 (L3) and 9 (chatzotzrot) plug
  the real handlers into the same callback slots. Tests can supply
  their own to inspect mid-route state.

Tests: azazel_processor 109 passed (was 88, +21). korban_processor
unchanged at 203 passed, 1 skipped. No regressions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 08:02:09 -04:00
..