7d46fdc7c9dea277aa1d79c318a77fc5f355929c
21
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
bbe2c4b7ba |
L1: the integrity check reads the scroll (tablet 38)
L1Reader.verify_integrity/0 read the hash stored in integrity.sha256
and compared that string with the hash compiled into the module. It
never opened a verse. On that comparison of a constant with its own
copy rested `l1_hash: pass`, the Cloud's "L1 corrupted — the cloud is
not visible", and Construction §1.2. A changed verse would have passed
every one of them. Every existing test passed too, because every test
asked about an untouched L1.
- L1Reader.compute_tree_hash/1: SHA-256 over every file under torah/ —
relative path, then bytes — in the order scripts/build_l1.py hashed
them (directories by path, files by name, plain string order). On the
real L1 it yields the pinned 156b1d27…; for a tampered copy it yields
the same hash the Python implementation does.
- VavPipeline.L1Integrity (new, supervised): reads and hashes the scroll
when the pipeline starts (~0.9 s in the image) and again every ten
minutes; logs and signals the assembly when the answer changes.
- verify_integrity/0 now requires BOTH: the recorded hash is the pinned
one, AND the scroll on disk hashes to it (answered from the witness's
last reading; computed on the spot where the witness is not running).
Callers are unchanged — the Shema context, the health check and the
status endpoint all inherit the real check.
- scripts/verify_l1.py: the same reading by hand. KOHEN.md and
KOHEN_PROMPT.md used the hollow comparison as their L1 gate; they now
run this.
- test/tablet38_l1_integrity_test.exs — 9 tests: the hash and its
order; one byte / one added file / one rename each change it; the
witness; and the whole path — with an altered scroll and an untouched
integrity.sha256, verify_integrity fails, health says l1_hash: fail,
and /peah/ask does not answer.
Specs: architecture §2.2 (which had been made to confess the hollow
check in
|
||
|
|
d7520cd69d |
specs §2 + images + Doorpost: the technology table says what is built; L1 is read-only in fact
Architecture §2 described a self-hosted server: Phoenix for L2,
PostgreSQL 16, Ubuntu 24.04 under systemd, rootless Podman isolating
Sanhedrin panels, Erlang distribution between them, systemd timers for
Shabbat and the cycle, L1 on a read-only mount. None of that is what
runs. Each row was checked against the code and the images before it
was rewritten.
§2.1 exact versions (no "9.6+"); Korban and Azazel Processors listed.
§2.2 L1: in every image, root-owned, no write bit; recorded hash
compared before every cycle. L2: a process in the pipeline, not
yet over HTTP and not yet called by the cycle. L3: PostgreSQL 17
managed, append-only by REVOKE.
§2.3 Kubernetes, one pod. OTP supervisors + the Deployment's probes.
Sanhedrin panels are Erlang processes in the pipeline's BEAM — no
container boundary. Message passing within one node; a port to
the engine; a Unix socket to the Talmudic Engine. Logging: stdout
and L3; halt events in memory. ShabbatTimer process, UTC stub.
No cycle timer: erev/voker is a stage of each cycle. The manna
volume.
A DEFECT FOUND BY CHECKING THE L1 ROW, AND FIXED:
The spec said L1 is a read-only filesystem. In all four images the
scroll was owned by the service user, mode 664 — `touch` inside
/mnt/torah succeeded. The process that reads Torah could have rewritten
it. All four Dockerfiles now copy L1 as root and strip every write bit
(dirs 555, files 444). Verified in each image as the service user:
cannot create, append, or delete. The hash is unchanged (it covers
paths and contents, not modes).
A DEFECT FOUND AND NOT FIXED, now written into the spec as it is:
L1Reader.verify_integrity/0 reads the hash stored in integrity.sha256
and compares it with the compiled-in constant. It never hashes the
verse files. `l1_hash: pass` witnesses which L1 was built into the
image, not that the files are unaltered. §2.2 says so in plain words.
deploy/ARCHITECTURE.md: L1 is in the images, not on a volume; the manna
volume is 1 GiB and holds the ledger too; l1-volume.yaml is vestigial.
Doorpost @expected_hash 1cd07079… → 7e5286b9…, in this commit, with the
witnessed file that moved it (architecture).
Read before committing: Elixir 474/0 (seeds 0, 42). Four images
rebuilt; full local stack (real engine binary, PostgreSQL with SSL,
migrations): Hebrew 200, English 422, health 7/7, 2 interpretations in
L3, no errors; talmudic and korban containers start.
Left as written: §1.4 hardware (a 64–128 GB server; the node has 4 GB),
§2.4 external models (Steps 21–22, unbuilt), §4's build steps
("Install PostgreSQL 16"), terumah §B (systemd, squashfs, Caddy,
nftables).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
f4e8e5afc8 |
specs §1.1/§1.5 + Doorpost: the pins say what is built, and the build obeys them
The pin audit (2026-10-04) found Construction §1 wrong in three ways.
1. §1.5's mix.exs block was not the real mix.exs.
phoenix ~> 1.7.11 was never a dependency (the API is Plug on
Bandit); `ecto` is `ecto_sql`; telemetry is transitive (locked
1.4.2, outside the stated ~> 1.2.1); bandit, plug and the sanhedrin
path dependency were missing. The block now matches mix.exs and adds
the versions mix.lock actually builds. Terumah's Elixir table
matched the wrong block and also said postgrex ~0.17.4; corrected.
2. §1.1's component table described a machine that is not the one
running. PostgreSQL 16.2 via apt → 17, managed (17.11 today).
Podman 4.9.3 → Kubernetes/containerd, managed; Podman is used
nowhere. Ubuntu 24.04.1 → what each image is in fact built on
(Ubuntu 22.04 for pipeline and engine, Debian 12 for korban and
talmudic) and the managed node (24.04). rebar3 3.23.0 → 3.22.0, the
version in the build image. Azazel Processor added as a component.
The spec is corrected to the deployment, not the reverse.
3. §1.1's rule — "No >=. No +. No ranges." — was not true of the spec's
own Elixir deps, and "lock files are committed" was not true of two
components. Both halves fixed:
- The rule now says what is the case: a manifest may give a range;
the committed lock is the pin; every image is built from its
lock; base images are pinned by tag; managed services are pinned
to a major version and patched by the provider.
- And the build was made to obey it. The Korban image installed
three packages named by hand and let pip pick every transitive
one on the day of the build, plus an unpinned poetry-core. It now
installs the lock's main group by exact version AND hash
(scripts/lock_to_requirements.py; --require-hashes --no-deps;
pip check). The image holds the lock's 12 packages and nothing
else.
- azazel_processor/poetry.lock: generated (it had none).
- kushya_generator/pyproject.toml + poetry.lock: the four §1.4 pins
(llama-cpp-python 0.2.55, peft 0.9.0, transformers 4.38.2,
datasets 2.17.1) were pinned nowhere in the repo. They are now
declared and locked (74 packages). The lock resolves; it has NOT
been installed or run — Step 21 is unbuilt and needs a GPU.
Its test config gains the korban and azazel source paths, which
the external backend needs: 3 tests there had been failing since
the azazel build, unnoticed because that suite is in no total.
Doorpost @expected_hash 39b66c31… → 1cd07079…, in this commit, with the
witnessed file that moved it (vayakhel).
Read before committing: Elixir 474/0 (seeds 0, 42) · korban 222 passed
1 skipped and azazel 125 passed, each with dependencies installed from
its lock by hash · kushya 17 passed (was 14 of 17) · korban image
starts, imports korban_processor + azazel_processor, pip check clean ·
pipeline image reports specification_identity verified 1cd07079….
Left as written: architecture §2's technology table still names
Phoenix, PostgreSQL 16, Ubuntu 24.04 and rootless Podman for Sanhedrin
isolation, and terumah §B still lists systemd, squashfs, Caddy,
nftables for a self-hosted server. Those describe a design, not a pin.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
41a3e51a43 |
deployment: manna on a persistent volume — the jar survives the restart
Manna was an emptyDir: every pod creation emptied it and the vessel sat unready until the kohen wrote the day's portion again. On 2026-10-04 that happened seven times in one day of rollouts. deploy/ARCHITECTURE.md has always specified a block-storage volume for it and manna-volume.yaml has always defined the claim; it was "not applied at Day 3" and stayed that way. - manna-volume.yaml: storageClassName exoscale-sbs (the cluster's only class is not marked default — without it the claim stays Pending), 1Gi. Applied; bound in 6 s. - deployment.yaml: the manna volume is the manna-pvc claim. `kubectl diff` against the live deployment showed this as the only change. - KOHEN.md: apply order (the claim before the pod), and the daily-ops note — write once a day, not once per restart. Verified on the live pod: manna written once onto the volume; then a rollout restart with NO provision written by anyone — the new pod was Ready and serving 49 s later, health 7/7, /peah/ask 200. What this does not change: the portion still expires by its own date and expires_at (Shemot 16:19-20). And the ledger is in memory — a restarted pod counts consumption from zero against the same day's allocation, as it always did; that is now reachable without the kohen's hand, and is recorded as open. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
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>
|
||
|
|
7e1a4473c7 |
deployment: replace in place — one replica, one node, no room to surge (tablet 31)
The default RollingUpdate (maxSurge 25% → one extra pod) needs 450m CPU beside the running pod; the node has 1700m allocatable and 1470m requested. The Tablet-30 rollout sat Pending for five minutes while the old image kept serving. maxSurge 0 / maxUnavailable 1: the old pod goes first, the new one schedules, and is Ready once the kohen writes manna. Patched live 2026-10-04; this makes the manifest match. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
d6d4475da3 |
deploy: an Exoscale LoadBalancer Service is two artifacts — the gate was never opened (tablet 29)
The NLB forwards to the Service's NodePort and healthchecks it on the node; the cloud controller does not open that port in the security group. shorashim-external had no rule for 30923 for 26 days, so 185.19.29.180 refused connections while the pod was Ready. Rule added by the kohen 2026-10-04; the command now sits beside the manifest. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
0344d095b2 |
probes: liveness asks "is it dead?", readiness asks "may it serve?" (kohen Day 3, restart 1)
TORAH ANCHOR — Shemot 16:4, and tamid §2.1 check 3: Manna provision read … If absent: WAIT. Alert senior kohanim. WAIT. Not die. Provision is not the health of the vessel. Killing the process does not produce manna; it re-runs init, re-spawns the Port, and waits again — three minutes later. WHAT THE KOHEN FOUND (BUILDER_HANDOFF_probes.md, 2026-09-07 20:35Z): A fresh pod after recreate. emptyDir manna empty — by design (KOHEN.md:406). manna_present honestly read "fail" — the Day-6 fix, correct per tamid §1.1. overall → "degraded" → /kodesh/health 503. BOTH probes pointed at /kodesh/health. Liveness: period 60s, default failureThreshold 3 → after three minutes the kubelet SIGTERMed a process whose log showed the Port alive, zero errors, l3_writable pass, esh_tamid pass. It was alive and serving. It was killed for lacking provision, and would be again every three minutes until the kohen wrote today.json — i.e. on EVERY pod creation. Introduced by a correct fix ( |
||
|
|
6e34001c58 |
deployment: split the migration role from the application role (kohen Day-3 42501)
TORAH ANCHOR — Vayikra 18:5-6 / the gilui arayot shape:
The kohen refused the easy fix on the grounds that dissolving a
required distinction for convenience is the boundary violation this
system's own halt rule names. That reading is correct and this commit
implements it rather than working around it.
THE BLOCKER (kohen Day 3, escalated):
** (Postgrex.Error) ERROR 42501 (insufficient_privilege)
permission denied for schema public
Could not create schema migrations table.
Ecto.Migrator.run/3 calls ensure_schema_migrations_table! BEFORE it
looks at what is pending (see vav_pipeline/lib/vav_pipeline/
release.ex — migrate/0 wraps Ecto.Migrator.run(&1, :up, all: true)).
So the migrate init container issues
CREATE TABLE IF NOT EXISTS "schema_migrations"
on EVERY pod start, even now that 20260428000001_create_azazel_records
is applied and nothing is pending. As shorashim_app — which has USAGE
but not CREATE on schema public, correctly — that is a permanent
failure and the pod never reaches Running.
ROOT CAUSE: the manifest did not implement what the runbook already
specified. KOHEN.md:184 says "the migration runs as avnadmin; the
application runs as shorashim_app", and then deployment.yaml gave the
init container and the app containers the same shorashim-secrets.
The two-role split existed as prose and not as configuration.
THE FIX:
deploy/k8s/deployment.yaml — migrate init container only:
+ env:
+ - name: DATABASE_URL
+ valueFrom:
+ secretKeyRef:
+ name: shorashim-migrate-secrets
+ key: DATABASE_URL
An explicit `env:` entry rather than swapping the secretRef, as the
kohen proposed. Same effect today, and strictly safer tomorrow:
`env:` takes precedence over every `envFrom` source, so exactly ONE
variable changes role and anything else that later lands in
shorashim-secrets is still inherited by the migrator. Swapping the
ref would have silently dropped such a key from the init container.
App containers keep shorashim-secrets untouched. Append-only holds:
the L3 tables stay avnadmin-owned with the REVOKEs in place, and
shorashim_app still has only INSERT/SELECT.
WHY NOT GRANT shorashim_app CREATE ON SCHEMA public — recorded, not
just decided. The kohen considered it, noted append-only would
technically survive, and rejected it because it hands the application
DDL power over the schema it writes into and collapses the app/
migration boundary KOHEN.md draws on purpose. Concurred, and the
reasoning is now written into deployment.yaml and secrets.yaml at the
two places someone would next hit 42501 — so the shortcut cannot be
quietly re-opened by whoever meets the error without this thread.
ALSO IN THIS COMMIT:
deploy/k8s/secrets.yaml — shorashim-migrate-secrets added as a
TEMPLATE (placeholder value, no credential), with the create command.
The command differs from the one in the escalation in two ways, both
deliberate:
1. The userinfo rewrite is done in Python, not with
`sed "s|//shorashim_app:[^@]*@|//avnadmin:${AVN_PW}@|"`. A
generated password containing `&` re-inserts the matched text,
one containing `|` ends the sed expression early, and `\` escapes.
The failure is silent — the result is a DATABASE_URL that looks
plausible and does not authenticate, which on Day 3 would read as
a second, different blocker. urllib.parse.quote(safe="") also
percent-encodes anything not URL-safe in the userinfo field,
which sed passes through raw.
2. The value goes through a `umask 077` temp file rather than
--from-literal, so the avnadmin password is never an argv entry
visible to `ps` on a shared host.
KOHEN.md — new step 3a-pre (create/verify the secret before applying),
and §Day-3b now says configmap.yaml is not optional on this pass
(it carries SHORESH_ENGINE_BINARY) and that secrets.yaml must NOT be
applied — it is a template and would overwrite both live secrets with
placeholders.
DELIBERATELY NO FALLBACK: if shorashim-migrate-secrets is absent the
pod stops at CreateContainerConfigError. Loud is correct here. A
fallback to the application role would reintroduce 42501 at a moment
when everything else looked healthy.
NOT DONE BY THE BUILDER: the secret itself. It carries a live DDL
credential and it is cluster state; both are the kohen's hands, not
mine. The command is in deploy/k8s/secrets.yaml.
VERIFIED:
- kubectl apply --dry-run=client on deployment.yaml, configmap.yaml
and secrets.yaml — all four objects valid (client-side only; no
cluster state touched)
- env/envFrom wiring re-read from the parsed manifest: migrate has
envFrom [shorashim-config, shorashim-secrets] + env DATABASE_URL
from shorashim-migrate-secrets; vav-pipeline has envFrom
[shorashim-config, shorashim-secrets] and no env override
- Doorpost.verify_identity() :ok — no @spec_files entry touched, so
no @expected_hash change
- l1/integrity.sha256 unchanged: 99bbd07a…4ea377
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
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> |
||
|
|
55bc5bdc44 |
builder fix for kohen Day-3 FAIL: image arch, azazel co-residence, runbook drift
ROOT CAUSE (kohen Day-3 escalation): None of the four Dockerfiles pinned --platform. Build on Apple Silicon shipped arm64-only images to a registry consumed by amd64 SKS nodes. Init container failed exec format error before producing logs. DOCKERFILE FIX (defense in depth): Pin --platform=linux/amd64 on every FROM line in all four Dockerfiles. The build artifact now self-corrects regardless of build host. AZAZEL CO-RESIDENCE (anomaly the kohen flagged): Azazel L3 migration shipped (20260428…create_azazel_records.exs) but no separate container existed. Reading the code: azazel_processor is imported by korban_processor.external_offering at runtime in the same Python process — not a separate process. Dockerfile.korban_processor now COPYs azazel_processor/src/ alongside korban_processor. Documented in the Dockerfile (AZAZEL CO-RESIDENT section) and in deployment.yaml. Both containers (vav-pipeline + korban-processor) now mount the shared tmp-shorashim volume so the Python→Elixir file bridge (azazel_ingest.ex polls /tmp/shorashim/azazel/pending_l3/) works end-to-end. CANONICAL BUILD/PUSH (scripts/build_and_push.sh): - Pins linux/amd64 by default; PLATFORMS env for multi-arch - Pre-flight checks (repo root, buildx, registry login) - Builds and pushes all four images with one command - Closes the gap KOHEN_PROMPT.md gate 0i would otherwise leave open KOHEN.md DRIFT (kohen recorded these as anomalies through Day 1-3): - Line 58 hash fragment 99bbd07a…0a0187b → 99bbd07a…4ea377 (correct) - Inheritance table: "Not yet created" claims rewritten as "Verify before assuming" — a prior partial setup may have left state, and the kohen prompt requires verifying not assuming - L1 PV row: marked NOT REQUIRED (L1 baked into images) - §Day 3a ConfigMap recipe marked vestigial (kept as comment) - §Day 3b: removed manna-volume.yaml from kubectl apply list (deployment uses emptyDir; PVC is for a future move) - §Day 3c: "5 containers" → "4 containers + 1 init" - §Day 3 fail-mode list expanded with the four most common causes, in order, including exec format error → gate 0i KOHEN_PROMPT.md: - Gate 0f corrected to four images (talmudic-engine is image #3; azazel is co-resident in korban-processor not a separate image) - Gate 0i added: image-architecture vs node-architecture verification before Day 1, with kubectl one-liner and docker manifest inspect per image. Drift handler in the gates section: 0i FAIL → escalate to builder, do not begin Day 1. NO SPEC FILES TOUCHED: Doorpost.verify_identity() still :ok — none of these files are in @spec_files. No atomic spec+hash commit needed. NEXT STEP FOR THE KOHEN: Once these images are rebuilt and pushed via scripts/build_and_push.sh (by anyone with the Harbor CLI secret + a working docker daemon), restart from Day 1. Existing DB role shorashim_app, ALTER DEFAULT PRIVILEGES, rotated DATABASE_URL, shorashim-config, and shorashim- registry secret all remain valid across the restart. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
66686329ed |
Move operator notes out of repo, add generic architecture doc
memory/infrastructure.md and memory/build_log.md held environment- specific details — hostnames, IPs, account names, credentials retrieval commands, local kubeconfig paths, operational status notes. These don't belong in a project repo; by convention they live in private operator notes. Moved to ~/shorashim-operator-notes/ (private, outside git). In their place, added deploy/ARCHITECTURE.md — a generic description of the deployment shape: components, external dependencies, configuration surface. No hostnames. No IPs. No account names. Any competent operator can read it and know how to deploy shorashim to their own infrastructure. Updated cross-references in AGENTS.md and BUILD_KORBAN.md to point to "the operator's private notes" instead of memory/. Project repo is spec + implementation + deployment shape. Operator notes are the kohen's personal log. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
324cf1a2ed |
Fix gitea-mcp env: use GITEA_ACCESS_TOKEN, not GITEA_TOKEN
The gitea-mcp server expects GITEA_ACCESS_TOKEN environment variable, not GITEA_TOKEN. Without the correct env var, every API call to the backend Gitea returns "token is required" — the MCP server can't authenticate to fetch user info, repos, etc. Also added GITEA_HOST env var as a backup to the --host flag. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
42370bfc24 |
Fix MCP connector: cert-manager URL typo and TCP probes
Two fixes for the mcp.shorashim.ch deployment: 1. cert-manager ClusterIssuer had acme-v2 (wrong) instead of acme-v02 (correct). Let's Encrypt ACME directory is at https://acme-v02.api.letsencrypt.org/directory 2. gitea-mcp HTTP probes timed out because GET /mcp hangs (SSE). Switched to TCP socket probes on port 8080. MCP server now responds correctly over HTTPS at mcp.shorashim.ch: curl -X POST https://mcp.shorashim.ch/mcp -d '{...initialize...}' returns {"result":{"serverInfo":{"name":"Gitea MCP Server"}}} Also: nginx-ingress configmap needed strict-validate-path-type=false to allow cert-manager HTTP-01 challenges. Applied via kubectl patch, not tracked in git manifests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
20e81114a5 |
Fix gitea-mcp deployment: add explicit command path
The gitea-mcp-server image has no entrypoint set (null). Args were being interpreted as the executable name. Added command: ["/app/gitea-mcp"] to invoke the binary correctly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
c7e0e6e703 |
Gitea MCP connector: K8s manifests for public claude.ai integration
Shemot 28:3 — v'asu et bigdei Aharon l'kadesho l'chahano li. The garments connect the service to those who approach. - gitea-mcp-deployment.yaml: Official gitea-mcp server in HTTP mode Connects to Gitea at 185.19.29.94 via PAT - gitea-mcp-ingress.yaml: TLS ingress at mcp.shorashim.ch cert-manager auto-provisions Let's Encrypt certificate - gitea-mcp-secret.yaml: Template for Gitea PAT (never committed with value) - cert-manager-issuer.yaml: Let's Encrypt ClusterIssuer Architecture: claude.ai → OAuth → mcp.shorashim.ch → Gitea API Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
2f1205eef1 |
Bake L1 Torah into container images, simplify K8s deployment
L1 (29MB, 5860 files) is read-only and immutable — it belongs inside the image, not on a separate volume. The scroll is sealed in the aron. It ships with the vessel. - All 4 Dockerfiles: COPY l1/ /mnt/torah/ in runtime stage - deployment.yaml: Removed L1 PVC volume and all L1 volumeMounts - deployment.yaml: Added imagePullSecrets for registry auth - deployment.yaml: Manna uses emptyDir (kohen writes via kubectl cp) - No CSI driver needed — eliminates Exoscale block storage dependency Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
5e76c60218 |
Update K8s deployment with Exoscale container registry URLs
All 4 images now reference exo.container-registry.com/the-recognition-protocol/: - vav-pipeline:latest (111MB) - shoresh-engine:latest (128MB) - talmudic-engine:latest (292MB) - korban-processor:latest (187MB) Images built and pushed. The garments are in the registry. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
edb74da0b4 |
Fix shoresh engine Dockerfile: Debian Buster repos are EOL
haskell:9.6.4-slim is based on Debian Buster, which reached EOL. The standard deb.debian.org repos return 404. Redirected to archive.debian.org for zlib1g-dev installation. Verified: shoresh-engine image builds successfully (128MB). All 4 Docker images now build: vav-pipeline: 111MB shoresh-engine: 128MB korban-processor: 187MB talmudic-engine: 292MB Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
f32e9afb03 |
Fix korban processor Dockerfile: pyswip 0.2.11 does not exist
pyswip 0.2.11 was never published to PyPI. Latest 0.2.x is 0.2.10. Changed pin from ==0.2.11 to ==0.2.10. Verified: korban-processor image builds successfully (187MB). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
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> |