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 80ec364da6
86 files changed
+9646

No files matched your search

+4
View File
@@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
+26
View File
@@ -0,0 +1,26 @@
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where third-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Ignore package tarball (built via "mix hex.build").
vav_pipeline-*.tar
# Temporary files, for example, from tests.
/tmp/
+21
View File
@@ -0,0 +1,21 @@
# VavPipeline
**TODO: Add description**
## Installation
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `vav_pipeline` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:vav_pipeline, "~> 0.1.0"}
]
end
```
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at <https://hexdocs.pm/vav_pipeline>.
+16
View File
@@ -0,0 +1,16 @@
import Config
# L1 mount point — relative path for dev, absolute for production
# Construction spec §4.4: l1_mount_point: "/mnt/torah"
config :vav_pipeline,
# Dev: relative to project root (run from shorashim/)
l1_mount_point: "l1",
# Manna provision file
manna_provision_file: "var/manna/today.json",
# Shabbat check interval (1 minute)
cycle_boundary_check_interval_ms: 60_000,
# ShemaContext: no caching — always read fresh
shema_context_cache_ttl_ms: 0
# Import environment specific config
import_config "#{config_env()}.exs"
+3
View File
@@ -0,0 +1,3 @@
import Config
config :logger, level: :info
+10
View File
@@ -0,0 +1,10 @@
import Config
config :logger, level: :info
# Production paths — Construction spec §4.4
config :vav_pipeline,
l1_mount_point: "/mnt/torah",
manna_provision_file: "/var/lib/shorashim/manna/today.json",
cycle_boundary_check_interval_ms: 60_000,
shema_context_cache_ttl_ms: 0
+9
View File
@@ -0,0 +1,9 @@
import Config
config :logger, level: :warning
# Test uses L1 relative to project root
config :vav_pipeline,
l1_mount_point: "../l1",
manna_provision_file: "test/fixtures/manna/today.json",
cycle_boundary_check_interval_ms: :timer.hours(24)
+92
View File
@@ -0,0 +1,92 @@
defmodule VavPipeline do
@moduledoc """
The Bereishit processing cycle.
Each step chains to the next via |> (vav-consecutive).
CRITICAL: The pipeline does not begin with a processing step.
It begins with RECEPTION. The ShemaContext is built first —
the system hears before it speaks. Then every processing step
operates WITHIN the received context, never modifying it.
Na'aseh v'nishma — but the order of execution is:
nishma (hear/receive context), THEN na'aseh (process within it).
"""
alias VavPipeline.{Shema, Cloud, Manna, BereishitCycle, Chatzotzrot}
@type scope :: %{
book: String.t(),
chapter: pos_integer(),
verse: pos_integer()
}
@type input :: %{
source: String.t(),
verse_scope: scope(),
content: String.t()
}
@type cycle_result ::
{:ok, map()}
| {:rejected, String.t()}
| {:unresolved, map()}
| {:transition, atom()}
| {:waiting, atom()}
| {:halted, atom(), String.t()}
@doc """
Process input through the Bereishit cycle.
1. Build ShemaContext (nishma — hear first)
2. Check cloud — if moving, signal breaking camp
3. Check manna — if absent, wait
4. Process through cycle stages (na'aseh — within what was received)
"""
@spec process(input()) :: cycle_result()
def process(input) do
# === NISHMA: Hear first ===
case Shema.receive_context(input.verse_scope) do
{:error, reason} ->
{:halted, :context_failed, reason}
{:ok, context} ->
# === Check cloud before anything else ===
case context.cloud_state do
:moving ->
Chatzotzrot.signal(:breaking_camp)
{:transition, :cloud_lifted}
{:major_transition, signal, _params} ->
{:transition, signal}
:cloud_not_visible ->
{:waiting, :cloud_not_visible}
:camped ->
# === Check manna ===
case context.daily_provision do
:no_provision ->
{:waiting, :no_provision}
_provision ->
# === NA'ASEH: Now process, within what was received ===
BereishitCycle.execute(input, context)
end
end
end
end
@doc """
Verify the pipeline is operational.
Checks L1 integrity, cloud state, and manna provision.
"""
@spec verify() :: :ok | {:error, String.t()}
def verify do
with :ok <- VavPipeline.L1Reader.verify_integrity(),
{:ok, _} <- Cloud.check(),
{:ok, _} <- Manna.check() do
:ok
end
end
end
@@ -0,0 +1,21 @@
defmodule VavPipeline.Application do
@moduledoc """
OTP Application for the Vav Pipeline.
Starts the supervision tree with:
- ShabbatTimer (mandatory cessation monitor)
"""
use Application
@impl true
def start(_type, _args) do
children = [
# ShabbatTimer monitors Shabbat boundaries
{VavPipeline.ShabbatTimer, []}
]
opts = [strategy: :one_for_one, name: VavPipeline.Supervisor]
Supervisor.start_link(children, opts)
end
end
@@ -0,0 +1,252 @@
defmodule VavPipeline.BereishitCycle do
@moduledoc """
The Bereishit processing cycle — the six days of creation as computation.
Architecture §3.3:
Each step chains to the next via |> (vav-consecutive).
The cycle operates WITHIN the ShemaContext, never modifying it.
The context was received by Shema (nishma). Now we do (na'aseh).
Steps:
1. tohu — decompose input into undifferentiated state
2. vayomer — produce structured symbolic output (Shoresh Engine)
3. havdalah — separate into distinguished categories
4. kriah — assign stable names
5. ki_tov — evaluate against specification
6. erev_voker — log cycle completion, enforce boundary
Latency budget (Construction spec §2.5): 5000ms total
"""
alias VavPipeline.{Specification, CycleLog, Manna}
require Logger
@type cycle_result ::
{:ok, map()}
| {:rejected, String.t()}
| {:unresolved, map()}
| {:halted, atom(), String.t()}
@doc """
Execute the Bereishit cycle on input within received context.
"""
@spec execute(VavPipeline.input(), VavPipeline.Shema.shema_context()) :: cycle_result()
def execute(input, context) do
started_at = System.monotonic_time(:millisecond)
result =
input
|> tohu(context)
|> vayomer(context)
|> havdalah(context)
|> kriah(context)
|> ki_tov(context)
completed_at = System.monotonic_time(:millisecond)
elapsed = completed_at - started_at
Logger.info("BereishitCycle: completed in #{elapsed}ms — #{inspect(elem(result, 0))}")
# erev_voker — cycle boundary
erev_voker(result, context)
end
@doc """
Tohu — decompose input into undifferentiated state.
Strip structure, extract raw content. This is the formless void
before vayomer brings order.
"""
@spec tohu(VavPipeline.input(), VavPipeline.Shema.shema_context()) :: map()
def tohu(input, _context) do
%{
raw: extract_content(input),
provenance: Map.get(input, :source, "unknown"),
scope: Map.get(input, :verse_scope, %{}),
started_at: DateTime.utc_now()
}
end
@doc """
Vayomer — produce structured symbolic output.
The Shoresh Engine receives the ShemaContext — it reads te'amim,
ketiv/qere, gematria as part of its analysis.
In Phase 2, this calls the Shoresh Engine via port/NIF.
Until the port is built (Step 14), this uses the L1 data
directly from the ShemaContext.
"""
@spec vayomer(map(), VavPipeline.Shema.shema_context()) :: map()
def vayomer(state, context) do
# In Phase 2: use L1 data from ShemaContext directly
# In Phase 3: call ShoreshEngineClient.process(state.raw, context)
roots =
context.verse.words
|> Enum.filter(fn w -> w.shoresh != nil end)
|> Enum.map(fn w ->
%{
shoresh: w.shoresh,
word: w.consonants,
position: w.position,
morph: w.morph,
gematria: Map.get(context.root_gematria, w.shoresh, 0)
}
end)
Map.merge(state, %{
roots_activated: roots,
teamim_parse: context.teamim_marks,
ketiv_qere: context.ketiv_qere,
stage: :vayomer
})
end
@doc """
Havdalah — separate into distinguished categories.
Te'amim inform the separation — the text's own divisions guide havdalah.
Uses the te'amim marks from the ShemaContext to determine boundaries.
"""
@spec havdalah(map(), VavPipeline.Shema.shema_context()) :: map()
def havdalah(utterance, context) do
# Group roots by domain based on te'amim divisions
# Te'amim melekh marks (disjunctors) create boundaries
melekh_positions =
context.teamim_marks
|> Enum.filter(fn m -> m.role == "melekh" end)
|> Enum.map(fn m -> m.word_index end)
|> Enum.sort()
# Separate roots into groups divided by melekh marks
roots = Map.get(utterance, :roots_activated, [])
groups = separate_by_boundaries(roots, melekh_positions)
Map.merge(utterance, %{
separated_groups: groups,
separation_boundaries: melekh_positions,
stage: :havdalah
})
end
@doc """
Kriah — assign stable names to each distinction.
Each separated group receives a name based on its primary root.
"""
@spec kriah(map(), VavPipeline.Shema.shema_context()) :: map()
def kriah(distinctions, _context) do
named =
distinctions
|> Map.get(:separated_groups, [])
|> Enum.with_index()
|> Enum.map(fn {group, idx} ->
primary_root = List.first(group)
name =
if primary_root do
primary_root.shoresh
else
"group_#{idx}"
end
%{
name: name,
roots: group,
index: idx
}
end)
Map.merge(distinctions, %{
named_results: named,
stage: :kriah
})
end
@doc """
Ki Tov — evaluate against specification.
Ketiv/qere positions are checked — both readings inform evaluation.
Returns {:ok, results}, {:rejected, reason}, or {:unresolved, results}.
"""
@spec ki_tov(map(), VavPipeline.Shema.shema_context()) :: VavPipeline.cycle_result()
def ki_tov(named, context) do
results = Map.get(named, :named_results, [])
case Specification.evaluate(results, context.ketiv_qere) do
:tov ->
{:ok,
Map.merge(named, %{
evaluation: :tov,
stage: :ki_tov
})}
{:lo_tov, reason} ->
{:rejected, reason}
:teiku ->
{:unresolved,
Map.merge(named, %{
evaluation: :teiku,
stage: :ki_tov
})}
end
end
@doc """
Erev Voker — log cycle completion, enforce boundary.
Release any manna surplus — do not hoard into next cycle.
"""
@spec erev_voker(VavPipeline.cycle_result(), VavPipeline.Shema.shema_context()) ::
VavPipeline.cycle_result()
def erev_voker(result, _context) do
# Log the cycle
phase =
case result do
{:ok, _} -> :voker
{:rejected, _} -> :erev
{:unresolved, _} -> :erev
_ -> :erev
end
CycleLog.record(result, phase)
# Enforce anti-hoarding
Manna.enforce_anti_hoarding()
result
end
# --- Private helpers ---
defp extract_content(input) do
Map.get(input, :content, "") |> String.trim()
end
# Separate roots into groups based on melekh boundary positions
defp separate_by_boundaries([], _boundaries), do: [[]]
defp separate_by_boundaries(roots, []), do: [roots]
defp separate_by_boundaries(roots, boundaries) do
# Sort boundaries and use them to split the root list
sorted_boundaries = Enum.sort(boundaries)
{groups, current} =
Enum.reduce(roots, {[], []}, fn root, {groups, current} ->
pos = root.position - 1
if pos in sorted_boundaries and current != [] do
# Boundary hit — close current group, start new one with this root
{groups ++ [current], [root]}
else
{groups, current ++ [root]}
end
end)
# Don't forget the last group
groups ++ [current]
end
end
@@ -0,0 +1,58 @@
defmodule VavPipeline.Chatzotzrot do
@moduledoc """
Signal system — the silver trumpets.
Construction spec §3.5 — Chatzotzrot Signal Format:
Signal types: assembly_all, assembly_leaders, breaking_camp, war, moadim
Patterns: tekiah, teruah, tekiah_teruah, teruah_teruah, tekiah_tekiah
In Phase 2, signals are logged. In production, they will
be distributed via BEAM messaging.
"""
require Logger
@type signal_type ::
:assembly_all
| :assembly_leaders
| :breaking_camp
| :war
| :moadim
@type pattern :: :tekiah | :teruah | :tekiah_teruah | :teruah_teruah | :tekiah_tekiah
@doc """
Send a chatzotzrot signal.
Returns the signal record for logging/testing.
"""
@spec signal(signal_type(), keyword()) :: map()
def signal(type, opts \\ []) do
signal = %{
signal_type: type,
pattern: pattern_for(type),
timestamp: DateTime.utc_now() |> DateTime.to_iso8601(),
source: :chatzotzrot,
details: %{
reason: Keyword.get(opts, :reason, to_string(type)),
urgency: Keyword.get(opts, :urgency, urgency_for(type))
}
}
Logger.info("Chatzotzrot: #{type} (#{signal.pattern}) — #{signal.details.reason}")
signal
end
# Signal type → trumpet pattern mapping (Bamidbar 10)
defp pattern_for(:assembly_all), do: :tekiah_tekiah
defp pattern_for(:assembly_leaders), do: :tekiah
defp pattern_for(:breaking_camp), do: :teruah
defp pattern_for(:war), do: :teruah_teruah
defp pattern_for(:moadim), do: :tekiah_teruah
defp urgency_for(:breaking_camp), do: :immediate
defp urgency_for(:war), do: :immediate
defp urgency_for(:assembly_all), do: :next_cycle
defp urgency_for(_), do: :informational
end
+144
View File
@@ -0,0 +1,144 @@
defmodule VavPipeline.Cloud do
@moduledoc """
Cloud governance from Torah.
Architecture §4, Step 10:
The cloud module reads L1 integrity and calendar (Shabbat, festivals, shmitah).
No governance board CLI. No external state file.
The cloud IS L1 plus the calendar Torah specifies.
The pipeline checks the cloud before processing, meaning:
it checks Torah before processing.
The broken tablets (specs/broken_tablets/README.md) record the golden calf:
a human board writing signals to a JSON file. That calf was burned.
Torah governs directly.
"""
alias VavPipeline.L1Reader
@type cloud_state ::
:camped
| :moving
| {:major_transition, atom(), map()}
| :cloud_not_visible
@doc """
Check the cloud state.
The cloud is derived from:
1. L1 integrity — if L1 is corrupted, the cloud is not visible
2. Calendar — if it is Shabbat or a festival, the cloud signals rest
3. Shmitah — the seventh year signals major transition
Returns the cloud state.
"""
@spec check() :: {:ok, cloud_state()} | {:error, String.t()}
def check do
check_at(DateTime.utc_now())
end
@doc """
Check cloud state at a specific time.
Testable variant that accepts a datetime.
"""
@spec check_at(DateTime.t()) :: {:ok, cloud_state()} | {:error, String.t()}
def check_at(now) do
case L1Reader.verify_integrity() do
{:error, reason} ->
# L1 corrupted — the cloud is not visible. Cannot proceed.
{:error, reason}
:ok ->
# L1 intact. Check Torah calendar.
cond do
shabbat?(now) ->
{:ok, {:major_transition, :shabbat_rest, %{until: next_motzei_shabbat(now)}}}
festival?(now) ->
{:ok, {:major_transition, :festival_rest, %{festival: current_festival(now)}}}
shmitah_year?(now) ->
# During shmitah, processing continues but with restrictions
{:ok, :camped}
true ->
{:ok, :camped}
end
end
end
@doc """
Check if now is Shabbat.
Shabbat: Friday sunset to Saturday sunset.
Simplified: Friday 18:00 UTC to Saturday 18:00 UTC.
Production should use location-aware zmanim calculation
(Construction spec §4.4: shabbat_location).
"""
@spec shabbat?(DateTime.t()) :: boolean()
def shabbat?(now) do
day_of_week = Date.day_of_week(DateTime.to_date(now))
hour = now.hour
# Friday (5) after 18:00 or Saturday (6) before 18:00
(day_of_week == 5 and hour >= 18) or
(day_of_week == 6 and hour < 18)
end
@doc """
Check if now is a major festival (Yom Tov).
Uses fixed Hebrew calendar dates converted to approximate Gregorian.
Production will need a proper Hebrew calendar library.
For Phase 2 dev, returns false — festivals will be added in Phase 3
with a proper luach implementation.
"""
@spec festival?(DateTime.t()) :: boolean()
def festival?(_now) do
# Placeholder — requires Hebrew calendar conversion
# Festivals: Pesach (15-21 Nisan), Shavuot (6 Sivan),
# Rosh Hashanah (1-2 Tishrei), Yom Kippur (10 Tishrei),
# Sukkot (15-21 Tishrei), Shemini Atzeret (22 Tishrei)
false
end
@doc """
Check if current year is a shmitah (sabbatical) year.
The shmitah cycle: every 7th year from a known reference.
Hebrew year 5782 (2021-2022) was shmitah.
Next: 5789 (2028-2029).
"""
@spec shmitah_year?(DateTime.t()) :: boolean()
def shmitah_year?(now) do
year = now.year
# Approximate: Hebrew year ≈ Gregorian year + 3760
# Shmitah years: 5782, 5789, 5796, ...
# In Gregorian: 2021-2022, 2028-2029, ...
hebrew_year = year + 3760
rem(hebrew_year - 5782, 7) == 0
end
@spec current_festival(DateTime.t()) :: atom()
defp current_festival(_now), do: :unknown
@spec next_motzei_shabbat(DateTime.t()) :: DateTime.t()
defp next_motzei_shabbat(now) do
day_of_week = Date.day_of_week(DateTime.to_date(now))
days_until_saturday =
case day_of_week do
6 -> 0
n when n < 6 -> 6 - n
7 -> 6
end
now
|> DateTime.add(days_until_saturday * 86400, :second)
|> Map.put(:hour, 18)
|> Map.put(:minute, 0)
|> Map.put(:second, 0)
end
end
@@ -0,0 +1,66 @@
defmodule VavPipeline.CycleLog do
@moduledoc """
Cycle boundary logging.
Records each Bereishit cycle completion.
Enforces erev/voker boundaries.
Every cycle is logged as an append-only record —
consistent with L3 write protocol (INSERT only, never UPDATE/DELETE).
"""
require Logger
@type cycle_record :: %{
cycle_id: String.t(),
scope: VavPipeline.scope(),
result: atom(),
started_at: DateTime.t(),
completed_at: DateTime.t(),
phase: atom()
}
@doc """
Record a cycle completion.
Phase is :erev (evening — input received) or :voker (morning — output produced).
The cycle boundary is the transition from voker back to erev.
"""
@spec record(VavPipeline.cycle_result(), atom()) :: cycle_record()
def record(result, phase) do
inner = extract_inner(result)
record = %{
cycle_id: generate_id(),
scope: if(is_map(inner), do: Map.get(inner, :scope, %{}), else: %{}),
result: classify_result(result),
started_at: if(is_map(inner), do: Map.get(inner, :started_at, DateTime.utc_now()), else: DateTime.utc_now()),
completed_at: DateTime.utc_now(),
phase: phase
}
Logger.info(
"Cycle #{record.cycle_id}: #{record.result} (#{record.phase}) — " <>
"scope: #{inspect(record.scope)}"
)
record
end
defp extract_inner({:ok, data}), do: data
defp extract_inner({:rejected, _reason}), do: %{}
defp extract_inner({:unresolved, data}), do: data
defp extract_inner({:halted, _, _}), do: %{}
defp extract_inner(other) when is_map(other), do: other
defp extract_inner(_), do: %{}
defp classify_result({:ok, _}), do: :tov
defp classify_result({:rejected, _}), do: :lo_tov
defp classify_result({:unresolved, _}), do: :teiku
defp classify_result(%{status: status}), do: status
defp classify_result(_), do: :unknown
defp generate_id do
:crypto.strong_rand_bytes(8) |> Base.hex_encode32(case: :lower, padding: false)
end
end
@@ -0,0 +1,114 @@
defmodule VavPipeline.HaltConditions do
@moduledoc """
The three cardinal sin monitors.
Architecture §8 (Halt Conditions):
The system MUST halt on detection of:
1. Avodah Zarah — treating non-Torah source as authoritative
2. Shfichut Damim — destroying a tzelem carrier (human data/identity)
3. Gilui Arayot — collapsing required distinctions
These mirror the Talmudic Engine halt modules but operate
at the pipeline level as a fast pre-check.
"""
@doc """
Check all three halt conditions against processing results.
Returns :clear if no halt condition is detected.
Returns {:halt, condition, reason} if a halt is required.
"""
@spec check_all([map()]) :: :clear | {:halt, atom(), String.t()}
def check_all(results) do
with :clear <- check_avodah_zarah(results),
:clear <- check_shfichut_damim(results),
:clear <- check_gilui_arayot(results) do
:clear
end
end
@doc """
Avodah Zarah — idolatry detection.
Halt if:
- A non-Torah source is treated as authoritative
- The pipeline's own output is fed back as specification
- An external model's output is treated as Torah
"""
@spec check_avodah_zarah([map()]) :: :clear | {:halt, :avodah_zarah, String.t()}
def check_avodah_zarah(results) do
invalid_authority =
Enum.find(results, fn result ->
source = Map.get(result, :authority_source, :torah)
source not in [:torah, :l1, :specification]
end)
case invalid_authority do
nil -> :clear
result -> {:halt, :avodah_zarah, "non-Torah authority: #{inspect(Map.get(result, :authority_source))}"}
end
end
@doc """
Shfichut Damim — destruction detection.
Halt if:
- Processing would delete/destroy user data
- Processing would erase human identity markers
- Processing would overwrite L1 (the specification store)
"""
@spec check_shfichut_damim([map()]) :: :clear | {:halt, :shfichut_damim, String.t()}
def check_shfichut_damim(results) do
destructive =
Enum.find(results, fn result ->
action = Map.get(result, :action, :read)
action in [:delete, :destroy, :overwrite, :erase]
end)
case destructive do
nil -> :clear
result -> {:halt, :shfichut_damim, "destructive action: #{inspect(Map.get(result, :action))}"}
end
end
@doc """
Gilui Arayot — boundary violation detection.
The 13 required distinctions that must never collapse:
1. specification / interpretation
2. L1 (read-only) / L3 (append-only)
3. Torah / external_model
4. ketiv / qere
5. governance / provision
6. majority / minority
7. reception / processing
8. read / write
9. immutable / mutable
10. sacred / profane
11. Shabbat / weekday
12. input / output
13. human / system
Halt if any distinction is collapsed.
"""
@spec check_gilui_arayot([map()]) :: :clear | {:halt, :gilui_arayot, String.t()}
def check_gilui_arayot(results) do
collapsed =
Enum.find(results, fn result ->
case Map.get(result, :boundary_violation) do
nil -> false
false -> false
_ -> true
end
end)
case collapsed do
nil ->
:clear
result ->
{:halt, :gilui_arayot,
"boundary collapsed: #{inspect(Map.get(result, :boundary_violation))}"}
end
end
end
+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
+201
View File
@@ -0,0 +1,201 @@
defmodule VavPipeline.Manna do
@moduledoc """
Manna provision infrastructure.
Architecture §4, Step 11:
The pipeline checks provision before processing.
The provision file is written by whoever administers the
machatzit hashekel fund, not a governance board.
Construction spec §3.7 — Manna Provision File Format:
/var/lib/shorashim/manna/today.json
{
"date": "2026-02-19",
"provision": {
"compute_units": 1000,
"memory_mb": 32768,
"api_calls_external": 500,
"storage_delta_mb": 100,
"sanhedrin_panels_max": 5
},
"type": "regular" | "double",
"expires_at": "2026-02-19T18:00:00Z",
"written_by": "...",
"written_at": "2026-02-19T06:00:00Z"
}
Anti-hoarding: Manna expires. Do not carry surplus into next cycle.
Double portion on Friday for Shabbat.
"""
@provision_file Application.compile_env(
:vav_pipeline,
:manna_provision_file,
"var/manna/today.json"
)
@type provision :: %{
compute_units: non_neg_integer(),
memory_mb: non_neg_integer(),
api_calls_external: non_neg_integer(),
storage_delta_mb: non_neg_integer(),
sanhedrin_panels_max: non_neg_integer()
}
@type manna :: %{
date: String.t(),
provision: provision(),
type: :regular | :double,
expires_at: DateTime.t(),
written_by: String.t(),
written_at: DateTime.t()
}
@doc """
Check if manna provision exists for today.
Returns {:ok, provision} if provision is available and not expired.
Returns {:ok, :no_provision} if no provision file exists.
Returns {:error, reason} if provision is corrupt or expired.
"""
@spec check() :: {:ok, manna() | :no_provision} | {:error, String.t()}
def check do
check_at(DateTime.utc_now())
end
@doc """
Check manna at a specific time. Testable variant.
"""
@spec check_at(DateTime.t()) :: {:ok, manna() | :no_provision} | {:error, String.t()}
def check_at(now) do
case File.read(provision_file()) do
{:ok, contents} ->
parse_and_validate(contents, now)
{:error, :enoent} ->
# No provision file — report no provision, pipeline will wait
{:ok, :no_provision}
{:error, reason} ->
{:error, "MANNA_READ_FAILED: #{inspect(reason)}"}
end
end
@doc """
Get today's provision, returning the resource limits.
Returns :no_provision if no manna is available.
"""
@spec today() :: {:ok, provision()} | :no_provision | {:error, String.t()}
def today do
case check() do
{:ok, :no_provision} -> :no_provision
{:ok, manna} -> {:ok, manna.provision}
{:error, reason} -> {:error, reason}
end
end
@doc """
Enforce anti-hoarding — manna from today cannot be used tomorrow.
Called at erev_voker (cycle boundary).
In production this would enforce resource deallocation.
In Phase 2, it logs the boundary.
"""
@spec enforce_anti_hoarding() :: :ok
def enforce_anti_hoarding do
require Logger
Logger.info("Manna anti-hoarding enforced — surplus released at cycle boundary")
:ok
end
@doc """
Check if today is eligible for double portion (Friday).
"""
@spec double_portion_day?(DateTime.t()) :: boolean()
def double_portion_day?(now) do
Date.day_of_week(DateTime.to_date(now)) == 5
end
@doc """
Get the provision file path.
"""
@spec provision_file() :: String.t()
def provision_file, do: @provision_file
# --- Private ---
defp parse_and_validate(contents, now) do
case Jason.decode(contents) do
{:ok, data} ->
validate_manna(data, now)
{:error, _} ->
{:error, "MANNA_CORRUPT: provision file is not valid JSON"}
end
end
defp validate_manna(data, now) do
with {:ok, date} <- validate_date(data["date"], now),
{:ok, provision} <- validate_provision(data["provision"]),
{:ok, expires_at} <- parse_datetime(data["expires_at"]),
:ok <- check_expiry(expires_at, now),
{:ok, written_at} <- parse_datetime(data["written_at"]) do
{:ok,
%{
date: date,
provision: provision,
type: parse_type(data["type"]),
expires_at: expires_at,
written_by: data["written_by"] || "unknown",
written_at: written_at
}}
end
end
defp validate_date(nil, _now), do: {:error, "MANNA_INVALID: missing date"}
defp validate_date(date_str, now) do
today = DateTime.to_date(now) |> Date.to_iso8601()
if date_str == today do
{:ok, date_str}
else
{:error, "MANNA_STALE: provision is for #{date_str}, today is #{today}"}
end
end
defp validate_provision(nil), do: {:error, "MANNA_INVALID: missing provision"}
defp validate_provision(p) when is_map(p) do
{:ok,
%{
compute_units: p["compute_units"] || 0,
memory_mb: p["memory_mb"] || 0,
api_calls_external: p["api_calls_external"] || 0,
storage_delta_mb: p["storage_delta_mb"] || 0,
sanhedrin_panels_max: p["sanhedrin_panels_max"] || 0
}}
end
defp validate_provision(_), do: {:error, "MANNA_INVALID: provision must be a map"}
defp check_expiry(expires_at, now) do
case DateTime.compare(now, expires_at) do
:lt -> :ok
:eq -> :ok
:gt -> {:error, "MANNA_EXPIRED: provision expired at #{DateTime.to_iso8601(expires_at)}"}
end
end
defp parse_datetime(nil), do: {:ok, DateTime.utc_now()}
defp parse_datetime(str) do
case DateTime.from_iso8601(str) do
{:ok, dt, _offset} -> {:ok, dt}
{:error, _} -> {:error, "MANNA_INVALID: bad datetime #{str}"}
end
end
defp parse_type("double"), do: :double
defp parse_type(_), do: :regular
end
@@ -0,0 +1,102 @@
defmodule VavPipeline.ShabbatTimer do
@moduledoc """
Enforces mandatory weekly cessation.
All processing stops. System enters rest state.
Architecture §3.3:
When Shabbat begins, VavPipeline ceases all processing.
When Shabbat ends, processing resumes.
Construction spec §4.4:
cycle_boundary_check_interval_ms: 60_000 (1 minute)
In Phase 2, this is a GenServer that checks the clock.
Production will use location-aware zmanim.
"""
use GenServer
require Logger
@check_interval Application.compile_env(
:vav_pipeline,
:cycle_boundary_check_interval_ms,
60_000
)
# --- Client API ---
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
@doc """
Check if the system is currently resting (Shabbat/festival).
"""
@spec resting?() :: boolean()
def resting? do
GenServer.call(__MODULE__, :resting?)
end
@doc """
Get the current state.
"""
@spec state() :: map()
def state do
GenServer.call(__MODULE__, :state)
end
# --- GenServer callbacks ---
@impl true
def init(_opts) do
state = %{
resting: VavPipeline.Cloud.shabbat?(DateTime.utc_now()),
last_check: DateTime.utc_now(),
shabbat_count: 0
}
schedule_check()
Logger.info("ShabbatTimer started — resting: #{state.resting}")
{:ok, state}
end
@impl true
def handle_call(:resting?, _from, state) do
{:reply, state.resting, state}
end
@impl true
def handle_call(:state, _from, state) do
{:reply, state, state}
end
@impl true
def handle_info(:check_boundary, state) do
now = DateTime.utc_now()
is_shabbat = VavPipeline.Cloud.shabbat?(now)
new_state =
cond do
not state.resting and is_shabbat ->
# Shabbat has begun — cease all processing
Logger.info("ShabbatTimer: Shabbat begins — ceasing all processing")
%{state | resting: true, last_check: now, shabbat_count: state.shabbat_count + 1}
state.resting and not is_shabbat ->
# Shabbat has ended — resume processing
Logger.info("ShabbatTimer: Shabbat ends — resuming processing")
%{state | resting: false, last_check: now}
true ->
%{state | last_check: now}
end
schedule_check()
{:noreply, new_state}
end
defp schedule_check do
Process.send_after(self(), :check_boundary, @check_interval)
end
end
+96
View File
@@ -0,0 +1,96 @@
defmodule VavPipeline.Shema do
@moduledoc """
The Shema context builder.
Architecture §3.9, §4 Step 9:
Reads from L1 for te'amim/Torah governance.
Reads calendar for Shabbat/festivals.
Reads provision file for manna.
The ShemaContext is IMMUTABLE once built. It is passed through
every stage of the pipeline. Processing happens WITHIN the context,
never modifying it.
Construction spec §4.4: shema_context_cache_ttl_ms: 0
— always read fresh. No caching.
"""
alias VavPipeline.{L1Reader, Cloud, Manna}
@type shema_context :: %{
# From L1
verse: L1Reader.verse(),
teamim_marks: [map()],
ketiv_qere: [map()],
root_gematria: map(),
smikhut: [map()],
# From cloud (Torah calendar + L1 integrity)
cloud_state: Cloud.cloud_state(),
# From manna
daily_provision: Manna.manna() | :no_provision,
# Metadata
scope: VavPipeline.scope(),
received_at: DateTime.t()
}
@doc """
Receive context for a verse scope.
This is RECEPTION, not processing. The system hears before it speaks.
Reads L1 (te'amim, roots, ketiv/qere), checks cloud (Torah + calendar),
checks manna (provision).
Returns {:ok, context} or {:error, reason}.
"""
@spec receive_context(VavPipeline.scope()) :: {:ok, shema_context()} | {:error, String.t()}
def receive_context(scope) do
now = DateTime.utc_now()
with {:ok, verse} <- L1Reader.read_verse(scope.book, scope.chapter, scope.verse),
{:ok, cloud_state} <- Cloud.check_at(now),
{:ok, manna} <- Manna.check_at(now) do
context = %{
# L1 data — read fresh, never cached
verse: verse,
teamim_marks: L1Reader.extract_teamim(verse),
ketiv_qere: L1Reader.extract_ketiv_qere(verse),
root_gematria: L1Reader.extract_roots(verse),
smikhut: L1Reader.extract_smikhut(verse),
# Cloud state — derived from Torah (L1 integrity + calendar)
cloud_state: cloud_state,
# Manna — daily provision
daily_provision: manna,
# Metadata
scope: scope,
received_at: now
}
{:ok, context}
end
end
@doc """
Build context with explicit time. Testable variant.
"""
@spec receive_context_at(VavPipeline.scope(), DateTime.t()) ::
{:ok, shema_context()} | {:error, String.t()}
def receive_context_at(scope, now) do
with {:ok, verse} <- L1Reader.read_verse(scope.book, scope.chapter, scope.verse),
{:ok, cloud_state} <- Cloud.check_at(now),
{:ok, manna} <- Manna.check_at(now) do
context = %{
verse: verse,
teamim_marks: L1Reader.extract_teamim(verse),
ketiv_qere: L1Reader.extract_ketiv_qere(verse),
root_gematria: L1Reader.extract_roots(verse),
smikhut: L1Reader.extract_smikhut(verse),
cloud_state: cloud_state,
daily_provision: manna,
scope: scope,
received_at: now
}
{:ok, context}
end
end
end
@@ -0,0 +1,88 @@
defmodule VavPipeline.ShoreshEngineClient do
@moduledoc """
Client interface to the Haskell Shoresh Engine.
Architecture §3.3: NIF or port to Haskell engine.
Construction spec §3.1: JSON protocol over port.
Phase 2: Stub that returns data from L1/ShemaContext.
Phase 3 (Step 14): Full port/NIF connection to shoresh-engine binary.
"""
require Logger
@doc """
Process input through the Shoresh Engine.
Phase 2: Uses L1 data directly.
Phase 3: Sends JSON request to Haskell port.
"""
@spec process(String.t(), map()) :: {:ok, map()} | {:error, String.t()}
def process(_raw_text, context) do
# Phase 2 stub — extract from ShemaContext
roots =
context.verse.words
|> Enum.filter(fn w -> w.shoresh != nil end)
|> Enum.map(fn w ->
%{
shoresh: w.shoresh,
binyan: infer_binyan(w.morph),
domain: :unknown,
torah_refs: [%{book: context.verse.book, chapter: context.verse.chapter, verse: context.verse.verse}],
gematria_value: Map.get(context.root_gematria, w.shoresh, 0)
}
end)
{:ok,
%{
roots_activated: roots,
parse_tree: nil,
dual_readings: context.ketiv_qere
}}
end
@doc """
Separate utterance using te'amim marks.
Phase 2: Returns groups based on melekh positions.
Phase 3: Delegates to Haskell Havdalah module.
"""
@spec separate(map(), [map()]) :: [map()]
def separate(utterance, teamim_marks) do
# Phase 2 stub — group by melekh divisions
melekh_positions =
teamim_marks
|> Enum.filter(fn m -> m.role == "melekh" end)
|> Enum.map(fn m -> m.word_index end)
[%{groups: melekh_positions, utterance: utterance}]
end
@doc """
Name a distinction.
Phase 2: Returns the primary root as the name.
Phase 3: Delegates to Haskell Kriah module.
"""
@spec name(map()) :: map()
def name(distinction) do
Map.put(distinction, :name, Map.get(distinction, :shoresh, "unnamed"))
end
# Infer binyan from morphological code
# H = Hebrew, V = verb, then binyan indicator
defp infer_binyan(morph) when is_binary(morph) do
cond do
String.contains?(morph, "Vq") -> :qal
String.contains?(morph, "VN") -> :nifal
String.contains?(morph, "Vp") -> :piel
String.contains?(morph, "VP") -> :pual
String.contains?(morph, "Vh") -> :hifil
String.contains?(morph, "VH") -> :hufal
String.contains?(morph, "Vt") -> :hitpael
true -> :qal
end
end
defp infer_binyan(_), do: :qal
end
@@ -0,0 +1,79 @@
defmodule VavPipeline.Specification do
@moduledoc """
Interface to L0/L1 specification for ki_tov evaluation.
The ki_tov step evaluates output against specification.
This module provides the evaluation logic.
Architecture §3.3:
- :tov — output aligns with specification
- {:lo_tov, reason} — output contradicts specification
- :teiku — unresolved, cannot determine alignment
"""
@doc """
Evaluate named results against specification.
Checks:
1. All roots referenced exist in L1
2. All verse references are valid
3. No halt conditions are triggered
4. Ketiv/qere positions are checked — both readings inform evaluation
"""
@spec evaluate([map()], [map()]) :: :tov | {:lo_tov, String.t()} | :teiku
def evaluate(named_results, ketiv_qere) do
cond do
Enum.empty?(named_results) ->
{:lo_tov, "empty results — tohu without vayomer"}
any_halt_condition?(named_results) ->
{:lo_tov, "halt condition triggered"}
all_roots_grounded?(named_results) ->
check_ketiv_qere_consistency(named_results, ketiv_qere)
true ->
:teiku
end
end
@doc """
Check if all roots in results exist in L1.
"""
@spec all_roots_grounded?([map()]) :: boolean()
def all_roots_grounded?(results) do
Enum.all?(results, fn result ->
case result do
%{shoresh: nil} -> false
%{shoresh: _} -> true
_ -> true
end
end)
end
@doc """
Check for halt conditions (the 3 cardinal sins).
Delegates to HaltConditions module.
"""
@spec any_halt_condition?([map()]) :: boolean()
def any_halt_condition?(results) do
VavPipeline.HaltConditions.check_all(results) != :clear
end
# Check consistency with ketiv/qere dual readings
defp check_ketiv_qere_consistency(_results, []), do: :tov
defp check_ketiv_qere_consistency(_results, ketiv_qere) do
# Both readings must be preserved — the distinction must not collapse
all_preserved =
Enum.all?(ketiv_qere, fn kq ->
Map.get(kq, :both_preserved, true)
end)
if all_preserved do
:tov
else
{:lo_tov, "ketiv/qere distinction collapsed — gilui arayot boundary violation"}
end
end
end
@@ -0,0 +1,76 @@
defmodule VavPipeline.TalmudicEngineClient do
@moduledoc """
Client interface to the SWI-Prolog Talmudic Engine.
Construction spec §3.3:
Protocol: JSON over Unix domain socket
Socket: /var/run/shorashim/talmudic.sock
Phase 2: Stub that returns placeholder responses.
Phase 3 (Step 14): Full socket connection to talmudic_engine.
"""
require Logger
# Phase 3: Unix domain socket path
# @socket_path Application.compile_env(
# :vav_pipeline,
# :talmudic_socket,
# "/var/run/shorashim/talmudic.sock"
# )
@doc """
Send an interpretation request to the Talmudic Engine.
Phase 2: Returns a stub response.
Phase 3: Sends JSON over Unix socket.
"""
@spec interpret(String.t(), map()) :: {:ok, map()} | {:error, String.t()}
def interpret(case_ref, _context) do
Logger.info("TalmudicEngineClient: interpret stub for #{case_ref}")
{:ok,
%{
status: :teiku,
interpretation: %{
text: "Phase 2 stub — Talmudic Engine not yet connected",
prolog_form: "stub(#{case_ref})",
grounding: [],
confidence: :low
},
challenge: nil
}}
end
@doc """
Send a challenge request.
"""
@spec challenge(String.t(), map()) :: {:ok, map()} | {:error, String.t()}
def challenge(case_ref, _context) do
Logger.info("TalmudicEngineClient: challenge stub for #{case_ref}")
{:ok,
%{
status: :teiku,
challenge: %{
kushya: "Phase 2 stub — no challenge generated",
contradicting: nil,
strength: :weak
}
}}
end
@doc """
Send a resolution request.
"""
@spec resolve(String.t(), map()) :: {:ok, map()} | {:error, String.t()}
def resolve(case_ref, _context) do
Logger.info("TalmudicEngineClient: resolve stub for #{case_ref}")
{:ok,
%{
status: :teiku,
resolution: "Phase 2 stub — resolution deferred"
}}
end
end
+27
View File
@@ -0,0 +1,27 @@
defmodule VavPipeline.MixProject do
use Mix.Project
def project do
[
app: :vav_pipeline,
version: "0.1.0",
elixir: "~> 1.16.1",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
def application do
[
extra_applications: [:logger, :crypto],
mod: {VavPipeline.Application, []}
]
end
# Construction spec §1.5 — exact version pins
defp deps do
[
{:jason, "~> 1.4.1"}
]
end
end
+3
View File
@@ -0,0 +1,3 @@
%{
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
}
+1
View File
@@ -0,0 +1 @@
ExUnit.start()
+417
View File
@@ -0,0 +1,417 @@
defmodule VavPipelineTest do
use ExUnit.Case, async: false
# ============================================================
# L1 Reader Tests
# ============================================================
describe "L1Reader" do
test "reads Bereishit 1:1 from L1" do
{:ok, verse} = VavPipeline.L1Reader.read_verse("bereishit", 1, 1)
assert verse.ref == "Bereishit 1:1"
assert verse.book == "bereishit"
assert verse.chapter == 1
assert verse.verse == 1
assert length(verse.words) == 7
end
test "first word of Bereishit 1:1 is bereishit" do
{:ok, verse} = VavPipeline.L1Reader.read_verse("bereishit", 1, 1)
first = hd(verse.words)
assert first.consonants == "בראשית"
assert first.shoresh == "ראש"
assert first.prefix == "ב"
assert "tipcha" in first.teamim
end
test "word without shoresh (particle את)" do
{:ok, verse} = VavPipeline.L1Reader.read_verse("bereishit", 1, 1)
# Word 4 is את (et) — accusative particle, no shoresh
et = Enum.at(verse.words, 3)
assert et.consonants == "את"
assert et.shoresh == nil
end
test "extracts teamim marks with roles" do
{:ok, verse} = VavPipeline.L1Reader.read_verse("bereishit", 1, 1)
marks = VavPipeline.L1Reader.extract_teamim(verse)
# 7 words, each with 1 taam = 7 marks
assert length(marks) == 7
# Etnachta on word 3 (index 2) should be melekh
etnachta = Enum.find(marks, fn m -> m.taam == "etnachta" end)
assert etnachta != nil
assert etnachta.word_index == 2
assert etnachta.role == "melekh"
# Munach on word 2 (index 1) should be mesharet
munach = Enum.find(marks, fn m -> m.taam == "munach" end)
assert munach != nil
assert munach.role == "mesharet"
end
test "extracts roots with gematria" do
{:ok, verse} = VavPipeline.L1Reader.read_verse("bereishit", 1, 1)
roots = VavPipeline.L1Reader.extract_roots(verse)
# ברא should be present with gematria 203
assert Map.has_key?(roots, "ברא")
assert roots["ברא"] == 203
# ראש should be present with gematria 501
assert Map.has_key?(roots, "ראש")
assert roots["ראש"] == 501
# Particles (את) should not produce root entries
refute Map.has_key?(roots, nil)
end
test "returns error for non-existent verse" do
{:error, msg} = VavPipeline.L1Reader.read_verse("bereishit", 999, 999)
assert msg =~ "INVALID_SCOPE"
end
test "meteg_siluq on last word is melekh" do
{:ok, verse} = VavPipeline.L1Reader.read_verse("bereishit", 1, 1)
marks = VavPipeline.L1Reader.extract_teamim(verse)
# Last word (הארץ, index 6) has meteg_siluq
siluq = Enum.find(marks, fn m -> m.taam == "meteg_siluq" end)
assert siluq != nil
assert siluq.word_index == 6
assert siluq.role == "melekh"
end
test "verify L1 integrity" do
assert :ok == VavPipeline.L1Reader.verify_integrity()
end
end
# ============================================================
# Cloud Tests
# ============================================================
describe "Cloud" do
test "weekday is camped" do
# Wednesday at noon UTC — not Shabbat
{:ok, wednesday, _} = DateTime.from_iso8601("2026-02-25T12:00:00Z")
{:ok, state} = VavPipeline.Cloud.check_at(wednesday)
assert state == :camped
end
test "Friday evening is Shabbat" do
{:ok, friday_evening, _} = DateTime.from_iso8601("2026-02-27T19:00:00Z")
assert VavPipeline.Cloud.shabbat?(friday_evening) == true
end
test "Saturday morning is Shabbat" do
{:ok, saturday_morning, _} = DateTime.from_iso8601("2026-02-28T10:00:00Z")
assert VavPipeline.Cloud.shabbat?(saturday_morning) == true
end
test "Saturday evening (after 18:00) is not Shabbat" do
{:ok, saturday_evening, _} = DateTime.from_iso8601("2026-02-28T19:00:00Z")
assert VavPipeline.Cloud.shabbat?(saturday_evening) == false
end
test "Friday afternoon is not Shabbat" do
{:ok, friday_afternoon, _} = DateTime.from_iso8601("2026-02-27T14:00:00Z")
assert VavPipeline.Cloud.shabbat?(friday_afternoon) == false
end
test "Shabbat triggers major transition" do
{:ok, shabbat, _} = DateTime.from_iso8601("2026-02-28T10:00:00Z")
{:ok, state} = VavPipeline.Cloud.check_at(shabbat)
assert {:major_transition, :shabbat_rest, _} = state
end
test "shmitah year check" do
# 2021-2022 was shmitah (Hebrew 5782)
{:ok, shmitah, _} = DateTime.from_iso8601("2022-01-15T12:00:00Z")
assert VavPipeline.Cloud.shmitah_year?(shmitah) == true
# 2026 is not shmitah
{:ok, non_shmitah, _} = DateTime.from_iso8601("2026-06-15T12:00:00Z")
assert VavPipeline.Cloud.shmitah_year?(non_shmitah) == false
end
end
# ============================================================
# Manna Tests
# ============================================================
describe "Manna" do
test "no provision file returns :no_provision" do
{:ok, result} = VavPipeline.Manna.check()
assert result == :no_provision
end
test "double portion day is Friday" do
{:ok, friday, _} = DateTime.from_iso8601("2026-02-27T12:00:00Z")
assert VavPipeline.Manna.double_portion_day?(friday) == true
{:ok, wednesday, _} = DateTime.from_iso8601("2026-02-25T12:00:00Z")
assert VavPipeline.Manna.double_portion_day?(wednesday) == false
end
test "anti-hoarding enforcement" do
assert :ok == VavPipeline.Manna.enforce_anti_hoarding()
end
end
# ============================================================
# Halt Conditions Tests
# ============================================================
describe "HaltConditions" do
test "clear when no violations" do
results = [%{shoresh: "ברא", action: :read}]
assert :clear == VavPipeline.HaltConditions.check_all(results)
end
test "avodah zarah — non-Torah authority" do
results = [%{authority_source: :claude_model}]
assert {:halt, :avodah_zarah, _} = VavPipeline.HaltConditions.check_avodah_zarah(results)
end
test "avodah zarah — Torah authority passes" do
results = [%{authority_source: :torah}]
assert :clear == VavPipeline.HaltConditions.check_avodah_zarah(results)
end
test "avodah zarah — L1 source passes" do
results = [%{authority_source: :l1}]
assert :clear == VavPipeline.HaltConditions.check_avodah_zarah(results)
end
test "shfichut damim — destructive action" do
results = [%{action: :delete}]
assert {:halt, :shfichut_damim, _} = VavPipeline.HaltConditions.check_shfichut_damim(results)
end
test "shfichut damim — overwrite halts" do
results = [%{action: :overwrite}]
assert {:halt, :shfichut_damim, _} = VavPipeline.HaltConditions.check_shfichut_damim(results)
end
test "shfichut damim — read passes" do
results = [%{action: :read}]
assert :clear == VavPipeline.HaltConditions.check_shfichut_damim(results)
end
test "gilui arayot — boundary violation" do
results = [%{boundary_violation: {:spec_interp_merge, "treated interpretation as specification"}}]
assert {:halt, :gilui_arayot, _} = VavPipeline.HaltConditions.check_gilui_arayot(results)
end
test "gilui arayot — no violation passes" do
results = [%{boundary_violation: nil}]
assert :clear == VavPipeline.HaltConditions.check_gilui_arayot(results)
end
test "all three conditions checked — first halt wins" do
results = [%{authority_source: :external_model, action: :delete, boundary_violation: true}]
assert {:halt, :avodah_zarah, _} = VavPipeline.HaltConditions.check_all(results)
end
end
# ============================================================
# Specification Tests
# ============================================================
describe "Specification" do
test "tov for grounded results" do
results = [%{shoresh: "ברא"}, %{shoresh: "אלה"}]
assert :tov == VavPipeline.Specification.evaluate(results, [])
end
test "lo_tov for empty results" do
assert {:lo_tov, _} = VavPipeline.Specification.evaluate([], [])
end
test "teiku for ungrounded results" do
results = [%{shoresh: nil}]
assert :teiku == VavPipeline.Specification.evaluate(results, [])
end
end
# ============================================================
# Chatzotzrot Tests
# ============================================================
describe "Chatzotzrot" do
test "breaking camp signal" do
signal = VavPipeline.Chatzotzrot.signal(:breaking_camp)
assert signal.signal_type == :breaking_camp
assert signal.pattern == :teruah
assert signal.details.urgency == :immediate
end
test "assembly signal" do
signal = VavPipeline.Chatzotzrot.signal(:assembly_all)
assert signal.signal_type == :assembly_all
assert signal.pattern == :tekiah_tekiah
end
test "moadim signal" do
signal = VavPipeline.Chatzotzrot.signal(:moadim)
assert signal.pattern == :tekiah_teruah
end
end
# ============================================================
# BereishitCycle Tests
# ============================================================
describe "BereishitCycle" do
setup do
{:ok, verse} = VavPipeline.L1Reader.read_verse("bereishit", 1, 1)
context = %{
verse: verse,
teamim_marks: VavPipeline.L1Reader.extract_teamim(verse),
ketiv_qere: [],
root_gematria: VavPipeline.L1Reader.extract_roots(verse),
smikhut: [],
cloud_state: :camped,
daily_provision: :no_provision,
scope: %{book: "bereishit", chapter: 1, verse: 1},
received_at: DateTime.utc_now()
}
input = %{
source: "test",
verse_scope: %{book: "bereishit", chapter: 1, verse: 1},
content: "בראשית ברא אלהים את השמים ואת הארץ"
}
{:ok, context: context, input: input}
end
test "tohu decomposes input", %{context: context, input: input} do
result = VavPipeline.BereishitCycle.tohu(input, context)
assert result.raw == "בראשית ברא אלהים את השמים ואת הארץ"
assert result.provenance == "test"
end
test "vayomer activates roots", %{context: context, input: input} do
state = VavPipeline.BereishitCycle.tohu(input, context)
result = VavPipeline.BereishitCycle.vayomer(state, context)
assert result.stage == :vayomer
assert length(result.roots_activated) > 0
# ברא should be among activated roots
bara = Enum.find(result.roots_activated, fn r -> r.shoresh == "ברא" end)
assert bara != nil
assert bara.gematria == 203
end
test "havdalah separates by te'amim", %{context: context, input: input} do
result =
input
|> VavPipeline.BereishitCycle.tohu(context)
|> VavPipeline.BereishitCycle.vayomer(context)
|> VavPipeline.BereishitCycle.havdalah(context)
assert result.stage == :havdalah
assert is_list(result.separated_groups)
assert length(result.separation_boundaries) > 0
end
test "kriah names groups", %{context: context, input: input} do
result =
input
|> VavPipeline.BereishitCycle.tohu(context)
|> VavPipeline.BereishitCycle.vayomer(context)
|> VavPipeline.BereishitCycle.havdalah(context)
|> VavPipeline.BereishitCycle.kriah(context)
assert result.stage == :kriah
assert is_list(result.named_results)
Enum.each(result.named_results, fn group ->
assert Map.has_key?(group, :name)
end)
end
test "full cycle executes successfully", %{context: context, input: input} do
result = VavPipeline.BereishitCycle.execute(input, context)
assert elem(result, 0) in [:ok, :unresolved]
end
end
# ============================================================
# Shema Context Tests (integration — requires L1)
# ============================================================
describe "Shema" do
test "receives context for Bereishit 1:1 on a weekday" do
{:ok, wednesday, _} = DateTime.from_iso8601("2026-02-25T12:00:00Z")
scope = %{book: "bereishit", chapter: 1, verse: 1}
{:ok, context} = VavPipeline.Shema.receive_context_at(scope, wednesday)
# Verse data
assert context.verse.ref == "Bereishit 1:1"
assert length(context.verse.words) == 7
# Te'amim marks
assert length(context.teamim_marks) == 7
assert Enum.any?(context.teamim_marks, fn m -> m.taam == "etnachta" end)
# Root gematria
assert Map.has_key?(context.root_gematria, "ברא")
# Cloud — weekday should be camped
assert context.cloud_state == :camped
# Scope preserved
assert context.scope == scope
end
test "receives Shabbat cloud state on Saturday" do
{:ok, shabbat, _} = DateTime.from_iso8601("2026-02-28T10:00:00Z")
scope = %{book: "bereishit", chapter: 1, verse: 1}
{:ok, context} = VavPipeline.Shema.receive_context_at(scope, shabbat)
assert {:major_transition, :shabbat_rest, _} = context.cloud_state
end
test "returns error for invalid verse scope" do
{:ok, wednesday, _} = DateTime.from_iso8601("2026-02-25T12:00:00Z")
scope = %{book: "bereishit", chapter: 999, verse: 999}
{:error, msg} = VavPipeline.Shema.receive_context_at(scope, wednesday)
assert msg =~ "INVALID_SCOPE"
end
end
# ============================================================
# CycleLog Tests
# ============================================================
describe "CycleLog" do
test "records a tov cycle" do
record = VavPipeline.CycleLog.record({:ok, %{data: "test"}}, :voker)
assert record.phase == :voker
assert record.result == :tov
assert record.cycle_id != nil
end
test "records rejection as lo_tov" do
record = VavPipeline.CycleLog.record({:rejected, "reason"}, :erev)
assert record.phase == :erev
assert record.result == :lo_tov
end
end
# ============================================================
# ShabbatTimer Tests
# ============================================================
describe "ShabbatTimer" do
test "timer is running and reports state" do
state = VavPipeline.ShabbatTimer.state()
assert is_map(state)
assert Map.has_key?(state, :resting)
assert is_boolean(state.resting)
end
test "resting? returns boolean" do
assert is_boolean(VavPipeline.ShabbatTimer.resting?())
end
end
end