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>
103 lines
3.9 KiB
Elixir
103 lines
3.9 KiB
Elixir
defmodule VavPipeline.SanhedrinConsult do
|
|
@moduledoc """
|
|
What a judge consults (Architecture §3.2: "consults the specification
|
|
(L0/L1)"). Registered in the `sanhedrin` application env as
|
|
`{__MODULE__, :consult}` at pipeline start; `judge.erl` calls it once
|
|
per vote with the step its seat performs — `:interpret`, `:challenge`
|
|
or `:resolve` — and the case's roots and question.
|
|
|
|
Tablet 55: before this the judge chose among six canned positions by a
|
|
hash of the clock. Now the only ground a vote can stand on is the
|
|
Talmudic Engine's answer over the case's roots — the same engine, the
|
|
same rules, the same verses the bedikah and the Bereishit cycle use.
|
|
When the engine's socket is not running, or answers with an error, the
|
|
judge is told `{:error, reason}` and abstains. Nothing here is answered
|
|
from a stub (tablet 41): `TalmudicEngineClient`'s stub fallbacks are
|
|
deliberately NOT used — a court that accepts on a stub is the calf.
|
|
"""
|
|
|
|
require Logger
|
|
|
|
alias VavPipeline.TalmudicEngineSocket
|
|
|
|
@books ~w(bereishit shemot vayikra bamidbar devarim)a
|
|
|
|
@typedoc "What the judge receives: a status the engine gave, the reading, its verses."
|
|
@type consultation :: %{
|
|
required(:status) => String.t(),
|
|
required(:text) => String.t(),
|
|
required(:verses) => [{atom(), pos_integer(), pos_integer()}],
|
|
optional(:kushya) => String.t(),
|
|
optional(:grounding) => [String.t()]
|
|
}
|
|
|
|
@doc """
|
|
Called from the judge (Erlang): `consult(Step, CaseRef, Roots, Question, Context)`.
|
|
Roots are the case's shoresh names as the cycle produces them; the
|
|
Erlang side hands binaries or charlists — both are accepted.
|
|
"""
|
|
@spec consult(:interpret | :challenge | :resolve, binary() | charlist(), list(), binary() | charlist(), map()) ::
|
|
{:ok, consultation()} | {:error, term()}
|
|
def consult(step, case_ref, roots, question, _context) when step in [:interpret, :challenge, :resolve] do
|
|
if Process.whereis(TalmudicEngineSocket) do
|
|
ctx = %{
|
|
context_roots: Enum.map(roots, &to_string/1),
|
|
question: to_string(question)
|
|
}
|
|
|
|
ref = "#{to_string(case_ref)}-#{step}"
|
|
|
|
case apply(TalmudicEngineSocket, step, [ref, ctx]) do
|
|
{:ok, %{"status" => status} = reply} when is_binary(status) ->
|
|
{:ok, normalize(status, reply)}
|
|
|
|
{:ok, other} ->
|
|
{:error, {:unexpected_reply, other}}
|
|
|
|
{:error, reason} ->
|
|
Logger.warning("SanhedrinConsult: engine #{step} failed for #{ref}: #{inspect(reason)}")
|
|
{:error, {:engine_unavailable, reason}}
|
|
end
|
|
else
|
|
{:error, :engine_socket_not_started}
|
|
end
|
|
end
|
|
|
|
def consult(step, _ref, _roots, _q, _ctx), do: {:error, {:unknown_step, step}}
|
|
|
|
@doc "Parse the engine's verse strings (`verse_ref(bereishit,1,1,...)`, `verse(shemot,3,14)`) into `{book, ch, vs}`."
|
|
@spec verses_from([String.t()]) :: [{atom(), pos_integer(), pos_integer()}]
|
|
def verses_from(strings) when is_list(strings) do
|
|
strings
|
|
|> Enum.flat_map(fn s ->
|
|
case Regex.run(~r/verse(?:_ref)?\((bereishit|shemot|vayikra|bamidbar|devarim)\s*,\s*(\d+)\s*,\s*(\d+)/, to_string(s)) do
|
|
[_, book, ch, vs] -> [{String.to_existing_atom(book), String.to_integer(ch), String.to_integer(vs)}]
|
|
_ -> []
|
|
end
|
|
end)
|
|
|> Enum.uniq()
|
|
end
|
|
|
|
def verses_from(_), do: []
|
|
|
|
@doc false
|
|
def books, do: @books
|
|
|
|
defp normalize(status, reply) do
|
|
interp = Map.get(reply, "interpretation") || %{}
|
|
chall = Map.get(reply, "challenge") || %{}
|
|
|
|
base = %{
|
|
status: status,
|
|
text: to_string(Map.get(interp, "text", "")),
|
|
verses: verses_from(List.wrap(Map.get(interp, "verses", []))),
|
|
grounding: List.wrap(Map.get(interp, "grounding", [])) |> Enum.map(&to_string/1)
|
|
}
|
|
|
|
case Map.get(chall, "kushya") do
|
|
k when is_binary(k) and k != "" -> Map.put(base, :kushya, k)
|
|
_ -> base
|
|
end
|
|
end
|
|
end
|