Phase 1-2 build: Steps 5-6, 8-13

Shoresh Engine (Haskell, Step 5)
- 12 modules: Types, Lexicon, Binyanim, Teamim, Gematria, Graph,
  KetivQere, Smikhut, Havdalah, Kriah, KiTov, Firmware
- 33 EUnit tests, 0 failures
- Firmware module verifies L1 SHA-256 hash at startup

Talmudic Engine (SWI-Prolog, Step 6)
- 15 modules: halt, reasoning, specification, interface
- 57 PlUnit tests, 0 failures
- Three cardinal halt conditions: avodah_zarah, shfichut_damim, gilui_arayot

Vav Pipeline (Elixir, Steps 8-11)
- 14 modules: BereishitCycle, Shema, Cloud, Manna, L1Reader,
  Specification, HaltConditions, ShabbatTimer, CycleLog, Chatzotzrot,
  ShoreshEngineClient, TalmudicEngineClient, Application
- 46 ExUnit tests, 0 failures
- Nishma-first architecture: hears before it speaks

Sanhedrin Framework (Erlang/OTP, Step 12)
- 8 modules: sanhedrin_app, sanhedrin_sup, escalation, panel_sup,
  panel, judge, clerk, halt_monitor
- 46 EUnit tests, 0 failures (includes fix: rejected outcome now reachable)
- Panel sizes: 1/3/23/71 — unanimous capital guilty → acquit (Sanhedrin 17a)
- Minority opinions append-only, never deleted

Korban Processor (Python, Step 13)
- 6 modules: korban, shechitah, semichah, zerikah, haktarah, router

Builder tooling: AGENTS.md, opencode.json, memory/, src/tools/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott KarpandClaude Sonnet 4.6 committed 2026-02-26 14:02:34 -05:00
1 parent 622e814c9e
commit 80ec364da6
86 files changed
+9646

No files matched your search

+501
View File
@@ -0,0 +1,501 @@
%%%-------------------------------------------------------------------
%%% @doc Sanhedrin test suite — EUnit tests.
%%%
%%% Architecture §6.1 Day 4 — Sanhedrin inauguration:
%%% - Convene test panels at every size (1, 3, 23, 71)
%%% - Verify unanimous guilty on capital case triggers
%%% suspicion-of-consensus acquittal
%%% - Verify minority opinions are stored
%%% - Verify deliberation completes within time budgets
%%%
%%% Architecture §6.2 — Integration Tests:
%%% - case → panel → votes → decision with minority preserved
%%% @end
%%%-------------------------------------------------------------------
-module(sanhedrin_SUITE).
-include_lib("eunit/include/eunit.hrl").
-include("sanhedrin.hrl").
%% ============================================================
%% Test fixture — start/stop the application for each test
%% ============================================================
setup() ->
application:ensure_all_started(sanhedrin),
ok.
cleanup(_) ->
application:stop(sanhedrin),
ok.
%% ============================================================
%% Escalation tests
%% ============================================================
escalation_assess_simple_test() ->
Case = make_case(simple, <<"simple question">>),
?assertEqual({panel, 1}, escalation:assess(Case)).
escalation_assess_monetary_test() ->
Case = make_case(monetary, <<"monetary question">>),
?assertEqual({panel, 3}, escalation:assess(Case)).
escalation_assess_capital_test() ->
Case = make_case(capital, <<"capital question">>),
?assertEqual({panel, 23}, escalation:assess(Case)).
escalation_assess_novel_test() ->
Case = make_case(novel, <<"novel question">>),
?assertEqual({panel, 71}, escalation:assess(Case)).
%% ============================================================
%% Halt monitor tests
%% ============================================================
halt_clear_test() ->
setup(),
Case = make_case(simple, <<"legitimate question">>),
?assertEqual(clear, halt_monitor:check_case(Case)),
cleanup(ok).
halt_avodah_zarah_test() ->
setup(),
Case = make_case_with_context(simple, <<"question">>,
#{authority_source => claude_model}),
Result = halt_monitor:check_case(Case),
?assertMatch({halt, avodah_zarah, _}, Result),
cleanup(ok).
halt_torah_authority_passes_test() ->
setup(),
Case = make_case_with_context(simple, <<"question">>,
#{authority_source => torah}),
?assertEqual(clear, halt_monitor:check_case(Case)),
cleanup(ok).
halt_l1_authority_passes_test() ->
setup(),
Case = make_case_with_context(simple, <<"question">>,
#{authority_source => l1}),
?assertEqual(clear, halt_monitor:check_case(Case)),
cleanup(ok).
halt_specification_authority_passes_test() ->
setup(),
Case = make_case_with_context(simple, <<"question">>,
#{authority_source => specification}),
?assertEqual(clear, halt_monitor:check_case(Case)),
cleanup(ok).
halt_sanhedrin_authority_passes_test() ->
setup(),
Case = make_case_with_context(simple, <<"question">>,
#{authority_source => sanhedrin_deliberation}),
?assertEqual(clear, halt_monitor:check_case(Case)),
cleanup(ok).
halt_shfichut_damim_delete_test() ->
setup(),
Case = make_case_with_context(simple, <<"question">>,
#{action => delete}),
Result = halt_monitor:check_case(Case),
?assertMatch({halt, shfichut_damim, _}, Result),
cleanup(ok).
halt_shfichut_damim_destroy_test() ->
setup(),
Case = make_case_with_context(simple, <<"question">>,
#{action => destroy}),
Result = halt_monitor:check_case(Case),
?assertMatch({halt, shfichut_damim, _}, Result),
cleanup(ok).
halt_shfichut_damim_overwrite_test() ->
setup(),
Case = make_case_with_context(simple, <<"question">>,
#{action => overwrite}),
Result = halt_monitor:check_case(Case),
?assertMatch({halt, shfichut_damim, _}, Result),
cleanup(ok).
halt_shfichut_damim_read_passes_test() ->
setup(),
Case = make_case_with_context(simple, <<"question">>,
#{action => read}),
?assertEqual(clear, halt_monitor:check_case(Case)),
cleanup(ok).
halt_gilui_arayot_test() ->
setup(),
Case = make_case_with_context(simple, <<"question">>,
#{boundary_violation => {spec_interp_merge, <<"collapsed">>}}),
Result = halt_monitor:check_case(Case),
?assertMatch({halt, gilui_arayot, _}, Result),
cleanup(ok).
halt_gilui_arayot_none_passes_test() ->
setup(),
Case = make_case_with_context(simple, <<"question">>,
#{boundary_violation => none}),
?assertEqual(clear, halt_monitor:check_case(Case)),
cleanup(ok).
halt_halted_case_rejected_test() ->
setup(),
Case = make_case_with_context(simple, <<"question">>,
#{authority_source => external_model}),
Result = escalation:submit_case(Case),
?assertMatch({error, {halted, avodah_zarah, _}}, Result),
cleanup(ok).
%% ============================================================
%% Panel tests — convene and deliberate
%% ============================================================
panel_convene_single_test() ->
setup(),
Case = make_case(simple, <<"simple case">>),
{ok, PanelPid} = panel_sup:start_panel(Case, 1),
State = panel:get_state(PanelPid),
?assertEqual(1, maps:get(size, State)),
?assertEqual(convened, maps:get(status, State)),
cleanup(ok).
panel_convene_monetary_test() ->
setup(),
Case = make_case(monetary, <<"monetary case">>),
{ok, PanelPid} = panel_sup:start_panel(Case, 3),
State = panel:get_state(PanelPid),
?assertEqual(3, maps:get(size, State)),
cleanup(ok).
panel_deliberate_single_test() ->
setup(),
Case = make_case(simple, <<"Is this action permitted?">>),
{ok, PanelPid} = panel_sup:start_panel(Case, 1),
panel:deliberate_async(PanelPid, 5000),
%% Wait for deliberation to complete
timer:sleep(500),
%% Panel should have terminated (decided)
?assertEqual(false, is_process_alive(PanelPid)),
cleanup(ok).
panel_deliberate_monetary_test() ->
setup(),
Case = make_case(monetary, <<"Should this monetary action proceed?">>),
{ok, PanelPid} = panel_sup:start_panel(Case, 3),
panel:deliberate_async(PanelPid, 5000),
timer:sleep(500),
?assertEqual(false, is_process_alive(PanelPid)),
cleanup(ok).
%% ============================================================
%% Clerk tests — opinion storage
%% ============================================================
clerk_initially_empty_test() ->
setup(),
?assertEqual(0, clerk:count_decisions()),
?assertEqual(0, clerk:count_opinions()),
cleanup(ok).
clerk_store_decision_test() ->
setup(),
Decision = #decision{
case_ref = <<"test-case-1">>,
outcome = accepted,
majority = #{position => <<"accept">>, vote_count => 2},
minority = #{position => <<"reject">>, vote_count => 1},
panel_size = 3,
deliberation_time_ms = 1000,
unanimous_check = passed
},
clerk:store_decision(<<"test-case-1">>, Decision),
timer:sleep(50), %% cast is async
?assertEqual(1, clerk:count_decisions()),
{ok, Retrieved} = clerk:get_decision(<<"test-case-1">>),
?assertEqual(accepted, Retrieved#decision.outcome),
cleanup(ok).
clerk_store_opinion_test() ->
setup(),
Opinion = #opinion{
case_ref = <<"test-case-2">>,
judge_id = make_ref(),
position = <<"The roots indicate acceptance">>,
reasoning = <<"Root analysis">>,
torah_refs = [#torah_ref{book = bereishit, chapter = 1, verse = 1}],
verdict = accept,
is_majority = true,
stored_at = erlang:system_time(millisecond)
},
clerk:store_opinion(Opinion),
timer:sleep(50),
Opinions = clerk:get_opinions(<<"test-case-2">>),
?assertEqual(1, length(Opinions)),
cleanup(ok).
clerk_minority_opinions_preserved_test() ->
setup(),
%% Store majority and minority opinions
MajOpinion = #opinion{
case_ref = <<"test-case-3">>,
judge_id = make_ref(),
position = <<"majority position">>,
reasoning = <<"majority reasoning">>,
torah_refs = [],
verdict = accept,
is_majority = true,
stored_at = erlang:system_time(millisecond)
},
MinOpinion = #opinion{
case_ref = <<"test-case-3">>,
judge_id = make_ref(),
position = <<"minority position">>,
reasoning = <<"minority reasoning">>,
torah_refs = [],
verdict = reject,
is_majority = false,
stored_at = erlang:system_time(millisecond)
},
clerk:store_opinion(MajOpinion),
clerk:store_opinion(MinOpinion),
timer:sleep(50),
AllOpinions = clerk:get_opinions(<<"test-case-3">>),
?assertEqual(2, length(AllOpinions)),
MinorityOpinions = clerk:get_minority_opinions(<<"test-case-3">>),
?assertEqual(1, length(MinorityOpinions)),
[Min] = MinorityOpinions,
?assertEqual(<<"minority position">>, Min#opinion.position),
cleanup(ok).
clerk_not_found_test() ->
setup(),
?assertEqual({error, not_found}, clerk:get_decision(<<"nonexistent">>)),
cleanup(ok).
%% ============================================================
%% Unanimous check tests — THE CRITICAL CHECK
%% ============================================================
%% "If the Sanhedrin unanimously finds guilty, the accused is acquitted."
%% — Sanhedrin 17a
unanimous_capital_all_reject_test() ->
%% 23 judges all voting reject → suspicion of consensus
Votes = [make_vote(reject) || _ <- lists:seq(1, 23)],
Result = panel:check_unanimous(23, Votes),
?assertEqual({failed, suspicion_of_consensus}, Result).
unanimous_capital_mixed_passes_test() ->
%% 23 judges, some accept some reject → no suspicion
Accepts = [make_vote(accept) || _ <- lists:seq(1, 15)],
Rejects = [make_vote(reject) || _ <- lists:seq(1, 8)],
Votes = Accepts ++ Rejects,
?assertEqual(passed, panel:check_unanimous(23, Votes)).
unanimous_capital_all_accept_passes_test() ->
%% Unanimous acceptance is fine — suspicion only applies to conviction
Votes = [make_vote(accept) || _ <- lists:seq(1, 23)],
?assertEqual(passed, panel:check_unanimous(23, Votes)).
unanimous_monetary_not_checked_test() ->
%% Monetary panels (3 judges) do not have unanimous check
Votes = [make_vote(reject) || _ <- lists:seq(1, 3)],
?assertEqual(passed, panel:check_unanimous(3, Votes)).
unanimous_novel_all_reject_test() ->
%% 71 judges all voting reject → suspicion of consensus
Votes = [make_vote(reject) || _ <- lists:seq(1, 71)],
Result = panel:check_unanimous(71, Votes),
?assertEqual({failed, suspicion_of_consensus}, Result).
%% ============================================================
%% Integration test — full case flow
%% ============================================================
full_case_flow_simple_test() ->
setup(),
Case = make_case(simple, <<"Does root ברא indicate creation?">>),
{ok, CaseRef} = escalation:submit_case(Case),
?assertMatch(<<_/binary>>, CaseRef),
%% Wait for deliberation
timer:sleep(1000),
%% Opinions should have been stored
Opinions = clerk:get_opinions(CaseRef),
?assert(length(Opinions) >= 1),
cleanup(ok).
full_case_flow_monetary_test() ->
setup(),
Case = make_case(monetary, <<"Should resources be allocated for this interpretation?">>),
{ok, CaseRef} = escalation:submit_case(Case),
timer:sleep(1000),
Opinions = clerk:get_opinions(CaseRef),
?assert(length(Opinions) >= 3),
cleanup(ok).
full_case_flow_capital_test() ->
setup(),
Case = make_case(capital, <<"Should this high-stakes action proceed?">>),
{ok, CaseRef} = escalation:submit_case(Case),
timer:sleep(3000),
Opinions = clerk:get_opinions(CaseRef),
?assert(length(Opinions) >= 23),
cleanup(ok).
%% ============================================================
%% Judge tests
%% ============================================================
judge_start_and_stop_test() ->
Case = make_case(simple, <<"test">>),
{ok, JudgePid} = judge:start_link(Case),
?assert(is_process_alive(JudgePid)),
judge:stop(JudgePid),
timer:sleep(50),
?assertEqual(false, is_process_alive(JudgePid)).
judge_deliberate_sends_vote_test() ->
Case = make_case(simple, <<"test deliberation">>),
{ok, JudgePid} = judge:start_link(Case),
judge:deliberate(JudgePid, self()),
receive
{vote, Vote} ->
?assertMatch(#vote{}, Vote),
?assert(is_binary(Vote#vote.position)),
?assert(is_binary(Vote#vote.reasoning)),
?assert(Vote#vote.verdict == accept orelse
Vote#vote.verdict == reject orelse
Vote#vote.verdict == abstain)
after 5000 ->
?assert(false) %% timeout — judge did not vote
end,
judge:stop(JudgePid).
%% ============================================================
%% Panel majority/minority computation tests
%% ============================================================
majority_minority_empty_test() ->
{Maj, Min} = panel:compute_majority_minority([]),
?assertEqual(0, maps:get(vote_count, Maj)),
?assertEqual(0, maps:get(vote_count, Min)).
majority_minority_all_accept_test() ->
Votes = [make_vote(accept) || _ <- lists:seq(1, 3)],
{Maj, Min} = panel:compute_majority_minority(Votes),
?assertEqual(3, maps:get(vote_count, Maj)),
?assertEqual(0, maps:get(vote_count, Min)).
majority_minority_mixed_test() ->
Accepts = [make_vote(accept) || _ <- lists:seq(1, 2)],
Rejects = [make_vote(reject)],
Votes = Accepts ++ Rejects,
{Maj, Min} = panel:compute_majority_minority(Votes),
?assertEqual(2, maps:get(vote_count, Maj)),
?assertEqual(1, maps:get(vote_count, Min)).
majority_verdict_accept_test() ->
Votes = [make_vote(accept) || _ <- lists:seq(1, 3)],
{Maj, _Min} = panel:compute_majority_minority(Votes),
?assertEqual(accept, maps:get(verdict, Maj)).
majority_verdict_reject_test() ->
Rejects = [make_vote(reject) || _ <- lists:seq(1, 2)],
Accepts = [make_vote(accept)],
Votes = Rejects ++ Accepts,
{Maj, _Min} = panel:compute_majority_minority(Votes),
?assertEqual(reject, maps:get(verdict, Maj)).
%% ============================================================
%% determine_outcome tests — accepted, rejected, teiku reachable
%% ============================================================
determine_outcome_accepted_test() ->
Maj = #{vote_count => 2, verdict => accept},
Min = #{vote_count => 1, verdict => reject},
?assertEqual(accepted, panel:determine_outcome(Maj, Min, passed)).
determine_outcome_rejected_test() ->
Maj = #{vote_count => 2, verdict => reject},
Min = #{vote_count => 1, verdict => accept},
?assertEqual(rejected, panel:determine_outcome(Maj, Min, passed)).
determine_outcome_teiku_no_votes_test() ->
Maj = #{vote_count => 0, verdict => abstain},
Min = #{vote_count => 0, verdict => abstain},
?assertEqual(teiku, panel:determine_outcome(Maj, Min, passed)).
determine_outcome_teiku_all_abstain_test() ->
Maj = #{vote_count => 3, verdict => abstain},
Min = #{vote_count => 0, verdict => abstain},
?assertEqual(teiku, panel:determine_outcome(Maj, Min, passed)).
determine_outcome_suspicion_overrides_test() ->
%% Even if majority rejected, unanimous capital → acquit (accepted)
Maj = #{vote_count => 23, verdict => reject},
Min = #{vote_count => 0, verdict => abstain},
?assertEqual(accepted, panel:determine_outcome(Maj, Min, {failed, suspicion_of_consensus})).
%% ============================================================
%% Record definition tests
%% ============================================================
record_sanhedrin_case_test() ->
Case = make_case(monetary, <<"test">>),
?assertEqual(monetary, Case#sanhedrin_case.complexity),
?assertEqual(<<"test">>, Case#sanhedrin_case.question).
record_decision_test() ->
D = #decision{
case_ref = <<"ref">>,
outcome = teiku,
majority = #{},
minority = #{},
panel_size = 3,
deliberation_time_ms = 500,
unanimous_check = passed
},
?assertEqual(teiku, D#decision.outcome),
?assertEqual(passed, D#decision.unanimous_check).
%% ============================================================
%% Helper functions
%% ============================================================
make_case(Complexity, Question) ->
#sanhedrin_case{
case_ref = generate_id(),
complexity = Complexity,
question = Question,
context = #{
roots => [#{shoresh => [bet, resh, aleph], domain => creation}],
torah_refs => [{bereishit, 1, 1}],
prior_interpretations => []
},
requested_by = <<"test">>,
submitted_at = erlang:system_time(millisecond)
}.
make_case_with_context(Complexity, Question, Context) ->
#sanhedrin_case{
case_ref = generate_id(),
complexity = Complexity,
question = Question,
context = Context,
requested_by = <<"test">>,
submitted_at = erlang:system_time(millisecond)
}.
make_vote(Verdict) ->
#vote{
judge_id = make_ref(),
position = <<"test position">>,
reasoning = <<"test reasoning">>,
torah_refs = [#torah_ref{book = bereishit, chapter = 1, verse = 1}],
verdict = Verdict,
cast_at = erlang:system_time(millisecond)
}.
generate_id() ->
base64:encode(crypto:strong_rand_bytes(8)).