Commit Graph
2 Commits
Author SHA1 Message Date
Scott KarpandClaude Opus 4.7 a2744c012a azazel_processor: commit 2 — vidui (property 3, enumerative commit)
Pass 4 / Finding F, commit 2 of 10 from BUILD_AZAZEL.md.

Vayikra 16:21 — v'hitvadah alav et kol avonot b'nei Yisrael v'et kol
pish'eihem l'chol chatotam. The high priest confesses ALL iniquities,
ALL transgressions, for ALL sins. The verb is hitpa'el (reflexive) —
the confession is self-addressed before it is addressed to the
system. The kol-three-times structure is mandatory.

ENGINEERING READ:

The commit message for a disposal must enumerate what is being
disposed of. Untagged disposal is not disposal; it is loss. Azazel
does not accept uncommitted writes. The enumeration is produced at
the moment of commit, not retrieved from a pre-existing log.

WHY OPERATOR-PRODUCED, NOT AUTO-DERIVED:

The Mishkan high priest speaks the confession aloud. The system did
not generate it from a classification enum. In software: the caller
of confess() MUST supply the descriptions, not have them
auto-generated from a classification → string table. If the table
generated the confession, the operator could dispose state without
ever naming it themselves; the confession would become an
autogenerated label, not a self-addressed enumeration. That is the
failure mode property 3 defends against.

The convenience helpers (confess_empty_vital, confess_halt,
confess_boundary_violation, confess_malformed_input) DO NOT hide
this requirement. Each requires the operator to supply the specific
item; the helper only routes which category it goes into.

THE THREE CATEGORIES:

  avonot   — iniquities. CATEGORICAL errors: halt-trips
             (avodah_zarah, gilui_arayot, shfichut_damim — the
             cardinal sins). Sins of orientation.
  peshaim  — transgressions. BOUNDARY violations: distinction
             collapses (gilui-shaped), L3 schema rejections.
             Sins of boundary-keeping.
  chatot   — sins. TARGET errors: empty-vital, malformed responses.
             The literal meaning of chet — missing the mark.

Most disposals fill exactly one category. The Vidui dataclass
REQUIRES all three fields (the enumeration is comprehensive at the
shape level), but the lists may be empty. What is forbidden is the
ABSENCE of a slot — and the case where ALL THREE slots are empty
(EmptyConfessionError, no untagged disposal).

CREATED:

  src/azazel_processor/vidui.py            ~250 lines
    Vidui                — frozen dataclass: avonot, peshaim, chatot,
                           confessor, confessed_at (all required;
                           categories are tuples for deep immutability;
                           __post_init__ rejects all-empty, empty-string
                           items, non-string items, anonymous confessor)
    EmptyConfessionError — raised on untagged-disposal attempts
    confess(*, avonot, peshaim, chatot, confessor)
                         — the operator-produced builder
    Vidui.to_text()      — kol-three-times rendering for L3 storage
    Vidui.category_summary()
                         — counts only, no items (for the public
                           observability endpoint, commit 8 / property 9)

  Convenience helpers (each requires operator-supplied item):
    confess_empty_vital(korban_summary, *, confessor)
    confess_halt(condition, detail, *, confessor)
    confess_boundary_violation(violation, *, confessor)
    confess_malformed_input(description, *, confessor)

  test/test_vidui.py                       27 tests covering:
    - structural (exactly five fields, no silent additions)
    - all category-fill combinations succeed
    - all-empty / empty-string / whitespace / non-string items raise
    - confessor required (TypeError) + non-empty/non-whitespace
      validated (EmptyConfessionError)
    - frozen dataclass (cannot mutate after construction)
    - categories are tuples (deep immutability — frozen=True does
      not propagate through container types)
    - confessed_at: timezone-aware UTC, set to current time
    - to_text() preserves kol-three-times structure; omits empty
      categories
    - category_summary() returns counts WITHOUT items
    - all four convenience helpers route correctly + still require
      confessor

Public surface updated in __init__.py.

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 18:49:34 -04:00
Scott KarpandClaude Opus 4.7 d06ecd244d azazel_processor: commit 1 — skeleton + stages (properties 11 & 12)
Pass 4 / Finding F, commit 1 of 10 from BUILD_AZAZEL.md. Lays the
load-bearing foundation: classification discipline (property 11) at
the type system level, and unmanaged-destination opacity (property
12) via a Python sentinel. Every later commit in the Azazel build
inherits these defenses for free.

CREATED:

  azazel_processor/
    pyproject.toml                      — Poetry config matching
                                          korban_processor; pythonpath
                                          includes ../korban_processor/src
                                          so the cross-package
                                          classification-discipline test
                                          can import both packages
    src/azazel_processor/
      __init__.py                       — public surface for commit 1:
                                          AzazelStage, AzazelStaged,
                                          AzazelStageError, ERETZ_GEZERAH,
                                          is_eretz_gezerah
      eretz_gezerah.py                  — the destination sentinel; module
                                          __getattr__ refuses any
                                          attribute beyond the two public
                                          names so introspection cannot
                                          discover further handles
      stages.py                          — AzazelStage enum (six
                                          checkpoints from the build doc:
                                          STAGED → CONFESSED → ROUTED →
                                          DESTROYED → ACKNOWLEDGED →
                                          RECORDED), AzazelStaged frozen
                                          dataclass, AzazelStageError
    test/
      test_eretz_gezerah.py             — 11 tests covering: sentinel
                                          identity stable across imports,
                                          not-a-string, no public attrs,
                                          repr leaks no destination info,
                                          identity (is) not equality (==),
                                          impostor with __eq__ rejected,
                                          module __getattr__ refuses
                                          unknown access, CI invariant
                                          forbidding destination-handle
                                          ASSIGNMENTS in source (catches
                                          regressions; mention in
                                          docstrings/__all__/getattr
                                          allowlist is fine)
      test_stages.py                    — 10 tests: intra-Azazel gating
                                          (happy path, mismatch raises
                                          naming both stages, frozen
                                          dataclass, six distinct
                                          checkpoints) and CROSS-PACKAGE
                                          classification discipline:
                                          KorbanStaged and AzazelStaged
                                          are distinct types; enum value
                                          spaces are disjoint; an Azazel
                                          function refuses a KorbanStaged
                                          input and vice versa; no
                                          conversion function exists in
                                          the public surface

PROPERTY COVERAGE THIS COMMIT:

  Property 11 (classification discipline):
    Defended by the type-distinctness of AzazelStaged vs.
    korban_processor.stages.Staged. Cross-package tests verify the
    two cannot be substituted at runtime; the absence of any
    korban_to_azazel/to_korban/from_korban/convert function in the
    public surface is verified by name-shape grep.

  Property 12 (unmanaged destination):
    Defended by the sentinel design: only object identity is exposed;
    no string handle exists; module __getattr__ refuses introspection;
    CI invariant catches assignment-shaped regressions across all of
    azazel_processor/src/.

PENDING (commits 2-10 per BUILD_AZAZEL.md):

  vidui (commit 2 — enumerative commit, property 3)
  shaliach + tevilah (commit 3 — role-separated operator, property 4)
  goral (commit 4 — blind routing, property 2)
  azazel orchestrator + state machine (commit 5 — properties 5, 7, 10)
  protocol envelope (commit 6 — protocol-restricted access, property 8)
  L3 azazel_records (commit 7 — operator-internal half of property 9)
  /peah/azazel_log endpoint (commit 8 — public half of property 9)
  Korban→Azazel routing on empty-vital + chatzotzrot wiring
    (commit 9 — properties 1, 6)
  Closure pass (commit 10)

Tests: azazel_processor 21 passed. korban_processor unchanged at
203 passed, 1 skipped (live-API). No regressions across the boundary.

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