tablet 55, second: the judge consults — the Sanhedrin no longer rolls dice
judge.erl chose one of six canned positions by a hash over the question,
its own pid and the clock; a panel of one "accepted" on zero roots and
zero references under "Torah references ground this interpretation" —
the calf, cast by the court itself. Architecture §3.2 says a judge
"consults the specification (L0/L1)"; Construction §3.2 says the
pipeline hands the case roots and torah_refs. Neither happened.
Sanhedrin (Erlang):
- judge.erl: seat N performs step N of the dialectic — interpret,
challenge, resolve, cycling — through a consult {M, F} in the app env.
resolved → accept with the verses; challenged (a kushya stands) →
reject; halt → reject; teiku → abstain. No consult, an error, or a
consult that raises → abstain, and the reasoning says so; the judge
stands. judge:start_link/2 takes the seat; panel.erl seats its judges.
- EUnit 54/0: seats cycle; no consult → abstain; resolved → accept with
#torah_ref{}; a raising consult → abstain and alive; a panel of three
with a kushya at seat 2 → accepted 2–1, minority preserved with verse.
Pipeline (Elixir):
- VavPipeline.SanhedrinConsult: what a judge asks — TalmudicEngineSocket
interpret/challenge/resolve over the case's roots, direct (the client's
stub fallbacks are NOT used: a court that accepts on a stub is the
calf). Engine absent → {:error, _} → the judge abstains. Registered in
the sanhedrin env at application start.
- router process_sanhedrin: the case is heard before it is submitted —
ScopeResolver → Shema context (cloud honoured) → tohu → vayomer
(Shoresh Engine over the verse) → roots; torah_refs = the scope;
complexity_hint names the panel (422 on an unknown one); one unit of
sanhedrin_panels_max per case (503 MANNA_DEPLETED when spent); the
decision engraved in L3 as an interpretation (panel_size, votes
for/against, processing_component "sanhedrin", coupling root→verse)
with the minority beside it (grounding_type achorai); the reply in
the Tamid's shape (position, reasoning, sources, votes) plus
grounding {scope, roots_consulted, recorded}.
- Tests: Elixir 628/0 (router 30, incl. monetary → panel 3; no engine →
teiku with 0 votes, never accepted; fake consult → 2–1 with sources;
manna → 503; unknown hint → 422); L3 test: interpretation + minority
rows land.
Pre-existing and untouched: with --include l3 the gate and hakhel
tests fail 17 times on main as well (the gate's local upstream double
refuses connections under the L3 flag) — a separate finding.
Doorpost pin unchanged (no @spec_files touched).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
7c40200dfc
commit
046010f0ed
8 files changed
+734
-107
No files matched your search
@@ -21,6 +21,38 @@ defmodule VavPipeline.BigdeiKodeshTest do
|
||||
|
||||
@opts Router.init([])
|
||||
|
||||
# Tablet 55: a consultation the judges can be pointed at (judge.erl reads
|
||||
# {M, F} from the sanhedrin env). Seat 2 (challenge) finds a kushya; the
|
||||
# other seats read the verse as resolved.
|
||||
def fake_consult(:challenge, _ref, _roots, _q, _ctx),
|
||||
do: {:ok, %{status: "challenged", text: "", kushya: "but the verse says otherwise", verses: [{:devarim, 6, 4}]}}
|
||||
|
||||
def fake_consult(_step, _ref, _roots, _q, _ctx),
|
||||
do: {:ok, %{status: "resolved", text: "the reading", verses: [{:bereishit, 1, 1}]}}
|
||||
|
||||
# A day's manna for the chatzer tests: a panel spends one unit of
|
||||
# sanhedrin_panels_max (tablet 55), so the ledger must hold a provision.
|
||||
defp write_provision(panels) do
|
||||
VavPipeline.MannaLedger.reset()
|
||||
on_exit(fn -> VavPipeline.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" => 50, "memory_mb" => 512, "api_calls_external" => 10, "storage_delta_mb" => 2, "sanhedrin_panels_max" => panels},
|
||||
"type" => "regular",
|
||||
"expires_at" => expires,
|
||||
"written_by" => "bigdei_kodesh_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)
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
# Pe'ah — Public Endpoint (Machaneh Yisrael)
|
||||
# ============================================================
|
||||
@@ -301,6 +333,8 @@ defmodule VavPipeline.BigdeiKodeshTest do
|
||||
end
|
||||
|
||||
test "POST /chatzer/sanhedrin with valid case deliberates" do
|
||||
write_provision(2)
|
||||
|
||||
conn =
|
||||
conn(:post, "/chatzer/sanhedrin", Jason.encode!(%{
|
||||
case: "Is cooking on Shabbat permitted for pikuach nefesh?"
|
||||
@@ -321,6 +355,8 @@ defmodule VavPipeline.BigdeiKodeshTest do
|
||||
# Tablet 55: the Tamid's own example — a case WITH sides — returned
|
||||
# 500 and took the halt monitor and escalation down with it.
|
||||
test "POST /chatzer/sanhedrin with sides (the Tamid's example) deliberates" do
|
||||
write_provision(2)
|
||||
|
||||
conn =
|
||||
conn(:post, "/chatzer/sanhedrin", Jason.encode!(%{
|
||||
case: "A borrower repaid before one witness; the lender denies receipt. Who bears the burden?",
|
||||
@@ -337,11 +373,134 @@ defmodule VavPipeline.BigdeiKodeshTest do
|
||||
assert conn.status == 200
|
||||
body = Jason.decode!(conn.resp_body)
|
||||
assert body["deliberation"]["outcome"] in ["accepted", "rejected", "teiku"]
|
||||
# the hint names the panel: monetary → three judges (sanhedrin.hrl)
|
||||
assert body["deliberation"]["panel_size"] == 3
|
||||
# the Tamid's shape — votes and sources, not vote_count and torah_refs
|
||||
assert Map.keys(body["deliberation"]["majority"]) |> Enum.sort() == ["position", "reasoning", "sources", "votes"]
|
||||
assert is_list(body["deliberation"]["majority"]["sources"])
|
||||
# the case was resolved before it was heard
|
||||
assert is_map(body["grounding"]["scope"])
|
||||
assert is_list(body["grounding"]["roots_consulted"])
|
||||
# and the gate is still standing afterwards
|
||||
assert Process.whereis(:halt_monitor) != nil
|
||||
assert Process.whereis(:escalation) != nil
|
||||
end
|
||||
|
||||
# Tablet 55: without a Talmudic Engine to consult, the judges abstain
|
||||
# and the court says teiku — it does not accept on nothing.
|
||||
test "POST /chatzer/sanhedrin with no engine to consult is teiku, not accepted" do
|
||||
# The socket process may run in the test env; no engine answers behind it.
|
||||
Application.put_env(:sanhedrin, :consult, {VavPipeline.SanhedrinConsult, :consult})
|
||||
write_provision(2)
|
||||
|
||||
conn =
|
||||
conn(:post, "/chatzer/sanhedrin", Jason.encode!(%{case: "bereishit 1:1 — what is created first?"}))
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> put_req_header("authorization", "Bearer chatzer-test-token")
|
||||
|> Router.call(@opts)
|
||||
|
||||
assert conn.status == 200
|
||||
body = Jason.decode!(conn.resp_body)
|
||||
assert body["deliberation"]["outcome"] == "teiku"
|
||||
assert body["deliberation"]["panel_size"] == 1
|
||||
assert body["deliberation"]["majority"]["votes"] == 0
|
||||
assert body["deliberation"]["minority"]["reasoning"] =~ "no consultation"
|
||||
end
|
||||
|
||||
# Tablet 55: with a consultation behind the judges, the dialectic
|
||||
# decides — interpret resolved, challenge finds a kushya, resolve
|
||||
# resolved → 2 accept, 1 reject; the minority preserved with its verse.
|
||||
test "POST /chatzer/sanhedrin monetary panel splits 2–1 from the dialectic" do
|
||||
Application.put_env(:sanhedrin, :consult, {__MODULE__, :fake_consult})
|
||||
write_provision(2)
|
||||
|
||||
try do
|
||||
conn =
|
||||
conn(:post, "/chatzer/sanhedrin", Jason.encode!(%{
|
||||
case: "devarim 6:4 — is the unity declared or commanded?",
|
||||
complexity_hint: "monetary"
|
||||
}))
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> put_req_header("authorization", "Bearer chatzer-test-token")
|
||||
|> Router.call(@opts)
|
||||
|
||||
assert conn.status == 200
|
||||
d = Jason.decode!(conn.resp_body)["deliberation"]
|
||||
assert d["panel_size"] == 3
|
||||
assert d["outcome"] == "accepted"
|
||||
assert d["majority"]["votes"] == 2
|
||||
assert d["majority"]["position"] == "the reading"
|
||||
assert d["majority"]["sources"] == ["bereishit 1:1"]
|
||||
assert d["minority"]["votes"] == 1
|
||||
assert d["minority"]["position"] == "but the verse says otherwise"
|
||||
assert d["minority"]["sources"] == ["devarim 6:4"]
|
||||
after
|
||||
Application.put_env(:sanhedrin, :consult, {VavPipeline.SanhedrinConsult, :consult})
|
||||
end
|
||||
end
|
||||
|
||||
# Devarim 17:9 — the decision is engraved in L3: an interpretation row
|
||||
# with the panel and the votes, and the minority beside it.
|
||||
@tag :l3
|
||||
test "POST /chatzer/sanhedrin engraves the decision and the minority in L3" do
|
||||
# The record is written from the request process (the router), so a
|
||||
# checkout here covers it; the sandbox is shared for the sake of the
|
||||
# panel's processes, which do not touch the Repo.
|
||||
:ok = Ecto.Adapters.SQL.Sandbox.checkout(VavPipeline.Repo)
|
||||
Ecto.Adapters.SQL.Sandbox.mode(VavPipeline.Repo, {:shared, self()})
|
||||
on_exit(fn -> Ecto.Adapters.SQL.Sandbox.mode(VavPipeline.Repo, :manual) end)
|
||||
Application.put_env(:sanhedrin, :consult, {__MODULE__, :fake_consult})
|
||||
write_provision(2)
|
||||
|
||||
try do
|
||||
before = VavPipeline.L3.Store.count_interpretations()
|
||||
before_min = VavPipeline.L3.Store.count_minority_opinions()
|
||||
|
||||
conn =
|
||||
conn(:post, "/chatzer/sanhedrin", Jason.encode!(%{case: "devarim 6:4", complexity_hint: "monetary"}))
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> put_req_header("authorization", "Bearer chatzer-test-token")
|
||||
|> Router.call(@opts)
|
||||
|
||||
assert conn.status == 200
|
||||
body = Jason.decode!(conn.resp_body)
|
||||
assert body["grounding"]["recorded"]["l3"] == "recorded"
|
||||
assert VavPipeline.L3.Store.count_interpretations() == before + 1
|
||||
assert VavPipeline.L3.Store.count_minority_opinions() == before_min + 1
|
||||
after
|
||||
Application.put_env(:sanhedrin, :consult, {VavPipeline.SanhedrinConsult, :consult})
|
||||
end
|
||||
end
|
||||
|
||||
# A panel is manna (Vayakhel §3.7): the day's sanhedrin_panels_max spent → 503.
|
||||
test "POST /chatzer/sanhedrin with the day's panels spent returns 503 MANNA_DEPLETED" do
|
||||
write_provision(1)
|
||||
body = Jason.encode!(%{case: "bereishit 1:1"})
|
||||
|
||||
post = fn ->
|
||||
conn(:post, "/chatzer/sanhedrin", body)
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> put_req_header("authorization", "Bearer chatzer-test-token")
|
||||
|> Router.call(@opts)
|
||||
end
|
||||
|
||||
assert post.().status == 200
|
||||
second = post.()
|
||||
assert second.status == 503
|
||||
assert Jason.decode!(second.resp_body)["error"]["code"] == "MANNA_DEPLETED"
|
||||
end
|
||||
|
||||
test "POST /chatzer/sanhedrin with an unknown complexity_hint returns 422" do
|
||||
conn =
|
||||
conn(:post, "/chatzer/sanhedrin", Jason.encode!(%{case: "x", complexity_hint: "cosmic"}))
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> put_req_header("authorization", "Bearer chatzer-test-token")
|
||||
|> Router.call(@opts)
|
||||
|
||||
assert conn.status == 422
|
||||
assert Jason.decode!(conn.resp_body)["error"]["guidance"]["issue"] =~ "complexity_hint"
|
||||
end
|
||||
|
||||
test "POST /chatzer/sanhedrin with malformed sides returns 422, not 500" do
|
||||
for bad <- ["not a list", [%{"no_position" => 1}], [%{"position" => "p", "evidence" => "not a list"}]] do
|
||||
conn =
|
||||
|
||||
Reference in new issue
Block a user