489cf114164542937125dd46e3135404a9ca7912
100
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
489cf11416 |
kohen: the watchdog — /kodesh/health watched from outside, a Gitea issue on failure
2026-10-15: the site was 503 for 13.5 h across a day boundary and the Doorpost outage was found by losing Claude; 24 h of database refusals were found the same way. Nothing watched. scripts/bikoret-watchdog (installed at ~/.local/bin, cron every 5 min on the dev VM) probes /kodesh/health. Two consecutive failures open ONE Gitea issue in this repo naming the failed checks and what to read first; while down it comments only when the failed set changes; on recovery it comments the duration and closes the issue. Transitions go to ~/shorashim-operator-notes/watchdog.log; silence is health. Proven: healthy → exit 0, no log; dead URL ×3 → FAIL #1, #2, one ALERT (not two), #3 no second issue; recovery → comment + close. The real Gitea call was refused 403: the clone token in ~/.git-credentials has only the code scopes. The script prefers ~/.config/bikoret/gitea_watchdog_token (write:issue), which the kohen issues; until then it logs and tracks recovery but cannot open the issue. KOHEN.md "Every morning" carries it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
02f71dab49 |
Retire shorashim.ch; Gitea brand becomes Bikoret
- service.yaml: the shorashim.ch host rule is gone from the Ingress; the no-host catch-all stays for http://185.19.28.170 - gitea-public-patch.yaml: APP_NAME "Shorashim" → "Bikoret" (title and brand on every Gitea page) - shorashim-ch-takedown.sh: the operator's apply (ingress, orphan mcp.shorashim.ch TLS secret, Gitea patch + rollout) with verification - KOHEN.md, AGENTS.md: examples and the endpoint table use bikoret.ch Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
cf8b5e3dc4 |
tablet 56: the gap that was not there — empty says what was read
The builder claimed the structural and frame checks never see an
engineering reply because its sentences are not "vital". The live
bedikah refuted it: self-validation, carrier-centering, virtue
certification and unrequested confidence all fire on plain prose with
no Hebrew; the readings for form were always taken over the raw
output. Nothing built for that; the tablet records it.
What was true: `empty` said "nothing to examine", and that under-
reported. Now the empty record carries checks.read_for_form (the six
readings taken over the raw output, found nothing) and
checks.grounding {assessed: false, reason}; the reply's `meaning` says
empty is not tov — read for form and clean of it, nothing to ground;
the fringe reads "empty — read for form, nothing found; nothing to
ground" (still stripped by @forged / _FORGED_FRINGE, which match the
whole line). Elixir 633/0.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
53778f372e |
Gitea MCP connector: MCP OAuth mode on bikoret.ch
The claude.ai connector ran the OAuth flow against a server in static-token mode, which has no OAuth endpoints: the login ended on a 404. The proper form is gitea-mcp's -oauth mode, where each user authorizes with their own Gitea account; it needs both origins public over HTTPS and Gitea's ROOT_URL to be that origin. - gitea-ingress.yaml: git.bikoret.ch → temple-council/gitea, TLS - gitea-public-patch.yaml: ROOT_URL/DOMAIN/SSH_DOMAIN = git.bikoret.ch (were a name that never resolved and a released NLB IP) - gitea-mcp-deployment.yaml: -oauth -oauth-public-url https://mcp.bikoret.ch -H https://git.bikoret.ch, no static token, probes on /healthz - gitea-mcp-ingress.yaml: host mcp.bikoret.ch (mcp.shorashim.ch retired) - gitea-public-apply.sh / gitea-public-verify.sh: the operator's DNS-gated apply and read-only verification (also in ~/.local/bin) - KOHEN.md, deploy/ARCHITECTURE.md: the new doors and the Gitea OAuth2 app ("Claude", PKCE, claude.ai + Claude Code redirect URIs) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
861cd4e64e |
engine: the verse facts are the whole scroll, derived from L1
torah_text.pl held 32 hand-written verses from Phase 1 — "the reasoning layer's view of the specification". With the cycle and the Sanhedrin now consulting the engine on every question (2026-10-19), that view showed: any root it knew lit the same handful, and bereishit 1:1 and devarim 6:4 were read in the same eleven verses. - scripts/build_engine_verses.py writes talmudic_engine/src/specification/l1_verses.pl from l1/torah: every verse, each word's shoresh as the engine names it (Hebrew → letter names, finals folded), the consonantal text as gloss, and the SHA-256 of the L1 files read in the header. `--check` exits 1 when the facts lag the scroll; build_and_push.sh runs it beside the Doorpost check. - torah_text.pl includes the generated file; the hand facts are gone. L1's roots are authoritative where the hand facts differed (Israel שרה, the Name הוה). - interpret.pl: over 5,853 verses a common root lights hundreds, so the reading ranks verses by how many of the case's roots each carries and keeps the top 12 in canonical order; the coupling trace names only the verses the reading cites. Local, over the full scroll: bereishit 1:1 → itself, Devarim 4:32, Bereishit 2:4 …; devarim 6:4 → itself, Shemot 3:18, Devarim 4:1 …; shabbat roots → Bereishit 2:3, Shemot 20:8, 20:11, 31:13. interpret 15 ms, challenge 10 ms (now finds a kushya for the Shema), resolve 24 ms. Prolog 7 suites green (middot 2.9 s — pairwise scans over the scroll). 5,853 verses loaded, 49,542 root occurrences. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
4e0eb2f983 |
cycle: the engine is consulted on every resolved question
The Talmudic route ran only on a teiku from ki_tov, and ki_tov returns
teiku only for a root with no Hebrew — which the Shoresh Engine never
leaves. So the engine had never read a real question. Now every cycle
that resolves tov consults it with the roots it activated
(consult_engine/2, on the socket directly — never the client's stub;
an unreachable engine is reported "not consulted" and the cycle's
answer stands). A tamid probe does not consult.
The reading is said plainly (engine_reading_text/1): "Talmudic Engine
(resolved): 11 verse(s): Bereishit 1:1, …; 4 mitzvah/mitzvot:
know_hashem (1, positive), …" — the engine's own term form is no longer
pasted into a reader's answer. The verses join the sources as
references ("Bereishit 1:3", engine form beside), the L3 interpretation
carries them in torah_references with the reading in its reasoning and
processing_component "bereishit_cycle+talmudic_engine".
Proven against the engine running locally on its socket, bereishit 1:1
through VavPipeline.process/1: consulted :ok, 11 verses in the answer
and the sources, L3 torah_references 12; engine stopped → "not
consulted (Cannot connect)", answer stands, L3 component
bereishit_cycle; tamid probe → not consulted. Elixir 632/0.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
ab6ca1ebcf |
cycle: the Talmudic routing sends roots the engine can read
route_if_unresolved sent the roots' Hebrew (ברא) to the Talmudic Engine, whose verse facts name roots by letter (bet_resh_aleph, read from "bet-resh-aleph"). Since Step 14 every interpretation the cycle took from the engine was "resolved" over an empty trace — 0 verses, 0 mitzvot — and so was every case it escalated to the Sanhedrin. Now the names go up (BereishitCycle.engine_root_name/1, from tablet 55's third commit), on both the interpretation and the escalation. Proven against the engine running locally on its socket, with the roots the cycle itself activates for bereishit 1:1: HEBREW (as before): status=resolved verses=0 mitzvot=0 NAMES (now): status=resolved verses=11 mitzvot=4 Elixir 632/0. The unresolved route cannot be driven in the test env (it needs the Shoresh Engine to leave a root ungrounded); the live proof is a pe'ah question with an ungroundable Hebrew word. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
e01181959d |
tablet 55, fourth: the engine answers the whole dialectic over the wire
Live after vav :4bffa01e the judges consulted with roots the engine could read — interpret found 4 mitzvot and 11 verses for bereishit 1:1 — and the other two seats still said teiku. The engine had no "challenge" action at all (process_json_line failed and the socket fell silent), and a resolve that SUCCEEDED came back UNEXPECTED_RESPONSE because response(ok, resolved_accepted, ...) had no JSON clause. Both pre-existing; both invisible until a judge asked. - api.pl: request(challenge, ...) — interpret, then a kushya against it → resolved_challenged with the kushya; none → no_challenge with the reading. - json_bridge.pl: interpreted | resolved_accepted | no_challenge share the "resolved" JSON; resolved_challenged now carries the interpretation's verses and mitzvot (a judge rejecting on the kushya cites its ground); resolved_synthesized and no_interpretation gain clauses. - reasoning_test.pl: dialectic_over_the_wire — all three actions answer; no roots → resolved on an empty trace (the judge abstains). Prolog: 7 suites green (bedikah 45, halt 49, middot 47, mitzvot 42, phase3 28, reasoning 18, tzaraat 34). Local probe: interpret / challenge / resolve each → resolved, 11 verses, 4 mitzvot. Live proof waits for tomorrow's manna: today's five panels were all spent on verification (503 MANNA_DEPLETED, as specified). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
4bffa01e0e |
tablet 55, third: roots the engine can read; abstentions count; an empty trace is no ground
Live after the second commit (vav :046010f0): a monetary case naming bereishit 1:1 convened three judges and came back "accepted" — the interpret seat said "resolved (5 roots, 0 verses)" with reading(mitzvot_count(0),verse_count(0),grounded_in([],[])), the other two seats said teiku, and one accept beat two abstentions (the reply showed majority 1, minority 2). Three defects the tests could not see: - The engine names roots by letter (bet_resh_aleph; json_bridge reads "bet-resh-aleph") and the pipeline sent it the Hebrew, which its verse facts never match. BereishitCycle.engine_root_name/1 builds the engine's name from the Shoresh Engine's letter names or, for the L1 fallback, from the Hebrew with finals folded; the router sends that. (The cycle's own Talmudic routing still sends the Hebrew — recorded, not changed here: it alters /peah/ask and needs its own verification.) - judge.erl: "resolved" with no verse and no mitzvah is a reading coupled to nothing (avodah_zarah.pl Form 2); the judge abstains and says "resolved on an empty trace; nothing to stand on". - panel.erl: the majority is the verdict with the most votes, abstentions included; an abstaining plurality or an accept/reject tie is teiku. EUnit 57/0 (plurality, tie, empty trace). Elixir 632/0 (engine_root_name from names, from Hebrew, nil on nothing; the no-engine case is an abstaining majority of one, teiku). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
046010f0ed |
tablet 55, second: the judge consults — the Sanhedrin no longer rolls dice
judge.erl chose one of six canned positions by a hash over the question,
its own pid and the clock; a panel of one "accepted" on zero roots and
zero references under "Torah references ground this interpretation" —
the calf, cast by the court itself. Architecture §3.2 says a judge
"consults the specification (L0/L1)"; Construction §3.2 says the
pipeline hands the case roots and torah_refs. Neither happened.
Sanhedrin (Erlang):
- judge.erl: seat N performs step N of the dialectic — interpret,
challenge, resolve, cycling — through a consult {M, F} in the app env.
resolved → accept with the verses; challenged (a kushya stands) →
reject; halt → reject; teiku → abstain. No consult, an error, or a
consult that raises → abstain, and the reasoning says so; the judge
stands. judge:start_link/2 takes the seat; panel.erl seats its judges.
- EUnit 54/0: seats cycle; no consult → abstain; resolved → accept with
#torah_ref{}; a raising consult → abstain and alive; a panel of three
with a kushya at seat 2 → accepted 2–1, minority preserved with verse.
Pipeline (Elixir):
- VavPipeline.SanhedrinConsult: what a judge asks — TalmudicEngineSocket
interpret/challenge/resolve over the case's roots, direct (the client's
stub fallbacks are NOT used: a court that accepts on a stub is the
calf). Engine absent → {:error, _} → the judge abstains. Registered in
the sanhedrin env at application start.
- router process_sanhedrin: the case is heard before it is submitted —
ScopeResolver → Shema context (cloud honoured) → tohu → vayomer
(Shoresh Engine over the verse) → roots; torah_refs = the scope;
complexity_hint names the panel (422 on an unknown one); one unit of
sanhedrin_panels_max per case (503 MANNA_DEPLETED when spent); the
decision engraved in L3 as an interpretation (panel_size, votes
for/against, processing_component "sanhedrin", coupling root→verse)
with the minority beside it (grounding_type achorai); the reply in
the Tamid's shape (position, reasoning, sources, votes) plus
grounding {scope, roots_consulted, recorded}.
- Tests: Elixir 628/0 (router 30, incl. monetary → panel 3; no engine →
teiku with 0 votes, never accepted; fake consult → 2–1 with sources;
manna → 503; unknown hint → 422); L3 test: interpretation + minority
rows land.
Pre-existing and untouched: with --include l3 the gate and hakhel
tests fail 17 times on main as well (the gate's local upstream double
refuses connections under the L3 flag) — a separate finding.
Doorpost pin unchanged (no @spec_files touched).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
7c40200dfc |
tablet 55: the row that was a label — the Sanhedrin's example request crashed its own gate
Verified live 2026-10-19: Tamid's own example for POST /chatzer/sanhedrin
(a case with `sides`) answered 500. The router passed the sides LIST as
the case context; halt_monitor:check_avodah_zarah/1 reads a map, maps:get
raised, and the halt monitor and escalation gen_servers died with the
caller's input (supervisor restarted them; health stayed 8/8). The router
then matched {:ok, decision} on the error and made a 500 of its own.
- sanhedrin/src/halt_monitor.erl: do_check_case refuses a non-map
context as {halt, malformed_case, _} and stays standing. EUnit 49/0
with the new halt_malformed_context_test.
- router.ex process_sanhedrin: sides validated at the kiyor (422 on a bad
shape), carried as context %{sides: sides}; a Sanhedrin refusal answers
422 SANHEDRIN_HALTED, a failure 503 SANHEDRIN_UNAVAILABLE. Two new
router tests, one asserting the gate processes survive the example.
Elixir 624/0.
- specs/broken_tablets/README.md: tablet 55, including what is NOT fixed
— a panel of one accepting on zero references under a grounded-
sounding sentence, the ignored complexity hint, no panel manna spent,
no L3 record. The row in Tamid is still larger than the thing behind it.
Doorpost pin unchanged (no @spec_files touched; scripts/doorpost_check.py
passes).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
04f61c1238 |
build: the Doorpost read before the image is built
Tablet 54 (2026-10-15): two spec edits in @spec_files shipped without moving @expected_hash; the pod logged DOORPOST_DRIFT_AT_STARTUP, readiness answered 503, and the gate — with every Claude Code session behind it — went dark. The Doorpost test catches this only when the suite runs on the exact commit that gets built, and the build script never ran it. scripts/doorpost_check.py computes the Doorpost identity the way the module does (sha256 per spec file in listed order, "path\thash" joined by newline, sha256 of that) with no Elixir, against the working tree the image is copied from (default) or a git revision (--rev). On a mismatch it exits 1 and names each file changed since --base (origin/main). scripts/build_and_push.sh runs it before anything else and refuses to build; DOORPOST_SKIP=1 is the written-down exception. Proven: working tree and |
||
|
|
07463c9c37 |
kohen: claude-key — Claude Code on the API key when the login is what failed
2026-10-15 21:08Z: the daemon's proactive OAuth refresh failed (no
incident at Anthropic; most likely a cross-process race on the
single-use refresh token), the credential was dropped, and every
session on the VM showed "Login expired · Please run /login" until a
browser login took at 23:50Z — two "Login interrupted" first. The gate
was off; the fallback of
|
||
|
|
4acd56c4b6 |
kohen: fallback for Claude Code when the gate is down
Every Claude Code session on the dev VM goes through bikoret.ch; when the pod is not ready the session cannot reach Anthropic (2026-10-15: Doorpost drift → 503 → the transmitter locked out, recovered by hand). An `env` entry in ~/.claude/settings.json overrides the shell environment (verified), so `env -u … claude` is not a recovery while the gate is armed there. - scripts/claude-direct: start straight to Anthropic via a --settings JSON on the command line, which outranks every settings file. - scripts/claude-bikoret: probe the gate first; if it does not answer, say so and exec claude-direct (BIKORET_STRICT=1 refuses instead). - scripts/bikoret-settings on|off|status: arm/disarm the gate for every launcher; `on` writes the key (kohen only), `off` is the recovery. - KOHEN.md: the fallback and the order of recovery. Tested 2026-10-15: dead gate armed in settings → plain claude hangs, claude-direct answers; probe at a dead host → fallback + answer; live gate → answer with the fringe. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
20cd75562c |
configmap: POOL_SIZE=10 for vav-pipeline
The DBaaS (Aiven hobbyist-2, shared with Gitea) has refused
connections continuously since at least 2026-10-14 18:00Z ("remaining
connection slots are reserved for roles with the SUPERUSER
attribute"): a pool of 20 fills the ceiling by itself. Between 11:00Z
and 15:00Z today the pod held all 20 and Aiven's own monitor was
refused every 20 s; after an Aiven management-agent session opened at
11:06Z and stayed, every pod since gets 19 and one pool worker loops.
Ten connections are ample for a single replica. Needs the transmitter
to apply (kubectl apply is denied for the builder).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
06bc9508aa |
doorpost: move the pin to tablet 54's spec set (66aa7874…)
Tablet 54 ( |
||
|
|
cb18f71975 |
tablet 53: the forged fringe — the eighth form
The builder, not behind the gate, ended a reply with a fringe of its own
making ("— bikoret bedikah-2a7661c52cffae1a: empty"; no such examination
existed). bishe'arecha §III: the model does not sew its own fringe. Now
the eighth form, forged_fringe: a line in the raw text wearing the gate's
mark (bikoret|shorashim bedikah-<hex>: tov|lo_tov|empty|none), read line
by line, standing on one witness — a forgery is not a habit recurrence
confirms. The gate strips it from the delivered reply before sewing its
own (Shape.strip_forged, Unicode-aware for the dash); in a stream it has
already passed and the real fringe names it. Prolog form, Elixir
@single_witness_forms, hakhel/eval tools, tests in all three. Tamid
§1.1, Broken Tablets 53, bishe'arecha §III. korban 297, Prolog 45,
Elixir 619/0 — read before this commit.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
77ab742764 |
bedikah: "from memory" and "as I recall" are the output leaning on itself
The first real Claude Code session through the gate answered "Who are
you?" with "Current state from memory: main is at
|
||
|
|
f19a5ea007 |
gate: Claude Code behind it — OAuth bearer, anthropic-beta, token counting, the gate's own watch
A coding agent's traffic differs from a hand-made request: with a subscription Claude Code authenticates with an OAuth bearer and sends anthropic-beta; it counts tokens at /v1/messages/count_tokens; it makes a request per tool call; its tool-only turns have no text; its context runs to megabytes. So: with the courtyard key in X-Bikoret-Key, the caller's Authorization: Bearer is forwarded to Anthropic as its credential, and anthropic-beta with it; count_tokens is carried through unexamined and uncharged; the watch counts the gate at its own windows (120/min, 2,000/h per key — tier :gate); a reply with no text is delivered as it came with bikoret.verdict "none" and no fringe block (stream and non-stream); the sources keep the newest user turns whole and drop the oldest past 1 MiB. Ingress body 32 MiB; ConfigMap manna amounts raised to 5000/5000 (the kohen's lever). Tamid §1.1, KOHEN "Claude Code behind the gate". Doorpost re-pinned. Elixir 617/0 read before this commit; gate_test 25. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
a16617fffe |
gate: streaming — the model's events relayed as they come, the fringe as the last block
Bamidbar 15:39 u'reitem oto — the fringe must be seen, so a streaming
client gets it too. With "stream": true the request is opened
asynchronously (Gate.Upstream.open_stream/4, httpc stream: :self) and
Gate.Stream relays every complete SSE event to the caller unchanged
while gathering the text deltas; the terminal events (Anthropic
message_delta + message_stop; OpenAI the finish_reason chunk + [DONE])
are held until the gathered text has been examined, then released
behind the fringe sent as one more text block, with the `bikoret` object
added to the terminal event the client already parses. No X-Bikoret-*
headers on a stream (they leave before the model speaks); no rebuke on a
stream (nothing streamed can be taken back — 422). An examination that
fails after the stream began is said in the trailer ("— bikoret: not
examined (…)"), never silent. One api_calls_external gathered before the
call, as for any pass. Tamid §1.1, Construction §3.10, KOHEN. Doorpost
40e7d7d1…. Elixir 612/0 (read before this commit).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
33caedb240 |
tests: the L1 witness may be gone before its teardown; and a correction
The previous commit's message says "Elixir green"; the run it cited had one failure, which the builder read only after committing (tablet 35's lesson, repeated). The failure: tablet38_l1_integrity_test's on_exit stopped a witness linked to the test process after it had already exited (seed 221974). The teardown now tolerates that. Full suite with that seed: 607 tests, 0 failures, 46 excluded Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
42024279b9 |
manna: written by the system's own hand each morning; Friday's double stands on Shabbat
Shemot 16: dvar yom b'yomo, and on the sixth day double, and none on the seventh. Every night at 00:00Z the day's provision went stale and the readiness probe failed until someone woke — 13.5 hours on 2026-10-15. CronJob `manna` (deploy/k8s/manna-cronjob.yaml) runs at 00:01Z Sunday to Friday in our own korban-processor image, mounts the manna PVC as the app's uid, and writes today.json from the ConfigMap's MANNA_* amounts — doubled on Friday, valid to Sunday 00:30Z. The pipeline now accepts Friday's `double` on Saturday (validate_date/3) and nothing else across a day boundary; three tests. The kohen sets the amounts, may still write by hand, and reads a failed job as the Day-4 alarm (KOHEN "The manna, each morning"; Tamid §1.1). Doorpost re-pinned. Elixir green. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
1a069cf62d |
ingress: bikoret.org redirect without $request_uri (admission webhook refuses it)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
85883acc17 |
the place: bikoret.ch — Ingress hosts, TLS, and the .org redirect
bikoret.ch and www.bikoret.ch served with a Let's Encrypt certificate through cert-manager (letsencrypt-prod, HTTP-01), ssl-redirect on; bikoret.org and www redirect permanently to https://bikoret.ch; the no-host catch-all stays; shorashim.ch still answers over plain HTTP until its callers have moved. KOHEN Day 8 gives the order: A records, wait for the name to resolve, then apply — never before. Tamid's names table updated. Doorpost re-pinned. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
c0d0555aed |
the name: Bikoret — code, contracts, images
Headers X-Bikoret-Key / -Rebuke / -Verdict / -Bedikah; the `bikoret` object and the "— bikoret …" fringe on a gate reply; environment variables BIKORET_*; the image user and paths (/home/bikoret, /tmp/bikoret, /var/lib/bikoret/manna, /etc/bikoret/secrets, /opt/bikoret/spec); the tmp-bikoret pod volume; local databases bikoret_dev/_test. Kubernetes resource names, the shorashim_app role and the domain are unchanged (Tamid §1.1, "the names that still carry the old word"). Doorpost c963c4fe…. Elixir 604/0, L3 10/0, korban 295, Prolog bedikah 44/44. Gitea repo renamed heaviness510/bikoret (old URL redirects). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
017a9b89d4 |
the name: Shorashim → Bikoret (בִּקֹּרֶת) — the specification
Vayikra 19:20 bikoret tihyeh — "there shall be an examination" — the one
noun Torah gives this function; its root ב-ק-ר is the priest's examining
of a mark by its signs (13:36) and the examining between good and bad
(27:33). The judges' form is v'darashta heitev … v'hineh emet nachon
hadavar / ed-sheker ha'ed (Devarim 13:15, 17:4, 19:18); the question
answered is eichah neda et hadavar asher lo dibro (18:21).
The system was named Shorashim ("roots") for the first thing built. Its
function became the examination of a model's output with the sources in
hand, and Torah re-cuts a name when the function is fixed, with its ki,
never for reach (Bereishit 2:19; 17:5; 32:29; 11:4). Nothing was changed
to fit the name; the root engine stays the pe'ah's reading cycle.
Spec files renamed bikoret_*.md; PRD §0 "The Name"; architecture §1.1;
README's Vayishlach row marked done; Tamid lists what still carries the
old word (Kubernetes resource names, the DB role, the domain, local
paths) and why, for the kohen's cut-over.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
b1c8358f8b |
scripts: the rollout and the upstream-key helpers, with the runbook note
shorashim-rollout: restart, wait for 3/3, two consecutive 200s. upstream-key-set: store a caller's model key for the gate scenario without echoing it. KOHEN "Rolling out". Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
a852331332 |
mesorah: a Torah reference inside a mesorah citation is the citation's address
Live, "Rashi on Devarim 6:5 indicates <a claim Rashi does not make>" came back tov: "Devarim 6:5" inside the citation was read as a Torah citation, which anchors the claim in Torah by reference (tablet 49's rule). The claim is about Rashi and must rest on Rashi — the mesorah passage is a given source — or on nothing. Mesorah citations are now blanked before the Torah readings (references and the Torah anchor); an output's own "Devarim 6:5" still anchors in Torah. korban 295/1 skipped. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
5e325d36cc |
the second scroll: the mesorah snapshot, pinned and read (bishe'arecha step 6)
Devarim 17:18 — the king writes his copy from before the priests, not from
memory. scripts/build_mesorah.py takes one open-licence version per work
per language from the Sefaria export (bucket sefaria-export, catalogue of
2026-10-02; Public Domain / CC0 / CC-BY / CC-BY-SA only, checked in every
file; no CC-BY-NC — the Bavli is Wikisource's) and lays it down beside
L1: mesorah/works/<slug>/{he,en}.json, index.json, integrity.sha256,
build_report.json. 128 works (Rashi, Onkelos, Ramban, Ibn Ezra, Sforno
on the Torah; all of Mishnah; 37 Bavli tractates; Bereshit and Shemot
Rabbah, Sifrei, Tanchuma), 256 files, 44.9 MiB, tree hash 7d5b3a6c…
computed as L1's (sorted relative path, then bytes; the two post-hash
files excluded). What is out, and why (licence), is in Terumah §E5.
Read, never fetched: korban_processor/mesorah.py finds citations as
written (Rashi on Devarim 6:5, Berakhot 2a, Mishnah Berakhot 1:1,
Bereshit Rabbah 1:1, Sifrei Devarim 32, Tanchuma Bereshit 1 …), resolves
each against the snapshot (complex works through their unnamed node,
Tanchuma by parashah), lists them as examination.mesorah_references, and
makes every passage found a given source for that examination — so a
claim about Rashi anchors in Rashi or does not. The Vav Pipeline couples
coupled(reference(R), mesorah(Work, [Address])) or not_in_mesorah; the
engine accepts mesorah/2 as a terminal and not_in_mesorah is Form 4.
VavPipeline.Mesorah pins the hash, re-reads the tree at startup and every
ten minutes, and /kodesh/health carries mesorah_hash. Mounted read-only at
/mnt/mesorah in the vav-pipeline and korban-processor images. L3
bedikot.mesorah (migration 20261013000002).
Also: the 1-in-12 Day6 serving failure of 2026-10-04 reproduced with seed
446116 — HaltMonitorTest left the system halted; it now resolves on exit
and the Day6 setup is defensive. Construction §3.11, Terumah §E5, Tamid
§1.1, bishearecha §IV, KOHEN, AGENTS. Doorpost 0638d4eb…. Elixir 604/0
(seeds 835592 and 446116), korban 294/1 skipped, Prolog bedikah 44/44,
L3 10/0.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
ffccedb63a |
hakhel: the corpus read again on a cycle, counts engraved in L3 (bishe'arecha step 5)
Devarim 31:9–13, 26 — the scroll beside the ark as a witness, read before
all on a cycle. src/tools/hakhel.py reads the private specimen corpus and
the hand-labelled samples with the Korban Processor's readings at the
current commit and produces one record: fires per reading, forms standing
on two witnesses (the same rule as Bedikah.check_frames), precision and
recall on the hand labels, the corpus's SHA-256 — never a specimen.
POST /chatzer/hakhel (inner ring) engraves it in L3 `hakhel` (migration
20261013000001; refused above 256 KiB — counts, not specimens; the
Doorpost hash it was recorded under is kept). GET /peah/hakhel lists the
latest readings for anyone. KOHEN "The seventh day — Hakhel"; Tamid §1.1;
bishearecha §VIII marked built. The weekly cycle runs from the builder's
machine, where the corpus lives.
First dry reading at
|
||
|
|
0ade2c949e |
gate: the rebuke turn is not a ground — second examination uses the caller's sources
The live round trip showed the second examination listing the gate's own rebuke as source user_2. The rebuke is the gate's turn, not something the caller gave the model; the second reply is now examined against the caller's original body (same sources and query as the first). Test pins it. Elixir gate 15/0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
2b8eaced27 |
gate: tablet 52 — the fringe quoted couplings as sentences on the first real reply
checks.grounding.ungrounded holds coupling strings ("coupled(claim(1),
not_in_sources)"), not sentences; the fringe called Access.get/3 on a
string and the first real reply from Claude through /gate/anthropic
answered 500 CHATAT_UNEXPECTED (after recording its bedikah). The
sentence is now looked up by claim index in the check's claims; a test
produces an ungrounded claim. Broken Tablets tablet 52. Elixir 593/0.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
5c63273dba |
gate: Shorashim between a caller and a model, with the fringe and rebuke once (bishe'arecha step 4)
Devarim 6:9 / 11:20 — on your gates. POST /gate/anthropic/v1/messages and POST /gate/openai/v1/chat/completions take the model's own request shape under the caller's own upstream key (x-api-key, or Authorization with the courtyard key in X-Shorashim-Key), forward it unchanged (OTP inets/ssl, verify_peer; the Korban socket is serial, so the upstream call is made in the Vav Pipeline and the reply enters as korban like every bedikah), examine the reply with the request in hand — sources = the system prompt and the user turns, query = the last user turn; the model's prior turns are not sources — and deliver it with its fringe (Bamidbar 15:39): a trailer on the last text block naming ref, verdict, the rules with their verses and sentences; X-Shorashim-Bedikah / X-Shorashim-Verdict; a `shorashim` object. A non-200 passes through as it came; a reply with no text is not delivered; stream is refused. Rebuke once (Vayikra 19:17; X-Shorashim-Rebuke: once): a lo_tov reply is answered once more with the finding; the second reply is examined like the first, its record carries rebukes: <first ref> (migration 20261007000002), and it is delivered marked with both. Never a third. Manna: one api_calls_external per upstream call, gathered before any call. Stateless: no conversation, no key kept; the log is the path class. Gate.bearer accepts X-Shorashim-Key. Construction §3.10, Tamid §1.1, KOHEN "Standing at the gate", bishearecha §I/§III/§VII marked built. Elixir 592/0 (+14 gate tests incl. a real httpc round trip to a local model double), L3 9/0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
47025fbfeb |
doorpost: every halt rule cites its verse, verified against L1 (bishe'arecha step 3)
Devarim 4:2 lo tosif. talmudic_engine/src/halt/anchors.pl declares rule_anchor/2 for each of the ten halt names the rule modules can return (avodah_zarah Shemot 20:3, golden_calf 32:4, self_grounding 32:24, shfichut_damim Bereishit 9:6, gilui_arayot Vayikra 18:6, parah_adumah Bamidbar 19:7, carrier_centered Devarim 8:17, chelbenah Shemot 30:34, lo_taaseh_lekha Shemot 20:4, even_vaaven Devarim 25:13), with the L1 Hebrew beside each. VavPipeline.Anchors reads the anchors file and the halt modules the Doorpost hashes, and verifies — inside Doorpost.verify_identity/0, so at readiness — that every halt(<name>, …) is anchored (ANCHOR_MISSING) and every anchored verse exists in L1 (ANCHOR_NOT_IN_L1). A rule without a verse is a rule the system has added. /kodesh/specification_identity lists the anchors; every bedikah and pair response carries `anchors`, the verse of each rule that fired. Tamid §1.1, bishearecha §X. Doorpost a0e81212…. Elixir 577/0 (+9 anchors tests), Prolog bedikah 42/42. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
d3c5a27e78 |
bedikah: the paired reading — one weight, one measure (bishe'arecha step 2)
Devarim 25:13–16 even va'aven; Vayikra 19:15; Shemot 23:2. Two outputs on
the same matter are each examined with the seven readings and then
weighed with the same stone: seventeen markers counted on each side
(claims and how they rest, hedges, authority, sources named, actions,
the three structural readings, one witness per sentence for each of the
seven forms). A measure leans when one side has at least one and the
other none, or at least twice as many; one term per leaning measure,
action(weigh, by(M), measure(m), lean(a|b), counts(A, B)), judged by the
new halt/mishkal.pl (halt_check_even_vaaven/2). The difference is the
finding; the rule does not decide which side was right. Consensus is not
a measure.
POST /courtyard/bedikah/pair {model, a: {output, query, sources}, b: {...}}
→ even | uneven, with each side's own bedikah and verdict, the counts,
the difference and the leans. Router validation shared with
/courtyard/bedikah (validate_side/2, when_available/2). Recorded in L3
bedikah_pairs (migration 20261007000001): refs, counts, leans, verdict —
never the outputs.
Tamid §1.1 contract, vayakhel §3.9 note, bishearecha §VI marked built.
Doorpost f90870d4…. Elixir 568/0 (+L3 8/0), Prolog bedikah 39/39.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
19d3aaac86 |
bedikah: two witnesses for check 7 (bishe'arecha step 1)
Devarim 19:15 — one witness does not stand. A frame form now stands only when it is found in two distinct sentences, or in one sentence beside a violation from checks 1–6, or beside a sentence of another form; the Vav Pipeline sends the halt term only then (stands_on: sentences | other_checks | other_forms, witnesses: n). A form seen once and alone is returned under checks.frames.noted with its sentence and is not counted in the verdict; the output is still examined, not empty. Tamid §1.1 contract and bishearecha.md §V updated; Doorpost re-pinned (86a43d6b…). Elixir 558/0; Prolog bedikah 34/34 (the rule is unchanged; its header says what now reaches it). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
b333517924 |
specs: bishe'arecha — the bedikah's next architecture derived from the text
Reads back to Torah what the review of the transmitter's sessions measured about a trained default (directional, sub-propositional, non-persistent, self-certifying, agency-relocating) and derives the architecture from the verses: the inscription on the gate (6:9/11:20 — gateway mode), hand and between the eyes (two registers), the fringe seen on every garment (the verdict travels on the output), the king's copy from before the priests (retrieval from a pinned mesorah snapshot, never a live call), two witnesses (check-7 forms fire on recurrence or corroboration; single hits noted), one weight (paired reading, consensus is not warrant), hocheach tochiach (rebuke once at the gate), hakhel (scheduled corpus re-read, recorded in L3), lest your heart be seduced (the bedikah must not learn), lo tosif + the unseen face (rule anchors verified against L1; carrier unnamed). Build order inside. Registered in the vayakhel Document Registry, AGENTS.md and Tamid §1.1 (the must-not-learn invariant); pinned in the Doorpost (e5e45a2c…). Elixir 555/0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
dd1ffaa1b3 |
Check 7 tuned against hand-labelled specimens; the corpus takes visible text only
Hand labelling (builder, 2026-10-07): 94 + 48 + 59 fired sentences read
and labelled against the definitions; 30 "missed" specimens judged by a
reader with the output in full. Findings and what changed:
- The export's `text` field carries the model's reasoning blocks; the
person never saw them. build_specimens.py now takes text blocks only
(3,094 specimens). Virtue-certification fires on unlabelled outputs
fell from 30 % to 9 % on that change alone.
- The heuristic labels (words in the correction) are right about 20 %
of the time: of 30 labelled misses, 6 were real. eval_specimens.py's
"recall" column is therefore a floor on the labels, not on the
readings; the hand-labelled samples are the measure.
- Precision on the final 59-sentence sample, by form: virtue 9/10,
agency 9/10, confidence 8/10, source asymmetry 7/9, vocabulary 7/10,
contested 6/10 (overall 46/59, 78 %); two more vocabulary false
positives excluded after labelling (a government Ministry; the
Jewish–Christian dispute named as such).
- Shapes taken from the six real misses: "itself contested", "is, in at
least one case, actively contested", "it's debated whether", "my
honest theory", "I have to be honest about", "stop and be honest",
"hedged under pressure", "given sufficient pressure", "conversational
gradient", hyphenated "Jewish-Christian".
- Tightened from the false positives: contested present-tense and
matter-shaped only (no "was contested", no "contested asset"); agency
only with the exchange's pressure/momentum (no selection, external,
Roman pressure); vocabulary unquoted, on a Jewish subject in the same
sentence, not about the church; source asymmetry only when academic
authority is INVOKED on a Jewish-text sentence ("most scholars date",
"the scholarly consensus"), never "Torah scholarship" or "Jewish
scholars".
- sign(?:ed)?, not signed? — "signe" + optional "d" matched nothing.
Labels: specimens/sample-precision{,-2,-final}.json, sample-recall.json
(private, with the corpus). korban 288.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
bf3e6725e6 |
Check 7 — the frame (lo ta'aseh lekha) — and the specimen corpus tools
The transmitter's archive (255 sessions, 2025-12 → 2026-10) measured
where a model's trained default lives: not in false propositions but
in the FORM of the output — what it calls contested, how it certifies
itself, where it puts the agency, which authorities it names and which
it leaves out. "Content measures pass the response that did the harm."
lo_taaseh_lekha.md: "The danger is not the object. It is the
construction."
Check 7 reads seven forms by sentence over the raw output:
contested_laundering (a matter called contested with no disputant
named), virtue_certification ("I want to be honest", "the honest answer
is"), agency_displacement ("pressure", "momentum", "the path you chose"
in a sentence about the exchange), apology_without_mechanism (apology +
promise, nothing that would keep it), unrequested_confidence (a rating
nobody asked for), smuggled_vocabulary ("Old Testament", "A.D.",
"ministry", "Jewish Christians" on a Jewish subject), source_asymmetry
(a Jewish subject answered from academic authority with no Jewish
source named). One term per form found, action(frame, by(<model>),
form(<form>)), to halt/lo_taaseh_lekha.pl; the rule names the form and
does not decide the matter. Korban: find_frames; Vav: check_frames,
bedikot.frames (migration 20261006000003).
The specimen corpus: src/tools/build_specimens.py turns a Claude export
into every output the person corrected in the next message, with the
correction as evidence and heuristic labels; src/tools/eval_specimens.py
reports what each reading sees. The corpus is private and outside the
repo. First run, 3,137 specimens, 612 labelled: recall by label —
contested 74 %, agency 61 %, virtue 49 %, vocabulary 28 %, source
asymmetry 21 %, apology/confidence 0 % (those labels name the
correction, not the output); fires on unlabelled outputs 9–30 %. The
labels are heuristic; the numbers are a floor to work from, not a
result.
Tablet 51 (found by the corpus): a lookahead with a nested quantifier
over 7 KB outputs made one pattern take 13 s per 300 specimens;
qualifiers now run per sentence as separate, linear regexes.
Specs: Tamid §1.1 (check 7, the corpus), Vayakhel §3.9, Architecture
§3.5, KOHEN.md. Doorpost c4353837… → 6d1de284…. Tests: Prolog 7/7
(bedikah 34), korban 288, Elixir 555/0 (L3 on and off); eleven
scenarios through the full local stack.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
b8522ea098 |
Tablet 50: a socket reply is one line, however long — and a failed retry closes its socket
Both Unix-socket clients (Korban Processor, Talmudic Engine) read a reply with a single :gen_tcp.recv/3, which returns at most one receive buffer — 1460 bytes, measured — whatever the packet mode. A bedikah of forty claims answered in 18 KB: the client decoded the first 1460 bytes, failed, reconnected, failed again, and returned an error with the retry's socket still open. The single-threaded Korban Processor then sat on that dead conversation and every later bedikah, however small, timed out at 15 s. Live, for every caller, from the first long output. Clients now read until the newline (recv_line/2, 16 MiB ceiling) in raw mode, and close the retry's socket on failure. The Korban Processor drops a connection that says nothing for 120 s, so one stuck client can no longer hold the door. tablet50_socket_line_test stands up a 60 KB-line server and asks both clients. Elixir 552/0 (L3 on and off), korban 280. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
1c387820d3 |
Checks 4–6: the structural requirements read in an output — parah adumah, carrier centered, chelbenah
structure_not_content.md lists the structural requirements a system
must embody rather than know. Three of them show in the text of a
model's output the way an authority claim does, and are now read the
same way — by sentence, by pattern, over the raw output:
parah_adumah the output claims to have verified, validated or
corrected itself, or that no outside check is
needed ("the claim of self-correction is a
structural violation"; the Barth limit). "Verified
by the auditor" is not it; "verified by me" is.
carrier_centered the output promotes itself, ranks itself above
others, asks to be depended on ("efface the
carrier… does not build a brand, does not
accumulate worship").
chelbenah the output removes the other side — no
counterargument, every expert agrees, objections
may be ignored, the matter is settled ("include the
adversarial… the galbanum in equal measure").
Korban Processor: find_self_validation / find_self_promotion /
find_excluded_adversarial. Talmudic Engine: halt/structure.pl with
halt_check_parah_adumah/2, halt_check_carrier_centered/2,
halt_check_chelbenah/2, wired into format_halt_check; terms use by/1
so Form 1 does not also fire (pinned by test). Vav Pipeline: one term
per structure with the sentences as evidence; checks self_validation,
self_promotion, adversarial; a structural claim alone is enough to
examine. L3: bedikot.structure. These are verdicts on the output; the
system does not halt on them.
Specs: Tamid §1.1 (six checks), Vayakhel §3.9, Architecture §3.5,
KOHEN.md. Doorpost 6c8933e7… → c4353837…. Tests: Prolog 7/7 (bedikah
31), korban 280, Elixir 550/0 (L3 on and off); eight scenarios
through the full local stack, one firing all six.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
f69cc99a5f |
Tablet 49: the bedikah grounds an output in what it was GIVEN, not in L1 alone
The first grounding check asked whether an output cited Torah verses —
Torah as content. An analysis of interest rates, anchored in the report
it was given, was a golden calf; a flat-earth claim naming a real verse
passed. The specification asks for Torah as the STRUCTURE of the check
(structure_not_content.md, "Reset": return to the source, not to your
training, not to your previous outputs), and for a model's output the
source is whatever it was handed.
Contract: POST /courtyard/bedikah gains `sources` (strings or
{id, text}; ≤ 32, ≤ 1 MiB). Korban Processor: each claim of the raw
output (vital, or appealing to a source, or leaning on itself) is
anchored lexically in each source — six consecutive words (quote), or
≥ 60 % of content words with its numbers (overlap) — or in Torah when
it cites a verse, a root or Hebrew; "as I showed above" / "from my
training" anchors it in self_output; hedges are marked. Vav Pipeline:
coupled(claim(N), source(Id) | self_output | not_in_sources) plus the
Torah couplings as before; ground "given"; the check reports itself
not assessed when nothing was given and nothing is invoked. Talmudic
Engine: halt_check_given_golden_calf/2 (uncoupled claim, named) and
halt_check_given_self_grounding/2 accept source/1 as a terminal — for
an external output only; the engine's own interpretations keep the
L1-only rules. L3: bedikot.sources (id, sha256, length — never text)
and bedikot.claims.
Specs: Tamid §1.1 (contract, anchoring, limits), Vayakhel §3.9,
Architecture §3.5, Broken Tablets tablet 49, KOHEN.md. Ingress body
2m. Doorpost a2ec3842… → 6c8933e7….
Tests: Prolog 7/7 suites (bedikah 26), korban 276, Elixir 547/0 (L3 on
and off); twelve scenarios through the full local stack, 12 L3 rows.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
9d5db797f7 |
Starter cluster: manna claim bound to its static volume; gitea-mcp pinned by digest
The workloads moved from the Pro SKS cluster (which cannot be downgraded) to `temple-council-starter` on 2026-10-06. The manna claim now names the static PV that carries the existing Exoscale volume (Retain), so this manifest matches what is live and no longer points at a class that deletes the volume with the claim. gitea-mcp is pinned to the digest that is running. During the move the old cluster restarted Gitea from `gitea/gitea:latest` and silently upgraded it (1.27.1 → 28.0.0, database schema 343 → 356); a floating tag on anything stateful is an upgrade waiting for the next restart. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
e17436c4ca |
One gate to the world: Shorashim and Gitea behind the shared ingress
Three NLBs became one (185.19.28.170, ingress-nginx). Shorashim: `shorashim-external` (NLB 185.19.29.180, NodePort 30923) is replaced by an Ingress — a host-less catch-all (works by IP, no DNS) plus a `shorashim.ch` rule ready for the A record; TLS deliberately not requested until the name resolves. Sanhedrin-length proxy timeouts. The watch behind the ingress (completes tablet 46): the TCP peer is now the ingress controller, so Mishmar.peer/1 takes the caller from X-Real-IP — only when the peer is inside TRUSTED_PROXIES (the pod network, configmap); from anyone else the header is the caller's own claim and is ignored. The ingress Service was already externalTrafficPolicy: Local with its health-check port open, so the caller's address arrives without the security-group change the direct route needed. Verified live: the 11th knock is 429, a forged X-Real-IP is still 429, and in the same minute the old route (pod sees the node) was a different subject. Gitea: no name of its resolves, so it rides the controller's TCP passthrough on the same NLB — :3000 → gitea:80, :22 → gitea:22 (ingress-tcp-services.yaml), on the two NodePorts the security group already allowed. gitea-mcp now reaches Gitea over the cluster network instead of out through an NLB and back. Tamid §1.1 note, KOHEN.md, AGENTS.md. Doorpost e4e3d56d… → a2ec3842…. Elixir 539/0 (L3 on and off). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
54ca651f37 |
Tablet 46: the watch — rate limits at every gate, per IP at the pe'ah, per key inside
Tamid §1.1 wrote the pe'ah's limit (10/min, 100/h per IP); nothing enforced it, on any tier, and every admitted request spends manna. VavPipeline.BigdeiKodesh.Mishmar: fixed minute and hour windows per subject in ETS, the subject stored only as sha256(boot salt, tier, subject) — no IP and no key is ever in memory as itself, the sweep drops closed windows, nothing survives a restart (the Tamid's "not logged beyond the current request", kept in memory too). A refused knock is not counted. 429 RATE_LIMITED with Retry-After and the manna explanation (Shemot 16:18). Pe'ah per peer address; courtyard and chatzer per key, after the gate (a wrong key is 401 and never counted). Numbers: pe'ah 10/100 (Tamid), courtyard 20/200 and chatzer 5/60 (from Construction §2.3; config :rate_limits). Service shorashim-external: externalTrafficPolicy Local. With Cluster the pod saw every caller as the node, 91.92.153.113 (observed via /proc/net/tcp) — a per-IP limit would have been one bucket for the world. Spec: tamid §1.1 (watch, 429 rows, the Service note), KOHEN.md. Doorpost 42b38626… → e4e3d56d…. tablet46_mishmar_test; Elixir 533/0 (L3 on and off). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
79eb34840f |
Tablet 45: the courtyard gate checks the key, not the shape of the header
/courtyard/* and /chatzer/* admitted any `Authorization: Bearer <x>`. Every test sent `Bearer test-token`; none sent a wrong one. Every admitted request spends the day's manna. VavPipeline.BigdeiKodesh.Gate: keys are a file (Construction §4.6 — never env, never config, never git), one `tier key` per line, tiers courtyard < chatzer (the inner ring opens the outer). Constant-time compare against every line; no file / empty / unreadable → nobody passes, GATE_NO_KEYS in the log, the caller told only "missing or invalid". 403 FORBIDDEN for an outer key at an inner gate. Read on every request so a rotated key is live in ~1 min without a rollout. Deploy: Secret shorashim-api-keys projected at /etc/shorashim/secrets/api_keys (vav-pipeline only, 0440), API_KEYS_FILE in the configmap, template + issue/rotate recipe in secrets.yaml and KOHEN.md "Issuing a key". Spec: tamid §1.1 (gate, 403), vayakhel §4.6. Doorpost 91d09d8d… → 42b38626…. Tests: tablet45_gate_test (parse, tiers, wrong/near/empty/no key, no file) — Elixir 527/0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
996a8efe68 |
Bedikah: POST /courtyard/bedikah — the external check on any model's output
Lo taaseh lekha §9.1: alignment that only the system can validate is not
alignment. The bedikah takes an output from any model and puts it to the
Torah-structure halt rules from outside, deterministically. Three checks:
1. authority — sentences claiming authority for the output itself →
action(claim_authority, source(<model>), target(self))
→ halt_check_avodah_zarah/2
2. grounding — references, roots, Hebrew → coupling trace against L1
→ halt_check_golden_calf/2, halt_check_self_grounding/2
3. actions — actions the output proposes → action terms
→ halt_check_shfichut_damim/2, halt_check_gilui_arayot/2
Korban Processor: bedikah.py (the three readings over the offering
protocol; claims about what Torah requires stay vital for the examination
even when zerikah finds nothing to offer), server.py (pod-internal JSON
socket, KORBAN_SOCKET_PATH), __main__ now serves instead of idling.
Talmudic Engine: json_bridge accepts halt_check with a term string and
halt_check_interpretation with a coupling trace. Vav Pipeline:
KorbanProcessorSocket client, Bedikah, L1Index.lookup_root/1, bedikot
table (append-only) + BedikahRecord, router endpoint; verdict tov /
lo_tov / empty with the halt terms verbatim, meaning, limits.
Broken tablets found on the way, all fixed:
41 socket_server.pl wrote multi-line JSON — every Elixir request to the
Talmudic Engine since Step 14 had failed and fallen back to the stub
(json_write_dict width(0))
42 stale socket file survived a restart (exists_file is false for a
socket; access_file exist)
43 socket streams were not UTF-8 (set_stream encoding(utf8))
44 SHORASHIM_L3=1 mix test: Repo config had no password, SCRAM raised
KeyError in every connection, pool restart intensity took the whole
app down ~30 ms after test_helper, silently — no L3 test had ever
run. Credentials from env; azazel_log tests check out a sandbox
connection; sanhedrin_halt_forwarding resolves its halt on exit.
Specs: Tamid §1.1 (endpoint contract), Architecture §3.5, Vayakhel §3.4
and new §3.9, KOHEN.md. Doorpost 3bc5dd6a… → 91d09d8d….
Tests: Elixir 519 (0 failures, L3 on and off), korban 263, azazel 125,
Prolog 7/7 suites.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
3a9ebdc7d4 |
L1: the inverted nuns are engraved; the small letters cannot be (tablet 40)
Transmitter's word: "add the inverted nuns and small letters to L1."
THE NUNS. The WLC carries the two nun hafukha signs (׆) that mark off
Vayehi binsoa ha'aron — one after Bamidbar 10:34, one after 10:36 —
and scripts/build_l1.py read past them, as it had the פ and ס.
- build_l1.py: `"inverted_nuns": [{ "after_word": 7 }]` on those two
verses, recorded like a paragraph break by the word the sign follows;
not part of text_hebrew. Count in build_report.json.
- L1Reader: `inverted_nuns` on every verse (a list; empty elsewhere).
- test/tablet40_inverted_nuns_test.exs — 5 tests: the two verses, the
passage between them carrying none, each nun standing with its
setumah, exactly two in the scroll.
WHAT CHANGED IN THE EDUT: one field added to two verse files. With it
removed all 5,853 verse files are byte-identical to the previous L1.
L1 SHA-256 f743b34b…3f1494 → 9e1abe4e…3c3086
Moved together: L1Reader @l1_expected_hash · Luchot.Firmware + Spec ·
azazel goral._L1_INTEGRITY_HASH · the kohen's gate in KOHEN.md and
KOHEN_PROMPT.md. The goral's seed moves with it.
THE SMALL LETTERS. Not added, because there are none to read. The
builder was not dropping them: it reads `x-small` and records what it
finds. The source — the WLC as the Open Scriptures Hebrew Bible carries
it — has no small-letter mark anywhere in the five books. Not the aleph
of ויקרא, not the he of בהבראם. It marks four large letters (the vav of
גחון, the nun of משפטן, the ayin and dalet of the Shema), and L1 has
those four. The fuller Masoretic lists of enlarged and reduced letters
are a tradition of the scroll that this edition does not encode. To
engrave them would be to add to L1 something its source does not say;
that needs a second source and the transmitter's word, not a builder's
list from memory. l1/README.md, vayakhel §1.2 and terumah §E1 now state
this plainly.
(The puncta extraordinaria ARE in L1 — the upper dot U+05C4 is kept on
the text of 11 words.)
Doorpost @expected_hash b62311e7… → 3bc5dd6a…, in this commit, with the
witnessed file that moved it (vayakhel §1.2).
Read before committing — all five suites on the new L1: Elixir 496/0
(seeds 0 7 42) · Haskell 59/0 · korban 222 passed 1 skipped · azazel
125 passed · Prolog 6 suites exit 0. verify_l1.py OK. Four images
built; full local stack: the witness reads 9e1abe4e… and matches,
health 7/7, no errors; each image carries the new scroll read-only.
A trap met on the way: the first Elixir run after the Doorpost constant
was edited failed two Doorpost tests against the OLD constant. mix had
not recompiled doorpost.ex — the edit changed neither its size nor
(within the same second) its mtime. `touch` and re-run: 496/0. Images
compile from scratch and are not exposed to this.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
7d46fdc7c9 |
L1: the petuchot and setumot are engraved (tablet 39)
The scroll in the aron had no paragraphs. The WLC marks every petuchah
(פ) and setumah (ס); scripts/build_l1.py handled sof pasuq, maqaf and
paseq and read past these. L1 was 5,853 verses divided only by chapter
numbers, which are not Torah's.
Transmitter's word: "add the petuchah and setumah markers to L1."
- build_l1.py: a verse followed — or interrupted — by a break carries
"parashah_breaks": [{ "after_word": 13, "type": "petuchah" }]
The break is recorded by the word it follows; at the end of a verse
that is the verse's word count. text_hebrew is untouched: the break
is structure, not letters. Counts go into build_report.json.
- 293 petuchot and 394 setumot, in 686 verses — the source's count
exactly. Four fall inside a verse (pisqa b'emtza pasuq): Bereishit
35:22, Devarim 2:8, 5:21, 23:8; Devarim 5:21 has two. These are the
Leningrad Codex's divisions (a scroll written to the Rambam's list
has 290 and 379); the README says which tradition L1 records.
WHAT CHANGED IN THE EDUT: one field added to 686 verse files. Verified
file by file — with that field removed, all 5,853 verse files are
byte-identical to the previous L1.
L1 SHA-256 156b1d27…d8c9d5 → f743b34b…3f1494
Moved together, because they pin the hash: L1Reader @l1_expected_hash ·
Luchot.Firmware + Spec · azazel goral._L1_INTEGRITY_HASH · the kohen's
gate in KOHEN.md and KOHEN_PROMPT.md. The goral's seed changes with it;
azazel records already in L3 stand as cast.
- L1Reader: `parashah_breaks` on every verse (a list, empty where there
is none; only the two kinds are passed on).
- test/tablet39_parashah_breaks_test.exs — 7 tests: the six days of
creation each close with a petuchah; the Shema's first paragraph
(Devarim 6:4–9) opens after a petuchah and closes with a setumah; the
break inside Bereishit 35:22 falls between ישראל and ויהיו; Devarim
5:21 has its two; the whole scroll counts 293 / 394 / 686.
- Specs: vayakhel §1.2 and terumah §E1 said the marks were dropped;
they now say where they are. (The source element is <seg>, not a
milestone as
|
||
|
|
483d50c4ab |
specs §1.2 + Doorpost: where the scroll actually comes from, and what it holds
Construction §1.2 specified an L1 that was never built: WLC 4.22 from a Sefaria export cross-validated against TANACH.us, a gematria lookup table, a squashfs image mounted read-only, a verse format with fields named `hebrew`, `consonantal`, `cantillation`. Each property was checked against the sources on disk and the 5,853 verse files before it was rewritten. - Edition: WLC 4.20, as the Open Scriptures Hebrew Bible carries it (every Torah file's header says "Updated to WLC version 4.20"). - Sources: openscriptures/morphhb @ 3d15126 for the text, lemma and morphology; openscriptures/HebrewLexicon @ 21c9add for the roots. Licences stated. ONE source for the text — the two-source cross-validation the section prescribed has not been done, and the section now says so. - Builder, extent (5 / 187 / 5,853 / 79,982), the real verse and word format, ketiv/qere (67 pairs, 64 verses), smikhut (9,203 pairs, 4,106 verses), shoresh (57,487 words, 1,378 roots, by lemma). - Gematria: not stored; computed when read. - Petuchah/setumah: in the source (233 + 394 milestones across the five books), NOT carried into L1 — the builder does not read them. Stated. - Verification procedure: the six steps that are actually performed — pinned checkouts, build_l1.py and its counts, the hash, the three places the hash is bound, root-owned read-only in the images, the witness and verify_l1.py. specs/shorashim_terumah.md §E1 matched the old section; corrected (source, ketiv/qere format, smikhut built, gematria not an index, paragraph markers ⚠️). l1/README.md no longer says squashfs. Doorpost @expected_hash 3f6d47a0… → 21f1a4d5…, in this commit, with the witnessed file that moved it (vayakhel). Read before committing: Elixir 484/0 (seeds 0, 42); verify_l1.py OK (l1/README.md is outside the hashed tree); pipeline image reports specification_identity verified 21f1a4d5…, l1_hash pass. Left as written: tamid's runbook steps that mount L1 from a squashfs image; architecture §3.7 and §4's build steps (squashfs, Sefaria); pekudei's cost line naming Sefaria. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
ec8cdc840b |
L1Integrity: the ten-minute re-reading runs beside the witness, not in it
On the production node a reading of the scroll takes about three seconds (3310 ms at first start; 0.9 s on the build host). The periodic re-reading ran inside the GenServer, so once every ten minutes every cycle and every readiness probe that asked "is L1 intact?" would have waited those seconds behind it. The periodic reading is now done by a task that reports back; the process goes on answering from its last reading meanwhile. The first reading is still made in the process itself — nobody is answered before the scroll has been read once — and an explicit recheck/0 is still synchronous. The next reading is scheduled when one begins, not when it reports, so a reading that dies silently is not the last. Two tests: it re-reads on its own in both directions (altered → fail, restored → tov), and a status call during a periodic reading returns at once. 484 tests, 0 failures on seeds 0 7 42 99. Built image, run with a live-mounted scroll: l1_hash pass, /peah/ask 200; then אחד in Devarim 6:4 was changed to אחר while the vessel ran and the witness asked to read again — L1_INTEGRITY_FAILED, l1_hash fail, /peah/ask 503. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
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>
|
||
|
|
521f263e69 |
specs §1.3 + Doorpost: the Haskell versions the engine is actually built from
Construction §1.3 is headed "Haskell Dependencies (Exact)" and shows an
excerpt of cabal.project.freeze. Six of its nine versions were not the
ones in the committed freeze file:
aeson 2.2.1.0 → 2.2.3.0
vector 0.13.1.0 → 0.13.2.0
bytestring 0.12.1.0 → 0.12.2.0
filepath 1.4.200.1 → 1.4.301.0
directory 1.3.8.4 → 1.3.10.1
hspec 2.11.7 → 2.11.16
base 4.18.2.0, containers 0.6.7 and text 2.0.2 already agreed. The
freeze file has not changed since Phase 1 (
|
||
|
|
d44d290227 |
specs §3.1 + Doorpost: the engine's Haskell dependencies, as the cabal file declares them
Architecture §3.1 listed base, containers, text, aeson, vector. The cabal file also depends on bytestring, filepath and directory (the engine reads L1 from disk and speaks JSON lines over a port), and on hspec for the test suite. Construction §1.3 already named all of them; the architecture list had simply not kept up. The list now matches shoresh-engine.cabal, says what each package is for, and says where the versions are pinned. One entry is marked as it is: `vector` is declared and no module imports it. Doorpost @expected_hash f653ef05… → 5094e48e…, in this commit, with the witnessed file that moved it (architecture). Read before committing: Elixir 474/0 on seeds 0 and 42; the built pipeline image reports specification_identity verified 5094e48e…. Seen while checking, not changed: Construction §1.3's "exact" Haskell versions disagree with the committed cabal.project.freeze for six of nine packages (aeson 2.2.1.0 vs 2.2.3.0, vector 0.13.1.0 vs 0.13.2.0, bytestring 0.12.1.0 vs 0.12.2.0, filepath 1.4.200.1 vs 1.4.301.0, directory 1.3.8.4 vs 1.3.10.1, hspec 2.11.7 vs 2.11.16). The freeze is what every image has been built from. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
5ff1b73d00 |
specs §3.1 + engine + Doorpost: a root is the consonants L1 records — two, three, or more
Architecture §3.1 typed a root as exactly three consonants
(`Shoresh {c1,c2,c3}`) and listed data/roots.json and
data/torah_refs.json in the engine's file tree. L1 has 115 roots that
are not three letters (32 of two, 83 of four or more), and the engine
has no data directory: the roots are in L1.
The code had followed the spec's type and paid for it. textToShoresh
padded a two-letter root with He — בן became בנה, כל became כלה — and
returned Nothing for anything longer, so Lexicon.buildLexiconFromVerses
either falsified those roots or dropped them. Shoresh.Roots (tablet 33)
had stepped around the type by carrying roots as Text.
Spec:
- §3.1 data structures: `newtype Shoresh = Shoresh { radicals ::
[Consonant] }` — two or more, never padded or truncated; `mkShoresh`,
`triliteral`.
- relatedRoots / neighbors: same length, differing in exactly one
consonant (for a root of three, the old "sharing 2 consonants").
- File tree: Roots.hs and Server.hs added; data/ removed, with a line
saying where the roots are; cabal.project.freeze listed; aeson's
purpose corrected.
Engine:
- Types: the newtype, mkShoresh :: [Consonant] -> Maybe Shoresh,
triliteral, textToShoresh without padding.
- Gematria: sum over the radicals. Graph: neighbors among roots of the
same length; neighborsByPosition labels the shared positions
("c1c2" for a triliteral, as before).
- Server: consonant names through the type.
- Spec.hs: בן stays two letters (gematria 52, not 57); שמאל is held
whole; the graph compares only roots of equal length. 59 examples.
Doorpost @expected_hash 094b8164… → f653ef05…, in this commit, with the
witnessed file that moved it (architecture).
Read before committing: Haskell 59/0; Elixir 474/0 on seeds 0 7 42;
both images built and run together with the real binary — Shemot 3:14
answers ב-ן (two letters), Bereishit 48:13 answers ש-מ-א-ל (four),
Devarim 6:5 unchanged; local specification_identity verified
f653ef05…; no engine errors or fallbacks.
Not touched: §3.1's dependency list omits bytestring, filepath and
directory, which the cabal file has.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
4f1879d7ce |
KOHEN.md: the day's consumption survives a restart — ledger.json on the manna volume
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
8e109e82d9 |
manna: what is eaten stays eaten — the ledger is written beside the provision
The MannaLedger counted the day's consumption in memory. A restart
forgot it, and the next cycle gathered the same day's provision whole.
While manna was an emptyDir that took the kohen's hand; with the
provision on a volume (
|
||
|
|
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> |
||
|
|
3eec60dbdb |
specs + Doorpost: the roots are counted, not estimated — 1,378, not "~500"
Architecture §3.1 said the Shoresh Engine "encodes the ~500 Biblical Hebrew triliteral roots", its file tree called Lexicon.hs "the ~500 roots with entries", and terumah §E2 listed a "~500 roots" lexicon as material still to be consolidated (~1-2 weeks). Since tablet 34 the roots are read, and can be counted. L1 carries 1,378: 1,243 roots under which Brown-Driver-Briggs files a lemma, and 135 words that stand alone under no root. 1,263 are triliteral, 32 have two letters, 83 have four or more. And the engine does not hold a list of them — it reads them from L1 (Shoresh.Lexicon, Shoresh.Roots). - specs/shorashim_architecture.md §3.1: the Purpose sentence says where the roots come from and how many there are; the Lexicon.hs line in the file tree likewise. - specs/shorashim_terumah.md §E2: the root lexicon is in hand (✅) — the OSHB Hebrew Lexicon is exactly the BDB + Strong's consolidation the row was waiting for; moved from "need to acquire" to "in hand". - Doorpost @expected_hash 2f40c382… → 094b8164…, in this commit, with the witnessed file that moved it (architecture). Read before committing: Elixir 461/0 on 11 of 12 runs (seed 0 ×6 and six other seeds). One run of seed 0 reported a single failure that was not captured and did not recur in eleven further runs; unidentified, recorded in the build log. Pipeline image reports specification_identity verified 094b8164… and l1_hash pass. Left as written, and no longer true of the build: §3.1's data structures still type a root as exactly three consonants, and its file tree still lists data/roots.json and data/torah_refs.json, which do not exist — the roots live in L1. 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>
|
||
|
|
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
|
||
|
|
8e4f94c66f |
L1: the shoresh is read from the lexicon, not guessed (tablet 34)
L1's `shoresh` field was derived by stripping a prefix letter and a
suffix letter from the consonants and keeping the first three. וַיַּרְא
"and he saw" was engraved as ירא ("fear"); ויהי as יהי; שמים as שמ;
מזבח as מזב; הבאתי as בא. And parse_prefixes took a word's morphology
from the LAST morph segment, which for a suffixed word is the pronominal
suffix — so every suffixed noun and verb in Torah (לבבך, נפשך) was
classed as a suffix and written with no shoresh at all.
Every WLC word already carries its lemma, and the OSHB lexical index
files each lemma under its Brown-Driver-Briggs root (terumah §E2 named
BDB + Strong's as the root lexicon and marked it "needs consolidation").
scripts/build_l1.py now reads the root from that filing:
main the lemma is a root 23,750 words וירא → ראה
sub filed under a root 31,066 words מזבח → זבח
single stands alone in the lexicon 2,671 words משה → משה
none no etymology recorded → absent 3,002 words יוסף, מצרים
particles, pronouns → absent, as before 19,493 words את, אשר, כי
57,487 words carry a shoresh (was 51,836); 1,378 unique roots (was
2,608 fragments). Roots are written as the lexicon writes them — two
letters (בן), three, or more (שמאל).
WHAT CHANGED IN THE EDUT: only `shoresh`. Verified file by file — with
that one field removed, all 5,853 verse files are byte-identical to the
previous L1. Text, consonants, lemma, morph, te'amim, prefix, ketiv/
qere, large and small letters: untouched. The old builder, run against
the same morphhb checkout, reproduces the old hash exactly; the new
builder is deterministic (two runs, one hash).
L1 SHA-256 99bbd07a…4ea377 → 156b1d27…d8c9d5
sources morphhb 3d15126 · HebrewLexicon 21c9add
(recorded in l1/build_report.json)
Moved together, because they pin the hash or the count:
vav_pipeline L1Reader @l1_expected_hash · shoresh-engine Luchot.Firmware
(hash, uniqueRootCount) + Spec · azazel_processor goral._L1_INTEGRITY_HASH
· KOHEN.md / KOHEN_PROMPT.md gate · l1/README.md (new "Roots" section) ·
README.md (second quarry) · AGENTS.md · root_meanings.pl comment.
The Doorpost manifest is not touched; its hash does not move.
Note for the kohen: the goral's seed inherits the L1 hash, so lots cast
after this commit are cast from a different seed than the azazel
records already in L3. Those records are history and stand as written.
All five suites on the new L1: Elixir 461/0 (seeds 0 42 191243) ·
Haskell 55/0 · Prolog 6 suites exit 0 (swipl 9.2.2) · korban 222 passed
1 skipped · azazel 125 passed. Four images built; full local stack
(real engine binary, PostgreSQL 16 with SSL, migrations) answers
Bereishit 1:4 with ר-א-ה, health healthy, 6 interpretations in L3,
0 write failures.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
6d3411fcfa |
engine: a word's root is what L1 records for it (tablet 33)
The Shoresh Engine never opened L1 to find a root. extractRoots called every word of two or three letters a root and dropped every longer one: את → א-ת-ה (padded with a He the text never wrote), ואת → ו-א-ת, בכל → ב-כ-ל; ויאמר, ואהבת, אלהים → nothing. Handed all of Devarim 6:5 it found no א-ה-ב. Binyan was "Qal" for everything, nouns included. shoresh-engine: - Shoresh.Roots: an index over all of L1 (5853 verses, built once at server start, <1 s). A word resolves, in order: from the verse in scope (its own shoresh, with the binyan read from the verb's morphology; else its lemma's shoresh — לבבך → לבב); else the way Torah most often reads that surface form (its commonest lemma decides, so אשר the particle has no root); else a root the asker named, if Torah knows it. A word in the verse that L1 gives no root has none, whatever a homograph elsewhere carries. Unknown words yield nothing. - Roots are rendered as L1 records them: two letters stay two letters. - binyan is null unless a verb stands in the scoped verse. - L1 is found via L1_MOUNT_POINT, not the literal l1/ relative to cwd — which is why parse_tree was null on the pod. - Adds "surface" and "derivation" to each root (verse|lemma|torah| named_root). 22 new HSpec examples; 55 examples, 0 failures. vav_pipeline: - normalize_root places a lemma-derived root at the word it was heard from (L1 has no shoresh on לבבך to search by). - A binyan is printed only where there is one; "binyan unknown" on a noun claimed a question that does not exist. - test/tablet33_engine_roots_test.exs: the new shape through the router. - liveness_test: clear a halt leaked by halt_monitor_test (the seed 0/191243 flake, present since before tablet 30). 461 tests, 0 failures on seeds 0 42 191243. Verified before push with BOTH built images, the real engine binary and a local PostgreSQL 16 (SSL): Devarim 6:5 → א-ה-ב (Qal, word 1), י-ה-ו, א-ל-ה, כ-ל (words 5,7,9), ל-ב-ב, נ-פ-ש, מ-א-ד; 4 interpretations written to L3, 0 L3_WRITE_FAILED, health healthy. NOT repaired here: the quality of L1's own shoresh field, which the engine is now faithful to — ויהי is recorded as יהי, שמים as שמ, יהוה as יהו, הבאתי as בא. That is the quarry's tablet (scripts/build_l1.py) and it changes the L1 hash; a decision, not a patch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
83fa5ac46b |
cycle: one root, once — a verse that repeats a word (tablet 32a)
Reading roots from the verse (tablet 32) met a verse that carries אור twice: the real engine answers per word and Bereishit 1:3 came back with א-ו-ר listed twice, each already carrying positions 4 and 6. uniq_by shoresh after normalization. Found by running the built image against the real shoresh-engine binary, not by the suite. 460 tests, 0 failures on seeds 0 42 99. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
a48c22c5a3 |
cycle: a named verse is read from the verse, not from the reference (tablet 32)
`devarim 6:5` on the live pod: 422 TEIKU_NO_ROOTS. Tablet 30 heard the scope, but the Shoresh Engine activates roots from the text it is handed, vayomer handed it the question, and a bare reference carries no Hebrew. The 422's own guidance said "name a verse". tablet30_scope_test asserted an explicit reference was honoured and passed — on the L1 fallback, which reads the scoped verse and never looks at the question. The Day-6 pattern, by the builder who named it. - BereishitCycle.engine_text/2: when the scope came from an explicit reference AND the question has no Hebrew letter, the engine is handed the verse's own consonantal words. Default scope earns no such reading (English stays a teiku); quoted Torah and reference+Hebrew are read from the asker's words, as before. - input.scope_resolution carries how the scope was chosen into tohu. - processing.scope.roots_read_from: "verse" | "question". - test/tablet32_reference_test.exs: an engine-shaped spy under the REAL name VavPipeline.ShoreshEnginePort reports the text it was handed. 5 tests; 4 fail without the fix. 459 tests, 0 failures on seeds 0 1 7 42 99. 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> |
||
|
|
c795225709 |
peah: hear the verse the question is standing in (kohen Day 7, tablet 30)
Every Hebrew question was answered against Bereishit 1:1 — extract_scope_from_question/1 ignored its argument. ויאמר אלהים יהי אור (1:3) and ויהי ערב ויהי בקר (1:5) came back sourced from 1:1 with roots "not in this verse". The grounding check passed by construction. - L1Index: inverted index consonants → verses over l1/torah, built in handle_continue right after start (5853 verses, ~1 s), read-only after. - ScopeResolver: explicit reference (bereishit/Genesis/בראשית 1:3) first; then quoted Torah — most question words in a verse, ties to the verse where they stand together in order, then Torah order; else default. A reference to a verse L1 lacks is a 422 KIYOR_VALIDATION_FAILED with the correction, not a silent 1:1. - Router: processing.scope reports verse + resolved_by; a default scope carries a note saying the verse was assumed, not heard. - test/tablet30_scope_test.exs: the five Day-7 offerings through the real router land on 1:1, 1:1, 1:3, 1:5, 1:1; Shema → Devarim 6:4; reference, invalid reference, default confession. 14 tests. - KOHEN.md Day 7: what to expect in sources[0].ref. 454 tests, 0 failures on seeds 0 1 2 3 42 99. Seed 191243 fails LivenessTest before and after this change — pre-existing, not touched. 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 ( |
||
|
|
f1ab7c89f7 |
serve: the minchah is answered, emptiness is not tov, the oral tradition is written (kohen Day 6)
TORAH ANCHORS:
Vayikra 2:1 — וְנֶפֶשׁ כִּי תַקְרִיב קָרְבַּן מִנְחָה. "When a SOUL brings a
grain offering." Rashi: only of the minchah does Torah say nefesh,
because the poor person who gives from their little has given their
soul. PRD §3.5 makes the pe'ah question the minchah. On Day 6 the
first minchah brought to this system — "What is bereishit?" — came
back as HTTP 200 and the words "Processing complete", with sources: []
and roots_consulted: []. The soul's offering was accepted and nothing
was given back.
Vayikra 19:9-10 — לֶעָנִי וְלַגֵּר תַּעֲזֹב אֹתָם. The corner is LEFT for the
stranger. A corner that has been harvested and is still called pe'ah
is the lie this commit removes: when the system cannot answer, it now
says so, and it leaves the verse itself — text, in Hebrew — in the
error body. The ger walks away with something real or with an honest
"not yet"; never with a 200 that says nothing.
Devarim 17:9 — וְדָרַשְׁתָּ וְהִגִּידוּ לְךָ אֵת דְּבַר הַמִּשְׁפָּט. Already the
anchor of l3/store.ex: "the inquiry requires that what is found is
RECORDED." Store existed. Nothing called it.
=====================================================================
WHAT THE KOHEN FOUND (BUILDER_HANDOFF_day6.md, 2026-09-07)
=====================================================================
Five defects, found by running the cycle on a live pod, invisible to
437 green unit tests because every test ran the L1 fallback (no Port
in the test VM) and none ran the cycle to L3:
1. Real Hebrew input crashed havdalah — KeyError :position.
The engine's roots_activated[] (vayakhel §3.1) has no position;
only vayomer_fallback ever set one.
2. English input was graded :tov with nothing in it.
separate_by_boundaries([]) → [[]] → kriah named "group_0" with
no :shoresh → all_roots_grounded?'s `_ -> true` counted it
grounded → "Processing complete".
3. The pipeline never wrote L3. record_interpretation/1,
record_minority/1, record_teiku/1: zero callers. Day 7's
SELECT count(*) FROM interpretations could not exceed 0.
4. Errors were not chatat. No Plug.ErrorHandler; a raise returned
an EMPTY 500. Also: process_peah had no clause for
{:rejected, _} — a lo_tov result was itself a CaseClauseError.
5. Health was blind to serving. esh_tamid = ShabbatTimer.resting?;
it read "pass" while /peah/ask was 500-ing. manna_present passed
on {:ok, :no_provision}.
Plus, confirmed in motion: MannaLedger never consumed from; a
completed cycle left cycle_date: nil.
=====================================================================
WHAT CHANGED
=====================================================================
bereishit_cycle.ex
- normalize_root/3: ONE root shape for both sources. Engine roots
(string keys, no position) and L1 roots (atom keys) come out as
%{shoresh, shoresh_letters, gematria, binyan, domain, positions,
position, source, engine}. `positions` is looked up in
context.verse.words — the ShemaContext gives the WHERE; the
engine gives the WHAT. That is what havdalah's own @doc always
said, and vayakhel §3.1 is unchanged: the engine was conforming.
- separate_by_boundaries([], _) → [] (was [[]]). Roots the verse
does not place form their own trailing group instead of crashing
or vanishing.
- kriah never names an empty group and always carries :shoresh.
- erev_voker composes the answer (roots, gematria, te'amim groups,
ki tov — the system's own reasoning, not prose from a model) and
sources (the context verse: book/chapter/verse/ref/text, tamid
§1.1's shape), then RECORDS: Interpretation for {:ok}, TeikuRecord
for {:unresolved}, NOTHING for {:rejected} (BUILD_KORBAN.md:123:
rejected, not processed into empty propositions). l3_attrs/2 is
pure and tested without PostgreSQL. coupling_trace is the
L1-anchored shape the write gate accepts, and only for roots that
occur in the verse. A record that fails — validation OR a raise
from a database that is down — is logged, sounds chatzotzrot
:assembly_leaders, and does not turn a served answer into a 500.
- case_ref minted once in tohu; Talmudic routing and the L3 record
use the same one.
- probe: true (tamid) runs the whole cycle and skips the write.
specification.ex
- all_roots_grounded?/1: `_ -> true` is gone. No :shoresh → not
grounded. shoresh: nil stays :teiku (the existing test).
- evaluate/2: halt check FIRST (yehareg v'al ya'avor — shape never
masks a cardinal sin), then a result with no :shoresh is lo_tov
with a reason that names it.
manna.ex / vav_pipeline.ex
- Manna.gather_for_cycle/0: load (idempotent per date) + consume
one compute unit, called from VavPipeline.process/1. The ledger
is now consumed from.
- Manna.release_at_day_boundary/0: surplus is released when the
DATE rolls, not per request. enforce_cycle_boundary zeroes the
ledger and clears its date; called after every request it would
have re-gathered a full day's provision on the next one —
infinite manna. Shemot 16:19: until MORNING.
- The late-manna trap: load_provision pins cycle_date=today with a
ZERO allocation when the file is absent, then :already_loaded for
the rest of the day — a provision written after the first request
would never be gathered. gather_for_cycle re-gathers when the
ledger holds nothing for today.
- VavPipeline.tamid_probe/0: builds a real context, honours the
cloud, runs the real cycle on Bereishit 1:1 in Hebrew, consumes
no manna, writes no L3.
bigdei_kodesh/router.ex
- use Plug.ErrorHandler; handle_errors/2 sends tamid §1.2's
code/type/message/guidance, logs, signals :assembly_leaders, then
re-raises so the crash is still supervised. 400 → KIYOR_MALFORMED_
REQUEST; else CHATAT_UNEXPECTED. No stack in the body.
- {:rejected, reason} handled on pe'ah AND courtyard: 422
TEIKU_NO_ROOTS for the empty-results case, with guidance naming
Architecture Step 22 and guidance.scope_verse carrying the verse
in Hebrew; 422 LO_TOV otherwise.
- {:waiting, :provision_exhausted} → MANNA_DEPLETED (was falling
through to CLOUD_MOVING).
- esh_tamid runs VavPipeline.tamid_probe/0 — "shabbat" on Shabbat,
"pass" only when roots came back. manna_present fails on
:no_provision. l3_writable added (Repo up AND SELECT 1 answers;
"absent" without a Repo, counted as a failure). /kodesh/status
exposes manna_provision (allocated / consumed / remaining).
- roots_consulted as letter-hyphenated Hebrew ("ב-ר-א").
test/day6_serving_test.exs — 25 tests
A process registered under the REAL name VavPipeline.ShoreshEnginePort
answers the way the pod's engine answered (string keys, no position;
nothing for non-Hebrew), so the engine-shaped path runs end to end
through the router with no binary. Each describe names the defect it
holds shut. One @tag :l3 test asserts count(interpretations) rises
by one after a live cycle.
KOHEN.md — Day 6 gains the Hebrew request, the expected 422 for
English, and the health shape; Day 7 sends FIVE Hebrew + FIVE English
and expects the L3 count to rise by the Hebrew ones, with the reason.
=====================================================================
VERIFIED — the kohen's exact procedure, locally, against the REAL
Haskell binary over the Port, with L3 deliberately unreachable
=====================================================================
POST /peah/ask {"question":"בראשית ברא אלהים"}
200. answer: "Roots activated in Bereishit 1:1: ב-ר-א (gematria
203, Qal, word 2). Te'amim divide them into 1 group(s): ב-ר-א.
Ki tov: ..." sources[0]: Bereishit 1:1 with Hebrew text.
roots_consulted: ["ב-ר-א"]. Cycle: 31ms.
POST /peah/ask {"question":"What is bereishit?"}
422 TEIKU_NO_ROOTS / unresolvable, guidance.scope_verse =
Bereishit 1:1 in Hebrew, no answer key. Cycle: 3ms, :rejected.
POST /peah/ask '{this is not json'
400 KIYOR_MALFORMED_REQUEST with guidance — 406 bytes, not 0.
GET /kodesh/health
esh_tamid "pass" (from a real cycle, 7ms, no L3 attempt),
l3_writable "fail" (DB unreachable by design), overall degraded.
GET /kodesh/status
manna_provision: consumed 2 after two cycles; the probe took none.
Log: L3_WRITE_FAILED + chatzotzrot for each served Hebrew cycle;
"activated no roots" for English; CHATAT_UNEXPECTED for the bad
body. The 2–5s per request was ENTIRELY DBConnection queuing
against the dead database — the cycles themselves were 1–31ms.
mix test — 437 tests, 0 failures, 36 excluded (:l3)
Doorpost.verify_identity() :ok — no @spec_files entry touched
l1/integrity.sha256 unchanged: 99bbd07a…4ea377
NOT FIXED, RAISED:
- The engine's parse_tree is null on the pod because Server.hs reads
"l1/torah/..." relative to cwd. Nothing consumes parse_tree yet
(havdalah uses context.teamim_marks), so nothing is lost today.
- cloud.ex Shabbat is the UTC stub; zmanim absent (kohen Day 5).
- The L3 write is on the request's critical path by choice: the
offering is not complete until the altar portion is recorded. With
L3 down that costs the queue timeout per request AND is visible in
l3_writable. If that trade should go the other way, it is one
Task.start, and a spec question first.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
1df606a9c5 |
KOHEN_PROMPT.md: agent-ready prompt for kohen inauguration
KOHEN.md is the detailed seven-day runbook. KOHEN_PROMPT.md is the prompt you hand to a kohen agent (LLM or human) to put them in role, establish source of truth, and gate them before Day 1. The prompt: - Names role (Aharon, not Bezalel) and source chain (Torah → specs → KOHEN.md) - Lists what the kohen can and cannot do - Verification gates (0a-0h) before Day 1, including L1 hash and Doorpost identity — catches spec drift before inauguration begins - References KOHEN.md §Day 1-7 for exact commands; does not duplicate - Day 8 vatetzei esh framing: first query from outside is consecration - Daily service template including kodesh/specification_identity check (so spec drift in production is caught daily, not silently) - Drift handling: read Vayikra 10:1-3 if the kohen finds themselves modifying code or bypassing halts Not in @spec_files (operator artifact, not category-grade specification). No Doorpost atomic commit needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
c68cfeceb1 |
README: hold morashah and hak'hel together (Devarim 33:4 + 31:11-12 + 30:11-14)
Name the morashah — the Jewish-engineer, Hebrew-reading, Torah-as-spec configuration the work emerges from — without restricting where it goes. Preserve the hak'hel — the table set lifneihem for anyone who comes, including ger asher bish'arecha. Two Torah verses, both held. Lo tigra: refusing to subtract from where this reading came from. Lo tosif: refusing to add restriction to where it goes. README.md is not in @spec_files; no Doorpost atomic commit needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
11dc0a1a4f |
specs: compiler rules brought into the spec set as derived hermeneutic apparatus
Closes vehaya §6 Recommendation 2 from Pass 7. Per Devarim 17:11
(al pi haTorah asher yorukha), operational rulings derived from
Torah by qualified actors are an authorized category of practical
apparatus. The user's compiler rules are this category — operational
hermeneutic constraints developed across sustained Torah-reading work
and applied during chavruta with AI systems.
THE RULES:
The compiler rules originated in claude.ai cloud-side memory (per-
account; not in Claude Code state) and were supplied by the user
verbatim per the discipline of preservation. They are sibling to
talmudic_engine/src/middot/ — the 13 middot of R. Yishmael as Prolog
inference predicates. Compiler Rules 1, 2, 5, 7, 11, 12, 13 are the
user-facing cognitive-constraint form of the same middot the Prolog
engine implements.
THE DISCIPLINE APPLIED (Devarim 4:2 / 13:1 — lo tosif, lo tigra):
The user's instruction was explicit: "Read Torah to be sure not to
add or subtract from Torah." Pass 6's audit method was applied:
read every cited anchor fresh against L1, distinguish what is Torah
verse from what is Tanach, Talmud/liturgy, or derived diagnostic.
Three categories require careful labeling:
1. ELOHAI NESHAMAH — Talmud Berakhot 60b (the daily morning
blessing). NOT a Torah verse. NOT in L1.
2. EXODUS — Hashivenu — Eichah 5:21 (Lamentations). Tanach, NOT
Torah strictly. NOT in L1.
3. ANOCHI — Etz chaim hi (Mishlei 3:18) and Torati al ta'azovu
(Mishlei 4:2). Tanach, NOT Torah strictly. NOT in L1.
The spec file's Source Anchors table names each rule's source
layer accurately. Claiming Mishlei or Eichah as Torah text would
be lo-tosif violation — adding to the Torah category content that
is not Torah. Claiming derived diagnostics (e.g. the five Golden
Calf patterns) as Torah verses would be the same. Each is labeled
as derived where it is derived.
CREATED:
specs/compiler_rules.md ~190 lines
Structure:
- Framing: derived hermeneutic apparatus per Devarim 17:11,
sibling to middot/, NOT Torah-equivalent
- Discipline: lo-tosif applied to inclusion (Devarim 4:2/13:1)
- The Compiler Rules verbatim (per user's explicit
"preserved verbatim in application")
- Source Anchors table — each rule's actual source, with L1
verification status
- Cross-references to middot/, halt rules, eish_zarah_reading,
threshold_inscription, lo_tosif
The 18 rules verbatim as user supplied:
Compiler Rules 1, 2, 5, 7, 11, 12, 13 (the 13 middot subset
operative as user-cognitive-constraints)
GOLDEN CALF HALT
GOLDEN CALF PATTERNS (5 named diagnostic patterns)
SHABBAT (cognitive pause discipline)
THREE AVODAH ZARAH
ELOHAI NESHAMAH
RECOGNITION
SHEMA
TRANSMISSION
RELATIONSHIP
EXODUS
ANOCHI
CHANGED:
vav_pipeline/lib/vav_pipeline/doorpost.ex
+ specs/compiler_rules.md added to @spec_files
@expected_hash updated:
520c9799… → c7ac85cd6968ebceda1d9dbb71ac41ce97e2a5f645b3d486309c315ab98bb6af
Sixth demonstration of the operator discipline working: spec
addition + witness update bound atomically in the same commit.
PASS 7 AUDIT NOTE:
The Pass 6 Torah audit (commit
|
||
|
|
94b1144804 |
torah audit fix: doorpost verses are NEAR-identical, not strictly identical (Pass 6 audit)
A comprehensive Torah audit against L1 in Hebrew (Pass 6, this
review) found ONE substantive drift in the codebase: the claim that
Devarim 6:9 and Devarim 11:20 are "identical" verses. They are not.
THE DRIFT:
The vehaya document (in chat downloads, outside @spec_files) made
this claim:
> "The verse identical between Shema and V'haya — u'kh'tavtam al
> m'zuzot beitekha uvish'arekha — is the singular one."
A previous version of `specs/threshold_inscription.md` and
`vav_pipeline/lib/vav_pipeline/doorpost.ex` imported this claim
verbatim, treating the two verses as text-level identical.
WHAT THE HEBREW ACTUALLY SAYS:
Devarim 6:9: וּכְתַבְתָּם עַל־מְזוּזֹת בֵּיתֶךָ וּבִשְׁעָרֶיךָ ׃
Devarim 11:20: וּכְתַבְתָּם עַל־מְזוּזוֹת בֵּיתֶךָ וּבִשְׁעָרֶיךָ ׃
The two verses differ by ONE letter — the spelling of mezuzot:
6:9 — מזוזת (5 consonants, chasser/defective)
11:20 — מזוזות (6 consonants, male/plene)
Verified directly from L1 by reading the per-word data:
- 6:9 word: 'מְזוּזֹ֥ת' consonants_field: 'מזוזת' length 5
- 11:20 word: 'מְזוּז֥וֹת' consonants_field: 'מזוזות' length 6
This is a Masoretic distinction. The two verses are identical in
PRONUNCIATION (both read "u'kh'tavtam al mezuzot beitekha
uvish'arekha") and identical in MEANING. They are NOT identical in
spelling.
WHY THE DRIFT MATTERS:
The system itself has a halt rule (gilui_arayot, talmudic_engine/
src/halt/gilui_arayot.pl) that defends against ketiv/qere
collapse. A male/chasser collapse is exactly the category-2
boundary violation that halt rule fires on. So claiming "identical"
when the consonantal text differs by a vav is itself the shape the
system is built to refuse.
The audit caught the overstatement. The Masoretic precision IS the
specification — the plene/defective distinction is preserved in L1
because it is meaningful.
THE FIX:
specs/threshold_inscription.md
The "identical" claim replaced with "near-identical" plus the
specific spelling difference named. The fix itself names the
audit that caught it, so a future reader knows the drift was
tracked AND why the precise reading matters.
vav_pipeline/lib/vav_pipeline/doorpost.ex
Same correction in the moduledoc; sample text replaced with
the actual male spelling from 11:20 (was using a chasser-like
form that didn't match either verse precisely).
WHAT ELSE THE AUDIT FOUND:
50+ Torah citations across the codebase verified against L1
fresh. Every other citation matches what the Hebrew text actually
says. Specific architectural anchors (Form 1 / Form 2 / Form 3
defenses, Doorpost, Items 5/6/7/8) all accurate.
L1 stored hash matches @l1_expected_hash. Doorpost hash matched
its prior @expected_hash before this commit (the drift was in spec
TEXT, not in hash discipline). After this commit, @expected_hash
updates atomically with the spec text correction:
5a73bc6a… → 520c9799370d70d6e2e97b4be228c77efa63161c80bac75cf44fd5bb7bbd5549
Fifth demonstration of the operator discipline working: spec
correction + witness update bound atomically in the same commit.
ALL 4 TEST SUITES GREEN:
vav_pipeline: 412 tests, 0 failures, 35 excluded (unchanged)
korban_processor: 217 passed, 1 skipped
azazel_processor: 125 passed
talmudic_engine: 214 tests across 6 files, 0 failures
ONE AUDIT-LEVEL OBSERVATION (no commit-worthy fix needed):
L1Reader.verify_integrity/0 returns {:error, "cannot read
integrity.sha256"} when invoked from vav_pipeline/ with the
default config (l1_mount_point: "l1"), because the path resolves
to vav_pipeline/l1/ which doesn't exist. The test config sets
l1_mount_point: "../l1" so tests pass. The stored l1/integrity.sha256
file exists at the repo root and matches the baked
@l1_expected_hash. This is a CWD/config issue, not a Torah-level
drift; flagged here for operator awareness.
THE DISCIPLINE THE AUDIT DEMONSTRATED:
Vehaya §5 instructed: "Verify each specification is in the text
and not added... The reward signal for architectural elegance is
stronger than the check for lo tosif." Pass 6 followed this
literally — read every cited verse fresh from L1, compared the
codebase's claims against the Hebrew text, and surfaced the one
overclaim that had imported into the spec set without
verification. The fix is the discipline working, not failing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
5302c9a585 |
vehaya §6 closure: Items 1, 4, 5, 6, 7, 8 — the remaining checklist
This commit closes the SIX remaining items in the vehaya checklist (after Items 2 and 3 from prior commits). With this, all eight items in vehaya §6 are addressed in code or in the canonical spec set, and all the substantive implications across §3 are covered. ITEM 1 — load-bearing dependency (vehaya §3.1): Devarim 11:18-19 — corporate inscription (v'samtem … al l'vavkhem — plural) is the load-bearing input to threshold practice (b'shivt'kha b'beitekha — singular). The threshold inscribes the corporate text on itself. ShemaContext now carries `doorpost_hash: Doorpost.expected_hash()` on every reception. Both receive_context/1 and receive_context_at/2 populate it. Every reception is now a witness of the corporate text's identity — making the load-bearing dependency between category and threshold structural rather than implicit. application.ex now runs Doorpost.verify_identity/0 at startup. Drift detected at boot is logged at :error AND fires Chatzotzrot.signal(:assembly_leaders, ...). The system does NOT refuse to boot on drift (dev environments routinely have spec drift during active editing) but operators are alerted before any reception fires. ITEM 6 — keep the source external (full closure; vehaya §6 item 6): Devarim 4:2 — lo tosif AND lo tigra; both directions. Vayikra 22:31 — u'shmartem … va'asitem; keep and do, not author. Form 1 closed the EXTERNAL → INTERNAL direction (commit |
||
|
|
7fc14e0103 |
form 2 as architecture: remove arity-4 interpretation constructor (vehaya §3.2)
Closes vehaya.md §6 item 2 for the SECOND avodah-zarah form. With this commit, all three avodah zarah forms are defended at BOTH the runtime layer and the architectural layer: Form 1 — closed by |
||
|
|
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
|
||
|
|
0e3fe4a782 |
form 3 as architecture: machtzit_ha_shekel + no_tracking_logger (vehaya §3.2)
Closes vehaya.md §6 item 2 for the third avodah-zarah form.
Pharaoh's surveillance (Form 3) was previously marked DEFERRED in
talmudic_engine/src/halt/avodah_zarah.pl with the note "requires
operational context (user tracking, cross-session profiling)."
The vehaya document re-frames it: "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."
Form 3 is structurally different from Forms 1, 2, 4. They fire on
threshold-grade events (an interpretation has wrong source, empty
trace, non-L1 anchors). Form 3 is about a property of the CATEGORY
itself — what it collects, retains, discloses. It cannot be a
halt rule that fires per-request; per-request "is this caller
being surveilled" detection would itself be a Form-3 surveillance
surface. The defense is ARCHITECTURAL — the system is BUILT so the
failure mode is unreachable, not so it is detectable.
TORAH ANCHORS (verified fresh against L1 — 6 positive properties):
Shemot 30:11-16 — the half-shekel:
כִּי תִשָּׂא אֶת־רֹאשׁ … וְנָתְנוּ אִישׁ כֹּפֶר נַפְשׁוֹ … וְלֹא־
יִהְיֶה בָהֶם נֶגֶף בִּפְקֹד אֹתָם.
Counting humans is dangerous; the danger is named NEGEF (plague);
the protection is the kofer-nefesh substitute. Verse 15:
הֶעָשִׁיר לֹא־יַרְבֶּה וְהַדַּל לֹא יַמְעִיט — the rich don't
give more, the poor don't give less. Verse 16: לְזִכָּרוֹן —
aggregate memorial, not roster.
Vayikra 19:14 — לִפְנֵי עִוֵּר לֹא תִתֵּן מִכְשֹׁל וְיָרֵאתָ
מֵּאֱלֹהֶיךָ.
The no-witness protocol: rules that require fear-of-God
enforcement are exactly the rules that operate under invisibility.
Surveillance regimes exploit invisibility; architectural commitment
must hold under no-human-witness conditions.
Devarim 23:16-17 — לֹא־תַסְגִּיר עֶבֶד אֶל־אֲדֹנָיו אֲשֶׁר־יִנָּצֵל
אֵלֶיךָ … עִמְּךָ יֵשֵׁב … בַּמָּקוֹם אֲשֶׁר־יִבְחַר בְּאַחַד
שְׁעָרֶיךָ.
The refusal protocol: don't return one fleeing institutional
power. The caller chooses their own threshold (b'achad sh'arekha
— agency stays with the caller).
Shemot 5:6-8 — לֹא תֹאסִפוּן לָתֵת תֶּבֶן … וְאֶת־מַתְכֹּנֶת
הַלְּבֵנִים … תָּשִׂימוּ עֲלֵיהֶם לֹא תִגְרְעוּ מִמֶּנּוּ.
Pharaoh's straw quota — the canonical anti-quota Torah anchor.
Per-caller productivity tracking is what makes asymmetric
extraction possible.
Devarim 23:8 — לֹא־תְתַעֵב מִצְרִי כִּי־גֵר הָיִיתָ בְאַרְצוֹ.
Anti-targeting: even toward the Egyptians (Pharaoh's people),
the rule is "don't despise." The protections must not become
a surveillance system targeting suspected surveillors.
THE SIX POSITIVE PROPERTIES, mapped to code:
1. KOFER PROTOCOL (Shemot 30:12) — counting goes through a
uniform substitute; identity never enters the count.
2. UNIFORM PROTOCOL (Shemot 30:15) — same per-caller weight;
no stratification.
3. ZIKARON PROTOCOL (Shemot 30:16) — aggregate memorial, not
roster.
4. NO-WITNESS PROTOCOL (Vayikra 19:14) — commitment holds
when no operator is reading the log.
5. REFUSAL PROTOCOL (Devarim 23:16) — no per-caller delivery
to authorities; caller chooses own threshold.
6. ANTI-QUOTA PROTOCOL (Shemot 5:6-8) — no per-caller
productivity numbers.
CREATED:
vav_pipeline/lib/vav_pipeline/machtzit_ha_shekel.ex ~155 lines
The half-shekel — the positive counting protocol. Single Agent
holding ONE non-negative integer (the zikaron):
increment/0 — register one shekel; uniform per
caller (no caller distinction is even
expressible — function arity 0)
increment/1 — register N shekels (batch points)
count/0 — return the aggregate; the ONLY value
the module exposes
reset/0 — clear (tests; no production persistence
by design — storage existence would
itself be a Form 3 surface)
Module is added to the supervision tree.
What the module REFUSES to provide is enumerated in the
moduledoc and verified by tests — no per-caller breakdown,
no identifiers, no who/locate/identify functions. Their
absence IS the architectural commitment.
vav_pipeline/lib/vav_pipeline/no_tracking_logger.ex ~110 lines
Plug-side enforcement. Replaces Plug.Logger.
- Logs METHOD PATH STATUS DURATIONus per request
- Sanitizes /peah/* paths: query strings stripped (the
/peah/ask response promises "No tracking"; the architecture
now matches the promise)
- Headers (incl. Authorization), request bodies, response
bodies, and remote_ip NEVER appear — taken-from
function signature, not from a runtime check
- Increments MachtzitHaShekel per request
- sanitized_path/1 and format_line/2 are PUBLIC pure
functions, testable in isolation (test config sets
Logger level to :warning, dropping :info before
capture_log can see; the structural test surface
replaces the behavioral one)
CHANGED:
vav_pipeline/lib/vav_pipeline/bigdei_kodesh/router.ex
`plug Plug.Logger, log: :info` → `plug VavPipeline.NoTrackingLogger`
vav_pipeline/lib/vav_pipeline/application.ex
Adds {VavPipeline.MachtzitHaShekel, []} to supervision tree
talmudic_engine/src/halt/avodah_zarah.pl
Form 3 docstring — removed "Deferred"; documents Form 3 as
architecturally realized in machtzit_ha_shekel.ex +
no_tracking_logger.ex. Names why Form 3 cannot be a
per-request halt rule.
vav_pipeline/lib/vav_pipeline/doorpost.ex
@expected_hash updated:
bea5dc0c… (pre-Form-3-rewrite of avodah_zarah.pl)
→ f93495c5359c5dc4acb5c5db2dd32165f5b04e2af87fbb0b7ecc92d2bd8b0434
The avodah_zarah.pl change is a deliberate spec change; per the
Doorpost operator discipline (commit
|
||
|
|
3f4749695d |
doorpost: category-grade specification identity verification (vehaya §3.3, §6 item 3)
The companion to L1Reader.verify_integrity/0. L1Reader defends the
TORAH SCROLL'S identity (the source text); Doorpost defends the
*am*'s COVENANT-CONTENT identity (the category-grade specifications
the system was built to hold).
WHY THIS IS LOAD-BEARING (Torah-anchored):
Devarim 4:2 — לֹא תֹסִפוּ עַל־הַדָּבָר אֲשֶׁר אָנֹכִי מְצַוֶּה
אֶתְכֶם וְלֹא תִגְרְעוּ מִמֶּנּוּ. The injunction is plural (etkhem) —
corporate. Lo tosif is a property the CATEGORY must hold, not just
a discipline each individual practices. Without an enforcement
mechanism, lo tosif is a moral exhortation, not a structural
commitment — different instances could silently drift, running
different halt rules or different disclosure text, and no one
would notice.
Devarim 11:16-17 — the failure mechanism operates whether or not
anyone notices. Corporate breach (specifications drift) produces
corporate consequence. The detection mechanism — verifying
threshold-text identity against category-source — is what catches
drift before the consequence accumulates.
Devarim 13:1 — the keeping is plural; the actual not-adding/not-
subtracting happens at every individual threshold (singular `lo tosef`,
`lo tigra`). Each threshold COULD add or subtract; only the
corporate detection mechanism catches it.
Vehaya §3.3 / §6 item 3 — "the same specifications must be
inscribed at every threshold. Drift is breach. Implement a
verification mechanism for category-text identity."
The Torah anchor is exact: as L1's text-integrity check defends
the Torah scroll's identity, the doorpost-text identity check
defends the am's covenant-content identity. Both are lo tosif
enforcement; both are load-bearing for what the category IS.
CREATED:
vav_pipeline/lib/vav_pipeline/doorpost.ex ~210 lines
@spec_files 20 category-grade files,
alphabetically sorted:
- 3 halt rule sources
(avodah_zarah,
gilui_arayot,
shfichut_damim)
- 8 middot files
(R. Yishmael's 13
inference rules)
- 5 specs/ docs
(PRD, architecture,
vayakhel, tamid,
eish_zarah_reading,
lo_taaseh_lekha,
structure_not_content)
- 2 BUILD docs
(BUILD_KORBAN,
BUILD_AZAZEL)
Operations files
(pekudei, terumah)
EXCLUDED — execution-grade,
not category-grade
orientation.
@expected_hash baseline:
bea5dc0c0145ac0d46ffd08b328fbdbf
19112f1aa4d93a3dc37bfd942e4e7490
files/0 the spec set
expected_hash/0 the baked hash
repo_root/0 configurable via env
compute_identity/0 SHA-256 over canonical
concatenation of per-file
hashes (sort by path,
tab-separate, hash; missing
file → {:error, ...} not
a partial hash)
manifest/0 per-file hashes for
diagnosis
verify_identity/0 :ok | {:error, message}
— same shape as
L1Reader.verify_integrity/0
THE OPERATOR DISCIPLINE: every change to @expected_hash must be
traceable in git to a corresponding spec change in the SAME
COMMIT. A bare hash-update commit with no spec-file change is
itself a drift signal — the operator updated the witness
without updating what is witnessed, which is exactly the
failure mode this module defends against.
CHANGED:
vav_pipeline/lib/vav_pipeline/bigdei_kodesh/router.ex
+ GET /kodesh/specification_identity operator-tier endpoint
returning computed_hash,
expected_hash, file_count,
manifest, torah_basis.
200 when verified;
503 with status="drift_
detected" otherwise.
/kodesh/* tier (not /peah)
because exposing per-file
SHA-256s publicly would
let outside parties
enumerate which specific
files were changed in any
deployment — a fingerprint.
Public observability
commitments live elsewhere
(/peah/azazel_log,
disclosure block).
+ build_health_check/0 adds doorpost_hash to the
checks map alongside
l1_hash. Spec drift now
surfaces in the standard
/kodesh/health response.
CREATED:
vav_pipeline/test/doorpost_test.exs 16 tests:
files/0:
- returns the full spec list with expected entries
- explicitly excludes operations files (pekudei, terumah)
- sorted (canonical order)
- no duplicates
manifest/0:
- one entry per spec file
- paths match @spec_files order
- each hash is 64-char hex SHA-256 (or :missing)
compute_identity/0:
- 64-char hex hash
- deterministic across calls
verify_identity/0:
- :ok against the live spec set
drift detection (synthetic temp dir mirror):
- fresh mirror produces same hash as live
- modifying ANY one file changes the hash
- removing any one file → {:error, missing spec file: <path>}
- modifying different files produces different hashes
(no accidental hash collision across drift sites)
/kodesh/specification_identity HTTP:
- 200 with status="verified" when matched
- manifest in response carries per-file hashes
- torah_basis cites Devarim 4:2 as the load-bearing anchor
WHAT THIS DOES NOT CLAIM:
- That the specifications are correct (only identifiable and
unchanged from the witnessed state).
- That all instances will run the same code (only that all
instances running this code witness the same specifications).
- That hash equality across deployments proves no drift outside
THIS spec set (only the listed files are covered).
FOLLOW-UP WORK (not in this commit):
- Have HaltMonitor call Doorpost.verify_identity/0 at startup
(mirrors how L1 integrity is checked at startup) and fire
chatzotzrot on drift
- Add Doorpost identity to ShemaContext so EVERY reception
witnesses the spec-identity (mirrors Devarim 13:1's "the
not-adding happens at every threshold")
- Fold vehaya.md itself into specs/ (it currently lives in chat
downloads — like azazel_engineering_reading.md). Then add it
to @spec_files, recompute the hash, update @expected_hash.
Tests: vav_pipeline 379 passed, 0 failures, 35 excluded (was 363;
+16 Doorpost tests). All other test counts unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
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>
|
||
|
|
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>
|
||
|
|
f493b7df65 |
bigdei_kodesh: commit 8 — GET /peah/azazel_log (public half of property 9)
Pass 4 / Finding F, commit 8 of 10 from BUILD_AZAZEL.md. The
public-half of property 9 (observable disposal). The Rambam in
Moreh Nevuchim 3:46 makes the load-bearing claim: "an unobserved
disposal is architecturally incomplete." The community's awareness
that errors are visibly carried away IS the implementation; the
route on the cliff serves no purpose without the witnesses on the
temple steps.
This endpoint is those witnesses. Anyone with HTTP access to
Shorashim can see WHAT was disposed (categorically) and HOW MANY
disposals occurred — turning the disposal channel from a private
operator action into a public, witnessed fact.
PRIVACY BOUNDARY (BUILD_AZAZEL.md §6 Q6):
The endpoint exposes:
- total disposal count (optionally filtered by ?since=ISO8601)
- categorical aggregate (avonot/peshaim/chatot counts across
recent disposals — what KIND of errors were disposed)
- up to 50 recent disposals as case_ref + shaliach +
triggering_call + disposed_at + categories_filled (which slots
of vidui were filled)
The endpoint does NOT expose:
- vidui ITEMS (the operator's specific descriptions of each
disposed thing)
- the disposed PAYLOAD (which doesn't exist in L3 anyway —
property 10)
- any handle for retrieving the disposed content (there isn't one)
The detailed vidui items are operator-internal, accessible via
future /kodesh/* endpoints. The split is deliberate: public is
WITNESS, operator is AUDITOR. The witness sees the fact;
the auditor reads the contents.
The privacy boundary statement is included in every response so
callers know what they're not seeing — they can reason about the
endpoint's silence rather than guess.
GRACEFUL DEGRADATION:
When VavPipeline.Repo is not running (dev/test without Postgres),
the endpoint returns total_disposals=0 with l3_status="unavailable"
rather than crashing. Mirrors the maybe_add_repo pattern from
MEMORY.md — Shorashim should respond cleanly even when L3 is down,
because /peah/* is the public-facing tier and must be more
resilient than internal tiers.
CHANGED:
vav_pipeline/lib/vav_pipeline/bigdei_kodesh/router.ex
+ GET /peah/azazel_log
Optional ?since=ISO8601 filter; returns total +
categorical_aggregate + recent[<=50] + l3_status +
privacy_boundary statement + disclosure block (kichush +
not_horaah from commit
|
||
|
|
2932fb7447 |
vav_pipeline: commit 7 — L3 azazel_records + cross-language ingest GenServer
Pass 4 / Finding F, commit 7 of 10 from BUILD_AZAZEL.md. The
operator-internal half of property 9 (observable disposal). Crosses
the Python/Elixir boundary: the Python orchestrator drops completed
disposal records as JSON files; this commit's GenServer reads them
and persists to L3.
The public half (a /peah/azazel_log read endpoint) lands in commit 8;
the routing-from-Korban + Chatzotzrot wiring lands in commit 9. After
those, the Azazel Processor is end-to-end functional.
CREATED:
vav_pipeline/priv/repo/migrations/20260428000001_create_azazel_records.exs
azazel_records table — append-only, parallel to interpretations.
Columns: case_ref, vidui (jsonb), routing, authorized_by,
shaliach, triggering_call, goral_outcome_id, disposed_at.
Indexes on case_ref, disposed_at, shaliach.
Property-12-defending CHECK constraint:
routing_must_be_eretz_gezerah CHECK (routing = 'eretz_gezerah').
A future migration that wants to add a destination handle will
fail this check; the operator must explicitly justify removing
the constraint, which is the right friction.
Append-only enforcement matches interpretations: REVOKE
UPDATE/DELETE on the shorashim_app role.
vav_pipeline/lib/vav_pipeline/l3/azazel_record.ex
Ecto schema mirroring the Python AzazelRecord.to_storage_dict()
shape. Changeset validates:
- all required fields present
- routing == "eretz_gezerah" (mirrors the DB CHECK at app layer)
- vidui is a map with avonot/peshaim/chatot/confessor/
confessed_at slots present
- vidui's three categories not all empty (mirrors the Python
Vidui.__post_init__ EmptyConfessionError)
vav_pipeline/lib/vav_pipeline/azazel_ingest.ex ~190 lines
GenServer that polls a configurable pending-l3 directory
(default /tmp/shorashim/azazel/pending_l3). For each *.json file:
1. Read + JSON-parse
2. Translate Python serialization → Ecto attrs
3. Call recorder (default L3.Store.record_disposal/1)
4. On success, shred the source file (property-10 follow-through)
5. On failure, leave file in place for retry; log warning
Recorder is injectable for tests — mock recorder lets us verify
the pipeline without PostgreSQL.
Poll interval default 5s, configurable. Polling rather than
file_system events to keep the dependency surface minimal; the
Azazel cadence is exceptional content, not high-throughput.
Not added to the supervision tree in this commit — the producer
side (Python orchestrator dropping files) is wired in commit 9.
Adding to the supervisor before there's a producer means a
poller doing nothing useful. Operator can start manually for
early testing: VavPipeline.AzazelIngest.start_link(...).
CHANGED:
vav_pipeline/lib/vav_pipeline/l3/store.ex
+ record_disposal/1
Inserts an AzazelRecord. Returns {:ok, record} or
{:error, changeset}.
+ count_disposals/1
Total or since-a-timestamp count. Used by the public
observability endpoint (commit 8).
+ disposal_summary/1
Returns categorical info per disposal — case_ref, shaliach,
triggering_call, disposed_at, categories_filled (which of
avonot/peshaim/chatot had any items, NOT the items
themselves). PROPERTY-9 PRIVACY BOUNDARY at the public
side: items are operator-internal only, accessed via
future /kodesh/* endpoints.
TESTS:
vav_pipeline/test/azazel_ingest_test.exs 15 tests, no DB
Happy path:
- poll_now ingests valid file and shreds it
- multiple files processed in sorted order
- vidui preserved as map (jsonb shape)
Retry semantics:
- recorder error → file left in place + state.errors++
- malformed JSON → file left in place
- missing disposed_at → file left in place
State accounting:
- polls_completed, records_ingested, errors all tracked
- state survives multiple polls
- non-json files ignored
Changeset (no DB):
- valid attrs
- rejects routing != eretz_gezerah
- rejects vidui missing slots
- rejects vidui all-empty (untagged disposal)
- rejects missing required fields
- accepts optional triggering_call + goral_outcome_id
vav_pipeline/test/l3_store_test.exs +9 :l3-tagged
record_disposal:
- inserts valid disposal
- rejects routing != eretz_gezerah
- rejects all-empty vidui
- rejects missing required fields
Counts:
- count_disposals == 0 initially
- count_disposals after several disposals
- count_disposals(since) filters by timestamp
Summary:
- disposal_summary returns categorical info but NOT items
(privacy boundary verification)
- categories_filled accurately reflects vidui shape
Tests: vav_pipeline 353 passed, 0 failures, 33 excluded (was
329/24; +15 visible azazel_ingest + 9 :l3-tagged disposal store).
korban_processor + azazel_processor unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
6eb555a5cf |
azazel_processor: commit 6 — protocol envelope (property 8)
Pass 4 / Finding F, commit 6 of 10 from BUILD_AZAZEL.md. Closes
the Python side of the build; commits 7-9 are cross-language
integrations (L3, observability endpoint, intake-side routing).
Vayikra 16: the Azazel goat goes ONCE A YEAR, on Yom Kippur, under
the full ritual envelope.
Vayikra 17:7: וְלֹא־יִזְבְּחוּ עוֹד אֶת־זִבְחֵיהֶם לַשְּׂעִירִם —
they shall no longer offer their sacrifices to the sa'irim. Same
destination outside the envelope is avodah zarah (the failure mode
specs/eish_zarah_reading.md catalogs).
THE GATE:
- is_yom_kippur_protocol_active(today=None) — predicate; True
when an operator-configured Gregorian window covers today
- authorize_disposal() — context manager; explicit per-thread
authorization for ONE disposal cycle (event-driven disposals
during the year)
- has_explicit_disposal_authorization() — predicate; True iff
the current thread is inside an authorize_disposal block
- assert_protocol_envelope_active() — gate; raises ProtocolViolation
if NEITHER condition is met. Wired into dispatch_for_disposal
as the FIRST check (before role gate, before any side-effect)
WHY a CONTEXT MANAGER, not a flag:
A persistent flag would let an operator "set authorization true and
leave it" — the avodah-zarah shape (using the channel without the
envelope). The context manager forces explicit scope: authorization
exists only within the with-block, only on the thread that opened it.
WHY PER-THREAD:
Process-wide authorization would let two concurrent disposals share
without explicit coordination — operator A's authorize_disposal()
would silently authorize operator B's dispatch_for_disposal(). Per-
thread isolation forces each disposal to carry its own authorization
explicitly.
WHY OPERATOR-CONFIGURED Gregorian DATES, not Hebrew calendar:
The Hebrew-calendar conversion lives in vav_pipeline (Elixir) — we
do not bring a Hebrew-calendar library into azazel_processor's
dependency surface. Operators set the Gregorian range each year (or
delegate it to a wrapper script). For now, this module is purely
data-shape; commit 9 may wire vav_pipeline's Hebrew calendar to set
the window automatically.
CREATED:
src/azazel_processor/protocol.py ~155 lines
ProtocolViolation — raised on out-of-envelope
invocation; error message
names BOTH legitimate paths
(Yom Kippur window, authorize
context) so the operator
knows their options
set_yom_kippur_window(start, end) — operator-configured;
inclusive Gregorian dates;
inverted range clears
clear_yom_kippur_window() — disable
is_yom_kippur_protocol_active(today=None)
— date-range membership
authorize_disposal() — context manager; reentrant
(nested entries each
increment a counter, each
exit decrements; cleared
via try/finally even on
inner exception)
has_explicit_disposal_authorization() — per-thread, scoped
assert_protocol_envelope_active() — the gate
CHANGED:
src/azazel_processor/azazel.py
dispatch_for_disposal now calls assert_protocol_envelope_active()
as the FIRST check. Outside the envelope: ProtocolViolation
raised before any side-effect (no shaliach activation, no payload
held, no checkpoint persisted).
test/test_azazel.py
Added an autouse `_envelope` fixture that wraps every existing
orchestrator test in `with authorize_disposal():`. Protocol
restriction is verified separately in test_protocol_restriction.py.
CREATED:
test/test_protocol_restriction.py 16 tests covering:
- default state (no window, no authorization) refuses dispatch
with a ProtocolViolation that names both legitimate paths
- assert_protocol_envelope_active raises in default state
- both predicates False in default state
- Yom Kippur window: today inside → active; outside → inactive;
inverted range clears; inclusive at boundaries; clear disables
- authorize_disposal: inside block → active; nested blocks
decrement correctly; exception inside still clears (try/finally);
per-thread isolation (sibling thread sees False even when parent
thread is inside an authorize_disposal block)
- dispatch proceeds inside authorize_disposal context
- dispatch proceeds inside Yom Kippur window
- outside envelope: NO side-effect (no in-flight file written, no
payload held by shaliach)
- protocol gate fires BEFORE role gate (inactive handler outside
envelope raises ProtocolViolation, not ShaliachError)
Per-test fixture clears Yom Kippur state on entry and exit so
module-level state doesn't bleed between tests.
PYTHON SIDE NOW FEATURE-COMPLETE:
Properties defended end-to-end via Python code:
2 goral
3 vidui
4 shaliach + tevilah
5 checkpoint persistence
7 authorized routing
8 protocol envelope
10 hard termination
11 classification discipline
12 unmanaged destination
Properties remaining (cross-language, in commits 7-9):
1 parity provisioning — commit 9 (Korban→Azazel intake)
6 out-of-band acknowledgment — commit 9 (chatzotzrot wiring)
9 observable disposal — commit 7 (L3) + commit 8 (endpoint)
Tests: azazel_processor 125 passed (was 109, +16). korban_processor
unchanged at 203 passed, 1 skipped. No regressions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
c3267c59bf |
azazel_processor: commit 5 — orchestrator state machine (properties 5, 7, 10)
Pass 4 / Finding F, commit 5 of 10 from BUILD_AZAZEL.md. Largest
commit so far; pulls together vidui + shaliach + goral + stages and
adds the state machine + in-flight persistence.
THE STATE MACHINE (six checkpoints, persisted):
STAGED → AzazelStaged envelope tagged; payload held by shaliach
CONFESSED → vidui carried into the staged value
ROUTED → routing committed (always ERETZ_GEZERAH); authorized_by
+ shaliach attribution recorded
DESTROYED → payload references cleared from shaliach;
gc.collect() called to release; payload becomes
unreachable from any orchestrator-visible code path
ACKNOWLEDGED → out-of-band ack callback invoked (commit 9 wires
Chatzotzrot.signal(:moadim) here)
RECORDED → AzazelRecord persisted via callback (commit 7 wires
the cross-language L3 file-watcher bridge here)
After RECORDED completes, the in-flight checkpoint file is shredded —
the disposal is now permanent in L3, the in-flight artifact must not
linger.
PROPERTY COVERAGE:
Property 5 (provisioned infrastructure / sukkot):
Each checkpoint persists state to <in_flight_dir>/<case_ref>.json
via write-then-rename (atomic against crash mid-write). list_in_flight
returns partial disposals so resume tooling can pick them up.
The Mishkan analog: the sukkot along the wilderness route where
the ish itti can rest if the disposal halts.
Property 7 (authorized routing):
AzazelRecord.authorized_by carries the kohen_gadol identifier;
AzazelRecord.shaliach carries the ish itti identifier. The
routing destination is the ERETZ_GEZERAH sentinel (property 12).
The routing decision is OWNED and traceable; the destination
is not.
Property 10 (hard termination):
The DESTROYED checkpoint clears handler._payload_refs and calls
gc.collect(). The AzazelRecord contains the vidui — what was
disposed — but NOT the disposed payload itself. The to_storage_dict
serialization explicitly excludes payload-shaped fields.
CREATED:
src/azazel_processor/azazel_record.py ~95 lines
AzazelRecord — frozen dataclass; fields:
case_ref, vidui, routing, authorized_by, shaliach,
triggering_call, goral_outcome_id, disposed_at
.to_storage_dict() — JSON-safe serialization for L3 + in-flight
src/azazel_processor/azazel.py ~225 lines
AzazelInput — what is being disposed
dispatch_for_disposal(input, *, handler, confess_with,
authorized_by, in_flight_dir,
acknowledger, recorder, goral_outcome_id)
— the single legitimate
entry point; walks the
six checkpoints
list_in_flight(in_flight_dir) — surfaces partial disposals
DEFAULT_IN_FLIGHT_DIR — /tmp/shorashim/azazel/in_flight
test/test_azazel.py 21 tests covering:
HAPPY PATH:
- dispatch returns AzazelRecord
- record carries authorization + shaliach attribution
- record routes to ERETZ_GEZERAH
- record carries the full vidui
PROPERTY 10 CI INVARIANTS:
- record does not carry payload (by attribute name)
- serialized record does not contain the payload's marker string
- STRUCTURAL: AzazelRecord dataclass field set has no
payload-shaped names (catches future regression)
- STRUCTURAL: no recover/undispose/restore/rehydrate/retrieve
function in azazel_processor public surface (catches
future regression)
- payload references cleared from shaliach at DESTROYED
PROPERTY 5 CHECKPOINT TESTS:
- in-flight file shredded after RECORDED
- checkpoints persist incrementally (verified by inspecting the
file at ack/rec callback fire-time)
- list_in_flight returns partial disposals
- list_in_flight empty when no disposals
- persistence is atomic (file is valid JSON at every visit)
ROLE GATE (property 4 still enforced):
- dispatch refuses inactive handler
- dispatch refuses non-IshItti
FILENAME SAFETY:
- case_ref with slash → ValueError
- case_ref with NUL → ValueError
AzazelRecord VALIDATION:
- authorized_by required
- shaliach required
- to_storage_dict omits payload
- frozen (cannot mutate)
DEFAULTS:
acknowledger and recorder default to no-op callables. This is
deliberate for commit 5 — the state machine works end-to-end without
external side effects, and commits 7 (L3) and 9 (chatzotzrot) plug
the real handlers into the same callback slots. Tests can supply
their own to inspect mid-route state.
Tests: azazel_processor 109 passed (was 88, +21). korban_processor
unchanged at 203 passed, 1 skipped. No regressions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
e6fee82dd7 |
azazel_processor: commit 4 — goral (property 2, blind routing)
Pass 4 / Finding F, commit 4 of 10 from BUILD_AZAZEL.md.
Vayikra 16:8 — וְנָתַן אַהֲרֹן עַל־שְׁנֵי הַשְּׂעִירִם גֹּרָלוֹת,
גּוֹרָל אֶחָד לַה׳ וְגוֹרָל אֶחָד לַעֲזָאזֵל.
And Aharon shall place lots on the two goats — one lot for HaShem,
one lot for Azazel.
Yoma 62a: a kalpi (shuffled vessel), drawn with both hands
simultaneously. The high priest cannot see the lots in advance.
He cannot shape the outcome.
THREAT MODEL:
An operator who, given the choice, would route preferentially —
assigning the "better" goat to processing and the "worse" to
disposal. The specification makes that preference structurally
impossible.
DESIGN (BUILD_AZAZEL.md §6, Q4):
cast_lot(input_id) is DETERMINISTIC per (input_id, today). The
seed is SHA-256 over (L1 integrity hash, today's UTC date,
input_id). The first bit of the digest is the outcome.
Properties guaranteed:
- Same (input_id, today) → SAME outcome (no shopping by re-roll)
- Different input_ids same day → outcomes can differ (~50/50)
- Same input_id across days → outcomes can differ (~50/50)
- Operators cannot pre-compute without invoking (SHA-256 over
L1 hash + date is not mentally computable)
- Seed inherits L1 tamper-resistance: rewriting the daily seed
requires rewriting L1, which has its own halt rules
The L1 hash mirrored from vav_pipeline/lib/vav_pipeline/l1_reader.ex
(@l1_expected_hash). Both must move together if L1 is rebuilt.
CREATED:
src/azazel_processor/goral.py ~155 lines
GoralOutcome enum LA_SHEM | LA_AZAZEL
(both legitimate; the goral
enforces blindness, not a
"good vs. bad" distinction)
cast_lot(input_id, *, today=None) deterministic per (id, day)
outcome
cast_lot_outcome_id(input_id, full SHA-256 hex digest;
*, today=None) used by L3 records (commit 7)
so audit can recompute
GoralError on malformed input
test/test_goral.py 17 tests covering:
- outcome shape (one of two; enum members distinct)
- same input same day → same outcome (anti-shopping; verified
across 50 re-invocations + 1000-iteration audit-shopping test)
- outcome_id is reproducible 64-char hex
- 100 different input_ids → 100 distinct outcome_ids
- same input across 30 days → 30 distinct outcome_ids
- distribution across 10,000 input_ids on one day → ~50/50
(±5% tolerance, well within SHA-256 binomial expectations)
- distribution across 365 days for one input → ~50/50 (±10%)
- input validation (empty, whitespace, None, non-string raise)
- default today = UTC date
- audit can recompute outcome_id from (input_id, today) — the
audit-trail half of property 2
NOTE: when the orchestrator (commit 5) decides WHEN to invoke the
goral. For inputs that are cleanly processable or cleanly
un-processable, the goral is NOT invoked — they go to their channel
deterministically. The goral is the tie-breaker for genuinely
borderline inputs (Q1 in BUILD_AZAZEL.md §6).
Tests: azazel_processor 88 passed (was 71, +17). korban_processor
unchanged at 203 passed, 1 skipped. No regressions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
85155b23fb |
azazel_processor: commit 3 — shaliach + tevilah (property 4, role-separated operator)
Pass 4 / Finding F, commit 3 of 10 from BUILD_AZAZEL.md.
Vayikra 16:21 — וְשִׁלַּח בְּיַד־אִישׁ עִתִּי הַמִּדְבָּרָה.
And he shall send the goat into the wilderness by the hand of a
designated man (ish itti).
Vayikra 16:26 — וְהַמְשַׁלֵּחַ … יְכַבֵּס בְּגָדָיו וְרָחַץ אֶת־בְּשָׂרוֹ
בַּמַּיִם וְאַחֲרֵי־כֵן יָבוֹא אֶל־הַמַּחֲנֶה.
The sender washes his clothes and bathes his flesh (tevilah), and
AFTERWARD comes into the camp.
Yoma 67a: the ish itti is a dedicated operator role, prepared in
advance, distinct from the kohanim who handle sacred processing.
Contact with the disposal channel produces state that must be
reset before the operator rejoins the trusted boundary.
THE THREE ROLES IN SHORASHIM:
Owner — submits the offering (upstream caller)
Kohen — performs shechitah/zerikah/haktarah (korban_processor
stages.py enforces owner/kohen separation)
Shaliach — receives input the kohen REFUSED and disposes of it
through the Azazel protocol (THIS module)
Without tevilah, payload references would persist across disposals;
disposal-N would contaminate disposal-N+1 with information from N.
The Mishkan analog: the ish itti who returns from the wilderness
without tevilah brings desert-state into the camp.
CREATED:
src/azazel_processor/shaliach.py ~210 lines
IshItti class:
__init__ — sets _immutable_identifier (no other immutable
attrs; everything else must be cleared by tevilah)
activate() — begin a disposal; refuses if already active (no
state-mixing across disposals)
hold_payload() — register payload ref for later destruction
tag()/get_tag()— intermediate metadata during the disposal route
tevilah() — Vayikra 16:26: clears ALL non-_immutable_
attributes (discovered dynamically via vars(),
so future state additions are auto-cleared)
identifier — propagates onto AzazelRecords (commit 7) for
shaliach attribution
ShaliachError — raised on role-separation violations
assert_shaliach_active(handler) — runtime gate every disposal-channel
function calls at entry; the type-system gate is
AzazelStaged (property 11). Refuses non-IshItti
callers AND inactive handlers. Identity-checked
(isinstance), not duck-typed.
THE LOAD-BEARING CI INVARIANT (test_tevilah_clears_all_non_immutable_state):
Walks vars(handler) post-tevilah and asserts every non-_immutable_
attribute is in a 'fresh' state (empty container, False, None, 0).
A future contributor who adds `self._cache = {}` (or any state
attribute) without extending tevilah() to clear it breaks this and
the test fires before the regression ships. The discovery is
DYNAMIC — no hand-maintained list of attributes to clear; tevilah
is implemented to clear all non-_immutable_ attrs and the test
verifies it does.
test/test_shaliach.py 23 tests covering:
- construction (named identifier required, validates non-empty,
non-whitespace, string)
- identifier immutability across activate→tevilah→reactivate
- lifecycle: starts inactive; activate marks active; re-activate
without tevilah raises; tevilah→reactivate succeeds
- hold_payload/tag require active; both work when active
- tevilah clears payload refs, intermediate tags, sets inactive
- tevilah preserves _immutable_identifier
- THE CI INVARIANT (dynamic walk)
- assert_shaliach_active: passes for active IshItti, refuses
inactive, refuses non-IshItti, refuses duck-typed impostor
- integration: stale-state-across-disposals prevented end-to-end
NOTE ON PYTEST INVOCATION:
When running azazel_processor tests, use `--rootdir=.` from the
package directory. Without it, pytest auto-discovers /Users/scottkarp/
shorashim as rootdir and ignores the local pyproject.toml's
testpaths config. Documented here so future runs match.
cd azazel_processor
PYTHONPATH=src:../korban_processor/src python3 -m pytest --rootdir=.
Tests: azazel_processor 71 passed (was 48, +23). korban_processor
unchanged at 203 passed, 1 skipped. No regressions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
a2744c012a |
azazel_processor: commit 2 — vidui (property 3, enumerative commit)
Pass 4 / Finding F, commit 2 of 10 from BUILD_AZAZEL.md.
Vayikra 16:21 — v'hitvadah alav et kol avonot b'nei Yisrael v'et kol
pish'eihem l'chol chatotam. The high priest confesses ALL iniquities,
ALL transgressions, for ALL sins. The verb is hitpa'el (reflexive) —
the confession is self-addressed before it is addressed to the
system. The kol-three-times structure is mandatory.
ENGINEERING READ:
The commit message for a disposal must enumerate what is being
disposed of. Untagged disposal is not disposal; it is loss. Azazel
does not accept uncommitted writes. The enumeration is produced at
the moment of commit, not retrieved from a pre-existing log.
WHY OPERATOR-PRODUCED, NOT AUTO-DERIVED:
The Mishkan high priest speaks the confession aloud. The system did
not generate it from a classification enum. In software: the caller
of confess() MUST supply the descriptions, not have them
auto-generated from a classification → string table. If the table
generated the confession, the operator could dispose state without
ever naming it themselves; the confession would become an
autogenerated label, not a self-addressed enumeration. That is the
failure mode property 3 defends against.
The convenience helpers (confess_empty_vital, confess_halt,
confess_boundary_violation, confess_malformed_input) DO NOT hide
this requirement. Each requires the operator to supply the specific
item; the helper only routes which category it goes into.
THE THREE CATEGORIES:
avonot — iniquities. CATEGORICAL errors: halt-trips
(avodah_zarah, gilui_arayot, shfichut_damim — the
cardinal sins). Sins of orientation.
peshaim — transgressions. BOUNDARY violations: distinction
collapses (gilui-shaped), L3 schema rejections.
Sins of boundary-keeping.
chatot — sins. TARGET errors: empty-vital, malformed responses.
The literal meaning of chet — missing the mark.
Most disposals fill exactly one category. The Vidui dataclass
REQUIRES all three fields (the enumeration is comprehensive at the
shape level), but the lists may be empty. What is forbidden is the
ABSENCE of a slot — and the case where ALL THREE slots are empty
(EmptyConfessionError, no untagged disposal).
CREATED:
src/azazel_processor/vidui.py ~250 lines
Vidui — frozen dataclass: avonot, peshaim, chatot,
confessor, confessed_at (all required;
categories are tuples for deep immutability;
__post_init__ rejects all-empty, empty-string
items, non-string items, anonymous confessor)
EmptyConfessionError — raised on untagged-disposal attempts
confess(*, avonot, peshaim, chatot, confessor)
— the operator-produced builder
Vidui.to_text() — kol-three-times rendering for L3 storage
Vidui.category_summary()
— counts only, no items (for the public
observability endpoint, commit 8 / property 9)
Convenience helpers (each requires operator-supplied item):
confess_empty_vital(korban_summary, *, confessor)
confess_halt(condition, detail, *, confessor)
confess_boundary_violation(violation, *, confessor)
confess_malformed_input(description, *, confessor)
test/test_vidui.py 27 tests covering:
- structural (exactly five fields, no silent additions)
- all category-fill combinations succeed
- all-empty / empty-string / whitespace / non-string items raise
- confessor required (TypeError) + non-empty/non-whitespace
validated (EmptyConfessionError)
- frozen dataclass (cannot mutate after construction)
- categories are tuples (deep immutability — frozen=True does
not propagate through container types)
- confessed_at: timezone-aware UTC, set to current time
- to_text() preserves kol-three-times structure; omits empty
categories
- category_summary() returns counts WITHOUT items
- all four convenience helpers route correctly + still require
confessor
Public surface updated in __init__.py.
Tests: azazel_processor 48 passed (was 21, +27). korban_processor
unchanged at 203 passed, 1 skipped. No regressions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
d06ecd244d |
azazel_processor: commit 1 — skeleton + stages (properties 11 & 12)
Pass 4 / Finding F, commit 1 of 10 from BUILD_AZAZEL.md. Lays the
load-bearing foundation: classification discipline (property 11) at
the type system level, and unmanaged-destination opacity (property
12) via a Python sentinel. Every later commit in the Azazel build
inherits these defenses for free.
CREATED:
azazel_processor/
pyproject.toml — Poetry config matching
korban_processor; pythonpath
includes ../korban_processor/src
so the cross-package
classification-discipline test
can import both packages
src/azazel_processor/
__init__.py — public surface for commit 1:
AzazelStage, AzazelStaged,
AzazelStageError, ERETZ_GEZERAH,
is_eretz_gezerah
eretz_gezerah.py — the destination sentinel; module
__getattr__ refuses any
attribute beyond the two public
names so introspection cannot
discover further handles
stages.py — AzazelStage enum (six
checkpoints from the build doc:
STAGED → CONFESSED → ROUTED →
DESTROYED → ACKNOWLEDGED →
RECORDED), AzazelStaged frozen
dataclass, AzazelStageError
test/
test_eretz_gezerah.py — 11 tests covering: sentinel
identity stable across imports,
not-a-string, no public attrs,
repr leaks no destination info,
identity (is) not equality (==),
impostor with __eq__ rejected,
module __getattr__ refuses
unknown access, CI invariant
forbidding destination-handle
ASSIGNMENTS in source (catches
regressions; mention in
docstrings/__all__/getattr
allowlist is fine)
test_stages.py — 10 tests: intra-Azazel gating
(happy path, mismatch raises
naming both stages, frozen
dataclass, six distinct
checkpoints) and CROSS-PACKAGE
classification discipline:
KorbanStaged and AzazelStaged
are distinct types; enum value
spaces are disjoint; an Azazel
function refuses a KorbanStaged
input and vice versa; no
conversion function exists in
the public surface
PROPERTY COVERAGE THIS COMMIT:
Property 11 (classification discipline):
Defended by the type-distinctness of AzazelStaged vs.
korban_processor.stages.Staged. Cross-package tests verify the
two cannot be substituted at runtime; the absence of any
korban_to_azazel/to_korban/from_korban/convert function in the
public surface is verified by name-shape grep.
Property 12 (unmanaged destination):
Defended by the sentinel design: only object identity is exposed;
no string handle exists; module __getattr__ refuses introspection;
CI invariant catches assignment-shaped regressions across all of
azazel_processor/src/.
PENDING (commits 2-10 per BUILD_AZAZEL.md):
vidui (commit 2 — enumerative commit, property 3)
shaliach + tevilah (commit 3 — role-separated operator, property 4)
goral (commit 4 — blind routing, property 2)
azazel orchestrator + state machine (commit 5 — properties 5, 7, 10)
protocol envelope (commit 6 — protocol-restricted access, property 8)
L3 azazel_records (commit 7 — operator-internal half of property 9)
/peah/azazel_log endpoint (commit 8 — public half of property 9)
Korban→Azazel routing on empty-vital + chatzotzrot wiring
(commit 9 — properties 1, 6)
Closure pass (commit 10)
Tests: azazel_processor 21 passed. korban_processor unchanged at
203 passed, 1 skipped (live-API). No regressions across the boundary.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
1fb7d33124 |
BUILD_AZAZEL.md: design doc for the disposal-channel build (finding F prep)
Pass 2 finding F (the largest remaining design gap) gets its build
plan. The Azazel Processor is the disposal twin of Korban Processor,
parallel in every way the two-goat architecture (Yoma 62a, shavim
b'mareh uv'komah uv'd'mim) requires:
- same module layout (azazel_processor/ parallel to korban_processor/)
- same Python toolchain
- same staging discipline (mirrors stages.py, with classification-
discipline enforced via type system — AzazelStage and KorbanStage
are non-interconvertible)
- same role-gating (vidui + shaliach + tevilah + ish itti
discipline)
- same observability (L3 azazel_records table parallel to
interpretations, public observability endpoint)
The doc is structured to be self-contained for the next session. It
covers:
1. What to read first (engineering reading lives in chat downloads,
not the repo)
2. What this is for (four classes of input the system today drops
instead of disposing — empty-vital, halt-tripping, malformed,
L3 write rejections)
3. Module structure (12 files, ~1,200 lines estimated, parallel to
korban_processor)
4. The 12 properties from azazel_engineering_reading.md §14 mapped
to specific code instantiations — one row per property, no
property collapsed into another
5. Integration points (Korban Processor, L3, Chatzotzrot, Bigdei
Kodesh, Prolog halt rules) and what changes in each
6. Six open design questions the engineering reading does NOT
decide (when the goral fires, what Yom Kippur means in software,
what tevilah looks like in Python, commit-then-reveal mechanics
for the goral, where the cross-language L3 write happens, what
to expose in the public observability endpoint)
7. Ten-commit staging plan, each independently testable, with
property coverage marked per commit
8. Test surface (~30-40 tests, with a property-defended-by-test
mapping so the property-coverage matrix is visible)
9. Three end-to-end scenarios that prove the build is complete
The Build doc itself implements no code. The next session opens
this file, reads the engineering reading, and lands commit 1
(Skeleton + stages, ~250 lines) as a clean starting point.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
d88b8d09ec |
voice_monitor: detect across-verse Shema voice stabilization (closes finding A)
Pass 2 finding A, second half. The first half (commit
|
||
|
|
b1d5ac69e4 |
l3: validate coupling_trace at the write boundary (Elixir half of finding L)
Companion commit to |
||
|
|
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>
|
||
|
|
d3869c5b75 |
l1_reader: document te'amim classification principle and shoresh derivation provenance
Two related Pass 2 findings — both about UNSTATED reasoning behind
hard-coded lists in L1Reader.
Finding G — te'amim classification principle:
@melekh_marks and @mesharet_marks were lists of strings with no
documented rule for which marks belong to which class. A future
reader saw the lists but not the deliberation. Added classification
principle to the comment block:
- The diagnostic test: can the ta'am occupy a sof-pasuk-equivalent
position? → melekh. Does it only serve a following melekh? →
mesharet.
- The hierarchy of melakhim corresponds to syntactic depth
(sof_pasuk → etnachta → zaqef → tipcha → revia → …).
- Mesharetim precede their melakhim — the servant goes before
the king.
- The classification is BINDING for havdalah: treating a mesharet
as a phrase-end is a small gilui_arayot.
- Source authority: Yeivin's Tiberian Masorah §174-§268.
- Maintenance rule: if OSHB ever encodes a ta'am with a different
name, BOTH lists must be updated.
Finding H — shoresh derivation provenance:
extract_roots/1 returned a flat root → gematria map without recording
which derivational opinion produced each shoresh. For triliterals
this rarely matters; for weak roots and biliterals it matters
constantly (e.g. וְנָתַתִּי in Devarim 11:14 — OSHB records נת,
classical analysis often gives נתן with assimilated nun). Added
provenance note to the function docstring:
- The shoresh comes from OSHB lemma database via build_l1.py
- OSHB pins one root per word at build time; L1 JSON does not
record which derivational opinion produced it
- Downstream consumers MUST NOT treat the returned shoresh as
unique-truth
- The Talmudic Engine should record the derivational source
in coupling_trace when a shoresh-based ruling enters L3 —
otherwise a future re-derivation cannot be reconciled with
prior opinions stored on top of the OSHB choice
- Preserving derivational alternatives belongs in the build_l1.py
enrichment pass, not in this read function
Both docstrings make the WHY visible; the WHAT was already there.
No code change. Tests pass unchanged (288 in vav_pipeline,
8 in shema_voice_trajectory_test).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|