Phase 1-2 build: Steps 5-6, 8-13

Shoresh Engine (Haskell, Step 5)
- 12 modules: Types, Lexicon, Binyanim, Teamim, Gematria, Graph,
  KetivQere, Smikhut, Havdalah, Kriah, KiTov, Firmware
- 33 EUnit tests, 0 failures
- Firmware module verifies L1 SHA-256 hash at startup

Talmudic Engine (SWI-Prolog, Step 6)
- 15 modules: halt, reasoning, specification, interface
- 57 PlUnit tests, 0 failures
- Three cardinal halt conditions: avodah_zarah, shfichut_damim, gilui_arayot

Vav Pipeline (Elixir, Steps 8-11)
- 14 modules: BereishitCycle, Shema, Cloud, Manna, L1Reader,
  Specification, HaltConditions, ShabbatTimer, CycleLog, Chatzotzrot,
  ShoreshEngineClient, TalmudicEngineClient, Application
- 46 ExUnit tests, 0 failures
- Nishma-first architecture: hears before it speaks

Sanhedrin Framework (Erlang/OTP, Step 12)
- 8 modules: sanhedrin_app, sanhedrin_sup, escalation, panel_sup,
  panel, judge, clerk, halt_monitor
- 46 EUnit tests, 0 failures (includes fix: rejected outcome now reachable)
- Panel sizes: 1/3/23/71 — unanimous capital guilty → acquit (Sanhedrin 17a)
- Minority opinions append-only, never deleted

Korban Processor (Python, Step 13)
- 6 modules: korban, shechitah, semichah, zerikah, haktarah, router

Builder tooling: AGENTS.md, opencode.json, memory/, src/tools/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott KarpandClaude Sonnet 4.6 committed 2026-02-26 14:02:34 -05:00
1 parent 622e814c9e
commit c039fa61e0
88 files changed
+10153

No files matched your search

+233
View File
@@ -0,0 +1,233 @@
defmodule VavPipeline.L1Reader do
@moduledoc """
Reads from the L1 Torah filesystem.
L1 is read-only. This module reads, it never writes.
The aron is sealed. The scroll is read, not rewritten.
File structure:
l1/torah/{book}/{chapter}/{verse}.json
l1/torah/index.json
l1/integrity.sha256
"""
@l1_root Application.compile_env(:vav_pipeline, :l1_mount_point, "l1")
# L0 firmware constant — from Luchot.Firmware (Haskell)
@l1_expected_hash "0ee0020aac6c6beb0e83c0638a861c73d37d9dbd39c30d3cb08c1878b481c844"
@type word :: %{
position: pos_integer(),
text: String.t(),
consonants: String.t(),
lemma: String.t(),
morph: String.t(),
teamim: [String.t()],
prefix: String.t() | nil,
shoresh: String.t() | nil
}
@type verse :: %{
ref: String.t(),
book: String.t(),
chapter: pos_integer(),
verse: pos_integer(),
text_hebrew: String.t(),
words: [word()]
}
@doc """
Read a verse from L1.
Returns the parsed verse data or an error.
"""
@spec read_verse(String.t(), pos_integer(), pos_integer()) ::
{:ok, verse()} | {:error, String.t()}
def read_verse(book, chapter, verse) do
path = verse_path(book, chapter, verse)
case File.read(path) do
{:ok, contents} ->
case Jason.decode(contents) do
{:ok, data} -> {:ok, normalize_verse(data)}
{:error, _} -> {:error, "L1_READ_FAILED: corrupt JSON at #{path}"}
end
{:error, :enoent} ->
{:error, "INVALID_SCOPE: #{book} #{chapter}:#{verse} not found in L1"}
{:error, reason} ->
{:error, "L1_READ_FAILED: #{inspect(reason)} at #{path}"}
end
end
@doc """
Extract teamim marks from a verse, with word indices.
Returns a list of %{word_index, taam, role} maps matching
the ShemaContext schema from Construction spec §3.1.
"""
@spec extract_teamim(verse()) :: [map()]
def extract_teamim(verse) do
verse.words
|> Enum.flat_map(fn word ->
Enum.map(word.teamim, fn taam ->
%{
word_index: word.position - 1,
taam: taam,
role: taam_role(taam)
}
end)
end)
end
@doc """
Extract ketiv/qere dual readings from a verse.
Currently L1 does not encode ketiv/qere directly —
this is a placeholder for when L1 is enriched.
Returns empty list for now.
"""
@spec extract_ketiv_qere(verse()) :: [map()]
def extract_ketiv_qere(_verse) do
# L1 does not yet encode ketiv/qere as separate fields.
# When it does, this will parse them.
[]
end
@doc """
Extract roots from a verse with gematria values.
"""
@spec extract_roots(verse()) :: map()
def extract_roots(verse) do
verse.words
|> Enum.filter(fn w -> w.shoresh != nil end)
|> Enum.map(fn w -> {w.shoresh, gematria(w.shoresh)} end)
|> Enum.uniq_by(fn {root, _} -> root end)
|> Map.new()
end
@doc """
Extract smikhut (construct-state) pairs from a verse.
Currently a placeholder — smikhut detection requires
morphological analysis beyond what L1 currently marks.
"""
@spec extract_smikhut(verse()) :: [map()]
def extract_smikhut(_verse) do
[]
end
@doc """
Verify L1 integrity by checking the SHA-256 hash.
This is a critical check — if L1 is corrupted, the pipeline
must halt. The specification store is inviolable.
"""
@spec verify_integrity() :: :ok | {:error, String.t()}
def verify_integrity do
hash_file = Path.join(l1_root(), "integrity.sha256")
case File.read(hash_file) do
{:ok, contents} ->
stored_hash = contents |> String.trim()
if stored_hash == @l1_expected_hash do
:ok
else
{:error, "L1_INTEGRITY_FAILED: hash mismatch — stored #{stored_hash}, expected #{@l1_expected_hash}"}
end
{:error, _} ->
{:error, "L1_INTEGRITY_FAILED: cannot read integrity.sha256"}
end
end
@doc """
Get the root path of L1.
"""
@spec l1_root() :: String.t()
def l1_root, do: @l1_root
# --- Private ---
defp verse_path(book, chapter, verse) do
Path.join([l1_root(), "torah", book, to_string(chapter), "#{verse}.json"])
end
defp normalize_verse(data) do
%{
ref: data["ref"],
book: data["book"],
chapter: data["chapter"],
verse: data["verse"],
text_hebrew: data["text_hebrew"],
words:
Enum.map(data["words"] || [], fn w ->
%{
position: w["position"],
text: w["text"],
consonants: w["consonants"],
lemma: w["lemma"],
morph: w["morph"],
teamim: w["teamim"] || [],
prefix: w["prefix"],
shoresh: w["shoresh"]
}
end)
}
end
# Te'amim classification — mirrors Shoresh Engine Teamim.hs
# Melekh = disjunctor (emperor/king), Mesharet = conjunctor (servant)
# Spellings must match what build_l1.py writes to L1 JSON.
# Verified against actual L1 data (bereishit-devarim ch1 sample).
@melekh_marks ~w(sof_pasuk etnachta segol_accent shalshelet zaqef_qatan
zaqef_gadol tipcha revia zarqa pashta yetiv tevir geresh
gershayim pazer telisha_gedola meteg_siluq zinor geresh_muqdam)
@mesharet_marks ~w(munach mercha mahapakh darga qadma
telisha_qetana yerah_ben_yomo)
defp taam_role(taam) when taam in @melekh_marks, do: "melekh"
defp taam_role(taam) when taam in @mesharet_marks, do: "mesharet"
defp taam_role(_), do: "unknown"
# Simple gematria — consonant values
@gematria_values %{
"א" => 1,
"ב" => 2,
"ג" => 3,
"ד" => 4,
"ה" => 5,
"ו" => 6,
"ז" => 7,
"ח" => 8,
"ט" => 9,
"י" => 10,
"כ" => 20,
"ך" => 20,
"ל" => 30,
"מ" => 40,
"ם" => 40,
"נ" => 50,
"ן" => 50,
"ס" => 60,
"ע" => 70,
"פ" => 80,
"ף" => 80,
"צ" => 90,
"ץ" => 90,
"ק" => 100,
"ר" => 200,
"ש" => 300,
"ת" => 400
}
defp gematria(text) do
text
|> String.graphemes()
|> Enum.reduce(0, fn char, acc ->
acc + Map.get(@gematria_values, char, 0)
end)
end
end