Files
bikoret/vav_pipeline/lib/vav_pipeline/manna.ex
T
Scott KarpandClaude Opus 4.6 242c49c9b2 MannaLedger: real anti-hoarding enforcement — d'var yom b'yomo (Step 26)
Shemot 16:4 — d'var yom b'yomo — the day's matter in its day.
Shemot 16:19 — al yoter mimenu ad boker — do not leave surplus.
Shemot 16:20 — vayotiru... vayarum tola'im vayiv'ash — hoarded manna rots.

- MannaLedger GenServer tracks consumption against daily provision
- load_provision/0 reads manna file, resets consumption to zero
- consume/2 deducts from allocated resource, rejects when exhausted
- remaining/1 and available?/2 for balance queries
- enforce_cycle_boundary/0 zeroes out all allocation, returns released amounts
- Chatzotzrot assembly_leaders signal when provision runs low (< 10%)
- Manna.enforce_anti_hoarding/0 now delegates to MannaLedger
- Added to supervision tree in application.ex
- 17 new tests with proper isolation (reset between tests)
- All 177 Elixir tests pass, 0 failures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 09:27:09 -04:00

223 lines
6.3 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).
Shemot 16:19 — al yoter mimenu ad boker.
Delegates to MannaLedger.enforce_cycle_boundary/0 for real enforcement.
"""
@spec enforce_anti_hoarding() :: :ok
def enforce_anti_hoarding do
require Logger
case VavPipeline.MannaLedger.enforce_cycle_boundary() do
{:ok, %{released: released}} ->
total = released |> Map.values() |> Enum.sum()
if total > 0 do
Logger.info("Manna anti-hoarding enforced — surplus released: #{inspect(released)}")
else
Logger.info("Manna anti-hoarding enforced — no surplus to release")
end
:ok
end
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_expires_at(data["expires_at"]),
:ok <- check_expiry(expires_at, now) do
{:ok,
%{
date: date,
provision: provision,
type: parse_type(data["type"]),
expires_at: expires_at,
written_by: data["written_by"] || "unknown",
written_at: parse_written_at(data["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
# expires_at is required — missing means invalid provision
defp parse_expires_at(nil), do: {:error, "MANNA_INVALID: missing expires_at"}
defp parse_expires_at(str) do
case DateTime.from_iso8601(str) do
{:ok, dt, _offset} -> {:ok, dt}
{:error, _} -> {:error, "MANNA_INVALID: bad expires_at datetime #{str}"}
end
end
# written_at is metadata — missing is tolerable
defp parse_written_at(nil), do: nil
defp parse_written_at(str) do
case DateTime.from_iso8601(str) do
{:ok, dt, _offset} -> dt
{:error, _} -> nil
end
end
defp parse_type("double"), do: :double
defp parse_type(_), do: :regular
end