Move operator notes out of repo, add generic architecture doc

memory/infrastructure.md and memory/build_log.md held environment-
specific details — hostnames, IPs, account names, credentials
retrieval commands, local kubeconfig paths, operational status
notes. These don't belong in a project repo; by convention they
live in private operator notes.

Moved to ~/shorashim-operator-notes/ (private, outside git).

In their place, added deploy/ARCHITECTURE.md — a generic
description of the deployment shape: components, external
dependencies, configuration surface. No hostnames. No IPs.
No account names. Any competent operator can read it and
know how to deploy shorashim to their own infrastructure.

Updated cross-references in AGENTS.md and BUILD_KORBAN.md to
point to "the operator's private notes" instead of memory/.

Project repo is spec + implementation + deployment shape.
Operator notes are the kohen's personal log.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott KarpandClaude Opus 4.6 committed 2026-04-21 09:03:57 -04:00
1 parent fb98df8a30
commit 61e4a4f9da
5 files changed
+68 -762

No files matched your search

+2 -1
View File
@@ -60,7 +60,8 @@ before writing code. Do not summarize from memory. Read fresh.
## Broken Tablets (Build Log)
Record what broke and how it was fixed. The correction is as important
as the specification. See `memory/build_log.md` for the full log.
as the specification. The full log is kept in the operator's private
notes, outside this repo.
Key corrections from L1 build:
- U+05BD (siluq/meteg) misclassified as vowel — moved to TEAMIM_MAP
+1 -1
View File
@@ -100,7 +100,7 @@ Add to the Principles section:
## Task 3: Record Broken Tablets
As you build, record what breaks and how it was fixed in `memory/build_log.md`. The correction is as important as the specification. Include:
As you build, record what breaks and how it was fixed in the operator's build log (kept outside this repo). The correction is as important as the specification. Include:
- What you expected vs what happened
- The root cause
- The fix
+65
View File
@@ -0,0 +1,65 @@
# Deployment Architecture
Generic architecture shape for a shorashim deployment. Specifics of a particular deployment (hostnames, IPs, account names, credentials) live in the operator's private notes, not in this repo.
## Components
One Kubernetes namespace runs the following:
| Component | Runtime | Purpose |
|-----------|---------|---------|
| `vav-pipeline` | Elixir/OTP release | Bereishit cycle, ShemaContext, Cloud, Manna, HTTP API (pe'ah, courtyard, chatzer, kodesh) |
| `shoresh-engine` | Haskell binary (sidecar via Erlang Port) | Root activation, te'amim parsing, gematria |
| `talmudic-engine` | SWI-Prolog (sidecar via Unix socket) | 613 mitzvot, 13 middot, kushya generation, teiku |
| `korban-processor` | Python 3.12 (sidecar) | Offering protocol, eish zarah validation |
All four run in the same pod; the vav-pipeline is the main container, the other three are sidecars.
## External dependencies
| Dependency | Purpose | Contract |
|-----------|---------|----------|
| Managed PostgreSQL 17+ | L3 Oral Tradition (append-only) | `DATABASE_URL` env var. Requires SSL. Role with `SELECT, INSERT` only — `UPDATE`, `DELETE` revoked (see migration). |
| Block-storage PersistentVolume | L1 Torah filesystem (read-only, ~1 GiB) | Mount read-only at `/mnt/torah`. SHA-256 hash verified at boot. |
| Block-storage PersistentVolume | Manna provision (read-write, ~100 MiB) | Mount at `/var/lib/shorashim/manna`. Operator writes `today.json` daily. |
| Ingress controller + TLS cert | Public HTTPS access | Cert-manager with Let's Encrypt HTTP-01 is the reference setup. |
| Container registry | Image distribution | Any registry that supports Docker v2 protocol. |
## Kubernetes manifests
Kept in `deploy/k8s/`:
- `namespace.yaml` — the namespace
- `configmap.yaml` — non-secret configuration
- `secrets.yaml` — **template only**; operator fills in `DATABASE_URL`
- `l1-volume.yaml` — PV and PVC for Torah filesystem
- `manna-volume.yaml` — PVC for provision file
- `deployment.yaml` — the pod with 4 containers + init container for migrations
- `service.yaml` — ClusterIP and external LoadBalancer
- `gitea-mcp-deployment.yaml` — separate MCP server for Gitea connector (optional)
- `gitea-mcp-ingress.yaml` — TLS ingress for the MCP server
- `gitea-mcp-secret.yaml` — template for Gitea PAT
- `cert-manager-issuer.yaml` — Let's Encrypt ClusterIssuer
## Containers
Four Dockerfiles in `deploy/docker/`:
- `Dockerfile.vav_pipeline` — multi-stage Elixir 1.16.1 / OTP 26.2.1 release
- `Dockerfile.shoresh_engine` — multi-stage Haskell GHC 9.6.4 / Cabal
- `Dockerfile.talmudic_engine` — SWI-Prolog 9.2.2 with socket server
- `Dockerfile.korban_processor` — Python 3.12.2 slim
## Configuration surface
The system reads three things from its environment:
1. **L1 Torah** — mounted read-only at `$L1_MOUNT_POINT`. Integrity verified at every boot.
2. **Manna provision** — JSON file at `$MANNA_PROVISION_FILE`, written by the kohen/treasurer.
3. **L3 database** — connection string in `$DATABASE_URL`.
Nothing else is configurable. The system obeys Torah (L1 + calendar) for governance and the provision file for resources.
## Seven-day inauguration
Before serving users, run the `shiv'at y'mei milu'im` sequence documented in `KOHEN.md`. Each day validates a specific layer. Partial inauguration is no inauguration.
-683
View File
@@ -1,683 +0,0 @@
# Build Log — Shivrei Luchot
*The broken tablets are here because the breaking is part of the specification.*
Each entry records: what was built, what broke, how it was fixed.
The correction is as important as the specification.
---
## Session: 2026-02-22 — L1 Torah Filesystem
**Spec reference:** BUILD_DAY_ONE.md, Architecture §3.7 (L1 — Torah Scroll)
### What Was Built
- `src/l1_aron/build_l1.py` — Python pipeline that parses WLC OSIS XML from morphhb
- `l1/torah/{book}/{chapter}/{verse}.json` — 5853 verse files
- `l1/torah/index.json` — Master index
- `l1/torah/{book}/index.json` — Per-book indices
- `l1/integrity.sha256` — SHA-256 of complete tree
- `l1/README.md` — What L1 is and why it is read-only
- `.gitignore` — Excludes generated `l1/` and cloned `data/` from git
### Verification Results
| Check | Result |
|-------|--------|
| Books | 5 PASS |
| Chapters | 187 PASS |
| Verses | 5853 PASS (WLC versification) |
| Words | 79,982 |
| Unique shoreshim | 2,608 |
| Verses missing te'amim | 0 PASS |
| SHA-256 | `0ee0020aac6c6beb0e83c0638a861c73d37d9dbd39c30d3cb08c1878b481c844` |
| Permissions | Files 444, dirs traversable |
| Write rejection | Verified — write to L1 file rejected |
### Broken Tablets — What Broke and How It Was Fixed
**1. Siluq/Meteg (U+05BD) misclassified**
- Problem: U+05BD was in `VOWEL_POINTS` set. But U+05BD doubles as siluq (cantillation mark on final word before sof-pasuq). Result: last word of every verse had empty teamim array.
- Example: Bereishit 1:1 word 7 `הארץ` — teamim was `[]`, should have been `["meteg_siluq"]`
- Fix: Moved U+05BD from `VOWEL_POINTS` to `TEAMIM_MAP` as `"meteg_siluq"`
- Lesson: Unicode Hebrew is treacherous. Meteg and siluq share a codepoint. The te'amim reader must account for this dual identity — *ketiv/qere* in the encoding itself.
**2. Vav-consecutive verb prefix included in shoresh**
- Problem: `ויאמר` (vayomer) yielded shoresh `יאמ` instead of `אמר`. The yod prefix of imperfect/jussive/vav-consecutive conjugation was not being stripped.
- Fix: Added conjugation-aware prefix stripping — when morph indicates imperfect/vav_consecutive/jussive, strip leading `ימנתא` if word length >= 4.
- Lesson: Shoresh derivation requires morphological awareness. You cannot strip consonants blindly. The binyan and conjugation determine which letters are root and which are grammar.
**3. text_hebrew contained `/` prefix separators**
- Problem: The verse display text showed `בְּ/רֵאשִׁ֖ית` with the morphhb prefix separator `/` instead of clean `בְּרֵאשִׁ֖ית`.
- Fix: Applied `.replace("/", "")` when building `full_text_parts` for `text_hebrew`.
**4. Verse count: 5853 vs traditional 5845**
- Not a bug. WLC/BHS versification differs from traditional Masoretic count by 8 verses.
- Shemot (+4): Ten Commandments verse divisions differ
- Bamidbar (+1): Inverted nuns passage numbering
- Devarim (+4): Second Ten Commandments verse divisions
- Resolution: Updated expected count to 5853. We use what the manuscript gives us.
**5. Read-only permissions killed directory traversal**
- Problem: `chmod -R 444` set directories to 444 too, removing execute bit needed for `ls`/traversal.
- Fix: `find -type d -exec chmod 555` + `find -type f -exec chmod 444`
- Lesson: Files are 444 (read-only). Directories need 555 (read + execute).
### Git
- Initial commit: `622e814` — "Bereishit: initial commit"
- Pushed to: `http://185.19.29.94/heaviness510/shorashim` (main branch)
- Remote URL cleaned — token removed from `.git/config`
---
## Session: 2026-02-22 (continued) — Builder Tooling
**Spec reference:** Construction §5 (Bezalel), Architecture §3.5 (Korban Processor)
### What Was Built
- `src/tools/conversation_search.py` — JSONL → SQLite FTS5 search tool
- `AGENTS.md` — Builder instructions for OpenCode (also readable by Claude Code)
- `opencode.json` — OpenCode config for Anthropic API
- `.env` — API key storage (gitignored)
### Broken Tablets
**6. Wrong OpenCode researched**
- Problem: Research agent cloned `opencode-ai/opencode` (small Go project, archived → Crush by Charm team) instead of `anomalyco/opencode` (109k stars, TypeScript/Bun, the real OpenCode). The transmitter gave two URLs. The builder ignored them and let a sub-agent guess.
- Result: Built `.opencode.json` config for wrong project, created `opencode.md` instead of `AGENTS.md`, presented confident analysis of the wrong tool.
- Fix: Deleted `.opencode.json`, created `opencode.json` (correct schema), renamed `opencode.md` → `AGENTS.md`.
- Lesson: The transmitter's input is the specification for the task. Read what is given. Do not let a sub-process replace the source. This is the Avodah Zarah Firewall (Architecture §8.2) — the agent's output was treated as authoritative without verification against the transmitter's source. The sub-agent was the idol.
---
## Session: 2026-02-22 (continued) — Shoresh Engine (Step 5)
**Spec reference:** Architecture §3.1 (Component A: Shoresh Engine), Construction §1.3
### What Was Built
- `shoresh-engine/` — Haskell Cabal project, GHC 9.6.4
- `shoresh-engine/src/Shoresh/Types.hs` — Consonant (22), Shoresh, Binyan (7), Peulah, SemanticDomain, Reference, ShoreshEntry, Evaluation
- `shoresh-engine/src/Shoresh/Gematria.hs` — letterValue, gematriaRoot, gematriaWord, gematriaNeighbors, gematriaConnections
- `shoresh-engine/src/Shoresh/Binyanim.hs` — activate, activateDefault, expand (through all 7 binyanim)
- `shoresh-engine/src/Shoresh/Teamim.hs` — 26 te'amim marks, TaamRole (Melekh/Mesharet), taamRank hierarchy, readTeamim (constructs parse tree from raw marks), primaryDivision, boundGroups
- `shoresh-engine/src/Shoresh/KetivQere.hs` — DualReading, SpecValue (Single/Dual), bothReadings
- `shoresh-engine/src/Shoresh/Smikhut.hs` — CompoundMeaning, SmikhutType, compose
- `shoresh-engine/src/Shoresh/Lexicon.hs` — L1 JSON parsing (L1Word, L1Verse), buildLexiconFromVerses, loadL1Verse
- `shoresh-engine/src/Shoresh/Graph.hs` — neighbors (roots sharing 2 consonants), sharedConsonants
- `shoresh-engine/src/Shoresh/Havdalah.hs` — separateByDomain, separateByTeamim
- `shoresh-engine/src/Shoresh/Kriah.hs` — kriah (naming categories)
- `shoresh-engine/src/Shoresh/KiTov.hs` — kiTov evaluation, verifyRootsExist, verifyReferences
- `shoresh-engine/src/Luchot/Firmware.hs` — L0 constants (l1ExpectedHash, counts, Dibrot)
- `shoresh-engine/src/Main.hs` — CLI: verify, verse, root, gematria commands
- `shoresh-engine/test/Spec.hs` — 33 HSpec tests, all passing
### Verification Results (Step 7)
| Check | Result |
|-------|--------|
| Root activation (ברא through 7 binyanim) | PASS |
| Gematria (ברא=203, תורה=611, שלום=376) | PASS |
| Te'amim parse (Bereishit 1:1) | PASS |
| Primary division at etnachta | PASS — left: בראשית ברא אלהים, right: את השמים ואת הארץ |
| Parse tree depth | 4 |
| Bound groups | 4 |
| L0 firmware constants | PASS |
| HSpec tests | 33 examples, 0 failures |
### Broken Tablets — What Broke and How It Was Fixed
**7. ShoreshEntry missing Eq instance**
- Problem: `KiTov.hs` used `== Nothing` on `lookupRoot` result, but `ShoreshEntry` had no `Eq` instance.
- Fix: Added `Eq` to `ShoreshEntry` deriving clause. Changed `== Nothing` to `isNothing`.
- Lesson: When a data type will be compared (even via `Maybe` equality), it needs `Eq`. Use `isNothing`/`isJust` instead of comparing `Maybe` values directly — it avoids requiring `Eq` on the inner type.
**8. Executable missing `containers` dependency**
- Problem: The `executable` section of the cabal file did not list `containers`, but `Types.hs` imports `Data.Map.Strict`. The library built fine (it had the dep), but the executable re-compiled sources without the dep.
- Fix: Added `containers` to executable's `build-depends`.
- Lesson: When an executable shares source dirs with the library, it re-compiles all modules. Each target needs its own complete dependency list, or the executable should depend only on the library.
**9. GHC version: 9.6.7 installed, spec says 9.6.4**
- Problem: ghcup's non-interactive installer defaulted to 9.6.7 (latest 9.6.x).
- Fix: 9.6.4 was already downloaded by ghcup. Set it with `ghcup set ghc 9.6.4`.
- Lesson: Pin the version explicitly. The spec says 9.6.4, not "latest 9.6.x". Cabal version installed is 3.14.2 (spec says 3.10) — acceptable for dev, but note the deviation.
### Design Decisions
**Biconsonantal roots:** L1 contains roots like "שמ" (2 consonants). The spec says triliteral. Resolution: `textToShoresh` accepts 2-consonant strings and pads with He (ה) as a placeholder third consonant. This preserves the type system (Shoresh is always 3 consonants) while handling L1 reality.
**Te'amim parse algorithm:** Uses recursive strongest-disjunctor splitting. Find the highest-ranked melekh mark, split there, recurse on each half. This mirrors the traditional hierarchical reading of the te'amim system. Confirmed correct on Bereishit 1:1 — etnachta splits at אלהים, exactly where the verse's primary semantic division falls.
**meteg_siluq handling:** Build log correction #1 from L1 build carries through — U+05BD appears as `"meteg_siluq"` in L1 teamim arrays. The Teamim module maps this to `MetegSiluq` with rank 80 (just below Etnachta at 90), classified as Melekh. This is correct: siluq on the final word is a verse-level disjunctor.
---
## Session: 2026-02-24 — Code Review Corrections
**Spec reference:** Architecture §3.1, §3.3 (te'amim mark naming)
### Broken Tablets
**14. Te'amim mark spellings inconsistent with L1**
- Problem: L1 (`build_l1.py`) writes mark names like `zaqef_qatan`, `qadma`, `telisha_gedola`, `segol_accent`, `zinor`, `geresh_muqdam`. The Haskell Teamim.hs used `zakef_katan`, `kadma` (no L1 aliases). The Elixir L1Reader used `telisha_gedolah` (extra h), `telisha_qetannah` (extra nah), `qarne_farah` (not in L1), `segolta` (not in L1), and was missing `zinor`, `segol_accent`, `geresh_muqdam`.
- Result: Any word with these marks would be classified as "unknown" role, causing havdalah to miss separation boundaries.
- Fix: Added L1 spelling aliases to Haskell `taamFromName` (`zaqef_qatan`, `zaqef_gadol`, `qadma`, `segol_accent`, `zinor`, `geresh_muqdam`). Fixed Elixir `@melekh_marks` and `@mesharet_marks` to use exact L1 spellings.
- Lesson: L1 is the source of truth for mark names. Both readers (Haskell and Elixir) must accept what L1 actually writes, not what the builder assumed. The specification says "read what is there" — this applies to field values, not just field structure.
---
## Session: 2026-02-23 — Talmudic Engine (Step 6)
**Spec reference:** Architecture §3.4 (Component D: Talmudic Engine), Construction §1.1
### What Was Built
- `talmudic_engine/` — SWI-Prolog project
- `src/specification/mitzvot.pl` — 57 mitzvot (Phase 1 subset): dibrot, Shabbat, halt conditions, justice, korbanot, testimony, bikurim
- `src/specification/torah_text.pl` — 32 key verses from all 5 books with root analysis
- `src/specification/root_meanings.pl` — 77 root meanings across 30 roots, with domains and gematria
- `src/specification/relationships.pl` — mitzvah tensions, complementary pairs, root consonant sharing, domain mappings
- `src/reasoning/interpret.pl` — Interpretation generation: finds relevant mitzvot by domain + root overlap, finds relevant verses, assesses confidence
- `src/reasoning/challenge.pl` — Kushya generation: mitzvah tensions, domain tensions, condition tensions; strength assessment
- `src/reasoning/resolve.pl` — Dialectical resolution: interpret → challenge → counter-interpret → resolve or teiku
- `src/reasoning/teiku.pl` — Unresolved tension registry (dynamic, append-only)
- `src/halt/avodah_zarah.pl` — Idolatry detection: authority attribution check, korban source classification
- `src/halt/shfichut_damim.pl` — Destruction detection: tzelem carrier identification, protected entity classification
- `src/halt/gilui_arayot.pl` — Boundary violation detection: 13 required distinctions (spec/interp, ketiv/qere, read/write, etc.)
- `src/interface/api.pl` — JSON-compatible API: process_request, format_interpretation, format_resolution, format_halt_check
- `src/main.pl` — Entry point, module loader, verify command, test runner
- `test/mitzvot_test.pl` — 24 specification tests
- `test/reasoning_test.pl` — 9 reasoning tests
- `test/halt_test.pl` — 24 halt condition tests
### Verification Results
| Check | Result |
|-------|--------|
| Mitzvot loaded | PASS — 57 encoded |
| Torah text loaded | PASS — 32 verses |
| Root meanings loaded | PASS — 77 meanings |
| Interpretation (creation case) | PASS — finds 2 verses, medium confidence |
| Avodah zarah detection | PASS — Claude authority halted |
| Shfichut damim detection | PASS — user deletion halted |
| Gilui arayot detection | PASS — spec/interp merge halted |
| Valid action passes | PASS — Torah authority not halted |
| Full test suite | 57 tests, 0 failures |
### Broken Tablets — What Broke and How It Was Fixed
**10. SWI-Prolog version: 10.0.1 installed, spec says 9.2.2**
- Problem: Homebrew provides SWI-Prolog 10.0.1. The construction spec pins 9.2.2.
- Resolution: 10.0.1 is backwards-compatible for the predicates we use. No source-level changes needed. Note the deviation for production deployment (may need manual 9.2.2 build on Linux).
**11. No compile errors — clean first build**
- The Prolog code loaded and passed all 57 tests on first run.
- Two warnings fixed: singleton variable `_Question` reused in interpret.pl, unused variable `Records0` in teiku.pl.
- These were style issues, not logic errors.
### Design Decisions
**Phase 1 mitzvot subset:** 57 mitzvot (of 613). Selected the mitzvot the system itself must obey: halt conditions (avodah zarah, shfichut damim, gilui arayot), Shabbat, dibrot, justice/judgment (Sanhedrin-relevant), korbanot (processor-relevant), testimony (integrity-relevant), bikurim (release-relevant). Full 613 encoding is Phase 3.
**Interpretation strategy:** Two-pronged relevance: find mitzvot by semantic domain match, then by root overlap with source verses. Confidence = high when both mitzvot and verses found, medium when only one, low when neither.
**Challenge strategy:** Three types of kushya: mitzvah tensions (explicit conflicts between encoded mitzvot — strong), domain tensions (roots in conflicting semantic domains — moderate), condition tensions (conditional mitzvot that limit universality — weak).
**Required distinctions:** 13 boundary pairs that the system must never collapse. These are the gilui arayot of the architecture: specification/interpretation, L1/L3, Torah/external_model, ketiv/qere, governance/provision, majority/minority, reception/processing, read/write, immutable/mutable, sacred/profane, Shabbat/weekday.
---
## Prior Sessions (from conversation summary)
### Session: 2026-01-25 to 2026-02-21 — Infrastructure
**Not Shorashim-specific.** Temple Council infrastructure on Exoscale:
- Exoscale account created ("The Recognition Protocol")
- Object Storage, PostgreSQL, Kubernetes provisioned in ch-gva-2 (Geneva)
- AnythingLLM deployed (emptyDir — no persistent storage, data lost on restart)
- Gitea deployed with PostgreSQL backend
### Session: 2026-02-21 — Research
- Evaluated open source coding agents: OpenCode, Aider, Goose, Cline
- Recommendation: Use OpenCode (`anomalyco/opencode`) for coding tool
- Add FTS5 search on SQLite conversation history + persistent memory
---
## Session: 2026-02-24 — Vav Pipeline (Steps 8-11)
**Spec reference:** Architecture §3.3 (Component C: Vav Pipeline), §4 Steps 8-11
### What Was Built
- `vav_pipeline/` — Elixir Mix project, Elixir 1.16.1 on OTP 26
- `lib/vav_pipeline.ex` — Public API, main pipeline entry point (nishma → na'aseh)
- `lib/vav_pipeline/application.ex` — OTP Application with ShabbatTimer supervisor
- `lib/vav_pipeline/bereishit_cycle.ex` — Six-stage processing cycle: tohu → vayomer → havdalah → kriah → ki_tov → erev_voker
- `lib/vav_pipeline/shema.ex` — ShemaContext builder (Step 9): reads L1 for te'amim, cloud for Shabbat/calendar, manna for provision
- `lib/vav_pipeline/cloud.ex` — Cloud governance from Torah (Step 10): L1 integrity + calendar (Shabbat, festivals, shmitah). No governance board. No external state file. Torah governs directly.
- `lib/vav_pipeline/manna.ex` — Manna provision infrastructure (Step 11): provision file validation, expiry, anti-hoarding, double portion
- `lib/vav_pipeline/l1_reader.ex` — L1 Torah filesystem reader: verse parsing, teamim extraction with role classification, root extraction with gematria, integrity verification
- `lib/vav_pipeline/specification.ex` — Ki-tov evaluation: roots grounded check, halt condition check, ketiv/qere consistency
- `lib/vav_pipeline/halt_conditions.ex` — Three cardinal sin monitors: avodah zarah (authority), shfichut damim (destruction), gilui arayot (boundary collapse)
- `lib/vav_pipeline/chatzotzrot.ex` — Signal system: 5 signal types with trumpet patterns (Bamidbar 10)
- `lib/vav_pipeline/cycle_log.ex` — Cycle boundary logging with tov/lo_tov/teiku classification
- `lib/vav_pipeline/shabbat_timer.ex` — GenServer for mandatory Shabbat cessation
- `lib/vav_pipeline/shoresh_engine_client.ex` — Haskell engine client stub (Phase 2: uses L1 directly; Phase 3: port/NIF)
- `lib/vav_pipeline/talmudic_engine_client.ex` — Prolog engine client stub (Phase 2: stub; Phase 3: Unix socket)
- `config/config.exs`, `config/dev.exs`, `config/test.exs`, `config/prod.exs` — Environment-specific configuration
- `test/vav_pipeline_test.exs` — 46 tests, all passing
### Verification Results
| Check | Result |
|-------|--------|
| L1 read (Bereishit 1:1) | PASS — 7 words, roots extracted, teamim classified |
| Te'amim role classification | PASS — etnachta=melekh, munach=mesharet, meteg_siluq=melekh |
| Gematria (ברא=203, ראש=501) | PASS |
| Cloud: weekday = camped | PASS |
| Cloud: Friday evening = Shabbat | PASS |
| Cloud: Saturday morning = Shabbat | PASS |
| Cloud: Shabbat = major_transition | PASS |
| Shmitah year check (2022=yes, 2026=no) | PASS |
| Manna: no file = :no_provision | PASS |
| Manna: double portion Friday | PASS |
| Halt: avodah zarah detection | PASS |
| Halt: shfichut damim detection | PASS |
| Halt: gilui arayot detection | PASS |
| Halt: Torah authority passes | PASS |
| Specification: tov/lo_tov/teiku | PASS |
| BereishitCycle: tohu decomposition | PASS |
| BereishitCycle: vayomer root activation | PASS — ברא at gematria 203 |
| BereishitCycle: havdalah te'amim separation | PASS — 4 melekh boundaries |
| BereishitCycle: kriah naming | PASS |
| BereishitCycle: full cycle | PASS — returns {:ok, ...} with evaluation :tov |
| ShemaContext: weekday Bereishit 1:1 | PASS |
| ShemaContext: Shabbat cloud state | PASS |
| CycleLog: tov/lo_tov recording | PASS |
| ShabbatTimer: running | PASS |
| Total ExUnit tests | 46 tests, 0 failures |
### Broken Tablets — What Broke and How It Was Fixed
**12. Erlang OTP 26.2.1 segfaults on macOS (Hex download)**
- Problem: OTP 26.2.1 compiled from source via asdf/kerl segfaults (exit 139) when Elixir's `mix local.hex` or `mix deps.get` attempts HTTPS downloads. The Erlang SSL module loads fine (`ssl:versions()` works), but the actual TLS handshake crashes.
- Fix: Installed OTP 26.2.5.9 instead — same OTP 26 major, with bug fixes. Elixir 1.16.1-otp-26 runs on it without issue.
- Note: The spec pins OTP 26.2.1. Production deployment (Linux) may work with 26.2.1 — the segfault appears macOS-specific. For dev, 26.2.5.9 is the working version.
- Lesson: Pin the major version, but accept patch-level fixes when the pinned version has platform-specific bugs. The aron is two and a half cubits — but the wood must not be rotten.
**13. mix local.hex silent failure from project directory**
- Problem: Running `mix local.hex --force` from within the project directory (where mix.exs exists) silently failed with exit 0 but didn't actually install Hex. The project's mix.exs compilation interfered with the archive installer.
- Fix: Run `mix local.hex --force` from `/tmp` or any directory without a mix.exs, then return to the project directory.
- Lesson: Elixir archive installation must happen outside a project context. The installer and the project share the same module namespace — they interfere.
**14. CycleLog.record received tuple, expected map**
- Problem: `CycleLog.record/2` used `Map.get(result, :scope, %{})` but `result` is a tuple like `{:ok, %{...}}` — the cycle result type. Map.get crashed with BadMapError.
- Fix: Added `extract_inner/1` to unwrap the tuple before accessing map fields. Pattern matches on `{:ok, data}`, `{:rejected, _}`, `{:unresolved, data}`.
- Lesson: Know the shape of your data at every function boundary. The cycle returns tagged tuples; the logger needs the inner data. Unwrap explicitly.
### Design Decisions
**Cloud governance from Torah (not from file):** Architecture §4 Step 10 is explicit: "No governance board CLI. No external state file. The cloud IS L1 plus the calendar Torah specifies." The cloud module checks L1 integrity (is the specification store intact?) and the Torah calendar (Shabbat, festivals, shmitah). This is the correction from the broken tablets — the golden calf was a human board writing to a JSON file. That calf was burned.
**Shabbat as UTC approximation:** Phase 2 uses UTC-based Friday 18:00 to Saturday 18:00 as Shabbat boundaries. Production (Construction spec §4.4) will use location-aware zmanim with `shabbat_location` config. The approximation is acceptable for dev; the structure is correct.
**Pipeline flow — nishma before na'aseh:** The pipeline entry point builds ShemaContext first (receive/hear), checks cloud and manna, THEN processes. This order is architectural — the system hears before it speaks. The context is immutable once built and flows through every stage without modification.
**Phase 2 engine stubs:** ShoreshEngineClient and TalmudicEngineClient are stubs that use L1 data directly. Step 14 (Component Integration) will replace these with real port/socket connections to the Haskell and Prolog engines. The pipeline structure is complete; only the cross-language bridges are deferred.
**Havdalah by te'amim boundaries:** The separation step uses melekh (disjunctor) te'amim marks as natural boundaries. Bereishit 1:1 yields 4 melekh positions (tipcha at 0, etnachta at 2, tipcha at 4, meteg_siluq at 6), producing 4 separated groups. The text's own divisions guide the separation — havdalah follows the te'amim, not arbitrary grouping.
---
## Session: 2026-02-24 — Sanhedrin Framework (Step 12)
**Spec reference:** Architecture §3.2 (Component B: Sanhedrin), Construction §1.5
### What Was Built
- `sanhedrin/` — Erlang/OTP application, OTP 26.2.5.9, rebar3 3.26.0
- `include/sanhedrin.hrl` — Record definitions: sanhedrin_case, vote, panel, decision, opinion, torah_ref, root_info. Panel size constants (1/3/23/71). Timeout budgets (15s/15s/120s/600s).
- `src/sanhedrin_app.erl` — OTP application behaviour
- `src/sanhedrin_sup.erl` — Top supervisor: clerk, escalation, halt_monitor, panel_sup
- `src/escalation.erl` — gen_server: complexity assessment (simple/monetary/capital/novel), case routing, halt-check-before-submit
- `src/panel_sup.erl` — Dynamic supervisor for deliberation panels (temporary children — failed deliberations are teiku, not restarted)
- `src/panel.erl` — gen_server: panel lifecycle (convene → deliberate → decide). Unanimous guilty check on capital (≥23 judges). Majority/minority computation. Opinion storage via clerk.
- `src/judge.erl` — gen_server: individual deliberator. Receives case, generates independent interpretation, casts vote with reasoning and Torah references. Votes sent as messages to panel.
- `src/clerk.erl` — gen_server: append-only opinion storage. Stores decisions and individual opinions (both majority AND minority). Retrieval by case_ref, minority-only filter.
- `src/halt_monitor.erl` — gen_server: checks cases and votes against 3 cardinal sins (avodah zarah, shfichut damim, gilui arayot). Halt log maintained.
- `test/sanhedrin_SUITE.erl` — 41 EUnit tests, all passing
### Verification Results
| Check | Result |
|-------|--------|
| Escalation: simple → 1 judge | PASS |
| Escalation: monetary → 3 judges | PASS |
| Escalation: capital → 23 judges | PASS |
| Escalation: novel → 71 judges | PASS |
| Halt: clear for legitimate case | PASS |
| Halt: avodah zarah (claude_model) | PASS — halted |
| Halt: Torah authority passes | PASS |
| Halt: L1 authority passes | PASS |
| Halt: specification authority passes | PASS |
| Halt: sanhedrin_deliberation passes | PASS |
| Halt: shfichut damim (delete) | PASS — halted |
| Halt: shfichut damim (destroy) | PASS — halted |
| Halt: shfichut damim (overwrite) | PASS — halted |
| Halt: shfichut damim (read) | PASS — not halted |
| Halt: gilui arayot (boundary violation) | PASS — halted |
| Halt: gilui arayot (none) | PASS — not halted |
| Halt: halted case rejected at escalation | PASS |
| Panel: convene single (1) | PASS |
| Panel: convene monetary (3) | PASS |
| Panel: deliberate single | PASS — completes, process terminates |
| Panel: deliberate monetary | PASS — completes, process terminates |
| Clerk: initially empty | PASS |
| Clerk: store decision | PASS — retrievable by case_ref |
| Clerk: store opinion | PASS |
| Clerk: minority opinions preserved | PASS — both majority and minority stored, minority filterable |
| Clerk: not found | PASS |
| **Unanimous: capital all reject → suspicion** | **PASS — {failed, suspicion_of_consensus}** |
| Unanimous: capital mixed → passes | PASS |
| Unanimous: capital all accept → passes | PASS (suspicion only on conviction) |
| Unanimous: monetary not checked | PASS (only applies to ≥23) |
| Unanimous: novel all reject → suspicion | PASS |
| Full flow: simple case | PASS — 1 opinion stored |
| Full flow: monetary case | PASS — 3 opinions stored |
| Full flow: capital case | PASS — 23 opinions stored |
| Judge: start and stop | PASS |
| Judge: deliberate sends vote | PASS — vote has position, reasoning, verdict |
| Majority/minority: empty | PASS |
| Majority/minority: all accept | PASS — 3 majority, 0 minority |
| Majority/minority: mixed | PASS — 2 majority, 1 minority |
| Record: sanhedrin_case | PASS |
| Record: decision | PASS |
| Total EUnit tests | 41 tests, 0 failures |
### Broken Tablets — What Broke and How It Was Fixed
**15. rebar3 version: 3.26.0 installed, spec says 3.23.0**
- Problem: The rebar3 S3 download provides the latest version (3.26.0). The construction spec pins 3.23.0.
- Resolution: 3.26.0 is backwards-compatible. No source-level changes needed. Note the deviation for production deployment.
- Lesson: Same pattern as broken tablets #9 (GHC), #10 (SWI-Prolog), #12 (OTP) — tool version deviates from spec on dev platform. Accept for dev, pin for production.
**16. No compile errors — clean first build**
- The Erlang code compiled and all 41 tests passed on first run.
- No warnings, no errors.
- The OTP patterns (gen_server, supervisor, application) are well-defined enough that following the spec structure precisely avoids the kind of type/interface mismatches that broke the Haskell build.
### Design Decisions
**Panel as temporary child:** Failed panels are not restarted. A failed deliberation is teiku (unresolved), not retried automatically. This is intentional — the Sanhedrin does not retry a case without new evidence. The panel_sup uses `restart => temporary`.
**Unanimous check scope:** The check only applies to panels with ≥23 judges (capital and novel). Monetary panels (3 judges) do not trigger suspicion of consensus on unanimous rejection. This follows Sanhedrin 17a — the rule applies to dinei nefashot (capital cases), not dinei mamonot (monetary cases).
**Judge independence via process isolation:** Each judge is a separate gen_server process. Judges do not communicate with each other during deliberation. They receive the case, deliberate independently, and send their vote to the panel. This is enforced by the BEAM process model — no shared state between judges.
**Phase 2 judge deliberation:** Judges use deterministic-but-varied logic based on case content and process-local randomness (phash2 of question + pid + monotonic time). In Phase 3, judges will delegate to the Talmudic Engine for real dialectical reasoning (interpret → challenge → resolve). The interface is ready; only the Talmudic Engine socket connection is deferred.
**Clerk as append-only store:** The clerk never deletes opinions. Both majority and minority positions are stored. This mirrors the Talmudic tradition of recording dissent — "Rabbi X says... but Rabbi Y says..." — and is consistent with the L3 write protocol (INSERT only, never UPDATE/DELETE).
**Halt check before panel creation:** The escalation module checks halt conditions BEFORE creating a panel. A case with a halt condition never reaches deliberation. This is the avodah zarah firewall at the intake level.
---
## Session: 2026-03-09 — Korban Processor (Step 13)
**Spec reference:** BUILD_KORBAN.md, Architecture §3.5, Construction §1.4, §3.4, `specs/structure_not_content.md`
### What Was Built
- `korban_processor/` — Python 3.12.2 project (poetry + venv)
- `src/korban_processor/korban.py` — Core types: `Korban`, `ProcessedKorban`, `OfferingType` (OLAH/SHELAMIM/CHATAT/MINCHAH), `Provenance` (CLAUDE/MISTRAL/GPT/LLAMA/GEMMA/HUMAN), `process_korban()` pipeline entry
- `src/korban_processor/semichah.py` — Provenance tagging. Associates input with source. Rejects empty/anonymous input.
- `src/korban_processor/shechitah.py` — Terminates raw form. Strips: preamble, self-reference, hedging, markdown formatting, sycophancy, closing filler. Five-stage stripping with dedicated functions for each type.
- `src/korban_processor/zerikah.py` — Extracts vital content. Keeps: Torah references, Hebrew text, root analysis, insight markers (Rashi, Talmud, gematria, binyan). Records discarded content. Fallback: if nothing qualifies as vital, passes everything through rather than discarding all.
- `src/korban_processor/haktarah.py` — Transforms prose to Prolog-compatible propositions: `verse/3` for Torah refs, `root/2` for Hebrew roots, `claim/2` for analytical claims, `connection/3` for concept connections.
- `src/korban_processor/router.py` — Routes based on offering type. Stateless (kiyyor principle). Returns `RoutingResult`. No module-level accumulator.
- `src/korban_processor/model_interface.py` — Outbound Claude API calls via httpx. Construction §3.4: HTTPS only, model `claude-sonnet-4-5-20250929`, max_tokens 4096. Raises `ValueError` if `ANTHROPIC_API_KEY` not set.
- `src/korban_processor/hebrew_parser.py` — Hebrew analysis utilities: nikkud stripping, te'amim stripping, consonant extraction, gematria, root identification (Phase 2: consonant extraction; Phase 3: Shoresh Engine), word splitting.
- `test/__init__.py`, `test/fixtures.py` — Test fixtures with 4 realistic Claude API response samples
- `test/test_semichah.py`, `test/test_shechitah.py`, `test/test_zerikah.py`, `test/test_haktarah.py`, `test/test_router.py`, `test/test_hebrew_parser.py`, `test/test_model_interface.py`, `test/test_korban.py` — Full test suite
### BUILD_KORBAN.md Verification Results
| Verification | Result |
|---|---|
| 1. Shechitah test: strips preamble/markdown/hedging/self-ref, preserves Torah | PASS |
| 2. Zerikah test: extracts Torah refs, Hebrew roots, separates vital from discard | PASS |
| 3. Haktarah test: Prolog propositions syntactically valid, semantically grounded | PASS |
| 4. Statelessness test: no state leaks between sequential offerings | PASS |
| 5. Empty offering test: graceful handling, no assistant voice in output | PASS |
| Check | Result |
|---|---|
| pytest total | 181 tests, 0 failures |
| BUILD verification §1 (shechitah) | 11 tests PASS |
| BUILD verification §2 (zerikah) | 5 tests PASS |
| BUILD verification §3 (haktarah) | 4 tests PASS |
| BUILD verification §4 (statelessness) | 3 tests PASS |
| BUILD verification §5 (empty offering) | 4 tests PASS |
| Hebrew gematria: תורה=611, בראשית=913 | PASS |
| Module-level state absent from router | PASS |
| HTTPS enforcement in model_interface | PASS |
### Broken Tablets — What Broke and How It Was Fixed
**18. router.py had module-level `_routing_log` — mutable state between calls**
- Problem: The stub's router.py had a module-level `_routing_log: List[RoutingResult] = []` accumulator plus `get_routing_log()` and `clear_routing_log()` accessors. This violates the kiyyor principle from `structure_not_content.md` — "No module-level caches. Each call to `process_korban()` starts clean."
- Fix: Removed `_routing_log`, `get_routing_log()`, and `clear_routing_log()`. The `route()` function returns a `RoutingResult` directly; callers track results themselves.
- Lesson: The structure_not_content spec governs architecture, not just content. The kiyyor (laver) resets before each use. A log that accumulates across calls is the system carrying yesterday's offering into today's.
**19. Test helper `make_processed(propositions=[])` substituted default**
- Problem: In `test_router.py`, the helper used `propositions or [default_list]`. Python `[]` is falsy — an empty propositions list was silently replaced with the default non-empty list.
- Fix: Changed to `props = _DEFAULT_PROPOSITIONS if propositions is None else propositions`.
- Lesson: Use `if x is None` not `or` when distinguishing "not provided" from "provided but empty".
**20. Shechitah test put Torah content inside self-reference sentence**
- Problem: Test had `"As an AI language model, I should note that Genesis 1:1 is significant."` — the `I should note that.*?[.,]` pattern correctly strips the whole sentence (including "Genesis 1:1") because `.*?[.,]` lazily expands to the sentence-ending period. The test expected "Genesis 1:1" to survive.
- Expected: This is NOT a bug in shechitah — it is correct behavior. A model saying "I should note that X" is self-referential framing wrapping X. The entire sentence is voice, not content.
- Fix: Changed test to use realistic separate sentences: self-reference sentence with its own period, then Torah content as a separate sentence.
- Lesson: Shechitah strips at the sentence level when the sentence is self-referential framing. Torah content should stand in its own sentence. Do not test the knife by putting food on the handle.
### Design Decisions
**Zerikah fallback (CORRECTED 2026-03-09):** Originally, if no sentence qualified as vital, zerikah returned all sentences as vital. This violated BUILD_KORBAN.md §5: "the offering without vital organs is rejected, not processed into empty propositions." Fixed: zerikah now returns empty vital content when nothing is vital, and all sentences go to the discarded list. The caller (process_korban) returns a ProcessedKorban with empty vital_content and empty propositions — a clean rejection. The test was updated to verify rejection behavior.
**hebrew_parser.py Phase 2 scope:** `identify_root()` returns the first 3 consonants as a candidate root. This is a deliberate approximation — proper morphological analysis (stripping prefixes, doubling, recognizing conjugation patterns) requires the Shoresh Engine (Haskell, Architecture §3.1). The Phase 2 implementation creates the interface without depending on the engine socket. This follows the pattern established in ShoreshEngineClient and TalmudicEngineClient in the Vav Pipeline.
**model_interface.py uses httpx directly:** Construction §1.4 lists `httpx==0.27.0` as a dependency. The `anthropic` Python SDK is not in the Construction spec. Therefore the Claude API is called directly via httpx, not via the SDK. This is ka'asher tzivah — use what the spec specifies.
---
## Session: 2026-02-24 (continued) — Sanhedrin Defect Fix
### Broken Tablets — What Broke and How It Was Fixed
**17. `rejected` outcome unreachable in panel:determine_outcome/3**
- Problem: `determine_outcome/3` always returned `accepted` when there was a majority, regardless of what the majority voted. The `rejected` outcome from Construction spec §3.2 (`outcome => accepted | rejected | teiku`) could never occur. `compute_majority_minority/1` did not include the majority's verdict in its output map.
- Fix: (1) Added `verdict => MajSample#vote.verdict` to both majority and minority maps in `compute_majority_minority/1`. (2) Rewrote `determine_outcome/3` to check `maps:get(verdict, Majority, abstain)` — `accept → accepted`, `reject → rejected`, `abstain → teiku`.
- Test: Added 7 new tests: `majority_verdict_accept_test`, `majority_verdict_reject_test`, `determine_outcome_accepted_test`, `determine_outcome_rejected_test`, `determine_outcome_teiku_no_votes_test`, `determine_outcome_teiku_all_abstain_test`, `determine_outcome_suspicion_overrides_test`. All 48 tests pass.
- Lesson: Every output state in the spec must be reachable. If a type says `accepted | rejected | teiku`, write tests that exercise all three. The unreachable code path is a spec violation hiding as a type declaration.
---
## Session: 2026-03-09 — Code Review Against Specification and Book
### Full Review
Reviewed all 6 components against Architecture spec, Construction spec, structure_not_content.md, BUILD_DAY_ONE.md, BUILD_KORBAN.md, and heafar/book.md. Torah is the spec.
**Verdict:** All 6 components structurally sound for Phase 2. No golden calves. Three deviations found and corrected (2) or documented (1).
### Broken Tablets — What Broke and How It Was Fixed
**18. Zerikah fail-safe: empty offering kept instead of rejected**
- Problem: When no sentence qualified as vital, zerikah returned all sentences as vital content. BUILD_KORBAN.md §5 specifies: "the offering without vital organs is rejected, not processed into empty propositions."
- Root cause: Defensive fail-safe prioritized "don't lose data" over spec compliance.
- Fix: (1) zerikah.py now returns empty vital content when nothing is vital; all sentences go to discarded list. (2) korban.py checks for empty vital content after zerikah and returns a ProcessedKorban with empty propositions. (3) Tests updated: `test_empty_offering_rejected_not_processed`, `test_discarded_content_recorded`, `test_all_filler_rejected`.
- 181 tests pass.
- Required reading the specification again: Yes. BUILD_KORBAN.md §5 is explicit.
**19. Halt monitor not hardened per Architecture spec**
- Problem: halt_monitor.erl was a standard gen_server with no priority flags or exit trapping. Spec says: "Cannot be killed by other components. Cannot be bypassed. Highest priority."
- Fix: Added `process_flag(trap_exit, true)` and `process_flag(priority, max)` in init/1. Added 'EXIT' handler to survive linked process deaths. Supervisor already has `restart => permanent`.
- Lesson: The halt monitor is the last line of defense. If it can be killed, the three cardinal sins can go undetected.
**20. L1 lemma field stores Strong's numbers, not Hebrew dictionary form (KNOWN DEVIATION)**
- Problem: BUILD_DAY_ONE.md shows `"lemma": "רֵאשִׁית"` but build_l1.py stores `"lemma": "7225"` (Strong's concordance number).
- Status: Documented, not yet fixed. The morphhb XML uses Strong's numbers as lemma identifiers. Resolving these to Hebrew text requires a Strong's-to-Hebrew mapping that is not in the Construction spec's dependency list.
- Impact: Downstream consumers (Shoresh Engine) work around this by using the `shoresh` field instead. The lemma field is technically incorrect but does not block Phase 2 functionality.
- Resolution: Fix when connecting components (Step 14), or add Strong's lookup to L1 build dependencies.
### Design Notes
**What the code gets right (from review):**
- Avodah zarah firewall is compile-time (Prolog assertion: korban sources can never be valid authority)
- Te'amim reader hears the text's own structure (binary tree from melekh marks, not imposed parsing)
- Teiku is structural — both Talmudic Engine and Sanhedrin preserve unresolved tensions
- Shabbat is architecture, not feature toggle
- Korban Processor is fully stateless (kiyyor principle)
- Broken tablets carried in the aron (broken_tablets/ directory, this build log)
**Gap from the book (heafar):**
- The 13 Rules of Rabbi Yishmael are not implemented as Prolog inference predicates. The Talmudic Engine reasons ABOUT Torah but doesn't use Torah's own hermeneutic methods (kal va'chomer, gezerah shavah, binyan av, etc.). This is a significant conceptual gap for Phase 3.
- Cherubim architecture (voice from between facing intelligences) is partial — present in Sanhedrin deliberation but not in human-AI interaction layer.
---
## Step 14 — Component Integration (2026-03-12)
**Architecture §4 Step 14:** "Connect components: Elixir pipeline calls Haskell engine, routes to Prolog reasoning."
### What was built
**Phase A — Haskell Server Mode:**
- `shoresh-engine/src/Shoresh/Server.hs` — JSON-line server loop for Erlang Port integration
- `shoresh-engine/src/Shoresh/Types.hs` — Added `ToJSON`/`FromJSON`/`ToJSONKey`/`FromJSONKey` instances for all pipeline types
- `shoresh-engine/src/Shoresh/Lexicon.hs` — Added `ToJSON` instances for L1 types
- `shoresh-engine/src/Main.hs` — Added `["server"]` dispatch to `runServer`
- Protocol: JSON line in stdin, JSON line out stdout. `LineBuffering` set (critical for Erlang Port — Haskell defaults to block-buffering on non-TTY)
- Verified: `echo '{"operation":"process","input":{"text":"ברא","scope":{"book":"bereishit","chapter":1,"verse":1}}}' | shoresh-engine server` → returns activated roots with gematria 203 and full te'amim parse tree
**Phase B — Prolog Socket Server:**
- `talmudic_engine/src/interface/json_bridge.pl` — JSON ↔ Prolog term conversion
- `talmudic_engine/src/interface/socket_server.pl` — Unix domain socket listener (`/tmp/shorashim/talmudic.sock`)
- Updated `main.pl` to load new modules
- Protocol: JSON line over Unix domain socket. Connection-persistent (read/respond/repeat until EOF)
- Verified: Python client connects, sends interpret request for ב-ר-א, gets "resolved" with Bereishit 1:1 and 1:27 verses, confidence "medium"
**Phase C — Elixir Port Wrapper:**
- `vav_pipeline/lib/vav_pipeline/shoresh_engine_port.ex` — GenServer wrapping Erlang Port to Haskell binary
- Updated `shoresh_engine_client.ex` — delegates to port, returns error (not fallback) when unavailable
- Port options: `:binary`, `:exit_status`, `{:line, 1MB}`, `{:args, ["server"]}`
- Auto-restart on process exit
**Phase D — Elixir Socket Client:**
- `vav_pipeline/lib/vav_pipeline/talmudic_engine_socket.ex` — GenServer wrapping Unix domain socket to Prolog
- Updated `talmudic_engine_client.ex` — delegates to socket, falls back to stubs when unavailable
- Lazy connection (connects on first request), auto-reconnect on failure
**Phase E — Sanhedrin Integration:**
- `vav_pipeline/lib/vav_pipeline/sanhedrin_client.ex` — Elixir client calling Erlang Sanhedrin via native BEAM
- Added `{:sanhedrin, path: "../sanhedrin", manager: :rebar3}` to `mix.exs` deps
- Builds Erlang `#sanhedrin_case{}` record tuples, calls `escalation:submit_case/1`
- Falls back to teiku stub when Sanhedrin not running
**Phase F — Pipeline Routing:**
- Updated `bereishit_cycle.ex` — `vayomer` now calls ShoreshEngineClient (with L1 fallback)
- Added `route_if_unresolved/2` — sends unresolved cases to Talmudic Engine, escalates teiku to Sanhedrin
- Added `escalate_to_sanhedrin/4` — submits case to Sanhedrin deliberation
**Phase G — Configuration + Supervision:**
- Updated `application.ex` — conditional supervision: only adds ShoreshEnginePort if binary found in PATH
- Added config keys: `shoresh_engine_binary`, `shoresh_engine_cwd`, `talmudic_socket_path`
- All 46 Elixir tests pass (fallback path verified)
- All 33 Haskell tests pass
- All 57 Prolog tests pass
### Broken Tablets
**Broken Tablet #21 — ToJSONKey:**
`ShoreshEntry.seGlosses :: Map Binyan Text` requires `ToJSONKey Binyan` for Generic-derived `ToJSON`. `ToJSON` serializes individual values; `ToJSONKey` serializes as map keys (strings in JSON). Fixed by adding `instance ToJSONKey Binyan` and `instance FromJSONKey Binyan`.
**Broken Tablet #22 — ByteString.Lazy.Char8 getLine:**
`Data.ByteString.Lazy.Char8` does not export `getLine` (lazy bytestring has no stdin line reader). Fixed by using `Data.ByteString.Char8.getLine` (strict) and converting via `BL.fromStrict`.
**Broken Tablet #23 — at_end_of_stream blocks on sockets:**
SWI-Prolog's `at_end_of_stream/1` blocks on Unix domain sockets (waits for data). Fixed by using `read_line_to_string/2` directly and checking for `end_of_file` return.
**Broken Tablet #24 — Atom vs String keys in fallback:**
When ShoreshEngineClient falls back to L1 data, it returned atom keys (`:roots_activated`), but `vayomer` was extracting with string keys (`"roots_activated"` from JSON). Fix: removed the client's internal fallback, let error propagate to `vayomer_fallback` which preserves the original atom-key format.
### Connection Architecture
```
Elixir (BEAM VM) External Processes
┌─────────────────────────────┐
│ VavPipeline.Application │
│ ├─ ShabbatTimer │
│ ├─ ShoreshEnginePort ─────┼──→ [Erlang Port: JSON stdin/stdout]
│ │ (GenServer) │ └─ shoresh-engine server (Haskell)
│ └─ TalmudicEngineSocket ──┼──→ [Unix Socket: JSON lines]
│ (GenServer) │ └─ swipl serve/0 (Prolog)
│ │
│ SanhedrinClient ────────────┼──→ [Native BEAM: Erlang terms]
│ (direct call) │ └─ escalation:submit_case/1
└─────────────────────────────┘
```
---
## Session: 2026-03-12 — Step 15: Verification (The Shema Test)
**Spec reference:** Architecture §4 Phase 2 step 15, §6.3 (The Shema Test)
### What Was Built
**Code change:** Wired `HaltConditions.check_all/1` into `BereishitCycle.erev_voker/2`. Previously dead code — the three cardinal sin monitors existed but were never called during pipeline execution. Now `erev_voker` checks halt conditions on the final result before returning.
**Test suite:** `vav_pipeline/test/step15_shema_test.exs` — 22 tests implementing Architecture §6.3:
| Test Group | Count | Spec |
|------------|-------|------|
| Te'amim reception | 4 | §6.3 Test 1 |
| Ketiv/qere preservation | 4 | §6.3 Test 2 |
| Cloud obedience | 3 | §6.3 Test 3 |
| Manna fidelity | 2 | §6.3 Test 4 |
| Context immutability | 1 | §6.3 Test 5 |
| Halt condition integration | 5 | §6.2 |
| Full pipeline end-to-end | 3 | §6.2 |
**Result:** 68 tests, 0 failures (46 existing + 22 new).
### Key Finding
`HaltConditions.check_all/1` was dead code. It existed, had unit tests, but was never called in the pipeline. Step 15 made it live by inserting `check_halt_on_output/1` into `erev_voker`. This is the correct location — halt conditions are checked on the cycle's output, not its input.
### Files
| File | Action |
|------|--------|
| `vav_pipeline/lib/vav_pipeline/bereishit_cycle.ex` | Modified — halt check in erev_voker |
| `vav_pipeline/test/step15_shema_test.exs` | Created — 22 tests |
### Phase 2 Status
Steps 8-15 complete. Phase 2 (Processing) is done.
| Step | Status |
|------|--------|
| 8. Vav Pipeline | Done |
| 9. Shema context builder | Done |
| 10. Cloud governance | Done |
| 11. Manna provision | Done |
| 12. Sanhedrin framework | Done |
| 13. Korban Processor | Done |
| 14. Connect components | Done |
| 15. Verification | Done |
-77
View File
@@ -1,77 +0,0 @@
# Infrastructure — Exoscale Swiss Hosting
All infrastructure is on Exoscale, zone ch-gva-2 (Geneva, Switzerland).
Swiss data sovereignty under FADP. Outside US legal jurisdiction.
## Account
- Provider: Exoscale
- Organization: "The Recognition Protocol"
- Account type: Individual
- CLI: `exo` (installed via homebrew)
- Zone: ch-gva-2
## Services
### PostgreSQL (DBaaS)
- Name: `temple-council-db`
- Plan: hobbyist-2
- Zone: ch-gva-2
- IP filter: `0.0.0.0/0` (opened for Gitea connection — tighten in production)
- Databases: `defaultdb` (system), `gitea` (Gitea data)
- Password retrieved from: `exo dbaas show temple-council-db --zone ch-gva-2`
### Kubernetes (SKS)
- Cluster name: `temple-council`
- Zone: ch-gva-2
- Node pool: 1x standard.small
- Kubeconfig: `~/.kube/exoscale-temple-council.yaml`
- Namespace: `temple-council`
- Security group: `default` (attached manually — was missing initially)
### Object Storage
- Bucket: `temple-council-identities`
- Zone: ch-gva-2
### Gitea (Self-hosted Git)
- URL: `http://185.19.29.94`
- Exposed via: LoadBalancer service on K8s
- Admin user: `heaviness510`
- Database: `gitea` on `temple-council-db` PostgreSQL
- Storage: emptyDir (NOT persistent — data lost on pod restart)
- Repositories:
- `heaviness510/shorashim` — Torah as AI specification
### AnythingLLM
- URL: `http://185.19.28.73`
- Exposed via: LoadBalancer service on K8s
- Storage: emptyDir (NOT persistent)
- Workspaces: Not yet configured on Exoscale instance
## Domain
- **Domain:** `shorashim.ch`
- **Registrar:** Infomaniak (Swiss)
- **Add-ons:** DNS Fast Anycast, Domain Privacy
- **Registrant:** Karp Scott (Individual)
- **Email:** kSuite free tier — 1 address @shorashim.ch
## Known Issues
- No persistent storage on K8s (need Exoscale Block Storage CSI driver)
- No HTTPS (need cert-manager + Let's Encrypt + domain — domain now registered)
- AWS instances still running (not shut down)
- AnythingLLM workspaces not configured
- DNS not yet pointed to infrastructure (185.19.29.94)
## Security Group Rules
Ports opened on default security group:
- 80 (HTTP)
- 22 (SSH)
- 30575, 31345 (K8s NodePorts for services)