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>
202 lines
5.7 KiB
Elixir
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
|