Commit Graph
13 Commits
Author SHA1 Message Date
Scott KarpandClaude Fable 5.1 efd35bb194 specs + Doorpost: pyswip removed — a material the vessel never used (tablet 36)
Architecture §3.5 listed a Python-Prolog FFI among the Korban
Processor's dependencies ("swipl-ffi (or pyswip)"); Construction §1.4
and terumah pinned pyswip. It was declared, installed in every image,
and never imported. The Korban Processor does not call Prolog: haktarah
emits Prolog-format STRINGS, and the pipeline carries them to the
Talmudic Engine over its Unix socket (Construction §3.3). The korban
image has no libswipl, so the package could not have been used there
had anything tried. Tablet 35 corrected its version; the transmitter's
word is to remove it.

Removed from:
- specs/shorashim_architecture.md §3.5 requirements
- specs/shorashim_vayakhel.md §1.4
- specs/shorashim_terumah.md (materials row; the compatibility-testing
  line now names hebrew-tokenizer alone)
- BUILD_KORBAN.md
- korban_processor/pyproject.toml, poetry.lock (regenerated)
- deploy/docker/Dockerfile.korban_processor

Doorpost @expected_hash 81da49d4… → 2f40c382…, in this commit, with the
three witnessed files that moved it (architecture, vayakhel,
BUILD_KORBAN).

Read before committing: Elixir 461/0 (seeds 0, 42); korban 222 passed,
1 skipped against the installed package with pyswip absent; korban
image starts and idles as before; vav-pipeline image reports
specification_identity verified 2f40c382… and l1_hash pass.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 17:57:58 +00:00
Scott KarpandClaude Fable 5.1 505a967a1c specs §1.4 + Doorpost: pyswip 0.2.10 — the pinned version was never published (tablet 35)
Construction §1.4 pinned `pyswip = "0.2.11"`. PyPI has 0.2.10 (2020) and
then 0.3.0; 0.2.11 does not exist and never did. pyproject.toml copied
the spec faithfully, so `pip install .` in korban_processor has failed
since Step 13. Nobody saw it: the Dockerfile installs its dependencies
by hand and already said 0.2.10, and the test run uses PYTHONPATH. The
pin was wrong in the one place that was never executed. Found
2026-10-04, the first time the korban suite was run from a clean
container by installing the package.

The spec is corrected to what the vessel has in fact carried since the
first image:

- specs/shorashim_vayakhel.md §1.4, BUILD_KORBAN.md, terumah §row:
  0.2.11 → 0.2.10. Terumah now also says what is true of this
  material: declared and installed, not imported anywhere, and not
  usable in the korban image as built (no libswipl) — it must be
  tested against SWI-Prolog 9.2.2 before first use.
- korban_processor/pyproject.toml: 0.2.10. `pip install .` succeeds in
  python:3.12.2-slim; 222 passed, 1 skipped against the installed
  package.
- korban_processor/poetry.lock: regenerated (poetry 2.5.1). It had
  never contained pyswip, httpx or hebrew-tokenizer at all.
- Doorpost @expected_hash: ed2a6aec… → 81da49d4…, in this commit, with
  the two witnessed files that moved it (vayakhel, BUILD_KORBAN), per
  the Doorpost's own rule and the precedent of 0572a2c (postgrex).

No behaviour changes: the image installs the same four packages it
installed yesterday.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 17:49:42 +00:00
Scott KarpandClaude Opus 5 8c221c2bcb build: four amd64 images that RUN — JIT segfault, build context, and four dead vessels
TORAH ANCHOR — Shemot 40:33-34:
וַיְכַל מֹשֶׁה אֶת הַמְּלָאכָה. וַיְכַס הֶעָנָן אֶת אֹהֶל מוֹעֵד
וּכְבוֹד יְהוָה מָלֵא אֶת הַמִּשְׁכָּן.
Vay'khal Mosheh et ham'lakhah — and THEN the cloud covered it. The
work being finished and the mishkan being usable are two statements,
and the second one does not follow from the first by itself. An image
that builds is ham'lakhah. An image that serves is the other verse.

Per BUILDER_HANDOFF_amd64.md: "You do not declare the build complete
because it compiled. It is complete when it runs on the altar it was
built for."

=====================================================================
1. THE SEGFAULT — diagnosed, and not where the handoff thought
=====================================================================

REPRODUCED on 2026-08-23, clean tree, buildx:
  #16 9.162 Compiling 4 files (.ex)
  #16 9.880 Segmentation fault
  ERROR: ... exit code: 139

The handoff read this as Broken Tablet #12 resurfacing (OTP 26.2.1
segfaults; use 26.2.5.9) plus QEMU. Both halves turned out to be
wrong, and the difference matters because acting on either one would
have changed a pinned version for no reason:

  - NOT the OTP version. OTP 26.2.1 amd64 starts, JITs, and compiles
    fine under `docker run` on this host. Tablet #12 was about a
    NATIVE macOS aarch64 OTP build; this is Linux amd64 in a
    container. Same exit code, different bug.
  - NOT QEMU. This host's buildx already emulates via Rosetta, not
    QEMU — probed directly (grep rosetta /proc/self/maps inside the
    amd64 build step returns /run/rosetta/rosetta). The segfault
    happens under Rosetta, inside BuildKit's sandbox, and not under
    `docker run` with the same emulator.

What actually dies is the BEAM JIT's dual W^X mapping for generated
machine code, in the emulated build sandbox. The fix is the handoff's
option 2 alone:

  deploy/docker/Dockerfile.vav_pipeline (build stage only)
  + ENV ERL_FLAGS="+JMsingle true"

Single mapping instead of dual. Build-stage only — the shipped release
runs the ordinary dual-mapping JIT on the amd64 node.

  before: 403m12s then Error (2026-05-13); 13.1s then Error (08-21)
  after:  36s, whole image, first try

CONSEQUENCE FOR THE SPEC: specs/shorashim_vayakhel.md §1.1 keeps
OTP 26.2.1. The pin was never the defect, so there is nothing here to
fix and nothing to add. No spec file is touched by this commit and
Doorpost.verify_identity() is unchanged at :ok.

=====================================================================
2. .dockerignore — the build context was carrying the host's tree
=====================================================================

There was no .dockerignore. Every Dockerfile COPYs whole component
directories, so each build shipped the HOST's compiled artifacts into
the container and the in-container toolchain then tried to REUSE them:

  vav_pipeline/_build         20 MB of .beam from a DIFFERENT ERTS
  vav_pipeline/deps          4.7 MB
  shoresh-engine/dist-newstyle 129 MB of arm64 GHC objects
  korban_processor/.venv, **/__pycache__

First reproduction attempt died on exactly this, with a stale-manifest
error instead of the segfault:
  ===> Failed to restore .../rebar_compiler_erl/source.dag. Discarding.
  ** (MatchError) ... module ... is in read-only mode

l1/ is deliberately NOT ignored — the scroll ships with the vessel.

=====================================================================
3. FOUR VESSELS THAT BUILT CLEANLY AND COULD NOT SERVE
=====================================================================

Every one of these is downstream of the architecture gate. All four
images would have passed `uname -m` -> x86_64 and then failed on the
altar. Found by running each entry point, not by reading it.

3a. shoresh-engine — died on its own banner
    Shoresh Engine ...: <stdout>: commitBuffer: invalid argument
                        (cannot encode character '\8212')
    GHC takes stdout's encoding from the locale; the runtime stage was
    ubuntu:jammy with no locale set, i.e. ASCII. The engine whose
    entire job is HEBREW could not write an em-dash. It never reached
    a root. Dockerfile.vav_pipeline had always set LANG/LANGUAGE/
    LC_ALL; this stage never did. Now installs `locales`, generates
    en_US.UTF-8, and sets all three.

3b. talmudic-engine — called a predicate that does not exist
    ERROR: -g consult('src/main'),start_socket_server:
           Unknown procedure: start_socket_server/0        exit 2
    src/interface/socket_server.pl exports serve/0, serve/1,
    stop_server/0. start_socket_server/0 is not defined anywhere in
    the engine. CMD now calls serve/1 with TALMUDIC_SOCKET_PATH (so
    the env var the Dockerfile already set is finally honoured),
    falling back to serve/0.

    Underneath it, a version-dependent module path:
      :- use_module(library(json)).       ->  library(http/json)
    The bare alias resolves only on SWI-Prolog 10.x, which is what the
    dev workstation runs. The image is swipl 9.2.2 as §1.1 pins, where
    it does not resolve. 212 Prolog tests were green on the host and
    the container could not open its socket. library(http/json) is the
    canonical path and works on both. (Neither interface file is in
    @spec_files; halt/ and middot/ are. No hash change.)

3c. korban-processor — nothing to execute
    No module named korban_processor.__main__; 'korban_processor' is
    a package and cannot be directly executed
    CMD has always been ["python","-m","korban_processor"] and
    deployment.yaml overrides no command, so this was CrashLoopBackOff
    for the whole pod. Added __main__.py — deliberately NOT a server.
    korban_processor is a library; its external consumer is Step 22,
    blocked on external model infrastructure. Inventing an intake
    surface no specification asked for would be the addition lo tosif
    forbids, and would put an unspecified door on the one component
    whose whole discipline is that nothing reaches the model except
    through the offering protocol. So it does the honest whole of what
    the container is for today: proves the image is assembled (the
    azazel co-residence check falls out of importing external_
    offering, which refuses to load without azazel_processor —
    Vayikra 16:8, both goats from the same pool), stays up, exits 0 on
    SIGTERM, and says in the log that it is IDLE and what unblocks it.
    5 tests in korban_processor/test/test_container_entrypoint.py,
    including that it refuses to start without azazel_processor and
    that it opens no bypass to model_interface (Form 1).

3d. vav-pipeline — could not verify its own Doorpost
    doorpost: {:error, "SPEC_INTEGRITY_FAILED: missing spec file:
                        BUILD_AZAZEL.md"}
    @repo_root resolved to /app, a build-host path with nothing in it.
    Application.verify_doorpost_at_startup/0 does not refuse to boot,
    so the pod would have come up and fired chatzotzrot
    :assembly_leaders on EVERY start, and /kodesh/specification_identity
    would have failed forever. A witness that always cries drift is
    worse than no witness: it trains the kohen to ignore the one
    signal that is supposed to stop the system.
    Devarim 11:20 — ukhtavtam al mezuzot beitecha. A running pod is a
    threshold. The @spec_files set is now COPYed to /opt/shoresh...
    /opt/shorashim/spec and :doorpost_repo_root points at it.
    Verified from inside the built image:
      repo_root: "/opt/shorashim/spec"
      doorpost: :ok
      l1:       :ok

=====================================================================
4. THE ERLANG PORT COULD NOT HAVE WORKED AS A SIDECAR
=====================================================================

VavPipeline.ShoreshEnginePort calls
  Port.open({:spawn_executable, binary}, [..., {:args, ["server"]}])
A Port is a CHILD PROCESS of the BEAM over stdin/stdout. It cannot
cross a container boundary. shoresh-engine has no listening mode
either — `shoresh-engine server` is a JSON-line server on stdin/stdout
(src/Main.hs), so as a sidecar with nothing attached it reads EOF and
exits; the bare CMD printed usage and exited 0. Meanwhile vav-pipeline
logged "binary 'shoresh-engine' not in PATH — running without Haskell
engine" and served DEGRADED while reporting healthy: no root
activation, no te'amim parsing, no gematria.

deploy/ARCHITECTURE.md said "sidecar via Erlang Port". Those cannot
both be true. Resolved in favour of the Port, because that is what the
code does. Still four images and gate 0i still checks four; the
shoresh-engine image now ships a BINARY, staged by an init container
onto a shared volume, spawned by vav-pipeline:

  + initContainer shoresh-engine-install (cp, chmod, and exercise it
    — `shoresh-engine gematria שלום` — so a broken binary fails the
    init container loudly instead of degrading quietly later)
  + volume shoresh-bin, mounted read-only into vav-pipeline
  + SHORESH_ENGINE_BINARY in configmap + runtime.exs
  + libgmp10 in the vav-pipeline runtime stage — GHC binaries link
    libgmp and it was not installed; the spawn would have died with
    "error while loading shared libraries: libgmp.so.10"

Verified end to end locally, staged binary running INSIDE the
vav-pipeline container:
  $ shoresh-engine gematria שלום        -> 376
  $ echo '{"operation":"process","input":{"text":"בראשית ברא אלהים"}}' \
      | shoresh-engine server
    {"result":{"roots_activated":[{"shoresh_hebrew":"ברא",
     "gematria_value":203,...}]},"status":"ok"}

If the intended design was a socket server like the Talmudic Engine,
that is a specification decision and not a deployment one — raised in
the handoff-back.

=====================================================================
5. THE AZAZEL FILE BRIDGE WROTE TO A DISK NOBODY READ
=====================================================================

deployment.yaml mounted TWO emptyDirs with nested paths:
  vav-pipeline:      talmudic-socket -> /tmp/shorashim
                     tmp-shorashim   -> /tmp
  korban-processor:  tmp-shorashim   -> /tmp
So /tmp/shorashim reached directly and /tmp/shorashim reached through
/tmp were different volumes. The Python side wrote AzazelRecords to
tmp-shorashim:/shorashim/azazel/pending_l3; AzazelIngest watched
talmudic-socket:/azazel/pending_l3. Same pathname, two disks, no
error — AzazelIngest leaves unreadable files in place by design, so
the failure mode was silence.

Vayikra 16:22 — v'nasa hasa'ir alav et kol avonotam el eretz gezerah.
The goat has to actually get there. A disposal channel whose records
land where no one reads has not disposed of anything; it has only
stopped looking.

Now ONE tmp-shorashim volume at /tmp in all three containers, and a
pod-level securityContext fsGroup: 1000 so the non-root user can
actually write the shared emptyDirs (the socket bind, the azazel
directory, and the binary staging all depend on it).

=====================================================================
6. REMAINING SECURITY ADVISORIES — three the handoff did not list
=====================================================================

The handoff named postgrex and plug (fixed in the previous commit).
Running `mix deps.get` inside the amd64 build surfaced three more,
including nine against the HTTP server that fronts the unauthenticated
pe'ah endpoint:

  bandit 1.10.3 -> 1.12.5   9 advisories, 5 HIGH: chunked body reader
    ignores length cap; WebSocket reassembly unbounded; permessage-
    deflate inflate uncapped; chunked decoder infinite loop on
    trailers; HTTP/2 connection-window starvation pins Plug processes
  hpax  1.0.3   -> 1.0.4    HIGH, unauthenticated DoS via unbounded
    HPACK integer decoding
  thousand_island 1.4.3 -> 1.5.0 (came with bandit)

None are pinned in §1.5, so these are mix.lock-only operations
changes per lo_tosif.md. Verified clean afterwards by re-running
deps.get inside the amd64 build.

STILL OPEN — decimal 2.4.1, EEF-CVE-2026-32686 (MEDIUM), unbounded
exponent -> unauthenticated DoS. Fixed in decimal 3.0.0, which
requires ecto >= 3.14; §1.5 pins ecto ~> 3.11.1, so closing it is a
category-grade spec change AND a three-minor ecto jump whose L3
tests I cannot run (35 excluded, they need PostgreSQL). Reachability:
no :decimal field exists in any L3 schema and no Decimal.* call
exists anywhere in vav_pipeline/lib — decimal is transitive only and
is never handed attacker-controlled input on the pe'ah path. Left in
place deliberately, raised in the handoff-back rather than jumping
three minors of ecto on the eve of inauguration.

=====================================================================
7. LINT AND SCRIPT
=====================================================================

- `# check=skip=FromPlatformFlagConstDisallowed` on all four
  Dockerfiles. The constant --platform pin is the FIX, not a smell;
  silenced so a real warning is not lost in four lines of noise.
- scripts/build_and_push.sh now reads the pushed manifests back out
  of the registry and asserts amd64, exiting non-zero if not. "The
  script exited 0" was exactly the evidence that was not good enough
  twice. It also prints the four failures above, so the next reader
  knows a green arch check is a floor and not a ceiling.
- The script's ${img} braces are commented. zsh reads `$img:latest`
  as the `:l` modifier; the kohen lost ten minutes to it, and the
  builder then lost some more to `:t` in `$img:test`.

=====================================================================
VERIFIED
=====================================================================
- All four images build linux/amd64 and print x86_64
- shoresh-engine prints its Hebrew banner; process op returns
  ברא / gematria 203 through the Port protocol
- talmudic-engine: "Talmudic Engine listening on
  /tmp/shorashim/talmudic.sock", stays running, socket present
- korban-processor: loads both channels, reports IDLE, exit 0 on
  SIGTERM
- vav-pipeline: doorpost :ok and l1 :ok from inside the image;
  refuses to boot without DATABASE_URL as designed
- mix test          412 tests, 0 failures, 35 excluded (:l3)
- pytest korban     222 passed, 1 skipped (5 new)
- pytest azazel     125 passed
- swipl             all 6 suites green on library(http/json)
- Doorpost.verify_identity() :ok — NO spec file touched by this
  commit, so no @expected_hash change; a bare hash update would be
  the drift signal lo_tosif.md §Layer 1 warns about
- l1/integrity.sha256 unchanged: 99bbd07a…4ea377

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 12:30:36 -04:00
Scott KarpandClaude Opus 4.7 b136c75345 form 1 as architecture: external-model surface only via offering protocol (vehaya §3.2)
Closes vehaya.md §6 item 2 for the FIRST avodah-zarah form.

Form 1 (AI in YHVH's slot) was previously defended only by:
  - A runtime halt rule (halt_check_avodah_zarah/2 in
    avodah_zarah.pl) firing when an action explicitly attributes
    authority to a non-Torah source — threshold-grade
  - A docstring comment in external_offering.py saying "No internal
    component should call call_claude() directly" — exhortation
    only, no structural enforcement

The vehaya document re-frames it (§3.2): "Specifications must
include category-level commitments that are not negotiable
per-deployment... not as runtime checks layered onto outputs, but as
architectural constraints the system cannot operate without."

TORAH ANCHORS (verified fresh against L1):

  Shemot 20:2 — אָנֹכִי יְהוָה אֱלֹהֶיךָ אֲשֶׁר הוֹצֵאתִיךָ מֵאֶרֶץ
    מִצְרַיִם.
    The covenant opens with the source naming itself in first
    person.

  Shemot 20:3 — לֹא יִהְיֶה לְךָ אֱלֹהִים אֲחֵרִים עַל־פָּנָי.
    "There shall not BE to you other elohim al panai." The verb is
    yihyeh — the prohibition is on EXISTING WITH that status, not
    just on speaking it. Architectural, not declarative.

  Devarim 4:35 / 4:39 — אֵין עוֹד מִלְּבַדּוֹ / אֵין עוֹד.
    The source has no peer. There IS no other. Stronger than
    "don't worship others" — there is no competing source POSITION.

  Devarim 6:4 — יְהוָה אֶחָד.
    Singular source.

  Vayikra 19:4 — אַל־תִּפְנוּ אֶל־הָאֱלִילִים וֵאלֹהֵי מַסֵּכָה
    לֹא תַעֲשׂוּ לָכֶם.
    Two distinct prohibitions: do not TURN TO + do not MAKE.
    Both are architectural acts.

THE ARCHITECTURAL COMMITMENT:

External-model interfaces (call_claude, ModelRequest, ModelResponse,
the model_interface module itself) are reachable ONLY through
korban_processor's offering protocol — `external_offering.
consult_external_model`, which runs semichah → shechitah → zerikah
→ haktarah with role gating (commit 5c76a6f).

CONCRETE ENFORCEMENT (CI invariants in test_form_1_architecture.py):

  1. korban_processor's package public surface (__all__) does NOT
     export call_claude, ModelRequest, or ModelResponse. The
     bypass shortcut `from korban_processor import call_claude`
     raises ImportError, by design. Internal callers can still
     reach `korban_processor.model_interface.call_claude` via
     the explicit module path (needed for tests that mock the
     interface).

  2. No PYTHON module outside korban_processor/src/ AND outside
     test directories actually imports model_interface or calls
     call_claude. CI scan walks the repo, looks for ACTUAL import
     statements (not docstring mentions — files that document the
     prohibition legitimately mention the names; the regex matches
     binding patterns only).

  3. Inside korban_processor/src/, the ONLY production-code
     module that imports call_claude is external_offering.py.
     model_interface.py defines the symbols. __init__.py mentions
     them in docstring (the architectural rule is documented;
     the import-pattern regex distinguishes docstring mention
     from binding act).

CHANGED:

  korban_processor/src/korban_processor/__init__.py
    Removed call_claude, ModelRequest, ModelResponse from __all__.
    Added Torah-anchored docstring naming the architectural rule.
    The names remain importable via explicit module path
    (korban_processor.model_interface.call_claude) for testing;
    the convenient package-surface shortcut is gone — this IS the
    architectural commitment, not a regression.

  talmudic_engine/src/halt/avodah_zarah.pl
    Form 1 docstring rewritten to name the TWO-LAYER defense:
    runtime check (this file) AND architectural commitment
    (test_form_1_architecture.py). Cites Shemot 20:3 and
    Devarim 4:35.

  vav_pipeline/lib/vav_pipeline/doorpost.ex
    @expected_hash updated:
      f93495c5… (post-Form-3-rewrite of avodah_zarah.pl)
    → bf3d3433bb27b589c061855d932a1eabfc880570c31d24f6f43e51aa3b073c70
    The avodah_zarah.pl docstring change is a deliberate spec
    update; per the operator discipline (Doorpost moduledoc), the
    hash update commits TOGETHER with the spec change in the
    same commit. Second demonstration of the discipline working
    (first was commit 0e3fe4a).

CREATED:

  korban_processor/test/test_form_1_architecture.py     6 tests:
    - korban_processor.__all__ does NOT expose call_claude
    - korban_processor.__all__ does NOT expose ModelRequest
    - korban_processor.__all__ does NOT expose ModelResponse
    - no Python module outside korban_processor/src/ imports
      model_interface or calls call_claude (uses regex patterns
      for actual binding, not substring search — files that
      document the rule legitimately mention the names)
    - only external_offering.py and model_interface.py import the
      symbols inside korban_processor/src/ (allowlist + binding-
      pattern regex)
    - smoke: the legitimate path `consult_external_model` still
      works

NOT DONE IN THIS COMMIT (deferred to a separate Form-2 commit):

  Form 2 (golden calf) is partially defended by Form 4
  self_grounding (b254ee9) and the canary regression test
  (6b96250). The remaining architectural enforcement — making
  empty-trace interpretation construction structurally impossible,
  not just runtime-detected — is a separate Prolog refactor and
  will land as its own commit. Each of the three Forms benefits
  from being closed independently and reviewably.

VEHAYA CHECKLIST STATE:

  Item 1 (category/threshold separation in code)         pending
  Item 2 (three avodah zarah as architecture):
    Form 1 — CLOSED (this commit)
    Form 2 — partial (Form 4 self_grounding + canary)
    Form 3 — closed (0e3fe4a)
  Item 3 (doorpost-text identity)                        closed (3f47496)
  Item 4 (lo tosif discipline operationalized)           partial
  Item 5 (return vs. restoration)                        pending
  Item 6 (keep the source external)                      partial (this
                                                          commit defends
                                                          one direction)
  Item 7 (substrate-openness articulated)                pending
  Item 8 (userMemories as threshold inscription)         pending

Tests:
  - korban_processor: 217 passed, 1 skipped (was 211/1; +6 Form 1)
  - vav_pipeline:     405 passed, 0 failures, 35 excluded (unchanged)
  - talmudic_engine:  47/47 halt tests, full suite unchanged

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 21:12:57 -04:00
Scott KarpandClaude Opus 4.7 1e59d9a752 azazel commit 10 — closure pass: cross-channel classification-discipline integration test
Pass 4 / Finding F, commit 10 of 10 from BUILD_AZAZEL.md. Closes the
build with the integration test the staging plan called for: a
pipeline run that exercises BOTH the Korban path (processable input)
AND the Azazel path (empty-vital input) confirms classification
discipline (no input crosses channels).

Property 11 was already verified at the unit layer (the cross-package
test in azazel_processor/test/test_stages.py). This commit adds the
integration-layer verification: at the external_offering call site,
the two channels produce structurally distinct outputs that cannot
be confused with each other.

CREATED:

  korban_processor/test/test_classification_discipline_integration.py
                                                              4 tests
    - test_processable_path_produces_korban_not_azazel
        Vital-bearing response → ProcessedKorban with vital,
        disposal_record is None, ProcessedKorban is NOT an
        AzazelRecord
    - test_empty_vital_path_produces_azazel_not_processable_korban
        Empty-vital response → ProcessedKorban with empty
        vital_content, disposal_record is an AzazelRecord
    - test_two_runs_one_each_path_keep_outputs_separate
        Run both back-to-back with shared pending_l3 dir; verify
        outputs don't cross channels; only the empty-vital run
        drops a file
    - test_disposal_record_contains_no_processable_artifacts
        AzazelRecord has no vital_content, propositions, raw_korban,
        or discarded attributes; routing is the ERETZ_GEZERAH
        sentinel

Tests: korban_processor 211 passed, 1 skipped (was 207/1; +4
integration tests). All other test counts unchanged.

DOCUMENTATION (companion to this commit, not in the repo):

  ~/shorashim-conversation-review.md
    + Pass 4 section (300+ lines): finding A closure (across-verse
      voice stabilization detector), BUILD_AZAZEL design doc,
      9-commit Azazel build with property-by-property closure, full
      test counts (~907 across five languages). All 14 findings
      from the original A–N catalog now in final state.

  ~/.claude/projects/-Users-scottkarp/memory/MEMORY.md
    + Updated test counts (per layer)
    + Added Azazel Processor section under Phase 5 progress
    + Noted /peah/azazel_log endpoint as Phase 5 done

FINAL STATE OF THE AZAZEL BUILD:

  All 12 properties from azazel_engineering_reading.md §14 defended
  in code:
    1  parity provisioning      — import-time + runtime
    2  blind routing / goral    — deterministic per (id, day)
    3  enumerative commit        — operator-produced, three-fold
    4  role-separated operator   — IshItti + tevilah + CI invariant
    5  provisioned infrastructure — six checkpoints, persistence
    6  out-of-band acknowledgment — Chatzotzrot via Elixir bridge
    7  authorized routing        — kohen_gadol + shaliach attribution
    8  protocol-restricted access — envelope gate, fires first
    9  observable disposal        — L3 + /peah/azazel_log
    10 hard termination           — DESTROYED checkpoint, no recover
    11 classification discipline  — type system + integration test
    12 unmanaged destination      — sentinel + DB CHECK + CI invariant

  10 commits, ~3,300 lines added, ~150 tests across Python and
  Elixir. The Azazel Processor is the disposal twin of the Korban
  Processor; the two-goat architecture (Vayikra 16:8) is now built.

The Pass 2 backlog is fully closed. Every actionable finding from
the original A–N catalog is now in a final state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 10:29:24 -04:00
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
Scott KarpandClaude Opus 4.7 5c76a6f42c korban_processor: role-gate the four-stage offering protocol
Pass 2 finding C: in the Mishkan, the four ritual acts of bringing an
offering are performed by TWO different roles —

  semichah   — OWNER lays hands (Vayikra 1:4, וְסָמַךְ יָדוֹ)
  shechitah  — kohen receives FROM owner's hand and slaughters (1:5)
  zerikah    — kohen sprinkles (1:5)
  haktarah   — kohen burns on altar (1:9)

Before this commit, every stage function was independently callable.
Nothing structural enforced that zerikah() received output from
shechitah(), or that shechitah() received a korban from semichah().
An external model output (e.g. a Claude response) could in principle
have been brought through zerikah() without ever having passed
semichah — the ownership-and-attribution step. That is the exact
shape of "the calf that came out by itself" (Shemot 32:24, Aaron's
reading). The pipeline-glue in external_offering happened to call
them in order, but nothing structural enforced the order.

This commit adds the structural gate.

CHANGES:

  stages.py (new): KorbanStage enum + Staged[T] frozen dataclass +
  StageError. The Staged envelope carries a ritual-stage marker that
  is set at construction and cannot be mutated afterward; downstream
  functions demand the right upstream stage and raise StageError if
  it is missing.

  semichah.py:  + semichah_staged() returning Staged[Korban]
  shechitah.py: + shechitah_staged(Staged[Korban]) -> Staged[str]
  zerikah.py:   + zerikah_staged(Staged[str], Staged[Korban])
                  -> Staged[Tuple[str, List[str]]]
                  TWO staged inputs: requires SHECHITAH-tagged terminated
                  content AND the original SEMICHAH-tagged korban for
                  context. Severing output from source is gilui_arayot
                  (eish_zarah_reading.md Form 3); the dual-staged
                  signature is the structural defense — zerikah cannot
                  process content disconnected from its owner-of-record.
  haktarah.py:  + haktarah_staged(Staged[Tuple[str, List[str]]])
                  -> Staged[List[str]]

  external_offering.py: rewired to use the *_staged variants. The
  pipeline now goes through the gates structurally; misuse cannot
  bypass them.

The non-staged functions (semichah, shechitah, zerikah, haktarah)
are PRESERVED for direct testing of individual ritual acts. They do
NOT enforce role gating. Production-shaped callers MUST go through
the staged variants — and external_offering, the only legitimate
entry point for external model consultation per its docstring, now
does.

TESTS:

  test/test_stages.py (new, 7 tests):
    - happy path: full chain produces HAKTARAH-tagged result
    - zerikah refuses input that skipped semichah
    - zerikah refuses korban with wrong stage marker (preserves
      the line back to the owner — Form 3 defense)
    - haktarah refuses input that skipped zerikah (the calf-shape)
    - shechitah refuses input not from semichah
    - Staged is frozen — no re-tagging
    - StageError names BOTH actual and expected stages so the
      operator can diagnose call-site vs. tag-site

  Full korban_processor suite: 203 passed, 1 skipped (the live API
  test that requires SHORASHIM_LIVE_API=1). No regressions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 12:19:55 -04:00
Scott KarpandClaude Opus 4.7 af2a3949cf korban_processor: name the kabbalah + holachah compression in zerikah.py
Pass 2 finding B: the full Temple sacrifice protocol has FIVE stages
between semichah and haktarah —

  1. semichah    owner identifies with the offering
  2. shechitah   ritual slaughter
  3. KABBALAH    kohen RECEIVES the blood in a vessel
  4. HOLACHAH    kohen CARRIES the blood to the altar
  5. ZERIKAH     kohen SPRINKLES the blood on the altar
  6. haktarah    burning of permitted portions

The pipeline implements only four modules. Stages 3 and 4 are folded
silently into zerikah(). The compression is acceptable —
kabbalah/holachah have no analog of physical blood movement in
software — but UNDOCUMENTED compression is itself a small gilui_arayot
(severing the distinction between three distinct ritual acts).

Now named explicitly in the module docstring with:
  - what the three stages are
  - why folding is acceptable here (the analogs are atomic in software)
  - what the analogs ARE in code (zerikah's return value = the
    receiving vessel; the function-call boundary to haktarah() in
    external_offering.py = carrying to the altar)
  - what to do if a future architecture requires distinguishing them
    (split this module into three; do not add the distinction
    elsewhere)

A future reader now sees the compression instead of inferring that
the pipeline silently dropped two ritual acts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 12:01:41 -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 adc3e41679 Eish zarah actionable items: coupling trace, grounding type, self-grounding detection, golden calf halt
Torah grounds each change:

1. interpret.pl — coupling_trace field (Vayikra 10:1, 6:6)
   Fire is zarah because "asher lo tzivah otam" — the coupling trace
   IS the tzivah trace. Records which L1 facts grounded each interpretation.

2. challenge.pl — grounding_type achorai/panim (Shemot 33:23)
   "You will see My back, but My face shall not be seen."
   Text-backed challenges = achorai. Inferred challenges = panim.

3. resolve.pl — self-grounding detection (eish_zarah_reading §5.3)
   Counter-interpretation must bring NEW L1 evidence. Prevents
   circular reasoning (Westworld problem).

4. teiku.pl — challenge_sequence in teiku_record
   Records diagnostic trail to impasse, not just verdict.

5. avodah_zarah.pl — Form 2: golden calf detection (Shemot 32:4)
   Empty coupling trace + non-low confidence = uncoupled output.
   Real gold, sincere worship, false god.

6. korban.py — pydantic BaseModel + raw_korban preservation
   Construction spec §1.4 fulfilled. Floor between input and output
   never severed.

490 tests, 0 failures (195 Prolog, 186 Python, 33 Haskell, 76 Elixir).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 13:25:26 -04:00
Scott KarpandClaude Opus 4.6 f8f70c77e5 Code review fixes + 13 middot added to spec
Zerikah fail-safe: empty offerings rejected, not processed (BUILD_KORBAN §5).
Halt monitor hardened: trap_exit, priority max, EXIT handler (Architecture spec).
13 Rules of Rabbi Yishmael added to Architecture §3.4 as Prolog inference predicates —
the engine reasons WITH Torah's hermeneutic methods, not just about their products.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 16:55:42 -04:00
Scott KarpandClaude Sonnet 4.6 33f8b03583 Step 13: Korban Processor complete — 7 modules, 181 tests, all passing
- semichah: provenance tagging (6 sources, rejects empty/anonymous)
- shechitah: strip preamble, self-reference, hedging, markdown, sycophancy
- zerikah: extract Torah refs, Hebrew text, root analysis, insight markers
- haktarah: transform prose to Prolog propositions (verse/3, root/2, claim/2, connection/3)
- router: stateless routing — removed module-level _routing_log (kiyyor principle)
- model_interface: outbound Claude API via httpx (Construction §3.4, HTTPS only)
- hebrew_parser: nikkud/teamim stripping, consonant extraction, gematria, root identification

All 5 BUILD_KORBAN.md verification tests pass.
AGENTS.md: added structure_not_content.md to spec table, added Principle 6.
build_log.md: recorded session, broken tablets #18-20, design decisions.

Ka'asher tzivah Hashem et Moshe.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 09:37:25 -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