Closes F1–F7 from the conversation-history review at
~/shorashim-conversation-review.md, per the Torah ruling for Option 2:
Devarim 12:13–14 — לֹא תַעֲלֶה עֹלֹתֶיךָ בְּכָל מָקוֹם אֲשֶׁר תִּרְאֶה
כִּי אִם בַּמָּקוֹם אֲשֶׁר יִבְחַר ה׳ ... שָׁם תַּעֲשֶׂה כֹּל
אֲשֶׁר אָנֹכִי מְצַוֶּךָּ.
The offering happens in one place. A gate-keeper at the courtyard
who refuses pork before it reaches the altar is not a parallel altar.
Devarim 13:1 — לֹא תֹסֵף עָלָיו וְלֹא תִגְרַע מִמֶּנּוּ.
The interface remains; no parallel detection authority is added.
The previous halt_monitor.erl claimed to mirror the Talmudic Engine
halt modules (Prolog) and the Vav Pipeline halt_conditions (Elixir),
but in practice it was a partial sketch with stub checks, no read API,
verb-only shfichut_damim that would fire on log rotation, and a
"cannot be bypassed" claim that the implementation didn't deliver.
The docstring overstated. Eish zarah by the repo's own definition —
real Erlang, sincerely written, but אֲשֶׁר לֹא צִוָּה אֹתָם.
Changes:
sanhedrin/include/sanhedrin.hrl
- Document the halt-relevant context keys explicitly
(authority_source, action, boundary_violation) — closes F1.
These keys were used by halt_monitor and the test suite but
were not in the .hrl record commentary. Interface drift fixed
by writing down the actual contract.
sanhedrin/src/halt_monitor.erl
- Re-framed as "Sanhedrin's gate-keeper": local courtyard refusal,
not parallel altar. Honest docstring.
- When the gate detects a halt, FORWARDS the canonical halt event
to VavPipeline.HaltMonitor.halt!/3 (single source of truth).
Best-effort: if Elixir module isn't loaded, local refusal still
works.
- Export audit_log/0 and halt_count/0 — closes F6 (was write-only
from outside the module).
- Remove process_flag(trap_exit, true) and the dead
handle_info({'EXIT', _, _}, _) clause — closes F7 (nothing was
ever linked).
- Remove unused check_authority_in_text/1 stub from the call chain
— closes F5. do_check_vote/1 now documents that vote-text
inspection is the upstream pipeline's responsibility.
- check_gilui_arayot/1 docstring now states truthfully: detection
of WHETHER an action collapses a distinction lives in
talmudic_engine/src/halt/gilui_arayot.pl; this gate reads the
flag — closes F2 framing.
- check_shfichut_damim/1 docstring now states: conservative
gate-keeper, fail-closed; the canonical tzelem-target check
lives in talmudic_engine/src/halt/shfichut_damim.pl — closes
F3 framing.
- "Cannot be bypassed" claim removed; truthful description added:
"enforced by callers (escalation.erl checks the return value
before convening a panel)" — closes F4.
vav_pipeline/test/sanhedrin_halt_forwarding_test.exs (NEW)
- 6 integration tests proving the forwarding chain end-to-end:
halt_monitor.erl is loadable from the same BEAM VM, exposes
audit_log/0 and halt_count/0 (F6 verification), and on each of
the three cardinal sin types fires HaltMonitor.halt!/3 with
component="sanhedrin/case". Verified that VavPipeline.HaltMonitor.
halted?/0 flips to true after each gate refusal and the event's
.condition matches the gate's halt type.
The local audit log on the gate-keeper is preserved (the Sanhedrin
operator can still see what was refused at the gate). The system-wide
halt audit lives in VavPipeline.HaltMonitor.audit_log/0 — one altar.
Existing 12 sanhedrin EUnit tests of the gateway behavior continue
to pass (verified via mix deps.compile sanhedrin — clean compile).
The 6 new Elixir-side integration tests pass (verified).
Test counts:
- Elixir: 280 (was 274; +6 new forwarding tests), 0 failures attributable
to this change. (3 pre-existing time-dependent test failures in
step31_shema_test.exs occur on Shabbat — system correctly returns
cloud_state == {:major_transition, :shabbat_rest, _} when run after
Friday sundown; tests assert :camped. Not caused by this change.)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
122 lines
4.4 KiB
Erlang
122 lines
4.4 KiB
Erlang
%%% sanhedrin.hrl — Record definitions for the Sanhedrin deliberation engine
|
|
%%%
|
|
%%% Architecture §3.2: Concurrent deliberation engine.
|
|
%%% Spawns panels of independent deliberators, collects votes,
|
|
%%% enforces mandatory dissent on capital decisions,
|
|
%%% stores minority opinions.
|
|
|
|
%% --- Case complexity levels ---
|
|
%% simple -> 1 judge
|
|
%% monetary -> 3 judges (dinei mamonot)
|
|
%% capital -> 23 judges (dinei nefashot)
|
|
%% novel -> 71 judges (Sanhedrin Gedolah)
|
|
-type complexity() :: simple | monetary | capital | novel.
|
|
|
|
%% --- Case outcome ---
|
|
-type outcome() :: accepted | rejected | teiku.
|
|
|
|
%% --- Unanimous check result ---
|
|
-type unanimous_check() :: passed | {failed, suspicion_of_consensus}.
|
|
|
|
%% --- Torah reference ---
|
|
-record(torah_ref, {
|
|
book :: atom(), %% bereishit | shemot | vayikra | bamidbar | devarim
|
|
chapter :: pos_integer(),
|
|
verse :: pos_integer()
|
|
}).
|
|
|
|
%% --- A root with domain ---
|
|
-record(root_info, {
|
|
shoresh :: list(atom()), %% e.g. [bet, resh, aleph]
|
|
domain :: atom() %% e.g. creation, justice, holiness
|
|
}).
|
|
|
|
%% --- Case submission (Pipeline → Sanhedrin) ---
|
|
%% Construction spec §3.2
|
|
%%
|
|
%% The `context` field is a map. Documented keys fall into two groups:
|
|
%%
|
|
%% A. Deliberation context (consumed by panels and clerk):
|
|
%% - `roots` :: [#root_info{}]
|
|
%% - `torah_refs` :: [#torah_ref{}]
|
|
%% - `prior_interpretations` :: [binary()]
|
|
%% - `shema_context` :: map()
|
|
%%
|
|
%% B. Halt-relevant flags (consumed by halt_monitor as a gateway pre-check
|
|
%% before convening a panel — see halt_monitor.erl):
|
|
%% - `authority_source` :: atom() (e.g. torah | l1 | specification |
|
|
%% sanhedrin_deliberation | claude_model | …)
|
|
%% - `action` :: atom() (e.g. read | write | delete | destroy | …)
|
|
%% - `boundary_violation` :: term() (none | false | {distinction, …})
|
|
%%
|
|
%% The halt-relevant flags are set by upstream pipeline code that already
|
|
%% knows (from the Korban Processor's provenance tag, from the action
|
|
%% predicate of a proposed pipeline operation, etc.) what the case carries.
|
|
%% halt_monitor reads them; it does not re-derive them.
|
|
-record(sanhedrin_case, {
|
|
case_ref :: binary(), %% UUID
|
|
complexity :: complexity(),
|
|
question :: binary(),
|
|
context :: map(), %% see record commentary above
|
|
requested_by :: binary(),
|
|
submitted_at :: integer() %% erlang:system_time(millisecond)
|
|
}).
|
|
|
|
%% --- Individual judge vote ---
|
|
-record(vote, {
|
|
judge_id :: reference(),
|
|
position :: binary(), %% The interpretation
|
|
reasoning :: binary(), %% Why this position
|
|
torah_refs :: [#torah_ref{}], %% Grounding references
|
|
verdict :: accept | reject | abstain,
|
|
cast_at :: integer()
|
|
}).
|
|
|
|
%% --- Panel record ---
|
|
-record(panel, {
|
|
panel_id :: binary(),
|
|
case_ref :: binary(),
|
|
size :: pos_integer(), %% 1, 3, 23, or 71
|
|
judges :: [pid()],
|
|
votes :: [#vote{}],
|
|
status :: convened | deliberating | decided | dissolved,
|
|
convened_at :: integer(),
|
|
decided_at :: integer() | undefined
|
|
}).
|
|
|
|
%% --- Deliberation decision ---
|
|
%% Construction spec §3.2 — output format
|
|
-record(decision, {
|
|
case_ref :: binary(),
|
|
outcome :: outcome(),
|
|
majority :: map(), %% #{position, reasoning, torah_refs, vote_count}
|
|
minority :: map(), %% #{position, reasoning, torah_refs, vote_count}
|
|
panel_size :: pos_integer(),
|
|
deliberation_time_ms :: non_neg_integer(),
|
|
unanimous_check :: unanimous_check()
|
|
}).
|
|
|
|
%% --- Opinion record (stored by clerk, never deleted) ---
|
|
-record(opinion, {
|
|
case_ref :: binary(),
|
|
judge_id :: reference(),
|
|
position :: binary(),
|
|
reasoning :: binary(),
|
|
torah_refs :: [#torah_ref{}],
|
|
verdict :: accept | reject | abstain,
|
|
is_majority :: boolean(),
|
|
stored_at :: integer()
|
|
}).
|
|
|
|
%% --- Panel size constants ---
|
|
-define(PANEL_SIMPLE, 1).
|
|
-define(PANEL_MONETARY, 3).
|
|
-define(PANEL_CAPITAL, 23).
|
|
-define(PANEL_NOVEL, 71).
|
|
|
|
%% --- Deliberation timeouts (Construction spec §2.2) ---
|
|
-define(TIMEOUT_SIMPLE, 15000). %% 15 seconds
|
|
-define(TIMEOUT_MONETARY, 15000). %% 15 seconds
|
|
-define(TIMEOUT_CAPITAL, 120000). %% 120 seconds
|
|
-define(TIMEOUT_NOVEL, 600000). %% 600 seconds
|