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>
This commit is contained in:
1 parent
671d45625c
commit
6a879fcf8b
4 files changed
+566
-4
No files matched your search
@@ -0,0 +1,241 @@
|
||||
defmodule VavPipeline.MannaLedgerTest do
|
||||
@moduledoc """
|
||||
Tests for the MannaLedger — manna consumption and anti-hoarding enforcement.
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
alias VavPipeline.MannaLedger
|
||||
|
||||
setup do
|
||||
MannaLedger.reset()
|
||||
:ok
|
||||
end
|
||||
|
||||
# Helper to load provision from a test fixture file
|
||||
defp write_test_provision(opts \\ []) do
|
||||
today = Date.utc_today() |> Date.to_iso8601()
|
||||
compute = Keyword.get(opts, :compute_units, 100)
|
||||
api_calls = Keyword.get(opts, :api_calls_external, 50)
|
||||
panels = Keyword.get(opts, :sanhedrin_panels_max, 3)
|
||||
type = Keyword.get(opts, :type, "regular")
|
||||
|
||||
expires =
|
||||
DateTime.utc_now()
|
||||
|> DateTime.add(6 * 3600)
|
||||
|> DateTime.to_iso8601()
|
||||
|
||||
provision = %{
|
||||
"date" => today,
|
||||
"provision" => %{
|
||||
"compute_units" => compute,
|
||||
"memory_mb" => 1024,
|
||||
"api_calls_external" => api_calls,
|
||||
"storage_delta_mb" => 10,
|
||||
"sanhedrin_panels_max" => panels
|
||||
},
|
||||
"type" => type,
|
||||
"expires_at" => expires,
|
||||
"written_by" => "test",
|
||||
"written_at" => DateTime.utc_now() |> DateTime.to_iso8601()
|
||||
}
|
||||
|
||||
path = VavPipeline.Manna.provision_file()
|
||||
File.mkdir_p!(Path.dirname(path))
|
||||
File.write!(path, Jason.encode!(provision))
|
||||
|
||||
on_exit(fn -> File.rm(path) end)
|
||||
:ok
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
# Loading provision — d'var yom b'yomo
|
||||
# ============================================================
|
||||
describe "load_provision" do
|
||||
test "loads provision from manna file" do
|
||||
write_test_provision(compute_units: 200, api_calls_external: 100)
|
||||
assert {:ok, :loaded} = MannaLedger.load_provision()
|
||||
|
||||
ledger = MannaLedger.ledger()
|
||||
assert ledger.allocated.compute_units == 200
|
||||
assert ledger.allocated.api_calls_external == 100
|
||||
assert ledger.consumed.compute_units == 0
|
||||
end
|
||||
|
||||
test "returns :already_loaded if same day" do
|
||||
write_test_provision()
|
||||
assert {:ok, :loaded} = MannaLedger.load_provision()
|
||||
assert {:ok, :already_loaded} = MannaLedger.load_provision()
|
||||
end
|
||||
|
||||
test "returns :no_provision when no manna file" do
|
||||
# Ensure provision file doesn't exist
|
||||
path = VavPipeline.Manna.provision_file()
|
||||
File.rm(path)
|
||||
|
||||
assert {:ok, :no_provision} = MannaLedger.load_provision()
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
# Consumption — v'laktu d'var yom b'yomo
|
||||
# ============================================================
|
||||
describe "consume" do
|
||||
setup do
|
||||
write_test_provision(compute_units: 10, api_calls_external: 5, sanhedrin_panels_max: 2)
|
||||
MannaLedger.load_provision()
|
||||
:ok
|
||||
end
|
||||
|
||||
test "consumes resource and returns remaining" do
|
||||
assert {:ok, 7} = MannaLedger.consume(:compute_units, 3)
|
||||
assert {:ok, 4} = MannaLedger.consume(:compute_units, 3)
|
||||
end
|
||||
|
||||
test "rejects when resource exhausted" do
|
||||
MannaLedger.consume(:compute_units, 10)
|
||||
assert {:error, :provision_exhausted} = MannaLedger.consume(:compute_units, 1)
|
||||
end
|
||||
|
||||
test "rejects when amount exceeds remaining" do
|
||||
MannaLedger.consume(:compute_units, 8)
|
||||
assert {:error, :provision_exhausted} = MannaLedger.consume(:compute_units, 5)
|
||||
end
|
||||
|
||||
test "different resources tracked independently" do
|
||||
MannaLedger.consume(:compute_units, 5)
|
||||
MannaLedger.consume(:api_calls_external, 3)
|
||||
|
||||
assert MannaLedger.remaining(:compute_units) == 5
|
||||
assert MannaLedger.remaining(:api_calls_external) == 2
|
||||
end
|
||||
|
||||
test "returns error when no provision loaded" do
|
||||
# Force a fresh cycle by enforcing boundary first
|
||||
MannaLedger.enforce_cycle_boundary()
|
||||
|
||||
# Now no provision is loaded
|
||||
assert {:error, :no_provision} = MannaLedger.consume(:compute_units, 1)
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
# Remaining and available?
|
||||
# ============================================================
|
||||
describe "remaining and available?" do
|
||||
setup do
|
||||
write_test_provision(compute_units: 20)
|
||||
MannaLedger.load_provision()
|
||||
:ok
|
||||
end
|
||||
|
||||
test "remaining returns correct balance" do
|
||||
assert MannaLedger.remaining(:compute_units) == 20
|
||||
MannaLedger.consume(:compute_units, 7)
|
||||
assert MannaLedger.remaining(:compute_units) == 13
|
||||
end
|
||||
|
||||
test "available? checks sufficiency" do
|
||||
assert MannaLedger.available?(:compute_units, 20)
|
||||
assert MannaLedger.available?(:compute_units, 1)
|
||||
refute MannaLedger.available?(:compute_units, 21)
|
||||
|
||||
MannaLedger.consume(:compute_units, 15)
|
||||
assert MannaLedger.available?(:compute_units, 5)
|
||||
refute MannaLedger.available?(:compute_units, 6)
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
# Cycle boundary enforcement — al yoter mimenu ad boker
|
||||
# ============================================================
|
||||
describe "enforce_cycle_boundary" do
|
||||
setup do
|
||||
write_test_provision(compute_units: 100, api_calls_external: 50)
|
||||
MannaLedger.load_provision()
|
||||
:ok
|
||||
end
|
||||
|
||||
test "releases surplus and zeroes allocation" do
|
||||
MannaLedger.consume(:compute_units, 30)
|
||||
MannaLedger.consume(:api_calls_external, 10)
|
||||
|
||||
{:ok, %{released: released}} = MannaLedger.enforce_cycle_boundary()
|
||||
|
||||
# 100 allocated - 30 consumed = 70 released
|
||||
assert released.compute_units == 70
|
||||
# 50 allocated - 10 consumed = 40 released
|
||||
assert released.api_calls_external == 40
|
||||
end
|
||||
|
||||
test "after enforcement, all resources are zero" do
|
||||
MannaLedger.consume(:compute_units, 50)
|
||||
MannaLedger.enforce_cycle_boundary()
|
||||
|
||||
assert MannaLedger.remaining(:compute_units) == 0
|
||||
assert MannaLedger.remaining(:api_calls_external) == 0
|
||||
end
|
||||
|
||||
test "after enforcement, consume returns :no_provision" do
|
||||
MannaLedger.enforce_cycle_boundary()
|
||||
assert {:error, :no_provision} = MannaLedger.consume(:compute_units, 1)
|
||||
end
|
||||
|
||||
test "full consumption leaves nothing to release" do
|
||||
MannaLedger.consume(:compute_units, 100)
|
||||
MannaLedger.consume(:api_calls_external, 50)
|
||||
|
||||
{:ok, %{released: released}} = MannaLedger.enforce_cycle_boundary()
|
||||
assert released.compute_units == 0
|
||||
assert released.api_calls_external == 0
|
||||
end
|
||||
|
||||
test "enforcement records timestamp" do
|
||||
MannaLedger.enforce_cycle_boundary()
|
||||
ledger = MannaLedger.ledger()
|
||||
assert ledger.last_enforcement != nil
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
# Manna.enforce_anti_hoarding integration
|
||||
# ============================================================
|
||||
describe "Manna.enforce_anti_hoarding integration" do
|
||||
setup do
|
||||
write_test_provision(compute_units: 50)
|
||||
MannaLedger.load_provision()
|
||||
:ok
|
||||
end
|
||||
|
||||
test "Manna.enforce_anti_hoarding delegates to ledger" do
|
||||
MannaLedger.consume(:compute_units, 20)
|
||||
assert :ok = VavPipeline.Manna.enforce_anti_hoarding()
|
||||
|
||||
# After enforcement, resources are zeroed
|
||||
assert MannaLedger.remaining(:compute_units) == 0
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
# Ledger state
|
||||
# ============================================================
|
||||
describe "ledger state" do
|
||||
test "tracks cycle count" do
|
||||
write_test_provision()
|
||||
initial = MannaLedger.ledger().cycle_count
|
||||
|
||||
MannaLedger.load_provision()
|
||||
assert MannaLedger.ledger().cycle_count == initial + 1
|
||||
|
||||
# Force new cycle
|
||||
MannaLedger.enforce_cycle_boundary()
|
||||
|
||||
# Can't easily test second load on same day since it returns :already_loaded
|
||||
# But cycle_count incremented on load
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user