TORAH ANCHOR — Shemot 16:4, and tamid §2.1 check 3: Manna provision read … If absent: WAIT. Alert senior kohanim. WAIT. Not die. Provision is not the health of the vessel. Killing the process does not produce manna; it re-runs init, re-spawns the Port, and waits again — three minutes later. WHAT THE KOHEN FOUND (BUILDER_HANDOFF_probes.md, 2026-09-07 20:35Z): A fresh pod after recreate. emptyDir manna empty — by design (KOHEN.md:406). manna_present honestly read "fail" — the Day-6 fix, correct per tamid §1.1. overall → "degraded" → /kodesh/health 503. BOTH probes pointed at /kodesh/health. Liveness: period 60s, default failureThreshold 3 → after three minutes the kubelet SIGTERMed a process whose log showed the Port alive, zero errors, l3_writable pass, esh_tamid pass. It was alive and serving. It was killed for lacking provision, and would be again every three minutes until the kohen wrote today.json — i.e. on EVERY pod creation. Introduced by a correct fix (f1ab7c8made health honest); the missing piece was the distinction between "not ready to serve" and "dead". WHAT CHANGED: bigdei_kodesh/router.ex GET /kodesh/live — 200 iff the BEAM answers HTTP and the HaltMonitor process is registered. Looks at NOTHING that is a wait state: no manna, no L3, no cloud, no probe cycle. And deliberately NOT "not halted": a HALTED system is a chosen state (a cardinal sin was detected); restarting it would clear the halt and lose the event. Liveness measures that the monitor is ALIVE, not that it is quiet. /kodesh/health is unchanged — it is readiness, and a vessel without provision correctly does not receive offerings (that is shema; the Service stops routing to it). deploy/k8s/deployment.yaml livenessProbe → /kodesh/live, failureThreshold 3 made explicit. readinessProbe stays on /kodesh/health. test/liveness_test.exs — 3 tests live 200 with NO manna while health is 503; live 200 while HALTED (and pe'ah answers 503 HALT_*, not a served result); live touches neither the ledger nor L3 nor the cycle. KOHEN.md §Day 3c — a fresh pod reads 2/3 Ready, 0 restarts until Day 4 writes manna, and that is correct; restarts on a pod whose log shows the Port alive and zero errors mean liveness is on the wrong endpoint. ALSO — a leak of my own fromf1ab7c8: The MannaLedger is a global GenServer keyed by date. The Day-6 "provision exhausted" test left it at allocated 1 / consumed 1 for today; when step31 Test 4 later wrote a fresh today.json the ledger answered :already_loaded, consume failed, and the cycle waited — a 1-in-5-seeds flake. Fixed where it leaked (day6 + liveness tests reset the ledger on exit) and where it landed (step31 Test 4 gathers from a clean ledger, with the reason written down). VERIFIED — the kohen's procedure, locally, DB deliberately dead: fresh, no manna live 200 health 503 critical (manna + l3) manna written live 200 health 503 degraded — l3_writable ONLY; manna_present flipped to pass manna removed live 200 health 503 exit/SIGTERM events in the container log: 0 mix test — 440 tests, 0 failures, 36 excluded, across seeds 0 1 2 7 42 kubectl apply --dry-run=client: deployment valid; liveness /kodesh/live (threshold 3), readiness /kodesh/health Doorpost.verify_identity() :ok — no @spec_files entry touched l1/integrity.sha256 unchanged: 99bbd07a…4ea377 RAISED, NOT FIXED: manna is an emptyDir, so every pod creation needs the kohen to write it again. manna-volume.yaml's PVC exists for exactly this and is "not applied at Day 3" by the runbook's own note. That is an operations decision, not a probe one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
590 lines
22 KiB
Elixir
590 lines
22 KiB
Elixir
defmodule VavPipeline.Day6ServingTest do
|
|
@moduledoc """
|
|
Kohen Day 6 (2026-09-07) — Sh'mirat HaMishmeret — the vessel ran and
|
|
did not serve. Five defects, each found by RUNNING the cycle on a live
|
|
pod, none visible to the unit tests, because every unit test ran the
|
|
L1 fallback (no Port in the test VM) and none ran the cycle to L3.
|
|
|
|
"Unit tests prove the vessel exists. Only the cycle proves it serves."
|
|
|
|
So this file does what the suite never did: it registers a process
|
|
under the REAL name `VavPipeline.ShoreshEnginePort` that answers the
|
|
way the Haskell engine answers — string keys, no `position`, nothing
|
|
at all for non-Hebrew input — and drives the whole path from HTTP
|
|
request to cycle result. The engine's shape is the one the kohen
|
|
captured from the pod, verbatim.
|
|
|
|
Each describe names the defect it holds shut.
|
|
"""
|
|
|
|
use ExUnit.Case, async: false
|
|
use Plug.Test
|
|
|
|
alias VavPipeline.{BereishitCycle, Shema, Specification, MannaLedger}
|
|
alias VavPipeline.BigdeiKodesh.Router
|
|
alias VavPipeline.L3.{CouplingTrace, Interpretation, TeikuRecord}
|
|
|
|
@opts Router.init([])
|
|
@scope %{book: "bereishit", chapter: 1, verse: 1}
|
|
# A Wednesday at noon — not Shabbat, not a festival.
|
|
@wednesday_noon "2026-02-25T12:00:00Z"
|
|
|
|
# The engine's answer for ברא, exactly as the pod delivered it over the
|
|
# Port on Day 6. Note what is NOT here: `position`.
|
|
@engine_bara %{
|
|
"shoresh" => ["bet", "resh", "aleph"],
|
|
"shoresh_hebrew" => "ברא",
|
|
"gematria_value" => 203,
|
|
"binyan" => "Qal",
|
|
"domain" => "unknown",
|
|
"torah_refs" => [],
|
|
"neighbors" => [],
|
|
"gematria_neighbors" => []
|
|
}
|
|
|
|
# ============================================================
|
|
# A stand-in for the Haskell engine, under its real name
|
|
# ============================================================
|
|
#
|
|
# ShoreshEngineClient checks Process.whereis(VavPipeline.ShoreshEnginePort)
|
|
# and then calls VavPipeline.ShoreshEnginePort.process/2, which does
|
|
# GenServer.call(__MODULE__, {:request, json_line}). Registering THIS
|
|
# process under that name puts the real module's client code on the
|
|
# path and only replaces the port itself. It answers like the engine:
|
|
# Hebrew in → the ברא root; anything else → no roots.
|
|
defmodule FakePort do
|
|
use GenServer
|
|
|
|
def start, do: GenServer.start(__MODULE__, %{}, name: VavPipeline.ShoreshEnginePort)
|
|
|
|
@impl true
|
|
def init(s), do: {:ok, s}
|
|
|
|
@impl true
|
|
def handle_call({:request, json_line}, _from, s) do
|
|
text = json_line |> Jason.decode!() |> get_in(["input", "text"]) || ""
|
|
|
|
roots =
|
|
if String.match?(text, ~r/[\x{05D0}-\x{05EA}]/u) do
|
|
[
|
|
%{
|
|
"shoresh" => ["bet", "resh", "aleph"],
|
|
"shoresh_hebrew" => "ברא",
|
|
"gematria_value" => 203,
|
|
"binyan" => "Qal",
|
|
"domain" => "unknown",
|
|
"torah_refs" => [],
|
|
"neighbors" => [],
|
|
"gematria_neighbors" => []
|
|
}
|
|
]
|
|
else
|
|
[]
|
|
end
|
|
|
|
{:reply, {:ok, %{"roots_activated" => roots, "parse_tree" => nil, "dual_readings" => []}}, s}
|
|
end
|
|
end
|
|
|
|
defp with_fake_port(fun) do
|
|
case Process.whereis(VavPipeline.ShoreshEnginePort) do
|
|
nil ->
|
|
{:ok, pid} = FakePort.start()
|
|
|
|
try do
|
|
fun.()
|
|
after
|
|
GenServer.stop(pid)
|
|
end
|
|
|
|
_real ->
|
|
# A real engine is registered on this host; the test is still
|
|
# meaningful against it.
|
|
fun.()
|
|
end
|
|
end
|
|
|
|
defp context do
|
|
{:ok, now, _} = DateTime.from_iso8601(@wednesday_noon)
|
|
{:ok, ctx} = Shema.receive_context_at(@scope, now)
|
|
ctx
|
|
end
|
|
|
|
# The ledger is a global GenServer. Every test that loads or consumes
|
|
# from it must leave it as it found it — an exhausted ledger leaked
|
|
# into an older test (step31, Test 4) reads :already_loaded, refuses
|
|
# the fresh provision that test writes, and the cycle waits.
|
|
defp write_provision(compute_units) do
|
|
MannaLedger.reset()
|
|
on_exit(fn -> MannaLedger.reset() end)
|
|
today = Date.utc_today() |> Date.to_iso8601()
|
|
expires = DateTime.utc_now() |> DateTime.add(6 * 3600) |> DateTime.to_iso8601()
|
|
|
|
provision = %{
|
|
"date" => today,
|
|
"provision" => %{
|
|
"compute_units" => compute_units,
|
|
"memory_mb" => 512,
|
|
"api_calls_external" => 5,
|
|
"storage_delta_mb" => 2,
|
|
"sanhedrin_panels_max" => 2
|
|
},
|
|
"type" => "regular",
|
|
"expires_at" => expires,
|
|
"written_by" => "day6_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))
|
|
path
|
|
end
|
|
|
|
# WLC text carries vowels and cantillation interleaved in canonical
|
|
# Unicode order (dagesh before sheva, etc.); compare consonants only.
|
|
defp consonants(text), do: String.replace(text, ~r/[\x{0591}-\x{05C7}]/u, "")
|
|
|
|
defp post_ask(question) do
|
|
conn(:post, "/peah/ask", Jason.encode!(%{question: question}))
|
|
|> put_req_header("content-type", "application/json")
|
|
|> Router.call(@opts)
|
|
end
|
|
|
|
# ============================================================
|
|
# Defect 1 — real engine output crashed havdalah
|
|
# ============================================================
|
|
describe "Defect 1: engine-shaped roots (no position) through havdalah" do
|
|
test "normalize_root anchors an engine root to the verse from the CONTEXT" do
|
|
ctx = context()
|
|
root = BereishitCycle.normalize_root(@engine_bara, ctx, :engine)
|
|
|
|
assert root.shoresh == "ברא"
|
|
assert root.shoresh_letters == ["bet", "resh", "aleph"]
|
|
assert root.gematria == 203
|
|
# ברא is the second word of Bereishit 1:1 — the ShemaContext says so.
|
|
assert root.positions == [2]
|
|
assert root.position == 2
|
|
assert root.source == :engine
|
|
assert root.engine == @engine_bara
|
|
end
|
|
|
|
test "havdalah → kriah → ki_tov do not crash on the pod's exact payload" do
|
|
ctx = context()
|
|
|
|
state =
|
|
%{source: "test", verse_scope: @scope, content: "בראשית ברא אלהים"}
|
|
|> BereishitCycle.tohu(ctx)
|
|
|> Map.merge(%{
|
|
roots_activated: [BereishitCycle.normalize_root(@engine_bara, ctx, :engine)],
|
|
root_source: :engine,
|
|
teamim_parse: ctx.teamim_marks,
|
|
ketiv_qere: ctx.ketiv_qere,
|
|
stage: :vayomer
|
|
})
|
|
|
|
# This is the exact call that raised KeyError :position on Day 6.
|
|
result =
|
|
state
|
|
|> BereishitCycle.havdalah(ctx)
|
|
|> BereishitCycle.kriah(ctx)
|
|
|> BereishitCycle.ki_tov(ctx)
|
|
|
|
assert {:ok, data} = result
|
|
assert data.evaluation == :tov
|
|
assert [%{shoresh: "ברא", name: "ברא", positions: [2]}] = data.named_results
|
|
end
|
|
|
|
test "a root the verse does not contain is kept apart, not dropped, not a crash" do
|
|
ctx = context()
|
|
|
|
stranger = %{@engine_bara | "shoresh_hebrew" => "שמר", "shoresh" => ["shin", "mem", "resh"]}
|
|
|
|
roots = [
|
|
BereishitCycle.normalize_root(@engine_bara, ctx, :engine),
|
|
BereishitCycle.normalize_root(stranger, ctx, :engine)
|
|
]
|
|
|
|
assert Enum.at(roots, 1).positions == []
|
|
assert Enum.at(roots, 1).position == nil
|
|
|
|
result =
|
|
%{roots_activated: roots}
|
|
|> BereishitCycle.havdalah(ctx)
|
|
|> BereishitCycle.kriah(ctx)
|
|
|
|
names = Enum.map(result.named_results, & &1.name)
|
|
assert "ברא" in names
|
|
assert "שמר" in names
|
|
# The unplaced root forms the trailing group.
|
|
assert List.last(result.named_results).name == "שמר"
|
|
end
|
|
|
|
test "L1-fallback roots pass through the same normalization (one shape)" do
|
|
ctx = context()
|
|
|
|
state =
|
|
%{source: "test", verse_scope: @scope, content: "בראשית ברא אלהים"}
|
|
|> BereishitCycle.tohu(ctx)
|
|
|> BereishitCycle.vayomer(ctx)
|
|
|
|
# Every root — whichever source produced it — has the one shape.
|
|
Enum.each(state.roots_activated, fn r ->
|
|
assert is_binary(r.shoresh)
|
|
assert is_list(r.positions)
|
|
assert Map.has_key?(r, :position)
|
|
assert r.source in [:engine, :l1]
|
|
end)
|
|
|
|
bara = Enum.find(state.roots_activated, &(&1.shoresh == "ברא"))
|
|
assert bara.gematria == 203
|
|
assert 2 in bara.positions
|
|
end
|
|
end
|
|
|
|
# ============================================================
|
|
# Defect 2 — emptiness was graded tov
|
|
# ============================================================
|
|
describe "Defect 2: no roots is lo tov, not 'Processing complete'" do
|
|
test "zero roots → zero groups → zero named results → rejected" do
|
|
ctx = context()
|
|
|
|
result =
|
|
%{roots_activated: [], raw: "What is bereishit?", case_ref: "t"}
|
|
|> BereishitCycle.havdalah(ctx)
|
|
|> BereishitCycle.kriah(ctx)
|
|
|
|
assert result.separated_groups == []
|
|
assert result.named_results == []
|
|
|
|
assert {:rejected, reason} = BereishitCycle.ki_tov(result, ctx)
|
|
assert reason =~ "empty results"
|
|
end
|
|
|
|
test "the Day-6 shape — a nameless group with no :shoresh — is lo tov, not tov" do
|
|
# This is literally what kriah emitted on the pod.
|
|
assert {:lo_tov, reason} = Specification.evaluate([%{name: "group_0", roots: [], index: 0}], [])
|
|
assert reason =~ "shoresh"
|
|
end
|
|
|
|
test "a halt marker is still a halt even when the result has no shoresh" do
|
|
# Yehareg v'al ya'avor — shape never masks a cardinal sin.
|
|
results = [%{authority_source: :external_model}]
|
|
assert {:lo_tov, "halt condition triggered"} = Specification.evaluate(results, [])
|
|
end
|
|
|
|
test "all_roots_grounded? no longer grounds by default" do
|
|
refute Specification.all_roots_grounded?([%{name: "group_0"}])
|
|
refute Specification.all_roots_grounded?([%{shoresh: nil}])
|
|
refute Specification.all_roots_grounded?([%{shoresh: ""}])
|
|
assert Specification.all_roots_grounded?([%{shoresh: "ברא"}])
|
|
end
|
|
|
|
test "a rejected cycle records nothing to L3 (BUILD_KORBAN.md:123)" do
|
|
assert :none = BereishitCycle.l3_attrs({:rejected, "empty results"}, context())
|
|
assert :none = BereishitCycle.l3_attrs({:halted, :avodah_zarah, "x"}, context())
|
|
end
|
|
end
|
|
|
|
# ============================================================
|
|
# The public ring, end to end, against an engine-shaped Port
|
|
# ============================================================
|
|
describe "/peah/ask against an engine that answers like the pod's" do
|
|
setup do
|
|
path = write_provision(50)
|
|
on_exit(fn -> File.rm(path) end)
|
|
:ok
|
|
end
|
|
|
|
test "Hebrew: 200 with a real answer, the verse as source, ב-ר-א consulted" do
|
|
with_fake_port(fn ->
|
|
conn = post_ask("בראשית ברא אלהים")
|
|
assert conn.status == 200, conn.resp_body
|
|
|
|
body = Jason.decode!(conn.resp_body)
|
|
|
|
refute body["answer"] == "Processing complete"
|
|
assert body["answer"] =~ "ב-ר-א"
|
|
assert body["answer"] =~ "203"
|
|
|
|
assert [source | _] = body["sources"]
|
|
assert source["book"] == "bereishit" or source["book"] == "Bereishit"
|
|
assert source["chapter"] == 1
|
|
assert source["verse"] == 1
|
|
assert is_binary(source["text"]) and String.contains?(consonants(source["text"]), "בראשית")
|
|
|
|
assert "ב-ר-א" in body["processing"]["roots_consulted"]
|
|
assert body["processing"]["cycle_phase"] == "voker"
|
|
assert Map.has_key?(body, "disclosure")
|
|
end)
|
|
end
|
|
|
|
test "English: 422 chatat that says what cannot yet be done, with the verse offered" do
|
|
with_fake_port(fn ->
|
|
conn = post_ask("What is bereishit?")
|
|
assert conn.status == 422, conn.resp_body
|
|
|
|
body = Jason.decode!(conn.resp_body)
|
|
err = body["error"]
|
|
|
|
assert err["code"] == "TEIKU_NO_ROOTS"
|
|
assert err["type"] == "unresolvable"
|
|
assert is_binary(err["message"]) and err["message"] != ""
|
|
assert err["guidance"]["resubmit"] == true
|
|
assert err["guidance"]["correction"] =~ "Step 22"
|
|
|
|
# The corner of the field: the verse in scope, in Hebrew.
|
|
sv = err["guidance"]["scope_verse"]
|
|
assert sv["chapter"] == 1 and sv["verse"] == 1
|
|
assert String.contains?(consonants(sv["text"]), "בראשית")
|
|
|
|
# Never "Processing complete" with sources: [] and a 200.
|
|
refute Map.has_key?(body, "answer")
|
|
assert Map.has_key?(body, "disclosure")
|
|
end)
|
|
end
|
|
|
|
test "manna is consumed per cycle and NOT zeroed per request" do
|
|
with_fake_port(fn ->
|
|
today = Date.utc_today() |> Date.to_iso8601()
|
|
|
|
assert post_ask("בראשית ברא אלהים").status == 200
|
|
l1 = MannaLedger.ledger()
|
|
assert l1.cycle_date == today
|
|
assert l1.consumed.compute_units == 1
|
|
|
|
assert post_ask("בראשית ברא אלהים").status == 200
|
|
l2 = MannaLedger.ledger()
|
|
assert l2.cycle_date == today
|
|
# Second request took a second unit. Before the fix, erev_voker
|
|
# zeroed the ledger after every request — this would read 1
|
|
# again (a fresh full day's provision each time).
|
|
assert l2.consumed.compute_units == 2
|
|
assert l2.allocated.compute_units == 50
|
|
end)
|
|
end
|
|
|
|
test "provision exhausted → 503 MANNA_DEPLETED, not a crash" do
|
|
path = write_provision(1)
|
|
on_exit(fn -> File.rm(path) end)
|
|
|
|
with_fake_port(fn ->
|
|
assert post_ask("בראשית ברא אלהים").status == 200
|
|
conn = post_ask("בראשית ברא אלהים")
|
|
assert conn.status == 503
|
|
assert Jason.decode!(conn.resp_body)["error"]["code"] == "MANNA_DEPLETED"
|
|
end)
|
|
end
|
|
end
|
|
|
|
# ============================================================
|
|
# Defect 3 — the pipeline never wrote L3
|
|
# ============================================================
|
|
describe "Defect 3: the cycle produces a valid L3 record" do
|
|
test "a served cycle yields a valid Interpretation with L1-grounded couplings" do
|
|
ctx = context()
|
|
|
|
{:ok, data} =
|
|
%{source: "test", verse_scope: @scope, content: "בראשית ברא אלהים"}
|
|
|> BereishitCycle.tohu(ctx)
|
|
|> BereishitCycle.vayomer(ctx)
|
|
|> BereishitCycle.havdalah(ctx)
|
|
|> BereishitCycle.kriah(ctx)
|
|
|> BereishitCycle.ki_tov(ctx)
|
|
|
|
assert {:interpretation, attrs, nil} = BereishitCycle.l3_attrs({:ok, data}, ctx)
|
|
|
|
assert attrs.status == "accepted"
|
|
assert attrs.panel_size == 1
|
|
assert attrs.vote_count_for == 1
|
|
assert attrs.vote_count_against == 0
|
|
assert attrs.processing_component == "bereishit_cycle"
|
|
assert "ברא" in attrs.roots_involved
|
|
assert Enum.any?(attrs.torah_references, &String.contains?(&1, "1:1"))
|
|
assert attrs.case_ref =~ ~r/^cycle-\d+-[0-9a-f]{6}$/
|
|
assert String.length(attrs.shema_context_hash) == 64
|
|
|
|
# Every coupling is the L1-anchored shape the write gate accepts.
|
|
assert attrs.coupling_trace != []
|
|
assert :ok = CouplingTrace.validate(attrs.coupling_trace)
|
|
assert Enum.any?(attrs.coupling_trace, &(&1 =~ ~r/^coupled\(root\(ברא\), verse\(bereishit, 1, 1\)\)$/))
|
|
|
|
# And the schema itself accepts it — no missing required field.
|
|
changeset = Interpretation.changeset(%Interpretation{}, attrs)
|
|
assert changeset.valid?, inspect(changeset.errors)
|
|
end
|
|
|
|
test "an unresolved cycle yields a valid TeikuRecord" do
|
|
ctx = context()
|
|
|
|
data = %{
|
|
case_ref: "cycle-1-abcdef",
|
|
roots_activated: [BereishitCycle.normalize_root(@engine_bara, ctx, :engine)],
|
|
evaluation: :teiku,
|
|
talmudic_interpretation: %{
|
|
"status" => "teiku",
|
|
"interpretation" => %{"text" => "Talmudic Engine not connected — teiku", "verses" => []},
|
|
"challenge" => nil
|
|
}
|
|
}
|
|
|
|
assert {:teiku, attrs} = BereishitCycle.l3_attrs({:unresolved, data}, ctx)
|
|
assert attrs.case_ref == "cycle-1-abcdef"
|
|
assert attrs.tension_description =~ "ברא"
|
|
assert attrs.interpretation_a =~ "teiku"
|
|
assert is_binary(attrs.interpretation_b)
|
|
|
|
changeset = TeikuRecord.changeset(%TeikuRecord{}, attrs)
|
|
assert changeset.valid?, inspect(changeset.errors)
|
|
end
|
|
|
|
test "a Sanhedrin-accepted cycle carries the panel and preserves the minority" do
|
|
ctx = context()
|
|
|
|
data = %{
|
|
case_ref: "cycle-2-abcdef",
|
|
roots_activated: [BereishitCycle.normalize_root(@engine_bara, ctx, :engine)],
|
|
resolution: :sanhedrin,
|
|
interpretation: "Majority reading",
|
|
sanhedrin_decision: %{
|
|
outcome: :accepted,
|
|
panel_size: 3,
|
|
majority: %{position: "A", reasoning: "Because Bereishit 1:1", torah_refs: ["Bereishit 1:1"], vote_count: 2},
|
|
minority: %{position: "B", reasoning: "But Shemot 20:11", torah_refs: ["Shemot 20:11"], vote_count: 1}
|
|
}
|
|
}
|
|
|
|
assert {:interpretation, attrs, minority} = BereishitCycle.l3_attrs({:ok, data}, ctx)
|
|
assert attrs.panel_size == 3
|
|
assert attrs.vote_count_for == 2
|
|
assert attrs.vote_count_against == 1
|
|
assert attrs.processing_component == "sanhedrin"
|
|
assert minority.dissenting_reasoning == "But Shemot 20:11"
|
|
assert minority.torah_references == ["Shemot 20:11"]
|
|
end
|
|
|
|
@tag :l3
|
|
test "a live cycle appends to interpretations (PostgreSQL)" do
|
|
:ok = Ecto.Adapters.SQL.Sandbox.checkout(VavPipeline.Repo)
|
|
path = write_provision(5)
|
|
on_exit(fn -> File.rm(path) end)
|
|
|
|
before = VavPipeline.L3.Store.count_interpretations()
|
|
|
|
with_fake_port(fn ->
|
|
assert {:ok, _} = VavPipeline.process(%{source: "peah", verse_scope: @scope, content: "בראשית ברא אלהים"})
|
|
end)
|
|
|
|
assert VavPipeline.L3.Store.count_interpretations() == before + 1
|
|
end
|
|
end
|
|
|
|
# ============================================================
|
|
# Defect 4 — errors are chatat, never an empty 500
|
|
# ============================================================
|
|
describe "Defect 4: every error is a chatat" do
|
|
test "a raise on the public path returns the tamid §1.2 body, then re-raises" do
|
|
# Plug.Parsers raises on a body that is not JSON. That raise used to
|
|
# reach the socket as an empty response. Now handle_errors/2 sends
|
|
# the chatat body first; Plug.ErrorHandler then re-raises the
|
|
# original exception so the crash is still logged and supervised.
|
|
conn =
|
|
conn(:post, "/peah/ask", "{this is not json")
|
|
|> put_req_header("content-type", "application/json")
|
|
|
|
assert_raise Plug.Parsers.ParseError, fn -> Router.call(conn, @opts) end
|
|
|
|
assert {status, _headers, body} = sent_resp(conn)
|
|
assert status == 400
|
|
err = Jason.decode!(body)["error"]
|
|
assert err["code"] == "KIYOR_MALFORMED_REQUEST"
|
|
assert err["type"] == "purification_needed"
|
|
assert is_binary(err["message"])
|
|
assert err["guidance"]["resubmit"] == true
|
|
assert err["guidance"]["correction"] =~ "Content-Type"
|
|
end
|
|
|
|
test "an unexpected exception is CHATAT_UNEXPECTED with guidance and no stack" do
|
|
conn =
|
|
conn(:post, "/peah/ask")
|
|
|> Plug.Conn.put_status(500)
|
|
|> Router.handle_errors(%{kind: :error, reason: %RuntimeError{message: "boom"}, stack: []})
|
|
|
|
assert conn.status == 500
|
|
err = Jason.decode!(conn.resp_body)["error"]
|
|
assert err["code"] == "CHATAT_UNEXPECTED"
|
|
assert err["type"] == "correction_needed"
|
|
assert err["message"] =~ "fault is the system's"
|
|
assert err["guidance"]["issue"] =~ "RuntimeError: boom"
|
|
refute conn.resp_body =~ "stacktrace"
|
|
end
|
|
|
|
test "{:rejected, _} from the cycle is handled — no CaseClauseError" do
|
|
# Before: process_peah had no clause for {:rejected, reason}, so a
|
|
# lo_tov result was itself a crash. The English test above proves
|
|
# the positive path; this pins the courtyard handler too.
|
|
path = write_provision(5)
|
|
on_exit(fn -> File.rm(path) end)
|
|
|
|
with_fake_port(fn ->
|
|
conn =
|
|
conn(:post, "/courtyard/offering", Jason.encode!(%{content: "What is bereishit?", offering_type: "minchah"}))
|
|
|> put_req_header("content-type", "application/json")
|
|
|> put_req_header("authorization", "Bearer test-token")
|
|
|> Router.call(@opts)
|
|
|
|
assert conn.status == 422
|
|
assert Jason.decode!(conn.resp_body)["error"]["code"] == "TEIKU_NO_ROOTS"
|
|
end)
|
|
end
|
|
end
|
|
|
|
# ============================================================
|
|
# Defect 5 — health must see the serving path
|
|
# ============================================================
|
|
describe "Defect 5: esh tamid pushes a real input" do
|
|
test "tamid_probe runs the real cycle, is marked probe, and is not recorded" do
|
|
assert {:ok, data} = VavPipeline.tamid_probe()
|
|
assert data.probe == true
|
|
assert data.provenance == "tamid"
|
|
assert Enum.any?(data.roots_activated, &(&1.shoresh == "ברא"))
|
|
# A probe builds a valid record and the write path skips it —
|
|
# the attrs are still sound, the ledger and L3 are untouched.
|
|
assert {:interpretation, _attrs, nil} = BereishitCycle.l3_attrs({:ok, data}, context())
|
|
end
|
|
|
|
test "tamid_probe does not consume manna" do
|
|
path = write_provision(5)
|
|
on_exit(fn -> File.rm(path) end)
|
|
|
|
MannaLedger.load_provision()
|
|
before = MannaLedger.ledger().consumed.compute_units
|
|
assert {:ok, _} = VavPipeline.tamid_probe()
|
|
assert MannaLedger.ledger().consumed.compute_units == before
|
|
end
|
|
|
|
test "/kodesh/health reports esh_tamid from the cycle and lists l3_writable" do
|
|
conn = conn(:get, "/kodesh/health") |> Router.call(@opts)
|
|
body = Jason.decode!(conn.resp_body)
|
|
|
|
# Runs the probe unless it is Shabbat right now.
|
|
assert body["checks"]["esh_tamid"] in ["pass", "shabbat"]
|
|
assert Map.has_key?(body["checks"], "l3_writable")
|
|
assert Map.has_key?(body["checks"], "manna_present")
|
|
end
|
|
|
|
test "manna_present fails when there is no provision (Day-4 finding)" do
|
|
File.rm(VavPipeline.Manna.provision_file())
|
|
conn = conn(:get, "/kodesh/health") |> Router.call(@opts)
|
|
assert Jason.decode!(conn.resp_body)["checks"]["manna_present"] == "fail"
|
|
end
|
|
|
|
test "/kodesh/status exposes the ledger the kohen was reading by hand" do
|
|
conn = conn(:get, "/kodesh/status") |> Router.call(@opts)
|
|
body = Jason.decode!(conn.resp_body)
|
|
assert Map.has_key?(body["manna_provision"], "cycle_date")
|
|
assert Map.has_key?(body["manna_provision"], "remaining")
|
|
end
|
|
end
|
|
end
|