tablet 55: the row that was a label — the Sanhedrin's example request crashed its own gate
Verified live 2026-10-19: Tamid's own example for POST /chatzer/sanhedrin
(a case with `sides`) answered 500. The router passed the sides LIST as
the case context; halt_monitor:check_avodah_zarah/1 reads a map, maps:get
raised, and the halt monitor and escalation gen_servers died with the
caller's input (supervisor restarted them; health stayed 8/8). The router
then matched {:ok, decision} on the error and made a 500 of its own.
- sanhedrin/src/halt_monitor.erl: do_check_case refuses a non-map
context as {halt, malformed_case, _} and stays standing. EUnit 49/0
with the new halt_malformed_context_test.
- router.ex process_sanhedrin: sides validated at the kiyor (422 on a bad
shape), carried as context %{sides: sides}; a Sanhedrin refusal answers
422 SANHEDRIN_HALTED, a failure 503 SANHEDRIN_UNAVAILABLE. Two new
router tests, one asserting the gate processes survive the example.
Elixir 624/0.
- specs/broken_tablets/README.md: tablet 55, including what is NOT fixed
— a panel of one accepting on zero references under a grounded-
sounding sentence, the ignored complexity hint, no panel manna spent,
no L3 record. The row in Tamid is still larger than the thing behind it.
Doorpost pin unchanged (no @spec_files touched; scripts/doorpost_check.py
passes).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
04f61c1238
commit
7c40200dfc
5 files changed
+184
-24
No files matched your search
@@ -1084,32 +1084,83 @@ defmodule VavPipeline.BigdeiKodesh.Router do
|
||||
resubmit: true
|
||||
})
|
||||
else
|
||||
case_ref = generate_id()
|
||||
# Tablet 55: the sides are the caller's arguments, not the halt
|
||||
# gate's context. The gate reads a MAP (authority_source, action);
|
||||
# handing it the sides list crashed the halt monitor and escalation
|
||||
# with the Tamid's own example request. The sides ride inside the
|
||||
# map, their shape checked at the door; and a refusal from the
|
||||
# Sanhedrin is answered, not matched into a 500.
|
||||
with {:ok, sides} <- sanhedrin_sides(params["sides"]) do
|
||||
case_ref = generate_id()
|
||||
|
||||
{:ok, decision} =
|
||||
VavPipeline.SanhedrinClient.submit_case(%{
|
||||
case_ref: case_ref,
|
||||
complexity: :simple,
|
||||
question: case_text,
|
||||
context: params["sides"] || %{},
|
||||
requested_by: "chatzer"
|
||||
})
|
||||
case VavPipeline.SanhedrinClient.submit_case(%{
|
||||
case_ref: case_ref,
|
||||
complexity: :simple,
|
||||
question: case_text,
|
||||
context: %{sides: sides},
|
||||
requested_by: "chatzer"
|
||||
}) do
|
||||
{:ok, decision} ->
|
||||
send_json(conn, 200, %{
|
||||
case_ref: decision.case_ref,
|
||||
deliberation: %{
|
||||
panel_size: decision.panel_size,
|
||||
majority: decision.majority,
|
||||
minority: decision.minority,
|
||||
outcome: decision.outcome,
|
||||
processing_time_ms: decision.deliberation_time_ms
|
||||
},
|
||||
note: "This is the system's reasoning, not a binding ruling. A human beit din may reach a different conclusion.",
|
||||
disclosure: disclosure()
|
||||
})
|
||||
|
||||
send_json(conn, 200, %{
|
||||
case_ref: decision.case_ref,
|
||||
deliberation: %{
|
||||
panel_size: decision.panel_size,
|
||||
majority: decision.majority,
|
||||
minority: decision.minority,
|
||||
outcome: decision.outcome,
|
||||
processing_time_ms: decision.deliberation_time_ms
|
||||
},
|
||||
note: "This is the system's reasoning, not a binding ruling. A human beit din may reach a different conclusion.",
|
||||
disclosure: disclosure()
|
||||
})
|
||||
{:error, "Halted" <> _ = reason} ->
|
||||
send_error(conn, 422, "SANHEDRIN_HALTED", %{
|
||||
issue: reason,
|
||||
correction: "The case was refused at the gate before deliberation. Amend it and resubmit.",
|
||||
resubmit: true
|
||||
})
|
||||
|
||||
{:error, reason} ->
|
||||
send_error(conn, 503, "SANHEDRIN_UNAVAILABLE", %{
|
||||
issue: reason,
|
||||
correction: "The Sanhedrin did not deliberate. Retry; if it recurs, the kohanim will find it in the log.",
|
||||
resubmit: true
|
||||
})
|
||||
end
|
||||
else
|
||||
{:error, issue} ->
|
||||
send_error(conn, 422, "KIYOR_VALIDATION_FAILED", %{
|
||||
issue: issue,
|
||||
correction: "'sides' is an optional list of {\"position\": string, \"evidence\": [string]}",
|
||||
resubmit: true
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# The optional sides: absent → []; a list of maps each with a string
|
||||
# "position" and an optional list of string "evidence" → kept as given;
|
||||
# anything else → refused at the kiyor.
|
||||
defp sanhedrin_sides(nil), do: {:ok, []}
|
||||
|
||||
defp sanhedrin_sides(sides) when is_list(sides) do
|
||||
if Enum.all?(sides, &side_ok?/1),
|
||||
do: {:ok, sides},
|
||||
else: {:error, "'sides' must be a list of {position, evidence[]} objects"}
|
||||
end
|
||||
|
||||
defp sanhedrin_sides(_), do: {:error, "'sides' must be a list"}
|
||||
|
||||
defp side_ok?(%{"position" => p} = side) when is_binary(p) do
|
||||
case Map.get(side, "evidence", []) do
|
||||
ev when is_list(ev) -> Enum.all?(ev, &is_binary/1)
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
defp side_ok?(_), do: false
|
||||
|
||||
# ============================================================
|
||||
# Internal — Health and Status
|
||||
# ============================================================
|
||||
|
||||
Reference in new issue
Block a user