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:
1 parent
622e814c9e
commit
c039fa61e0
88 files changed
+10153
No files matched your search
@@ -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
|
||||
Reference in new issue
Block a user