Files
Scott KarpandClaude Opus 4.7 6eb555a5cf azazel_processor: commit 6 — protocol envelope (property 8)
Pass 4 / Finding F, commit 6 of 10 from BUILD_AZAZEL.md. Closes
the Python side of the build; commits 7-9 are cross-language
integrations (L3, observability endpoint, intake-side routing).

Vayikra 16: the Azazel goat goes ONCE A YEAR, on Yom Kippur, under
the full ritual envelope.
Vayikra 17:7: וְלֹא־יִזְבְּחוּ עוֹד אֶת־זִבְחֵיהֶם לַשְּׂעִירִם —
they shall no longer offer their sacrifices to the sa'irim. Same
destination outside the envelope is avodah zarah (the failure mode
specs/eish_zarah_reading.md catalogs).

THE GATE:

  - is_yom_kippur_protocol_active(today=None) — predicate; True
    when an operator-configured Gregorian window covers today
  - authorize_disposal() — context manager; explicit per-thread
    authorization for ONE disposal cycle (event-driven disposals
    during the year)
  - has_explicit_disposal_authorization() — predicate; True iff
    the current thread is inside an authorize_disposal block
  - assert_protocol_envelope_active() — gate; raises ProtocolViolation
    if NEITHER condition is met. Wired into dispatch_for_disposal
    as the FIRST check (before role gate, before any side-effect)

WHY a CONTEXT MANAGER, not a flag:

A persistent flag would let an operator "set authorization true and
leave it" — the avodah-zarah shape (using the channel without the
envelope). The context manager forces explicit scope: authorization
exists only within the with-block, only on the thread that opened it.

WHY PER-THREAD:

Process-wide authorization would let two concurrent disposals share
without explicit coordination — operator A's authorize_disposal()
would silently authorize operator B's dispatch_for_disposal(). Per-
thread isolation forces each disposal to carry its own authorization
explicitly.

WHY OPERATOR-CONFIGURED Gregorian DATES, not Hebrew calendar:

The Hebrew-calendar conversion lives in vav_pipeline (Elixir) — we
do not bring a Hebrew-calendar library into azazel_processor's
dependency surface. Operators set the Gregorian range each year (or
delegate it to a wrapper script). For now, this module is purely
data-shape; commit 9 may wire vav_pipeline's Hebrew calendar to set
the window automatically.

CREATED:

  src/azazel_processor/protocol.py        ~155 lines
    ProtocolViolation                     — raised on out-of-envelope
                                            invocation; error message
                                            names BOTH legitimate paths
                                            (Yom Kippur window, authorize
                                            context) so the operator
                                            knows their options
    set_yom_kippur_window(start, end)     — operator-configured;
                                            inclusive Gregorian dates;
                                            inverted range clears
    clear_yom_kippur_window()             — disable
    is_yom_kippur_protocol_active(today=None)
                                          — date-range membership
    authorize_disposal()                  — context manager; reentrant
                                            (nested entries each
                                            increment a counter, each
                                            exit decrements; cleared
                                            via try/finally even on
                                            inner exception)
    has_explicit_disposal_authorization() — per-thread, scoped
    assert_protocol_envelope_active()     — the gate

CHANGED:

  src/azazel_processor/azazel.py
    dispatch_for_disposal now calls assert_protocol_envelope_active()
    as the FIRST check. Outside the envelope: ProtocolViolation
    raised before any side-effect (no shaliach activation, no payload
    held, no checkpoint persisted).

  test/test_azazel.py
    Added an autouse `_envelope` fixture that wraps every existing
    orchestrator test in `with authorize_disposal():`. Protocol
    restriction is verified separately in test_protocol_restriction.py.

CREATED:

  test/test_protocol_restriction.py      16 tests covering:
    - default state (no window, no authorization) refuses dispatch
      with a ProtocolViolation that names both legitimate paths
    - assert_protocol_envelope_active raises in default state
    - both predicates False in default state
    - Yom Kippur window: today inside → active; outside → inactive;
      inverted range clears; inclusive at boundaries; clear disables
    - authorize_disposal: inside block → active; nested blocks
      decrement correctly; exception inside still clears (try/finally);
      per-thread isolation (sibling thread sees False even when parent
      thread is inside an authorize_disposal block)
    - dispatch proceeds inside authorize_disposal context
    - dispatch proceeds inside Yom Kippur window
    - outside envelope: NO side-effect (no in-flight file written, no
      payload held by shaliach)
    - protocol gate fires BEFORE role gate (inactive handler outside
      envelope raises ProtocolViolation, not ShaliachError)

  Per-test fixture clears Yom Kippur state on entry and exit so
  module-level state doesn't bleed between tests.

PYTHON SIDE NOW FEATURE-COMPLETE:

  Properties defended end-to-end via Python code:
    2  goral
    3  vidui
    4  shaliach + tevilah
    5  checkpoint persistence
    7  authorized routing
    8  protocol envelope
    10 hard termination
    11 classification discipline
    12 unmanaged destination

  Properties remaining (cross-language, in commits 7-9):
    1  parity provisioning      — commit 9 (Korban→Azazel intake)
    6  out-of-band acknowledgment — commit 9 (chatzotzrot wiring)
    9  observable disposal       — commit 7 (L3) + commit 8 (endpoint)

Tests: azazel_processor 125 passed (was 109, +16). 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:06:27 -04:00
..