Files
bikoret/vav_pipeline/test
Scott KarpandClaude Opus 4.7 1a27a283ae azazel commit 9 — Korban→Azazel intake routing + Chatzotzrot ack (properties 1 + 6)
Pass 4 / Finding F, commit 9 of 10 from BUILD_AZAZEL.md. Closes the
last two property gaps: 1 (parity provisioning, end-to-end) and 6
(out-of-band acknowledgment via Chatzotzrot). The Azazel Processor
is now functionally complete; commit 10 is the closure-and-review
pass with no further code.

PROPERTY 1 — PARITY PROVISIONING, two layers:

LAYER 1 (import-time): external_offering imports azazel_processor
at module load. If the import fails, external_offering refuses to
load with a Torah-anchored ImportError. The two-goat architecture is
SYMMETRIC OR IT IS BROKEN — operators cannot run the processing
channel without the disposal channel being importable. The strongest
formulation: if you cannot dispose, you cannot process.

LAYER 2 (runtime): when zerikah produces no vital content,
consult_external_model now routes the korban to disposal rather than
returning an empty ProcessedKorban with the rejection in `discarded`.
The empty-vital case is named (vidui), routed (eretz_gezerah),
recorded (L3 azazel_records), and acknowledged (chatzotzrot :moadim).

PROPERTY 6 — OUT-OF-BAND ACKNOWLEDGMENT:

The Python orchestrator drops the AzazelRecord JSON in the pending-l3
directory. The Elixir AzazelIngest GenServer reads it, persists to
L3 via record_disposal/1, AND fires Chatzotzrot.signal(:moadim, ...)
with a categorical summary (case_ref + shaliach + categories_filled).
The chatzotzrot is the side channel — distinct from the disposal
channel itself. The orchestrator does NOT know whether the signal
was successfully transmitted; the disposal completes regardless.

The :moadim signal type was the right choice: Yom Kippur completion
semantics. This disposal cycle is closed; the public fact (property
9) is now witnessable via /peah/azazel_log.

PRIVACY BOUNDARY ALSO APPLIES TO THE CHATZOTZROT:

The signal's `reason` field carries a CATEGORICAL summary, not the
vidui items. A test verifies that a known secret string in a vidui
chatot does NOT leak into the chatzotzrot signal — the operator-side
event channel is held to the same privacy boundary as the public
endpoint.

CHANGED:

  korban_processor/src/korban_processor/external_offering.py
    + Import azazel_processor at module load (raises ImportError
      with Torah-anchored explanation if azazel_processor is not
      importable — property 1, layer 1)
    + DEFAULT_AZAZEL_PENDING_L3_DIR
    + ExternalConsultation.disposal_record: Optional[AzazelRecord]
        Non-None iff the consultation was routed to disposal
    + _content_addressable_case_ref(content, source_query)
        SHA-256-based stable identifier for audit
    + _make_pending_l3_recorder(pending_dir)
        Returns a recorder callable that drops the JSON file
        atomically (write to .tmp, then os.replace)
    + _route_to_azazel(...) — internal helper running the full
        protocol envelope:
        - opens authorize_disposal context
        - activates a fresh IshItti shaliach
        - builds vidui via confess_empty_vital
        - dispatches via dispatch_for_disposal
        - tevilah's the shaliach
    + consult_external_model:
        - new keyword args azazel_kohen_gadol, azazel_shaliach_id,
          azazel_pending_l3_dir
        - branch on `if not vital.strip()`: route to disposal,
          attach record to ExternalConsultation

  vav_pipeline/lib/vav_pipeline/azazel_ingest.ex
    + ingest_file/2 fires Chatzotzrot.signal(:moadim, ...) after
      successful L3 write, BEFORE shredding the source file. So the
      ack comes BEFORE the in-flight artifact disappears — recovery
      is possible if either side crashes.
    + Process.whereis(Chatzotzrot)-guarded so tests without the full
      supervision tree still work.
    + chatzotzrot_summary/1 builds the categorical summary
      (case_ref + shaliach + categories_filled). Privacy boundary:
      no vidui items, only counts/category names.

TESTS:

  korban_processor/test/test_external_offering.py             +4 tests
    - empty-vital response routes to azazel:
      - processed.vital_content == ""
      - disposal_record is an AzazelRecord with kohen + shaliach
      - pending-l3 file dropped with correct shape
    - processable response does NOT create disposal_record (the
      negative case — vital content goes through normally)
    - PRIVACY: pending-l3 file does not contain the model's
      response text (verified with a known secret_phrase)
    - case_ref is content-addressable: same content + same
      source_query → same case_ref (audit can recompute)

  vav_pipeline/test/azazel_ingest_test.exs                    +2 tests
    - successful ingest fires Chatzotzrot :moadim signal
      (subscribe :all, observe — :moadim routes to :all per
      chatzotzrot.ex routing table)
    - PRIVACY: chatzotzrot signal does NOT include vidui item
      content (known secret_phrase verified absent from the signal)

Tests:
  - korban_processor: 207 passed, 1 skipped (was 203/1; +4)
  - azazel_processor: 125 passed (unchanged)
  - vav_pipeline:     363 passed, 0 failures, 35 excluded
                      (was 361/35; +2 azazel_ingest chatzotzrot tests)

Property coverage after commit 9:
  All 12 properties of azazel_engineering_reading.md §14 defended
  by code:
    1  parity provisioning      ✓ (import-time + runtime)
    2  blind routing / goral     ✓
    3  enumerative commit        ✓
    4  role-separated operator   ✓
    5  provisioned infrastructure ✓
    6  out-of-band acknowledgment ✓
    7  authorized routing        ✓
    8  protocol-restricted access ✓
    9  observable disposal       ✓ (operator-internal + public)
    10 hard termination          ✓
    11 classification discipline ✓
    12 unmanaged destination     ✓

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