Files
bikoret/vav_pipeline/lib/vav_pipeline/manna.ex
T
Scott KarpandClaude Sonnet 4.6 80ec364da6 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>
2026-02-26 14:02:34 -05:00

202 lines
5.7 KiB
Elixir

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