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:
Scott KarpandClaude Fable 5.1 committed 2026-10-19 16:44:53 +00:00
1 parent 04f61c1238
commit 7c40200dfc
5 files changed
+184 -24

No files matched your search

+19 -3
View File
@@ -145,13 +145,29 @@ handle_info(_Info, State) ->
%% Internal — gate-keeper checks
%%====================================================================
do_check_case(Case) ->
Context = Case#sanhedrin_case.context,
%% The context is a map (authority_source, action, ...). Anything else is
%% a malformed case, refused here — never reasoned about, never a crash.
%% Tablet 55 (2026-10-19): the Tamid's own example request carried a
%% LIST as context; maps:get raised, and this gen_server and escalation
%% died with the caller's input. A gate that can be felled by what it is
%% handed is not a gate. Fail closed, stay standing.
do_check_case(#sanhedrin_case{context = Context}) when is_map(Context) ->
with_chain([
fun() -> check_avodah_zarah(Context) end,
fun() -> check_shfichut_damim(Context) end,
fun() -> check_gilui_arayot(Context) end
]).
]);
do_check_case(#sanhedrin_case{context = Other}) ->
{halt, malformed_case,
iolist_to_binary([<<"case context must be a map, got ">>,
atom_to_binary(type_name(Other), utf8)])}.
type_name(T) when is_list(T) -> list;
type_name(T) when is_binary(T) -> binary;
type_name(T) when is_atom(T) -> atom;
type_name(T) when is_tuple(T) -> tuple;
type_name(T) when is_number(T) -> number;
type_name(_) -> other.
do_check_vote(_Vote) ->
%% Vote-level halt detection is the responsibility of the upstream
+10
View File
@@ -58,6 +58,16 @@ halt_clear_test() ->
?assertEqual(clear, halt_monitor:check_case(Case)),
cleanup(ok).
%% Tablet 55: a non-map context is refused, and the monitor survives it.
halt_malformed_context_test() ->
{ok, _} = application:ensure_all_started(sanhedrin),
Sides = [#{<<"position">> => <<"a">>, <<"evidence">> => [<<"x">>]}],
Case = make_case_with_context(simple, <<"question">>, Sides),
?assertMatch({halt, malformed_case, _}, halt_monitor:check_case(Case)),
%% still standing, and still judging
?assertEqual(clear, halt_monitor:check_case(make_case(simple, <<"q">>))),
ok.
halt_avodah_zarah_test() ->
setup(),
Case = make_case_with_context(simple, <<"question">>,