Commit Graph
6 Commits
Author SHA1 Message Date
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 5 0572a2cffd specs §1.5 + Doorpost: postgrex ~> 0.22.3 — ma'akeh before the roof is walked on
TORAH ANCHOR — Devarim 22:8:
כִּי תִבְנֶה בַּיִת חָדָשׁ וְעָשִׂיתָ מַעֲקֶה לְגַגֶּךָ וְלֹא תָשִׂים
דָּמִים בְּבֵיתֶךָ כִּי יִפֹּל הַנֹּפֵל מִמֶּנּוּ.
"When you BUILD a new house, make a parapet for your roof, and do not
place blood in your house, for the faller will fall from it."

The obligation attaches at BUILD time and to the BUILDER — ki tivneh,
not ki teshev. The kohen who dwells in the house does not owe the
parapet; the one who builds it does. That is why this is Bezalel's
commit and not the kohen's, and why it lands before the vessel is
handed over rather than after.

The roof in question is /peah/ask. It is unauthenticated BY DESIGN —
Vayikra 19:9-10, lo techalleh pe'at sadcha b'kutzrecha. The corner is
left open on purpose. Ma'akeh is not a wall around the corner; it is
what keeps a deliberate opening from becoming a fall.

WHAT WAS FOUND (kohen escalation, BUILDER_HANDOFF_amd64.md §Second
Finding, confirmed by `mix deps.get` advisory output in an amd64
container on 2026-08-23):

  postgrex 0.17.5  VULNERABLE
    EEF-CVE-2026-32687 / GHSA-r73h-97w8-m54h   HIGH
      SQL injection via channel name in
      Postgrex.Notifications.listen/3 and unlisten/3
      introduced 0.16.0, fixed 0.22.2
    EEF-CVE-2026-58225 / GHSA-4mw9-4qgj-m97w   LOW
      SQL injection via unescaped dollar-quote in reconnect replay
      → notification denial of service
      introduced 0.16.0, fixed 0.22.3

  plug 1.19.1      VULNERABLE
    EEF-CVE-2026-54892 / GHSA-j43x-5hjq-rgxf   HIGH
      quadratic-time decoding of nested query/body parameters
      → denial of service
      1.19.x line fixed in 1.19.3

Affected ranges read from api.osv.dev, not from the advisory titles.
There is no 0.17.6; the postgrex fix does not exist below 0.22.2, so
the pin must cross two minor lines. 0.22.3 is the lowest version that
clears BOTH postgrex advisories.

THE SPEC CHANGE (Layer 1, lo_tosif.md):

  specs/shorashim_vayakhel.md §1.5
    {:postgrex, "~> 0.17.4"}  →  {:postgrex, "~> 0.22.3"}

  vav_pipeline/lib/vav_pipeline/doorpost.ex
    @expected_hash c7ac85cd…b98bb6af  →  ed2a6aec…6b72c2e22

Committed atomically per specs/lo_tosif.md Layer 1: the spec file and
its witness move together, in one reviewable unit, with the Torah
anchor named. Verified before the hash was updated that the mechanism
actually fired — verify_identity/0 returned SPEC_INTEGRITY_FAILED and
chatzotzrot :assembly_leaders (teruah) sounded on the drift. The
doorpost is not decorative; it caught this change on the way through.

NOT A SPEC CHANGE:
plug is not pinned in §1.5, so the plug bump (1.19.1 → 1.20.3) is an
operations change per lo_tosif.md "What is NOT a spec change" and
touches mix.lock only. Same for the transitive movers that came with
the resolution: db_connection 2.9.0→2.10.2, decimal 2.3.0→2.4.1,
jason 1.4.4→1.4.5, plug_crypto 2.1.1→2.2.0, telemetry 1.4.1→1.4.2.

VERIFIED:
- `mix deps.get` reports no advisories against the resolved tree
- `mix compile --force` clean
- `mix test` — 412 tests, 0 failures, 35 excluded (:l3, need PostgreSQL)
- VavPipeline.Doorpost.verify_identity() → :ok
- l1/integrity.sha256 unchanged: 99bbd07a…4ea377

FLAGGED, NOT FIXED — §1.5 has further drift this commit deliberately
does not touch. The block still lists {:phoenix, "~> 1.7.11"} and
{:ecto, "~> 3.11.1"}, while vav_pipeline/mix.exs actually depends on
ecto_sql, bandit, plug and the sanhedrin path dep, and has never
depended on phoenix. Correcting that is a larger category-grade
question than the security finding I was handed, and lo tosif says
the builder does not widen a commanded change on his own motion.
Raised in the handoff-back instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 12:00:32 -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 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 c67672777b Step 14: Connect components — chamishim karsay zahav
Three connections built and tested:
- Haskell Shoresh Engine via Erlang Port (JSON lines over stdin/stdout)
- Prolog Talmudic Engine via Unix domain socket (JSON over /tmp/shorashim/talmudic.sock)
- Erlang Sanhedrin via native BEAM calls

New: Server.hs (JSON-line server loop), json_bridge.pl + socket_server.pl
(Prolog socket interface), ShoreshEnginePort + TalmudicEngineSocket +
SanhedrinClient (Elixir GenServer wrappers). BereishitCycle now routes
unresolved cases through Talmudic Engine and Sanhedrin. All connections
gracefully degrade — pipeline falls back to L1 data when engines unavailable.

46 Elixir tests, 33 Haskell tests, 57 Prolog tests — 0 failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 17:39:21 -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