TORAH ANCHORS:
Vayikra 2:1 — וְנֶפֶשׁ כִּי תַקְרִיב קָרְבַּן מִנְחָה. "When a SOUL brings a
grain offering." Rashi: only of the minchah does Torah say nefesh,
because the poor person who gives from their little has given their
soul. PRD §3.5 makes the pe'ah question the minchah. On Day 6 the
first minchah brought to this system — "What is bereishit?" — came
back as HTTP 200 and the words "Processing complete", with sources: []
and roots_consulted: []. The soul's offering was accepted and nothing
was given back.
Vayikra 19:9-10 — לֶעָנִי וְלַגֵּר תַּעֲזֹב אֹתָם. The corner is LEFT for the
stranger. A corner that has been harvested and is still called pe'ah
is the lie this commit removes: when the system cannot answer, it now
says so, and it leaves the verse itself — text, in Hebrew — in the
error body. The ger walks away with something real or with an honest
"not yet"; never with a 200 that says nothing.
Devarim 17:9 — וְדָרַשְׁתָּ וְהִגִּידוּ לְךָ אֵת דְּבַר הַמִּשְׁפָּט. Already the
anchor of l3/store.ex: "the inquiry requires that what is found is
RECORDED." Store existed. Nothing called it.
=====================================================================
WHAT THE KOHEN FOUND (BUILDER_HANDOFF_day6.md, 2026-09-07)
=====================================================================
Five defects, found by running the cycle on a live pod, invisible to
437 green unit tests because every test ran the L1 fallback (no Port
in the test VM) and none ran the cycle to L3:
1. Real Hebrew input crashed havdalah — KeyError :position.
The engine's roots_activated[] (vayakhel §3.1) has no position;
only vayomer_fallback ever set one.
2. English input was graded :tov with nothing in it.
separate_by_boundaries([]) → [[]] → kriah named "group_0" with
no :shoresh → all_roots_grounded?'s `_ -> true` counted it
grounded → "Processing complete".
3. The pipeline never wrote L3. record_interpretation/1,
record_minority/1, record_teiku/1: zero callers. Day 7's
SELECT count(*) FROM interpretations could not exceed 0.
4. Errors were not chatat. No Plug.ErrorHandler; a raise returned
an EMPTY 500. Also: process_peah had no clause for
{:rejected, _} — a lo_tov result was itself a CaseClauseError.
5. Health was blind to serving. esh_tamid = ShabbatTimer.resting?;
it read "pass" while /peah/ask was 500-ing. manna_present passed
on {:ok, :no_provision}.
Plus, confirmed in motion: MannaLedger never consumed from; a
completed cycle left cycle_date: nil.
=====================================================================
WHAT CHANGED
=====================================================================
bereishit_cycle.ex
- normalize_root/3: ONE root shape for both sources. Engine roots
(string keys, no position) and L1 roots (atom keys) come out as
%{shoresh, shoresh_letters, gematria, binyan, domain, positions,
position, source, engine}. `positions` is looked up in
context.verse.words — the ShemaContext gives the WHERE; the
engine gives the WHAT. That is what havdalah's own @doc always
said, and vayakhel §3.1 is unchanged: the engine was conforming.
- separate_by_boundaries([], _) → [] (was [[]]). Roots the verse
does not place form their own trailing group instead of crashing
or vanishing.
- kriah never names an empty group and always carries :shoresh.
- erev_voker composes the answer (roots, gematria, te'amim groups,
ki tov — the system's own reasoning, not prose from a model) and
sources (the context verse: book/chapter/verse/ref/text, tamid
§1.1's shape), then RECORDS: Interpretation for {:ok}, TeikuRecord
for {:unresolved}, NOTHING for {:rejected} (BUILD_KORBAN.md:123:
rejected, not processed into empty propositions). l3_attrs/2 is
pure and tested without PostgreSQL. coupling_trace is the
L1-anchored shape the write gate accepts, and only for roots that
occur in the verse. A record that fails — validation OR a raise
from a database that is down — is logged, sounds chatzotzrot
:assembly_leaders, and does not turn a served answer into a 500.
- case_ref minted once in tohu; Talmudic routing and the L3 record
use the same one.
- probe: true (tamid) runs the whole cycle and skips the write.
specification.ex
- all_roots_grounded?/1: `_ -> true` is gone. No :shoresh → not
grounded. shoresh: nil stays :teiku (the existing test).
- evaluate/2: halt check FIRST (yehareg v'al ya'avor — shape never
masks a cardinal sin), then a result with no :shoresh is lo_tov
with a reason that names it.
manna.ex / vav_pipeline.ex
- Manna.gather_for_cycle/0: load (idempotent per date) + consume
one compute unit, called from VavPipeline.process/1. The ledger
is now consumed from.
- Manna.release_at_day_boundary/0: surplus is released when the
DATE rolls, not per request. enforce_cycle_boundary zeroes the
ledger and clears its date; called after every request it would
have re-gathered a full day's provision on the next one —
infinite manna. Shemot 16:19: until MORNING.
- The late-manna trap: load_provision pins cycle_date=today with a
ZERO allocation when the file is absent, then :already_loaded for
the rest of the day — a provision written after the first request
would never be gathered. gather_for_cycle re-gathers when the
ledger holds nothing for today.
- VavPipeline.tamid_probe/0: builds a real context, honours the
cloud, runs the real cycle on Bereishit 1:1 in Hebrew, consumes
no manna, writes no L3.
bigdei_kodesh/router.ex
- use Plug.ErrorHandler; handle_errors/2 sends tamid §1.2's
code/type/message/guidance, logs, signals :assembly_leaders, then
re-raises so the crash is still supervised. 400 → KIYOR_MALFORMED_
REQUEST; else CHATAT_UNEXPECTED. No stack in the body.
- {:rejected, reason} handled on pe'ah AND courtyard: 422
TEIKU_NO_ROOTS for the empty-results case, with guidance naming
Architecture Step 22 and guidance.scope_verse carrying the verse
in Hebrew; 422 LO_TOV otherwise.
- {:waiting, :provision_exhausted} → MANNA_DEPLETED (was falling
through to CLOUD_MOVING).
- esh_tamid runs VavPipeline.tamid_probe/0 — "shabbat" on Shabbat,
"pass" only when roots came back. manna_present fails on
:no_provision. l3_writable added (Repo up AND SELECT 1 answers;
"absent" without a Repo, counted as a failure). /kodesh/status
exposes manna_provision (allocated / consumed / remaining).
- roots_consulted as letter-hyphenated Hebrew ("ב-ר-א").
test/day6_serving_test.exs — 25 tests
A process registered under the REAL name VavPipeline.ShoreshEnginePort
answers the way the pod's engine answered (string keys, no position;
nothing for non-Hebrew), so the engine-shaped path runs end to end
through the router with no binary. Each describe names the defect it
holds shut. One @tag :l3 test asserts count(interpretations) rises
by one after a live cycle.
KOHEN.md — Day 6 gains the Hebrew request, the expected 422 for
English, and the health shape; Day 7 sends FIVE Hebrew + FIVE English
and expects the L3 count to rise by the Hebrew ones, with the reason.
=====================================================================
VERIFIED — the kohen's exact procedure, locally, against the REAL
Haskell binary over the Port, with L3 deliberately unreachable
=====================================================================
POST /peah/ask {"question":"בראשית ברא אלהים"}
200. answer: "Roots activated in Bereishit 1:1: ב-ר-א (gematria
203, Qal, word 2). Te'amim divide them into 1 group(s): ב-ר-א.
Ki tov: ..." sources[0]: Bereishit 1:1 with Hebrew text.
roots_consulted: ["ב-ר-א"]. Cycle: 31ms.
POST /peah/ask {"question":"What is bereishit?"}
422 TEIKU_NO_ROOTS / unresolvable, guidance.scope_verse =
Bereishit 1:1 in Hebrew, no answer key. Cycle: 3ms, :rejected.
POST /peah/ask '{this is not json'
400 KIYOR_MALFORMED_REQUEST with guidance — 406 bytes, not 0.
GET /kodesh/health
esh_tamid "pass" (from a real cycle, 7ms, no L3 attempt),
l3_writable "fail" (DB unreachable by design), overall degraded.
GET /kodesh/status
manna_provision: consumed 2 after two cycles; the probe took none.
Log: L3_WRITE_FAILED + chatzotzrot for each served Hebrew cycle;
"activated no roots" for English; CHATAT_UNEXPECTED for the bad
body. The 2–5s per request was ENTIRELY DBConnection queuing
against the dead database — the cycles themselves were 1–31ms.
mix test — 437 tests, 0 failures, 36 excluded (:l3)
Doorpost.verify_identity() :ok — no @spec_files entry touched
l1/integrity.sha256 unchanged: 99bbd07a…4ea377
NOT FIXED, RAISED:
- The engine's parse_tree is null on the pod because Server.hs reads
"l1/torah/..." relative to cwd. Nothing consumes parse_tree yet
(havdalah uses context.teamim_marks), so nothing is lost today.
- cloud.ex Shabbat is the UTC stub; zmanim absent (kohen Day 5).
- The L3 write is on the request's critical path by choice: the
offering is not complete until the altar portion is recorded. With
L3 down that costs the queue timeout per request AND is visible in
l3_writable. If that trade should go the other way, it is one
Task.start, and a spec question first.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
585 lines
22 KiB
Elixir
585 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
|
|
|
|
defp write_provision(compute_units) do
|
|
MannaLedger.reset()
|
|
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
|