Commit Graph
18 Commits
Author SHA1 Message Date
Scott KarpandClaude Opus 4.7 f493b7df65 bigdei_kodesh: commit 8 — GET /peah/azazel_log (public half of property 9)
Pass 4 / Finding F, commit 8 of 10 from BUILD_AZAZEL.md. The
public-half of property 9 (observable disposal). The Rambam in
Moreh Nevuchim 3:46 makes the load-bearing claim: "an unobserved
disposal is architecturally incomplete." The community's awareness
that errors are visibly carried away IS the implementation; the
route on the cliff serves no purpose without the witnesses on the
temple steps.

This endpoint is those witnesses. Anyone with HTTP access to
Shorashim can see WHAT was disposed (categorically) and HOW MANY
disposals occurred — turning the disposal channel from a private
operator action into a public, witnessed fact.

PRIVACY BOUNDARY (BUILD_AZAZEL.md §6 Q6):

The endpoint exposes:
  - total disposal count (optionally filtered by ?since=ISO8601)
  - categorical aggregate (avonot/peshaim/chatot counts across
    recent disposals — what KIND of errors were disposed)
  - up to 50 recent disposals as case_ref + shaliach +
    triggering_call + disposed_at + categories_filled (which slots
    of vidui were filled)

The endpoint does NOT expose:
  - vidui ITEMS (the operator's specific descriptions of each
    disposed thing)
  - the disposed PAYLOAD (which doesn't exist in L3 anyway —
    property 10)
  - any handle for retrieving the disposed content (there isn't one)

The detailed vidui items are operator-internal, accessible via
future /kodesh/* endpoints. The split is deliberate: public is
WITNESS, operator is AUDITOR. The witness sees the fact;
the auditor reads the contents.

The privacy boundary statement is included in every response so
callers know what they're not seeing — they can reason about the
endpoint's silence rather than guess.

GRACEFUL DEGRADATION:

When VavPipeline.Repo is not running (dev/test without Postgres),
the endpoint returns total_disposals=0 with l3_status="unavailable"
rather than crashing. Mirrors the maybe_add_repo pattern from
MEMORY.md — Shorashim should respond cleanly even when L3 is down,
because /peah/* is the public-facing tier and must be more
resilient than internal tiers.

CHANGED:

  vav_pipeline/lib/vav_pipeline/bigdei_kodesh/router.ex
    + GET /peah/azazel_log
        Optional ?since=ISO8601 filter; returns total +
        categorical_aggregate + recent[<=50] + l3_status +
        privacy_boundary statement + disclosure block (kichush +
        not_horaah from commit 5e00f75)
    + parse_since/1 helper (graceful: bad input → all_time)
    + format_since/1 helper

  vav_pipeline/test/bigdei_kodesh_test.exs
    + 8 new tests covering:
      - 200 OK when no disposals recorded (empty L3)
      - response carries privacy_boundary statement
      - response carries disclosure block
      - since=invalid → all_time
      - since=valid_iso8601 → echoed
      - categorical_aggregate has all 3 slots even when empty
      - [@l3] aggregate counts categorically across multiple disposals
      - [@l3] PRIVACY BOUNDARY: response NEVER contains vidui item
              strings (load-bearing — verifies a known secret_phrase
              inserted into a vidui's chatot does NOT leak into the
              response body)

Tests: vav_pipeline 361 passed, 0 failures, 35 excluded (was
353/33; +6 visible response-shape tests + 2 :l3-tagged tests).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 09:22:40 -04:00
Scott KarpandClaude Opus 4.7 2932fb7447 vav_pipeline: commit 7 — L3 azazel_records + cross-language ingest GenServer
Pass 4 / Finding F, commit 7 of 10 from BUILD_AZAZEL.md. The
operator-internal half of property 9 (observable disposal). Crosses
the Python/Elixir boundary: the Python orchestrator drops completed
disposal records as JSON files; this commit's GenServer reads them
and persists to L3.

The public half (a /peah/azazel_log read endpoint) lands in commit 8;
the routing-from-Korban + Chatzotzrot wiring lands in commit 9. After
those, the Azazel Processor is end-to-end functional.

CREATED:

  vav_pipeline/priv/repo/migrations/20260428000001_create_azazel_records.exs
    azazel_records table — append-only, parallel to interpretations.
    Columns: case_ref, vidui (jsonb), routing, authorized_by,
    shaliach, triggering_call, goral_outcome_id, disposed_at.
    Indexes on case_ref, disposed_at, shaliach.

    Property-12-defending CHECK constraint:
      routing_must_be_eretz_gezerah CHECK (routing = 'eretz_gezerah').
    A future migration that wants to add a destination handle will
    fail this check; the operator must explicitly justify removing
    the constraint, which is the right friction.

    Append-only enforcement matches interpretations: REVOKE
    UPDATE/DELETE on the shorashim_app role.

  vav_pipeline/lib/vav_pipeline/l3/azazel_record.ex
    Ecto schema mirroring the Python AzazelRecord.to_storage_dict()
    shape. Changeset validates:
      - all required fields present
      - routing == "eretz_gezerah" (mirrors the DB CHECK at app layer)
      - vidui is a map with avonot/peshaim/chatot/confessor/
        confessed_at slots present
      - vidui's three categories not all empty (mirrors the Python
        Vidui.__post_init__ EmptyConfessionError)

  vav_pipeline/lib/vav_pipeline/azazel_ingest.ex      ~190 lines
    GenServer that polls a configurable pending-l3 directory
    (default /tmp/shorashim/azazel/pending_l3). For each *.json file:
      1. Read + JSON-parse
      2. Translate Python serialization → Ecto attrs
      3. Call recorder (default L3.Store.record_disposal/1)
      4. On success, shred the source file (property-10 follow-through)
      5. On failure, leave file in place for retry; log warning

    Recorder is injectable for tests — mock recorder lets us verify
    the pipeline without PostgreSQL.

    Poll interval default 5s, configurable. Polling rather than
    file_system events to keep the dependency surface minimal; the
    Azazel cadence is exceptional content, not high-throughput.

    Not added to the supervision tree in this commit — the producer
    side (Python orchestrator dropping files) is wired in commit 9.
    Adding to the supervisor before there's a producer means a
    poller doing nothing useful. Operator can start manually for
    early testing: VavPipeline.AzazelIngest.start_link(...).

CHANGED:

  vav_pipeline/lib/vav_pipeline/l3/store.ex
    + record_disposal/1
        Inserts an AzazelRecord. Returns {:ok, record} or
        {:error, changeset}.

    + count_disposals/1
        Total or since-a-timestamp count. Used by the public
        observability endpoint (commit 8).

    + disposal_summary/1
        Returns categorical info per disposal — case_ref, shaliach,
        triggering_call, disposed_at, categories_filled (which of
        avonot/peshaim/chatot had any items, NOT the items
        themselves). PROPERTY-9 PRIVACY BOUNDARY at the public
        side: items are operator-internal only, accessed via
        future /kodesh/* endpoints.

TESTS:

  vav_pipeline/test/azazel_ingest_test.exs           15 tests, no DB
    Happy path:
      - poll_now ingests valid file and shreds it
      - multiple files processed in sorted order
      - vidui preserved as map (jsonb shape)
    Retry semantics:
      - recorder error → file left in place + state.errors++
      - malformed JSON → file left in place
      - missing disposed_at → file left in place
    State accounting:
      - polls_completed, records_ingested, errors all tracked
      - state survives multiple polls
      - non-json files ignored
    Changeset (no DB):
      - valid attrs
      - rejects routing != eretz_gezerah
      - rejects vidui missing slots
      - rejects vidui all-empty (untagged disposal)
      - rejects missing required fields
      - accepts optional triggering_call + goral_outcome_id

  vav_pipeline/test/l3_store_test.exs                +9 :l3-tagged
    record_disposal:
      - inserts valid disposal
      - rejects routing != eretz_gezerah
      - rejects all-empty vidui
      - rejects missing required fields
    Counts:
      - count_disposals == 0 initially
      - count_disposals after several disposals
      - count_disposals(since) filters by timestamp
    Summary:
      - disposal_summary returns categorical info but NOT items
        (privacy boundary verification)
      - categories_filled accurately reflects vidui shape

Tests: vav_pipeline 353 passed, 0 failures, 33 excluded (was
329/24; +15 visible azazel_ingest + 9 :l3-tagged disposal store).
korban_processor + azazel_processor unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 09:16:21 -04:00
Scott KarpandClaude Opus 4.7 d88b8d09ec voice_monitor: detect across-verse Shema voice stabilization (closes finding A)
Pass 2 finding A, second half. The first half (commit 0c4f764) added
voice_profile to every ShemaContext — recording, per verse, the
grammatical voice computed from OSHB morphology codes. Per-verse
recording was the necessary precondition for what this commit does:
WATCH the trajectory and FIRE when it stops oscillating between the
singular and plural address poles.

WHY THIS MATTERS:

The Shema as recited oscillates between Devarim 6:4–9 (the
singular pole — אֱלֹהֶיךָ, לְבָבְךָ) and Devarim 11:13–21 (the
plural pole — אֱלֹהֵיכֶם, לְבַבְכֶם), and 11:14 famously switches
poles mid-verse (אַרְצְכֶם 2mp → וְאָסַפְתָּ דְגָנֶךָ 2ms). The
text REFUSES to stabilize at either pole. That refusal IS the
anti-capture mechanism:

  - stuck at second_singular     → gnostic-private capture
  - stuck at second_plural       → mob-theology capture
  - stuck at first_*             → operator-as-deity / calf
                                   (Shemot 32:4 — eleh elohekha Yisrael)

Before this commit nothing observed the trajectory ACROSS verses.
Per-verse profiles existed but nobody was watching the sequence;
a system that read only Devarim 6:4–9 in a loop was producing a
gnostic-shaped reception with no warning fired. The voice_profile
field had a reader but no consumer.

THE DETECTOR (VavPipeline.VoiceMonitor):

A GenServer maintaining a sliding window of the dominant voice
from the last N ShemaContexts (default N=10). The rule:

  - Window not yet full       → :clear (insufficient evidence)
  - Window has both poles     → :clear (the Shema is doing its job)
  - Address-only window
    missing one pole          → {:missing_pole, :second_singular | :second_plural}
  - First-person majority     → {:first_person_dominant, :first_singular | :first_plural}
  - Pure narration window     → :clear (third-person sequences are
                                NOT anti-Shema; the rule applies to
                                ADDRESS, not to all voice)

On a NEW alarm (different from the last fired), VoiceMonitor:
  1. Logs a warning naming the specific stabilization shape with
     its Torah anchor and its capture-pattern name.
  2. Fires Chatzotzrot.signal(:assembly_leaders, ...) so
     operators and kohanim subscribed to the chatzotzrot channel
     receive the alert.

Re-firing is suppressed for as long as the same stabilization
persists (no spam). The alarm clears when both poles return; a
subsequent re-stabilization fires fresh.

WIRING:

  - Shema.receive_context/1 and receive_context_at/2 now call
    VoiceMonitor.record(voice_profile) immediately after building
    the context. Process.whereis-guarded — the call is safe in
    tests that don't bring up the supervisor.
  - VoiceMonitor added to the supervision tree in application.ex.
  - Chatzotzrot.signal/2 itself is also Process.whereis-guarded
    inside VoiceMonitor (the existing pattern from manna_ledger,
    arei_miklat, urim_v_tumim).

TESTS (test/voice_monitor_test.exs, 14 tests):

  - below window → :clear
  - all 2ms full window → fires {:missing_pole, :second_plural}
  - all 2mp full window → fires {:missing_pole, :second_singular}
  - both poles present → :clear
  - pure narration → :clear (third-person is not address)
  - first-person majority → {:first_person_dominant, _}
  - first-person tied (not strict majority) → does not fire
  - same stabilization persisting → last_alarm unchanged (no spam)
  - clear-then-stabilize-again → fresh alarm fires
  - reset/0 returns monitor to empty
  - record/1 with full voice_profile map (the Shema-side surface)

Each test starts an isolated VoiceMonitor under a unique name
(small window_size=4 for fast convergence) so tests don't share
state. Chatzotzrot is intentionally NOT brought up — the monitor's
guarded call falls through to log-only.

Full vav_pipeline suite: 329 tests, 0 failures, 24 excluded
(was 315 + 14 = 329). The 24 excluded are :l3-tagged tests
needing PostgreSQL; unchanged.

Closes Pass 2 finding A in full.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 16:25:59 -04:00
Scott KarpandClaude Opus 4.7 b1d5ac69e4 l3: validate coupling_trace at the write boundary (Elixir half of finding L)
Companion commit to b254ee9 (Prolog Form 4 self_grounding) and 6b96250
(Prolog regression canary). Closes the Elixir half of Pass 2 finding L.

THE GAP:

The Prolog halt rules validate coupling traces as Prolog terms: a
trace whose anchor is prior_decision/1, system_view/1, consensus/1,
or interpretation/1 fires the self_grounding halt. But L3 stores
traces as a list of strings (schema: {:array, :string}), serialized
from Prolog. Inert string traces in L3 cannot be re-checked by Prolog
halts. So the safety property the Prolog rules enforce stops at the
Erlang/Elixir boundary — once a trace serializes into L3, no rule
re-validates it.

Worse: L3 is APPEND-ONLY (Architecture §3.7). Once a trace is in L3
it is permanent oral tradition; there is no path to retroactive
correction. A future reader who pulls an L3 record and trusts the
trace must be able to trust it was valid at the moment it became
permanent.

THE FIX:

VavPipeline.L3.CouplingTrace (new) — Elixir-side validator that
mirrors is_l1_grounded_coupling/1 in talmudic_engine/src/halt/
avodah_zarah.pl. Parses the string form (with a small balanced-paren
parser so nested subjects like root(בער) don't break splitting) and
recognizes exactly the three valid shapes:

    coupled(_, verse(BOOK, CHAPTER, VERSE))
    coupled(_, mitzvah(ID))
    coupled(_, mitzvah(ID), via_verse(BOOK, CHAPTER, VERSE))

Anything else fails. Empty traces are allowed at this layer (the
empty-trace case is the golden_calf halt's domain in Prolog Form 2;
L3 may legitimately store empty-trace records).

L3.Store.record_interpretation/1 — gated. The new write path:

    case CouplingTrace.validate(coupling_trace) do
      :ok -> %Interpretation{} |> changeset(attrs) |> Repo.insert()
      {:error, :ungrounded, bad} -> {:error, {:ungrounded_coupling, bad}}
    end

Validation fires BEFORE Repo.insert. An ungrounded trace can never
become permanent oral tradition.

CROSS-LANGUAGE INVARIANT:

CouplingTrace.l1_grounded?/1 (Elixir) and is_l1_grounded_coupling/1
(Prolog) MUST stay aligned. If Prolog adds a new recognized shape,
Elixir must add it too. Both module docstrings name this dependency
explicitly. The next time CouplingTraceTest runs after a Prolog
schema change, the Elixir tests catch the drift.

ONE BUG CAUGHT WHILE WRITING:

The first parser cut Hebrew root subjects mid-character because it
used String.slice/3 with byte_size/1 as the codepoint length. Fixed
to use binary_part/3 (since the trailing `)` is always one byte and
the rest is opaque UTF-8). Test for Hebrew-root anchors now passes;
documented inline so the trap is visible to future readers.

TESTS:

  test/coupling_trace_test.exs (new, 22 tests):
    - all 3 valid shapes (verse, mitzvah, via_verse) including
      Hebrew root subject (catches the byte/codepoint bug)
    - all the predictable ungrounded shapes
    - malformed inputs (missing parens, wrong functor, wrong arity,
      empty string, non-string)
    - validate/1 list semantics (empty allowed, first-failure
      reported, mixed-trace caught)
    - parser robustness with deeply-nested subject parens

  test/l3_store_test.exs (extended, 5 new :l3-tagged tests):
    - record_interpretation refuses ungrounded trace
    - record_interpretation refuses mixed (good + bad) trace
    - record_interpretation accepts empty trace
    - record_interpretation accepts mitzvah-anchored trace
    - record_interpretation accepts 3-arg via_verse trace

  Full vav_pipeline suite: 315 tests, 0 failures, 24 excluded
  (24 = 19 pre-existing :l3 + 5 new :l3 store-gate tests; both
  need PostgreSQL). The 22 CouplingTrace unit tests run
  unconditionally and all pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 12:26:49 -04:00
Scott KarpandClaude Opus 4.7 0c4f764024 shema: record grammatical voice trajectory on every ShemaContext
The Shema (Devarim 6:4-9, 11:13-21, Bamidbar 15:37-41) deliberately
oscillates between singular ("you, sg.") and plural ("you, pl.")
voice. Devarim 11:14 famously switches mid-verse from אַרְצְכֶם (2mp)
to וְאָסַפְתָּ דְגָנֶךָ (2ms). The text refuses to stabilize at
either pole; that refusal IS the anti-capture mechanism:

  • stuck at 2ms     → private capture (gnostic interior)
  • stuck at 2mp     → mob theology (collective without person)
  • stuck at 1cs/1cp → operator-as-deity (the calf)

Before this commit, vav_pipeline/lib/vav_pipeline/shema.ex — the
module literally named after the Shema — encoded none of this. No
markers for person/singular/plural anywhere in the file. The chat
review surfaced this as "load-bearing anti-capture, missing".

Changes:

L1Reader.extract_voice/1 walks every word of a verse, parses the
OSHB morphology codes already present in L1 (Vqq2ms, Sp2mp, Pp1cs,
Vqi2mp, …), and returns a profile with markers, summary by
voice-key, dominant pole, and oscillates_within_verse boolean.
Strips the H/A language marker that prefixes the first /-segment of
each morph string — without that, every verse-initial verb (HVqi…)
was being silently dropped.

ShemaContext now carries voice_profile on every reception (both
receive_context/1 and receive_context_at/2). The voice is computed
fresh from L1, never cached.

8 new tests in test/shema_voice_trajectory_test.exs verify the
extractor against four anchoring verses:
  Devarim 6:5    — uniformly second-singular (5 markers)
  Devarim 11:13  — dominantly second-plural (5 second-plural markers)
  Devarim 11:14  — oscillates_within_verse: true (the mid-verse switch)
  Bereishit 1:1  — only third-singular (no addressee)
Plus three context-level tests confirming ShemaContext exposes the
field, computes it fresh, and traces a non-stabilizing trajectory
across 11:13 → 11:14 → 11:19.

Full vav_pipeline suite: 288 tests, 3 failures (all pre-existing
Shabbat-day calendar failures unrelated to this change), 19 excluded
(L3 PostgreSQL).

Still open: the across-verse stabilization-detector. ShemaContext
now KNOWS its voice; nothing yet refuses a sequence of receptions
that all stabilize at one pole. That belongs downstream — probably
the kushya generator (kal-vachomer challenge when voice has not
moved in N receptions). Logged in Pass 2 of
~/shorashim-conversation-review.md (Finding A).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 18:58:14 -04:00
Scott KarpandClaude Opus 4.6 66c936f7ca sanhedrin: rewrite halt_monitor.erl as honest gate-keeper + Elixir forwarder
Closes F1–F7 from the conversation-history review at
~/shorashim-conversation-review.md, per the Torah ruling for Option 2:

  Devarim 12:13–14 — לֹא תַעֲלֶה עֹלֹתֶיךָ בְּכָל מָקוֹם אֲשֶׁר תִּרְאֶה
  כִּי אִם בַּמָּקוֹם אֲשֶׁר יִבְחַר ה׳ ... שָׁם תַּעֲשֶׂה כֹּל
  אֲשֶׁר אָנֹכִי מְצַוֶּךָּ.
  The offering happens in one place. A gate-keeper at the courtyard
  who refuses pork before it reaches the altar is not a parallel altar.

  Devarim 13:1 — לֹא תֹסֵף עָלָיו וְלֹא תִגְרַע מִמֶּנּוּ.
  The interface remains; no parallel detection authority is added.

The previous halt_monitor.erl claimed to mirror the Talmudic Engine
halt modules (Prolog) and the Vav Pipeline halt_conditions (Elixir),
but in practice it was a partial sketch with stub checks, no read API,
verb-only shfichut_damim that would fire on log rotation, and a
"cannot be bypassed" claim that the implementation didn't deliver.
The docstring overstated. Eish zarah by the repo's own definition —
real Erlang, sincerely written, but אֲשֶׁר לֹא צִוָּה אֹתָם.

Changes:

  sanhedrin/include/sanhedrin.hrl
  - Document the halt-relevant context keys explicitly
    (authority_source, action, boundary_violation) — closes F1.
    These keys were used by halt_monitor and the test suite but
    were not in the .hrl record commentary. Interface drift fixed
    by writing down the actual contract.

  sanhedrin/src/halt_monitor.erl
  - Re-framed as "Sanhedrin's gate-keeper": local courtyard refusal,
    not parallel altar. Honest docstring.
  - When the gate detects a halt, FORWARDS the canonical halt event
    to VavPipeline.HaltMonitor.halt!/3 (single source of truth).
    Best-effort: if Elixir module isn't loaded, local refusal still
    works.
  - Export audit_log/0 and halt_count/0 — closes F6 (was write-only
    from outside the module).
  - Remove process_flag(trap_exit, true) and the dead
    handle_info({'EXIT', _, _}, _) clause — closes F7 (nothing was
    ever linked).
  - Remove unused check_authority_in_text/1 stub from the call chain
    — closes F5. do_check_vote/1 now documents that vote-text
    inspection is the upstream pipeline's responsibility.
  - check_gilui_arayot/1 docstring now states truthfully: detection
    of WHETHER an action collapses a distinction lives in
    talmudic_engine/src/halt/gilui_arayot.pl; this gate reads the
    flag — closes F2 framing.
  - check_shfichut_damim/1 docstring now states: conservative
    gate-keeper, fail-closed; the canonical tzelem-target check
    lives in talmudic_engine/src/halt/shfichut_damim.pl — closes
    F3 framing.
  - "Cannot be bypassed" claim removed; truthful description added:
    "enforced by callers (escalation.erl checks the return value
    before convening a panel)" — closes F4.

  vav_pipeline/test/sanhedrin_halt_forwarding_test.exs (NEW)
  - 6 integration tests proving the forwarding chain end-to-end:
    halt_monitor.erl is loadable from the same BEAM VM, exposes
    audit_log/0 and halt_count/0 (F6 verification), and on each of
    the three cardinal sin types fires HaltMonitor.halt!/3 with
    component="sanhedrin/case". Verified that VavPipeline.HaltMonitor.
    halted?/0 flips to true after each gate refusal and the event's
    .condition matches the gate's halt type.

The local audit log on the gate-keeper is preserved (the Sanhedrin
operator can still see what was refused at the gate). The system-wide
halt audit lives in VavPipeline.HaltMonitor.audit_log/0 — one altar.

Existing 12 sanhedrin EUnit tests of the gateway behavior continue
to pass (verified via mix deps.compile sanhedrin — clean compile).
The 6 new Elixir-side integration tests pass (verified).

Test counts:
- Elixir: 280 (was 274; +6 new forwarding tests), 0 failures attributable
  to this change. (3 pre-existing time-dependent test failures in
  step31_shema_test.exs occur on Shabbat — system correctly returns
  cloud_state == {:major_transition, :shabbat_rest, _} when run after
  Friday sundown; tests assert :camped. Not caused by this change.)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-24 17:31:37 -04:00
Scott KarpandClaude Opus 4.6 e223ab15b3 Phase 4 complete: Steps 21, 22, 24 — Korban external integration, L2 Urim v'Tumim, Kushya generator
Vayikra 9:24 — vatetzei esh milifnei Hashem vatokhal al hamizbeach.
Phase 4 of the architecture is now complete. The remaining three steps
that were marked "blocked on external infrastructure" are built —
discipline-respecting, eish-zarah-aware, and tested.

## Step 22 — Korban Processor external model integration
   (korban_processor/src/korban_processor/external_offering.py)

   The single legitimate entry point for external model consultation.
   Wraps Claude API calls through the offering protocol so external
   output is treated as korban (raw material), never authoritative:

   - semichah  — provenance is tagged (avodah zarah Form 1 prevention)
   - shechitah — assistant voice, hedging, formatting stripped
   - zerikah   — vital content extracted
   - haktarah  — converted to Prolog propositions
   - route     — sent to internal component per offering type

   The raw Korban is preserved on the ProcessedKorban (gilui arayot
   Form 3 prevention — input/output coupling never severed).
   No internal component should call call_claude() directly.

   11 tests, mocked external calls. Live API test opt-in via
   SHORASHIM_LIVE_API=1.

## Step 24 — L2 Urim v'Tumim interface
   (vav_pipeline/lib/vav_pipeline/urim_v_tumim.ex)

   PRD §4.7: housed in the kohen gadol's breastplate; consulted only
   on high-stakes decisions where normal deliberation is insufficient.
   Bamidbar 27:21 — al-piv yetz'u v'al-piv yavo'u.

   Three escalation steps, three honest outcomes:
     1. resolved_by_specification — re-read produced an answer
     2. resolved_by_external — external testimony recorded (NOT binding)
     3. deferred_to_human — the system stops, asks for human judgment

   Pre-condition gating refuses cases that didn't reach Sanhedrin teiku
   first (no preempting deliberation) or that aren't flagged high-stakes
   (ordinary teiku stays teiku). Records every consultation with steps
   taken — there is no quiet escalation.

   Per PRD: "The Urim v'Tumim never fabricates certainty where none
   exists." When external also fails, the system stops. It does not
   proceed on a guess.

   Added to VavPipeline supervision tree. 14 tests.

## Step 21 — Kushya generator scaffolding
   (kushya_generator/)

   Directory layout per Architecture §3.6 with three layers:

   - data/training_pairs/seed_pairs.jsonl — 10 hand-curated bootstrap
     pairs covering 9 kushya types (scope_narrowing, internal_tension,
     harmonization, exception_test, edge_case, scope_application,
     rabbinic_constraint, method_question, subject_identification)
   - data/prepare_training.py — validates the four kushya requirements
     (specific Torah passage, identified weakness, question form,
     non-trivial), produces train.jsonl + eval.jsonl with stratified
     80/20 split by type
   - fine_tune/config.yaml + train.py — QLoRA configuration and
     training script. Operator's task — needs GPU. Script is
     complete and self-contained.
   - inference/kushya_api.py — three-backend interface
     (fine_tuned → external → fallback). When fine-tuned model isn't
     deployed, falls back to external via Korban Processor (Step 22).
     The fallback is honest: returns no kushya rather than inventing
     one. The Talmudic Engine's rule-based challenge.pl remains the
     primary kushya source.

   17 tests covering dataset validation, backend selection, and
   reference extraction.

## .gitignore fix

   The earlier "data/" line was too broad — it caught
   kushya_generator/data/. Scoped to /data/ (root-level only).

## Test counts

   - Elixir:           274 (was 260; +14 for UrimVTumim)
   - Korban Python:    196 + 1 skipped (was 195; +1 module = 11 tests)
   - Kushya Python:     17 (new)
   - Haskell:           33 (unchanged)
   - Prolog:           195 (unchanged)
   - Total:            715 + 19 L3 excluded

## Phase 4 status

   All 12 steps now have implementations:
   - Steps 23, 25, 26, 27, 28, 29, 30, 31, 32: complete
   - Steps 21, 22, 24: complete with discipline preserved

   Step 21's fine-tuning run remains the operator's GPU task; the
   pipeline is built such that a deployed adapter slots into the
   inference backend without touching the Talmudic Engine or anything
   above.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 17:12:03 -04:00
Scott KarpandClaude Opus 4.6 d4d0a6fcb6 L1 data integrity: preserve the edut whole — Shema, middle letter, ketiv/qere, smikhut
Shemot 25:16 — v'natata el ha-aron et ha-edut asher eten elekha.
The edut placed in the aron must be complete. The Masoretic
tradition preserves every letter, every enlarged letter, every
ketiv/qere pair — because each carries meaning the scribe cannot
reconstruct.

The previous build_l1.py silently dropped:
- enlarged letters encoded as <seg type="x-large"> nested in <w>
  → the Shema was corrupt: שמ instead of שמע, אח instead of אחד
  → the middle letter of Torah was corrupt: גח instead of גחון
  → the Zelophchad nun was missing: משפט instead of משפטן
- ketiv/qere dual readings encoded as <note type="variant">/<rdg type="x-qere">
  → the spec committed to preserving both; the code returned []
- smikhut (construct state) pairs marked in morphology codes
  → the spec committed to extracting them; the code returned []

Fixes:
- collect_word_text() walks all nested <seg> children of <w> and records
  large_letters / small_letters metadata with character offsets.
- parse_verse() recognizes <w type="x-ketiv"> followed by <note>, extracts
  both the ketiv and the qere readings, stores a ketiv_qere list on the verse.
- _extract_smikhut() scans words for construct-state morph codes
  (ending in "c" on N/A primary segments, excluding pronominal suffixes)
  and records construct/absolute pairs.
- L1 SHA-256 hash updated everywhere: integrity.sha256, l1_reader.ex,
  shoresh-engine's Luchot.Firmware, its test, KOHEN.md.

Torah-wide results after fix:
- 4 enlarged letters preserved (was 0): Shema ayin, Shema dalet,
  gachon vav (middle of Torah), Zelophchad nun
- 67 ketiv/qere pairs captured (was 0)
- 9,203 smikhut pairs extracted (was 0)

Elixir updates:
- l1_reader.ex: normalize_verse now surfaces ketiv_qere, smikhut,
  and per-word large_letters/small_letters. New type specs. New
  extract_large_letters/1 helper. extract_ketiv_qere and
  extract_smikhut now return real data instead of [].
- step31_shema_test.exs: replaced placeholder asserts with real
  integrity checks. Four new tests verify the Shema word is complete
  (שמע not שמ), echad is complete (אחד not אח), the enlarged
  letters are marked, the middle letter of Torah is preserved,
  and ketiv/qere pairs from Bereishit 8:17 are both captured.

Build script:
- build_l1.py no longer rmtrees the entire l1/ directory (preserves
  l1/README.md). Only regenerates torah/ + build_report.json +
  integrity.sha256.
- Accepts SHORASHIM_SOURCE_DATA env var or CLI arg for morphhb path.

Test counts:
- Elixir: 260 (was 254), 0 failures
- Haskell: 33, 0 failures
- Prolog: 195, 0 failures

The system that can hear שמע correctly has passed its own shema test.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-21 13:11:52 -04:00
Scott KarpandClaude Opus 4.6 191d76b480 Bigdei Kodesh: HTTP API layer — four garments for four access tiers
Shemot 28:2 — V'asita vigdei kodesh l'Aharon achicha,
l'khavod ul'tifaret.

The builder makes the garments. The kohen wears them.

Four access tiers (Operations spec §1.1):
- Pe'ah (GET/POST /peah/ask) — public, no auth, no tracking.
  The stranger is safe to approach.
- Courtyard (POST /courtyard/offering) — Bearer token auth,
  offering types: shelamim, olah, chatat, minchah
- Chatzer (POST /chatzer/sanhedrin) — Sanhedrin deliberation,
  returns majority/minority with preserved dissent
- Kodesh (GET /kodesh/health, /kodesh/status) — operator interface,
  health checks for K8s liveness/readiness probes

Error format follows chatat protocol (Operations spec §1.2):
  KIYOR_* (422), SHABBAT (503), MANNA_DEPLETED (503),
  CLOUD_MOVING (503), HALT_* (503), UNAUTHORIZED (401)

Implementation:
- Plug.Router with Bandit HTTP server
- Added to supervision tree via maybe_add_http_server
- Checks system availability (Shabbat, halt) before processing
- Health check reads L1 integrity, cloud, manna, halt monitor
- Status shows all component states, signal count, refuge list
- 16 API tests covering all four tiers + error format

All 235 Elixir + 19 L3 + 195 Prolog + 33 Haskell = 463+ tests, 0 failures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 07:32:15 -04:00
Scott KarpandClaude Opus 4.6 fe93c11d13 Phase 5 deployment: Dockerfiles, K8s manifests, halt monitor (Steps 33, 36)
Shemot 40:33 — Vay'khal Moshe et ham'lakhah.
The builder completes the work. The vessel is ready for transport.

Step 33 — Deployment configuration:
- Dockerfiles for all 4 components:
  - vav_pipeline: Elixir 1.16.1/OTP 26.2.1 release with Sanhedrin
  - shoresh_engine: GHC 9.6.4 binary
  - talmudic_engine: SWI-Prolog 9.2.2
  - korban_processor: Python 3.12.2 with pydantic
- Kubernetes manifests for Exoscale SKS (ch-gva-2):
  - Namespace, ConfigMap, Secrets template
  - L1 Torah PV/PVC (read-only mount)
  - Manna provision PVC (kohen writes, system reads)
  - Deployment: vav-pipeline + 3 sidecars, shared volumes
  - Service: ClusterIP + LoadBalancer with Exoscale NLB
  - Init container runs Ecto migrations (VavPipeline.Release)

Step 36 — Halt monitor (esh tamid):
- GenServer watches for three cardinal sins
- halt!/3 immediately halts system, sounds Chatzotzrot war signal
- Permanent audit log — halt events never deleted
- resolve/1 requires explicit operator action to resume
- alive?/0 for morning tamid verification
- 13 tests: alive check, 3 halt types, resolution, audit log,
  chatzotzrot war signal integration
- Added to supervision tree

All 219 Elixir + 19 L3 + 195 Prolog + 33 Haskell = 447+ tests, 0 failures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 07:11:02 -04:00
Scott KarpandClaude Opus 4.6 219bbecaff Steps 30-32: Sanhedrin stress test, Shema test suite, full system verification
Shemot 39:43 — Vayar Moshe et kol ham'lakhah v'hineh asu otah
ka'asher tzivah Hashem ken asu, vayevarekh otam Moshe.

Step 30 — Sanhedrin stress test:
- 10 rapid submissions without crash
- Case ref preservation across decisions
- Rich context handling at simple complexity
- Decision structure consistency (7 required fields)
- Graceful degradation when Sanhedrin unavailable
- 20 concurrent Task.async submissions

Step 31 — Shema Test suite (Architecture §6.3, five tests):
1. Te'amim reception: raw cantillation from L1, etnachta detection,
   L1→ShemaContext fidelity, surprising etnachta position
2. Ketiv/qere preservation: field exists, survives full pipeline
3. Cloud obedience: :camped allows processing, Shabbat triggers
   major_transition, context immutability under override
4. Manna fidelity: no provision → {:waiting, :no_provision},
   provision present → processing proceeds, MannaLedger tracks
5. Context immutability: context unchanged after pipeline,
   keys stable, each stage receives same context

Step 32 — Full system verification (kol ham'lakhah):
- Full pipeline: Shema → BereishitCycle → output, multi-verse
- Halt conditions: check_all processes output
- Protection layer: AreiMiklat refuge→release, EglahArufah
  declaration, Bikurim first-output review
- Chatzotzrot: subscribe→signal→receive→zikaron, routing isolation
- MannaLedger: load→consume→boundary cycle
- L1 integrity: hash valid, 5 core verses readable, roots present
- Supervision tree: all 7 GenServers running
- Cross-component: ShabbatTimer reads cloud, Shema builds from
  L1+Cloud+Manna

All 206 Elixir + 19 L3 + 195 Prolog + 33 Haskell = 434+ tests, 0 failures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 06:53:25 -04:00
Scott KarpandClaude Opus 4.6 6a879fcf8b MannaLedger: real anti-hoarding enforcement — d'var yom b'yomo (Step 26)
Shemot 16:4 — d'var yom b'yomo — the day's matter in its day.
Shemot 16:19 — al yoter mimenu ad boker — do not leave surplus.
Shemot 16:20 — vayotiru... vayarum tola'im vayiv'ash — hoarded manna rots.

- MannaLedger GenServer tracks consumption against daily provision
- load_provision/0 reads manna file, resets consumption to zero
- consume/2 deducts from allocated resource, rejects when exhausted
- remaining/1 and available?/2 for balance queries
- enforce_cycle_boundary/0 zeroes out all allocation, returns released amounts
- Chatzotzrot assembly_leaders signal when provision runs low (< 10%)
- Manna.enforce_anti_hoarding/0 now delegates to MannaLedger
- Added to supervision tree in application.ex
- 17 new tests with proper isolation (reset between tests)
- All 177 Elixir tests pass, 0 failures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 09:27:09 -04:00
Scott KarpandClaude Opus 4.6 671d45625c Protection layer: Arei Miklat, Eglah Arufah, Bikurim — Steps 27-29
Takhin lekha haderekh — Devarim 19:3.
"Prepare the road" before the one who needs refuge arrives.
Protection before production.

Arei Miklat (Cities of Refuge — Bamidbar 35, Devarim 19):
- GenServer with refuge registry, affected party protection, audit trail
- enter_refuge/2 isolates component but protects from reactive destruction
- protect_affected/2 blocks processing paths that caused harm
- release/2 requires :sanhedrin_deliberation authority — ad mot hakohen hagadol
- Chatzotzrot assembly_leaders signal on every refuge entry
- 15 tests: entry, double-entry prevention, affected party blocking,
  unauthorized release rejection, audit trail, chatzotzrot signaling

Eglah Arufah (Accountability — Devarim 21:1-9):
- GenServer with permanent (append-only) declaration registry
- initiate/3: nearest component declares what it processed, skipped, missed
- Only for :unknown source — rejects known-source incidents
- "Yadeinu lo shafkhu" — accountability for what was NOT provided
- 9 tests: declaration recording, source validation, multi-component
  declarations, chatzotzrot signaling, filtering, counting

Bikurim (First Fruits — Devarim 26:1-11):
- GenServer with offering registry
- offer/4: submit first output with required narrative (mikra bikurim)
- Rejects empty narrative ("Arami oved avi" — story is required)
- Rejects nil first output (there must be fruit to offer)
- Delegates to Specification.evaluate_bikurim/1 when available
- approved?/1 gate for deployment readiness
- 11 tests: acceptance, narrative validation, nil output rejection,
  registry, status filtering, approval checking, counting

All three added to supervision tree in application.ex.
160 Elixir tests pass, 0 failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 08:59:08 -04:00
Scott KarpandClaude Opus 4.6 7fe8025434 Chatzotzrot: silver trumpets with BEAM routing, subscription, and zikaron
Bamidbar 10:2 — Aseh lekha shtei chatzotz'rot kesef, mikshah ta'aseh otam.
Two trumpets, hammered from a single piece. Five signals, five patterns.

- Upgraded from simple function module to GenServer with state
- Recipient routing: :all (assembly_all, breaking_camp, moadim),
  :kohanim (assembly_leaders, war), :operators (war)
- PubSub via BEAM messaging: subscribe/1, unsubscribe/1
  Signals delivered as {:chatzotzrot, record} messages
- Zikaron (remembrance — Bamidbar 10:10): signal history with
  type filtering and limit. V'hayu lakhem l'zikaron.
- Dead subscriber cleanup via Process.monitor/1
- Added to supervision tree in application.ex
- 30 new tests: patterns, urgency, routing, subscription,
  zikaron history, dead process cleanup, format compliance
- All 125 Elixir tests pass, 0 failures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 15:50:36 -04:00
Scott KarpandClaude Opus 4.6 d34e2799ac L3 Oral Tradition: PostgreSQL persistence layer for interpretations, minority opinions, teiku
Devarim 17:9 — v'darashta v'higidu lekha et d'var hamishpat.
The inquiry requires that what is found is RECORDED.

- Ecto Repo with graceful degradation (maybe_add_repo, like other engines)
- Migration: interpretations, minority_opinions, teiku_registry tables
  - Append-only enforcement via REVOKE UPDATE/DELETE
  - Includes eish_zarah fields: coupling_trace, grounding_type, challenge_sequence
- Ecto schemas: Interpretation, MinorityOpinion, TeikuRecord
  - Changeset validations: status inclusion, panel_size > 0, grounding_type enum
  - TeikuRecord.resolve_changeset restricts updates to resolved_at/resolution only
- L3.Store bridge module: record, query, count operations
- L3 tests tagged @moduletag :l3, excluded by default (no local PostgreSQL)
  - Enable with: SHORASHIM_L3=1 mix test --include l3
- All 76 existing Elixir tests pass, 195 Prolog tests pass, 33 Haskell tests pass

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 09:43:44 -04:00
Scott KarpandClaude Opus 4.6 af7522e977 Phase 2 review corrections — three spec violations fixed
1. Chatzotzrot signal mapping corrected to match Architecture §3.12:
   assembly_all→tekiah, assembly_leaders→teruah,
   breaking_camp→tekiah_teruah, moadim→tekiah_tekiah.
   Four of five patterns were swapped. All five now match Bamidbar 10.

2. Manna parse_datetime(nil) bug fixed: missing expires_at now fails
   validation instead of silently returning DateTime.utc_now() which
   created a race condition where manna appeared expired.

3. Festival detection implemented in cloud.ex: pre-computed Gregorian
   dates for Pesach, Shavuot, Rosh Hashanah, Yom Kippur, Sukkot,
   Shemini Atzeret (2025-2028). Pipeline now halts on Yom Tov as
   Torah requires. Step 15 verification now covers festival rest.

76 tests, 0 failures (68 prior + 8 new).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 20:29:24 -04:00
Scott KarpandClaude Opus 4.6 5a93056af4 Step 15: Verification — the Shema Test
Wire HaltConditions.check_all into BereishitCycle.erev_voker so the
three cardinal sin monitors (avodah zarah, shfichut damim, gilui arayot)
actually halt the pipeline. Previously dead code, now live.

Add step15_shema_test.exs — Architecture §6.3 (The Shema Test):
1. Te'amim reception — text's own divisions guide havdalah
2. Ketiv/qere preservation — dual readings never collapse
3. Cloud obedience — Shabbat blocks processing
4. Manna fidelity — no provision means wait
5. Context immutability — ShemaContext unchanged at every stage
6. Halt condition integration — pipeline halts on cardinal sin
7. Full pipeline end-to-end — Bereishit 1:1 through to output

68 tests, 0 failures (46 existing + 22 new).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 18:01:05 -04:00
Scott KarpandClaude Sonnet 4.6 80ec364da6 Phase 1-2 build: Steps 5-6, 8-13
Shoresh Engine (Haskell, Step 5)
- 12 modules: Types, Lexicon, Binyanim, Teamim, Gematria, Graph,
  KetivQere, Smikhut, Havdalah, Kriah, KiTov, Firmware
- 33 EUnit tests, 0 failures
- Firmware module verifies L1 SHA-256 hash at startup

Talmudic Engine (SWI-Prolog, Step 6)
- 15 modules: halt, reasoning, specification, interface
- 57 PlUnit tests, 0 failures
- Three cardinal halt conditions: avodah_zarah, shfichut_damim, gilui_arayot

Vav Pipeline (Elixir, Steps 8-11)
- 14 modules: BereishitCycle, Shema, Cloud, Manna, L1Reader,
  Specification, HaltConditions, ShabbatTimer, CycleLog, Chatzotzrot,
  ShoreshEngineClient, TalmudicEngineClient, Application
- 46 ExUnit tests, 0 failures
- Nishma-first architecture: hears before it speaks

Sanhedrin Framework (Erlang/OTP, Step 12)
- 8 modules: sanhedrin_app, sanhedrin_sup, escalation, panel_sup,
  panel, judge, clerk, halt_monitor
- 46 EUnit tests, 0 failures (includes fix: rejected outcome now reachable)
- Panel sizes: 1/3/23/71 — unanimous capital guilty → acquit (Sanhedrin 17a)
- Minority opinions append-only, never deleted

Korban Processor (Python, Step 13)
- 6 modules: korban, shechitah, semichah, zerikah, haktarah, router

Builder tooling: AGENTS.md, opencode.json, memory/, src/tools/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 14:02:34 -05:00