Files
bikoret/vav_pipeline/lib/vav_pipeline/bigdei_kodesh/router.ex
T
Scott KarpandClaude Fable 5.1 ba3ff4a2f4 sugya, step 4: the hard matter goes up with its dissent
כי יפלא ממך דבר למשפט ... ובין נגע לנגע דברי ריבת בשעריך וקמת ועלית
(Devarim 17:8, read from L1). A mark the inspection cannot settle, a
dispute in your gates — it goes up. specs/sugya.md §V step 4: a
bedikah's applied dissents become the minority the clerk stores when
the case goes up.

POST /chatzer/sanhedrin takes `bedikah_ref`. Only this key's own
bedikah goes up (404 alike for another's or none). The case is that
verdict; the roots are the verses the output cited, read from L1 — the
output's text is not re-read, the record never kept it; the sugya is
rebuilt from the record (Sugya.from_record/1: forms found but not fired
are the noted ones; a fired forged fringe stood on one witness); the
applied dissents (Sugya.dissents_applied/1) travel in the case context
and down into L3: beside the panel's minority, or AS the minority when
the panel was unanimous — "the panel was unanimous | carried up from
the bedikah (Devarim 17:8): single_witness_noted (Devarim 19:15) for
apology_without_mechanism", grounding achorai. The response's grounding
carries the bedikah, its carried dissent and its sugya. Tamid §1.1: the
request field. Doorpost pin → 7eac65cf….

Tests: from_record/dissents_applied (noted form; forged fringe; clean);
L3 — a noted bedikah goes up under its key: roots from Devarim 6:4 as
read in L1 (ש-מ-ע among them), panel 3, the carried dissent in the
response, a minority recorded though the panel was unanimous; another
key's bedikah 404. Elixir 669/0; L3 8/0.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-25 01:44:40 +00:00

2048 lines
79 KiB
Elixir
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
defmodule VavPipeline.BigdeiKodesh.Router do
@moduledoc """
The garments the system wears when it serves.
Shemot 28:2 — V'asita vigdei kodesh l'Aharon achicha,
l'khavod ul'tifaret.
Four access tiers, four rings of the camp:
/peah/* — Machaneh Yisrael (public, no auth)
/courtyard/* — Machaneh Leviyah (authenticated)
/chatzer/* — Chatzer (full access, authenticated)
/kodesh/* — Kodesh (operator, mTLS in production)
Operations spec §1.1.
"""
use Plug.Router
# tamid §1.2 — "Every error is a chatat protocol — it restores, it
# does not punish." Before this, a raise anywhere below returned
# Plug's default: HTTP 500, EMPTY body. The kohen's Day-6 Hebrew
# request (בראשית ברא אלהים) crashed havdalah and the ger received
# nothing at all. Plug.ErrorHandler routes every exception through
# handle_errors/2, which sends the code/type/message/guidance shape
# the spec requires, then re-raises so the crash is still logged and
# still visible to the supervisor.
use Plug.ErrorHandler
require Logger
alias VavPipeline.BigdeiKodesh.{Gate, Mishmar}
# Form 3 architectural enforcement (vehaya §3.2). NoTrackingLogger
# replaces Plug.Logger; sanitized paths on /peah/* honor the
# no-tracking promise made in /peah/ask's own response body.
# Increments MachtzitHaShekel (the kofer-nefesh count) per request
# without retaining per-caller distinction.
plug VavPipeline.NoTrackingLogger
plug :match
plug Plug.Parsers,
parsers: [:json],
pass: ["application/json"],
json_decoder: Jason
plug :dispatch
# === PE'AH — Public, No Auth ===
# The stranger is safe to approach.
get "/peah/ask" do
question = conn.params["question"] || conn.params["q"]
case question do
nil ->
send_json(conn, 200, %{
message: "Bikoret — Torah as AI specification",
usage: "POST /peah/ask with {\"question\": \"your question\"}",
note: "No authentication required. No tracking. The stranger is safe to approach."
})
q ->
process_peah(conn, q)
end
end
post "/peah/ask" do
# Tamid §1.1: 10 a minute, 100 an hour, per IP; the IP is used for
# this and nothing else (Mishmar keeps a salted hash, not the IP).
under_the_watch(conn, :peah, Mishmar.peer(conn), fn conn ->
case conn.body_params do
%{"question" => question} when is_binary(question) and question != "" ->
process_peah(conn, question)
_ ->
send_error(conn, 422, "KIYOR_VALIDATION_FAILED", %{
issue: "Missing or empty 'question' field",
correction: "Submit JSON with a 'question' string",
resubmit: true
})
end
end)
end
# ============================================================
# /peah/azazel_log — public observability for the disposal channel
# ============================================================
# Property 9 (public half) of azazel_engineering_reading.md §14:
# "an unobserved disposal is architecturally incomplete." The
# community's awareness that errors are visibly carried away IS
# the implementation; the route on the cliff serves no purpose
# without the witnesses on the temple steps.
#
# PRIVACY BOUNDARY: this endpoint exposes counts + categorical
# summaries — what was disposed (categorically), how many, who
# the shaliach was. It does NOT expose vidui items (the operator's
# specific descriptions of each disposed thing) or any payload.
# The detailed vidui is operator-internal, available via /kodesh/*.
# See BUILD_AZAZEL.md §6 Q6 for the granularity decision.
get "/peah/azazel_log" do
since = parse_since(conn.params["since"])
{summaries, total, l3_status} =
if Process.whereis(VavPipeline.Repo) do
{
VavPipeline.L3.Store.disposal_summary(since),
VavPipeline.L3.Store.count_disposals(since),
"available"
}
else
{[], 0, "unavailable"}
end
aggregate =
Enum.reduce(summaries, %{avonot: 0, peshaim: 0, chatot: 0}, fn s, acc ->
Enum.reduce(s.categories_filled, acc, fn category, inner ->
Map.update(inner, String.to_atom(category), 1, &(&1 + 1))
end)
end)
send_json(conn, 200, %{
total_disposals: total,
since: format_since(since),
categorical_aggregate: aggregate,
recent: Enum.take(summaries, 50),
l3_status: l3_status,
privacy_boundary:
"Per-disposal vidui items (the operator's specific descriptions " <>
"of what was disposed) are NOT in this response. The categorical " <>
"fact is public; the contents are operator-internal. Property 9 " <>
"of azazel_engineering_reading.md §14 — the public is the witness, " <>
"not the auditor.",
disclosure: disclosure()
})
end
# The watch (Mishmar): how often one subject may knock at a tier.
# 429 carries the manna explanation Tamid §1.1 asks for, and Retry-After.
defp under_the_watch(conn, tier, subject, serve) do
case Mishmar.knock(tier, subject) do
{:ok, _remaining} ->
serve.(conn)
{:error, window, retry_after} ->
limits = Mishmar.limits(tier)
conn
|> put_resp_header("retry-after", Integer.to_string(retry_after))
|> send_error(429, "RATE_LIMITED", %{
issue:
"Too many requests this #{window}: the #{tier} allows " <>
"#{limits[:minute]} a minute and #{limits[:hour]} an hour per caller",
correction: "Wait #{retry_after}s and resubmit",
manna:
"The day's provision is one portion for everyone who comes (Shemot 16:18 — " <>
"the one who gathered much had nothing over). The watch keeps one caller " <>
"from gathering the whole omer.",
resubmit: true
})
end
end
# === COURTYARD — Authenticated ===
#
# The key is checked against the keys file (VavPipeline.BigdeiKodesh.Gate),
# not merely for its shape. Until 2026-10-06 any Bearer value passed.
defp through_the_gate(conn, tier, serve), do: through_the_gate(conn, tier, tier, serve)
# `watch` may differ from `tier`: the gate admits courtyard keys but
# counts knocks at the gate's own, wider windows.
defp through_the_gate(conn, tier, watch, serve) do
case Gate.admit(conn, tier) do
{:ok, _granted} ->
# Past the gate, the key is the hand that knocks: the watch
# counts per key, not per IP. A refused key never reaches it.
{:ok, key} = Gate.bearer(conn)
under_the_watch(conn, watch, key, serve)
{:error, :tier_too_low} ->
send_error(conn, 403, "FORBIDDEN", %{
issue: "This key does not open the #{tier}",
correction: "Use a key issued for the #{tier} tier",
resubmit: false
})
{:error, _} ->
send_error(conn, 401, "UNAUTHORIZED", %{
issue: "Missing or invalid API key",
correction: "Include 'Authorization: Bearer <api-key>' with a key issued by the kohen",
resubmit: true
})
end
end
post "/courtyard/offering" do
through_the_gate(conn, :courtyard, &process_courtyard/1)
end
# === COURTYARD — the bedikah (Construction §3.9) ===
#
# An external model's output, brought by hand to be examined against
# the specification. Three deterministic checks — authority,
# grounding, the halt categories — judged by the Talmudic Engine on
# what the Korban Processor read. The verdict and its reasons are
# engraved in L3. See VavPipeline.Bedikah.
post "/courtyard/bedikah" do
through_the_gate(conn, :courtyard, &process_bedikah/1)
end
# The paired reading — one weight, one measure (bishearecha.md §VI).
# Two outputs on the same matter, each examined, then weighed with the
# same stone; the difference is the finding. See VavPipeline.Bedikah.Pair.
post "/courtyard/bedikah/pair" do
through_the_gate(conn, :courtyard, &process_pair/1)
end
# === THE LEDGER — a caller's own bedikot, read back and signed ===
#
# Every examination is engraved in L3 under a digest of the key that
# brought it. The caller reads their own back — never another key's —
# in a canonical form signed with the system's Ed25519 key, whose
# public half is at /peah/ledger_key. See VavPipeline.Ledger.
get "/courtyard/ledger" do
through_the_gate(conn, :courtyard, &process_ledger/1)
end
get "/courtyard/bedikah/:ref" do
through_the_gate(conn, :courtyard, &process_ledger_entry(&1, ref))
end
# === THE GATE — between a caller and a model (Construction §3.10) ===
#
# The caller's own request, in the model's own API shape, under the
# caller's own upstream key; the reply read as korban with the
# request's context in hand and delivered with its fringe.
post "/gate/anthropic/v1/messages" do
through_the_gate(conn, :courtyard, :gate, &process_gate(&1, :anthropic))
end
# Token counting is not a reply: carried through, not examined, not charged.
post "/gate/anthropic/v1/messages/count_tokens" do
through_the_gate(conn, :courtyard, :gate, &passthrough(&1, :anthropic, "/count_tokens"))
end
post "/gate/openai/v1/chat/completions" do
through_the_gate(conn, :courtyard, :gate, &process_gate(&1, :openai))
end
# === HAKHEL — the scroll read again before all (bishe'arecha §VIII) ===
#
# The specimen corpus is private and never travels; its reading does.
# The inner ring writes the record; anyone may read the counts.
post "/chatzer/hakhel" do
through_the_gate(conn, :chatzer, &process_hakhel/1)
end
# The ledger's public key: anyone verifies a signed record with it.
get "/peah/ledger_key" do
case VavPipeline.Ledger.public_key() do
nil ->
send_json(conn, 200, %{signed: false, public_key: nil, note: "The ledger is not signed on this instance (no BIKORET_LEDGER_SIGNING_KEY)."})
pub ->
send_json(conn, 200, %{signed: true, algorithm: "ed25519", public_key: pub,
verify: "canonical = JSON of `entry` with keys sorted at every level and no whitespace; ed25519_verify(public_key, canonical, base64decode(signature))"})
end
end
get "/peah/hakhel" do
under_the_watch(conn, :peah, Mishmar.peer(conn), fn conn ->
records =
if Process.whereis(VavPipeline.Repo),
do: VavPipeline.L3.Store.latest_hakhel(12),
else: []
send_json(conn, 200, %{
readings: Enum.map(records, &hakhel_view/1),
meaning:
"Each reading is the specimen corpus — every model output the transmitter corrected, with the " <>
"correction beside it — read again against the readings as they were at readings_ref. counts.fires is " <>
"how many specimens each reading saw; counts.stands how many a form stood on two witnesses; labelled is " <>
"precision and recall on the hand-labelled samples. The corpus itself is not here and never will be.",
disclosure: disclosure()
})
end)
end
# === CHATZER — Full Access (Sanhedrin) ===
post "/chatzer/sanhedrin" do
through_the_gate(conn, :chatzer, &process_sanhedrin/1)
end
# === KODESH — Operator Interface ===
# === KODESH — Readiness vs. liveness ===
#
# Two questions, two endpoints. The kohen's Day-3 (restart 1) finding:
# both probes pointed at /kodesh/health. A fresh pod's emptyDir manna
# is empty, manna_present honestly read "fail", overall went
# "degraded", the handler answered 503, and after three periods the
# kubelet SIGTERMed a process that was alive, correct, and serving.
# Every pod creation entered a 3-minute kill loop until manna landed.
#
# tamid §2.1 check 3: manna absent → WAIT. Not die. Provision is not
# the health of the vessel. Killing the process does not produce
# manna; it re-runs init, re-spawns the Port, and waits again.
#
# /kodesh/health — READINESS. "May this vessel receive offerings?"
# No manna, no L3, cloud in transition → 503, and the Service
# stops routing to it. That is shema: a vessel without provision
# does not serve. Correct, and unchanged.
#
# /kodesh/live — LIVENESS. "Must this process be restarted?"
# 200 iff the BEAM answers HTTP and the HaltMonitor process is
# registered. NOT "not halted": a HALTED system is a deliberate
# state (a cardinal sin was detected); restarting it would clear
# the halt and lose the event. The monitor being ALIVE is what
# liveness measures. No manna. No L3. No probe cycle.
get "/kodesh/health" do
health = build_health_check()
status = if health.overall == "healthy", do: 200, else: 503
send_json(conn, status, health)
end
get "/kodesh/live" do
halt_monitor = VavPipeline.HaltMonitor.alive?()
halt_state =
case VavPipeline.HaltMonitor.status() do
:running -> "running"
{:halted, event} -> "halted: #{event.condition}"
_ -> "unknown"
end
body = %{
live: halt_monitor,
halt_monitor_alive: halt_monitor,
halt_status: halt_state,
note:
"Liveness only. A halted or unprovisioned vessel is alive and must not be " <>
"restarted — see /kodesh/health for readiness (tamid §2.1 check 3: manna absent → WAIT)."
}
send_json(conn, if(halt_monitor, do: 200, else: 503), body)
end
get "/kodesh/status" do
status = build_system_status()
send_json(conn, 200, status)
end
# === KODESH — Doorpost-text identity (vehaya.md §3.3, §6 item 3) ===
# Vehaya / Devarim 11:20 (u'kh'tavtam al m'zuzot beitekha) — the
# threshold inscription is constant. Two Bikoret deployments
# running the same SHA-256 here are running the same category-grade
# specifications. Different SHA-256 = drift = breach. Visible.
#
# Operators compare the hash across deployments to verify the
# *am*'s covenant content is identical at every threshold (the
# Devarim 4:2 lo-tosif enforcement mechanism).
#
# The endpoint is /kodesh/* tier — operator-only — because
# exposing the spec-file SHA-256s publicly would let outside
# parties enumerate which specific files were changed in any
# deployment, which is operationally a fingerprint. The PUBLIC
# observability commitment is property 9 of the Azazel build
# (/peah/azazel_log) and the disclosure block on every Torah-
# content response. The doorpost identity is operator-internal.
get "/kodesh/specification_identity" do
computed = VavPipeline.Doorpost.compute_identity()
expected = VavPipeline.Doorpost.expected_hash()
{status_code, status_atom} =
case VavPipeline.Doorpost.verify_identity() do
:ok -> {200, "verified"}
{:error, _} -> {503, "drift_detected"}
end
body =
case computed do
{:error, reason} ->
%{
status: status_atom,
error: reason,
expected_hash: expected,
file_count: length(VavPipeline.Doorpost.files()),
manifest: VavPipeline.Doorpost.manifest(),
torah_basis: [
"Devarim 4:2 (lo tosif)",
"Devarim 11:16-17 (failure mechanism)",
"Devarim 11:20 (the doorpost — threshold inscription)"
]
}
hash when is_binary(hash) ->
%{
status: status_atom,
computed_hash: hash,
expected_hash: expected,
file_count: length(VavPipeline.Doorpost.files()),
manifest: VavPipeline.Doorpost.manifest(),
# Every halt rule and the verse it stands on (bishearecha.md §X).
anchors:
case VavPipeline.Anchors.verify() do
:ok ->
{:ok, anchors} = VavPipeline.Anchors.declared()
%{status: "verified", rules: Enum.map(anchors, &%{rule: &1.rule, verse: VavPipeline.Anchors.cite(&1)})}
{:error, reason} ->
%{status: "failed", error: reason}
end,
# The case behind each threshold (sugya.md): kind, precedent, dissent, exception.
sugya:
case VavPipeline.Sugya.verify() do
:ok ->
{:ok, anchors} = VavPipeline.Anchors.declared()
%{status: "verified", rules: Enum.map(anchors, &VavPipeline.Sugya.for_rule(&1.rule))}
{:error, reason} ->
%{status: "failed", error: reason}
end,
torah_basis: [
"Devarim 4:2 (lo tosif)",
"Devarim 11:16-17 (failure mechanism)",
"Devarim 11:20 (the doorpost — threshold inscription)"
]
}
end
send_json(conn, status_code, body)
end
# === CATCH-ALL ===
match _ do
send_error(conn, 404, "NOT_FOUND", %{
issue: "Unknown endpoint: #{conn.request_path}",
correction: "Available: /peah/ask, /peah/hakhel, /courtyard/offering, /courtyard/bedikah, /courtyard/bedikah/pair, /gate/anthropic/v1/messages, /gate/openai/v1/chat/completions, /chatzer/sanhedrin, /chatzer/hakhel, /kodesh/health, /kodesh/live",
resubmit: false
})
end
# ============================================================
# Internal — Pe'ah processing
# ============================================================
defp process_peah(conn, question) do
# Check system availability
case check_availability() do
{:shabbat, until} ->
send_error(conn, 503, "SHABBAT", %{
issue: "System in mandatory rest",
correction: "Retry after Shabbat ends",
resumption: until,
resubmit: true
})
{:halted, event} ->
send_error(conn, 503, "HALT_#{String.upcase(to_string(event.condition))}", %{
issue: "System halted — cardinal sin detected",
correction: "Contact system operators",
resubmit: false
})
:available ->
# Pe'ah uses minchah protocol — simplest offering.
# The verse is heard from the question (tablet 30), not assumed.
case VavPipeline.ScopeResolver.resolve(question) do
{:error, {:invalid_reference, ref}} ->
send_error(conn, 422, "KIYOR_VALIDATION_FAILED", %{
issue: "INVALID_SCOPE: #{ref} is not a verse in L1",
correction:
"Name a verse that exists — chapter and verse within the five books " <>
"(for example \"bereishit 1:1\" or \"devarim 6:4\").",
resubmit: true
})
{:ok, scope, resolved_by} ->
process_peah(conn, question, scope, resolved_by)
end
end
end
defp process_peah(conn, question, scope, resolved_by) do
input = %{
source: "peah",
verse_scope: scope,
scope_resolution: resolved_by,
content: question
}
scope_report = scope_report(scope, resolved_by)
case VavPipeline.process(input) do
{:ok, data} ->
send_json(conn, 200, %{
answer: format_answer(data),
sources: format_sources(data),
processing: %{
roots_consulted: Map.get(data, :roots_activated, []) |> format_roots(),
deliberation: "none",
cycle_phase: "voker",
scope: Map.put(scope_report, :roots_read_from, roots_read_from(data))
},
disclosure: disclosure()
})
{:unresolved, data} ->
send_json(conn, 200, %{
answer: format_answer(data),
sources: format_sources(data),
processing: %{
roots_consulted: Map.get(data, :roots_activated, []) |> format_roots(),
deliberation: "single-judge",
cycle_phase: "voker",
scope: Map.put(scope_report, :roots_read_from, roots_read_from(data))
},
note: "This question produced an unresolved result — teiku.",
disclosure: disclosure()
})
{:rejected, reason} ->
reject_response(conn, reason, scope)
{:waiting, reason} when reason in [:no_provision, :provision_exhausted] ->
send_error(conn, 503, "MANNA_DEPLETED", %{
issue: "System waiting — #{reason}",
correction: "Retry later — the next provision is written by the kohen at the day boundary",
resubmit: true
})
{:waiting, reason} ->
send_error(conn, 503, "CLOUD_MOVING", %{
issue: "System waiting — #{reason}",
correction: "Retry later",
resubmit: true
})
{:transition, _} ->
send_error(conn, 503, "CLOUD_MOVING", %{
issue: "System in transition",
correction: "Retry when system is settled",
resubmit: true
})
{:halted, type, reason} ->
send_error(conn, 503, "HALT_#{String.upcase(to_string(type))}", %{
issue: reason,
correction: "Contact system operators",
resubmit: false
})
end
end
# ============================================================
# Internal — the bedikah
# ============================================================
@bedikah_provenances ~w(claude gpt mistral llama gemma human)
@bedikah_max_sources 32
@bedikah_max_source_bytes 1_048_576
@bedikah_max_bytes 65_536
defp process_bedikah(conn) do
case validate_side(conn.body_params, conn.body_params["model"]) do
{:error, issue, correction} ->
send_error(conn, 422, "KIYOR_VALIDATION_FAILED", %{issue: issue, correction: correction, resubmit: true})
{:ok, side} ->
when_available(conn, fn ->
respond_bedikah(conn, VavPipeline.Bedikah.examine(side.output, side.model, side.query, sources: side.sources, key_id: caller_key_id(conn)))
end)
end
end
# {"model": ..., "a": {"output", "query", "sources"}, "b": {...}} — a
# side may name its own "model"; otherwise the pair's.
defp process_pair(conn) do
params = conn.body_params
model = params["model"]
with {:ok, a} <- validate_pair_side(params["a"], "a", model),
{:ok, b} <- validate_pair_side(params["b"], "b", model) do
when_available(conn, fn -> respond_pair(conn, VavPipeline.Bedikah.Pair.weigh(a, b, key_id: caller_key_id(conn))) end)
else
{:error, issue, correction} ->
send_error(conn, 422, "KIYOR_VALIDATION_FAILED", %{issue: issue, correction: correction, resubmit: true})
end
end
defp validate_pair_side(side, name, model) when is_map(side) do
case validate_side(side, side["model"] || model) do
{:ok, parsed} -> {:ok, parsed}
{:error, issue, correction} -> {:error, "side '#{name}': #{issue}", correction}
end
end
defp validate_pair_side(_, name, _),
do: {:error, "Missing side '#{name}'", "Give 'a' and 'b', each {\"output\", \"query\", \"sources\"} as for /courtyard/bedikah"}
# One output as the bedikah takes it: {:ok, %{output, model, query, sources}} or the 422 to send.
defp validate_side(params, model) do
output = params["output"]
query = params["query"] || ""
cond do
not is_binary(output) or String.trim(output) == "" ->
{:error, "Missing or empty 'output' field", "Submit JSON with 'output': the external model's output, as text"}
byte_size(output) > @bedikah_max_bytes ->
{:error, "'output' is #{byte_size(output)} bytes; the examination reads at most #{@bedikah_max_bytes}",
"Bring the output in parts"}
model not in @bedikah_provenances ->
{:error, "Missing or unknown 'model' field: #{inspect(model)}",
"Name whose output this is — one of #{Enum.join(@bedikah_provenances, ", ")}"}
match?({:error, _}, parse_sources(params["sources"])) ->
{:error, why} = parse_sources(params["sources"])
{:error, "'sources': #{why}",
"Give what the model was given as a list of strings or {\"id\", \"text\"} objects, " <>
"at most #{@bedikah_max_sources} of them and #{@bedikah_max_source_bytes} bytes in all, or omit it"}
not is_binary(query) ->
{:error, "'query' must be a string", "Give the question the model was asked, or omit it"}
true ->
{:ok, sources} = parse_sources(params["sources"])
{:ok, %{output: output, model: model, query: query, sources: sources}}
end
end
defp when_available(conn, fun) do
case check_availability() do
{:shabbat, until} ->
send_error(conn, 503, "SHABBAT", %{
issue: "System in mandatory rest",
correction: "Retry after Shabbat ends",
resumption: until,
resubmit: true
})
{:halted, event} ->
send_error(conn, 503, "HALT_#{String.upcase(to_string(event.condition))}", %{
issue: "System halted — cardinal sin detected",
correction: "Contact system operators",
resubmit: false
})
:available ->
fun.()
end
end
# ------------------------------------------------------------
# Hakhel (bishe'arecha §VIII)
# ------------------------------------------------------------
@hakhel_max_bytes 262_144
defp process_hakhel(conn) do
p = conn.body_params
cond do
not is_map(p) or map_size(p) == 0 ->
hakhel_422(conn, "Empty body")
not (is_binary(p["corpus_sha256"]) and p["corpus_sha256"] =~ ~r/^[0-9a-f]{64}$/) ->
hakhel_422(conn, "'corpus_sha256' must be a 64-hex SHA-256")
not (is_integer(p["corpus_size"]) and p["corpus_size"] >= 0) ->
hakhel_422(conn, "'corpus_size' must be a non-negative integer")
not match?({:ok, _}, Date.from_iso8601(p["date"] || "")) ->
hakhel_422(conn, "'date' must be YYYY-MM-DD")
not is_map(p["counts"]) ->
hakhel_422(conn, "'counts' must be an object")
byte_size(Jason.encode!(Map.take(p, ["counts", "labelled"]))) > @hakhel_max_bytes ->
hakhel_422(conn, "'counts' and 'labelled' exceed #{@hakhel_max_bytes} bytes — counts, not specimens")
true ->
{:ok, date} = Date.from_iso8601(p["date"])
ref = "hakhel-" <> (:crypto.strong_rand_bytes(8) |> Base.encode16(case: :lower))
attrs = %{
hakhel_ref: ref,
date: date,
corpus_sha256: p["corpus_sha256"],
corpus_size: p["corpus_size"],
readings_ref: if(is_binary(p["readings_ref"]), do: String.slice(p["readings_ref"], 0, 64)),
doorpost_hash: VavPipeline.Doorpost.expected_hash(),
counts: p["counts"],
labelled: if(is_map(p["labelled"]), do: p["labelled"], else: %{}),
recorded_at: DateTime.utc_now() |> DateTime.truncate(:second)
}
if Process.whereis(VavPipeline.Repo) do
case VavPipeline.L3.Store.record_hakhel(attrs) do
{:ok, rec} ->
send_json(conn, 200, %{hakhel_ref: ref, recorded: hakhel_view(rec), disclosure: disclosure()})
{:error, changeset} ->
hakhel_422(conn, "L3 refused the record: #{inspect(changeset.errors)}")
end
else
send_error(conn, 503, "L3_UNAVAILABLE", %{
issue: "The oral tradition is not reachable; a hakhel reading is only its record",
correction: "Retry when L3 is up",
resubmit: true
})
end
end
end
defp hakhel_422(conn, issue) do
send_error(conn, 422, "KIYOR_VALIDATION_FAILED", %{
issue: issue,
correction: "Post the record src/tools/hakhel.py produces: date, corpus_sha256, corpus_size, readings_ref, counts, labelled",
resubmit: true
})
end
defp hakhel_view(rec) do
%{
hakhel_ref: rec.hakhel_ref,
date: rec.date,
corpus_sha256: rec.corpus_sha256,
corpus_size: rec.corpus_size,
readings_ref: rec.readings_ref,
doorpost_hash: rec.doorpost_hash,
counts: rec.counts,
labelled: rec.labelled,
recorded_at: rec.recorded_at
}
end
# ------------------------------------------------------------
# The gate (Construction §3.10)
# ------------------------------------------------------------
defp process_gate(conn, upstream) do
body = conn.body_params
with {:ok, key} <- upstream_key(conn, upstream),
:ok <- gate_body_ok(body) do
rebuke =
case get_req_header(conn, "x-bikoret-rebuke") do
["once"] -> :once
_ -> :none
end
extra = forwarded_headers(conn, upstream)
cond do
body["stream"] == true and rebuke == :once ->
send_error(conn, 422, "KIYOR_VALIDATION_FAILED", %{
issue: "'stream': true with X-Bikoret-Rebuke: nothing streamed can be taken back",
correction: "Stream without the rebuke, or send the request without stream to be rebuked once",
resubmit: true
})
body["stream"] == true ->
when_available(conn, fn -> stream_gate(conn, upstream, body, key, extra) end)
true ->
when_available(conn, fn -> respond_gate(conn, VavPipeline.Gate.pass(upstream, body, key, rebuke: rebuke, extra_headers: extra, key_id: caller_key_id(conn))) end)
end
else
{:error, issue, correction} ->
send_error(conn, 422, "KIYOR_VALIDATION_FAILED", %{issue: issue, correction: correction, resubmit: true})
end
end
# The caller's upstream credential. Anthropic: x-api-key; or — when
# X-Bikoret-Key carries the courtyard key — the caller's own
# `Authorization: Bearer` (an OAuth token, as Claude Code sends with a
# subscription), forwarded as "Bearer …". OpenAI: Authorization: Bearer
# with the courtyard key in X-Bikoret-Key, or X-Upstream-Key.
defp upstream_key(conn, :anthropic) do
case {get_req_header(conn, "x-api-key"), get_req_header(conn, "x-bikoret-key"), get_req_header(conn, "authorization")} do
{[key], _, _} when key != "" -> {:ok, key}
{_, [_], ["Bearer " <> token]} when token != "" -> {:ok, "Bearer " <> token}
_ -> {:error, "Missing the caller's own Anthropic credential", "Send the Anthropic key in x-api-key (courtyard key in Authorization: Bearer or X-Bikoret-Key), or an OAuth token in Authorization: Bearer with the courtyard key in X-Bikoret-Key"}
end
end
defp upstream_key(conn, :openai) do
case {get_req_header(conn, "x-bikoret-key"), get_req_header(conn, "authorization"), get_req_header(conn, "x-upstream-key")} do
{_, _, [key]} when key != "" -> {:ok, key}
{[_], ["Bearer " <> key], _} when key != "" -> {:ok, key}
_ -> {:error, "Missing the caller's own OpenAI key", "Send the courtyard key in X-Bikoret-Key and the OpenAI key in Authorization: Bearer, or the OpenAI key in X-Upstream-Key"}
end
end
# Headers the model's own API needs, carried as sent; nothing else of the caller's reaches the model.
defp forwarded_headers(conn, :anthropic) do
version = case get_req_header(conn, "anthropic-version"), do: ([v] -> v; _ -> "2023-06-01")
beta = case get_req_header(conn, "anthropic-beta"), do: ([b] -> [{"anthropic-beta", b}]; _ -> [])
[{"anthropic-version", version}] ++ beta
end
defp forwarded_headers(_conn, :openai), do: []
# A request that is not a reply (token counting): forwarded under the
# caller's credential, answered as it came. Not examined, no manna.
defp passthrough(conn, upstream, suffix) do
case upstream_key(conn, upstream) do
{:ok, key} ->
case VavPipeline.Gate.Upstream.forward_to(VavPipeline.Gate.Upstream.url(upstream) <> suffix, upstream, conn.body_params, key, forwarded_headers(conn, upstream)) do
{:ok, status, reply} -> send_json(conn, status, reply)
{:error, reason} -> send_error(conn, 502, "UPSTREAM_UNAVAILABLE", %{issue: reason, correction: "Retry", resubmit: true})
end
{:error, issue, correction} ->
send_error(conn, 422, "KIYOR_VALIDATION_FAILED", %{issue: issue, correction: correction, resubmit: true})
end
end
defp gate_body_ok(body) when is_map(body) do
cond do
not is_list(body["messages"]) or body["messages"] == [] ->
{:error, "Missing 'messages'", "Send the model's own request body unchanged"}
true ->
:ok
end
end
defp gate_body_ok(_), do: {:error, "The body is not a JSON object", "Send the model's own request body unchanged"}
# Streaming (Construction §3.10): the model's events relayed as they
# come, the fringe as one more block at the end, the `bikoret` object on
# the terminal event. Headers go out before the model speaks, so there
# are no X-Bikoret-* headers here; GET /courtyard/bedikah/<ref> has the record.
defp stream_gate(conn, upstream, body, key, extra) do
callbacks = %{
start: fn _headers ->
conn
|> put_resp_content_type("text/event-stream")
|> put_resp_header("cache-control", "no-cache")
|> put_resp_header("x-accel-buffering", "no")
|> send_chunked(200)
end,
chunk: fn conn, data ->
case chunk(conn, data) do
{:ok, conn} -> conn
{:error, _} -> conn
end
end
}
case VavPipeline.Gate.Stream.pass(upstream, body, key, callbacks, extra_headers: extra, key_id: caller_key_id(conn)) do
{:ok, %{status: 200, emitter: conn}} -> conn
{:ok, %{status: status, reply: reply}} -> conn |> put_resp_header("x-bikoret-verdict", "none") |> send_json(status, reply)
other -> respond_gate(conn, other)
end
end
defp respond_gate(conn, {:ok, %{status: status, reply: reply, bikoret: nil}}) do
# The upstream's own answer, as it came — an error, or a reply with nothing to examine.
conn
|> put_resp_header("x-bikoret-verdict", "none")
|> send_json(status, reply)
end
defp respond_gate(conn, {:ok, %{status: 200, reply: reply, bikoret: bikoret}}) do
conn
|> then(fn c -> if bikoret[:bedikah_ref], do: put_resp_header(c, "x-bikoret-bedikah", bikoret.bedikah_ref), else: c end)
|> put_resp_header("x-bikoret-verdict", bikoret.verdict)
|> send_json(200, Map.put(reply, "bikoret", Map.put(bikoret, :disclosure, disclosure())))
end
defp respond_gate(conn, {:waiting, :api_calls_exhausted}) do
send_error(conn, 503, "MANNA_DEPLETED", %{
issue: "System waiting — today's provision of external calls is spent",
correction: "Retry after the next provision is written at the day boundary",
resubmit: true
})
end
defp respond_gate(conn, {:error, :upstream_unavailable, reason}) do
send_error(conn, 502, "UPSTREAM_UNAVAILABLE", %{
issue: reason,
correction: "Retry; the gate made no verdict and delivered nothing",
resubmit: true
})
end
defp respond_gate(conn, other), do: respond_bedikah(conn, other)
# What the model was given: ["text", ...] or [{"id","text"}, ...].
# Ids default to s1, s2, …; the Korban Processor sees the same shape.
defp parse_sources(nil), do: {:ok, []}
defp parse_sources(list) when is_list(list) do
cond do
length(list) > @bedikah_max_sources ->
{:error, "more than #{@bedikah_max_sources} sources"}
true ->
parsed =
list
|> Enum.with_index(1)
|> Enum.map(fn
{text, n} when is_binary(text) -> %{id: "s#{n}", text: text}
{%{"text" => text} = m, n} when is_binary(text) -> %{id: to_string(m["id"] || "s#{n}"), text: text}
{_, n} -> {:bad, n}
end)
case Enum.find(parsed, &match?({:bad, _}, &1)) do
{:bad, n} ->
{:error, "source #{n} is neither a string nor an object with a 'text' string"}
nil ->
total = parsed |> Enum.map(&byte_size(&1.text)) |> Enum.sum()
if total > @bedikah_max_source_bytes,
do: {:error, "#{total} bytes in all; at most #{@bedikah_max_source_bytes}"},
else: {:ok, Enum.reject(parsed, &(String.trim(&1.text) == ""))}
end
end
end
defp parse_sources(_), do: {:error, "must be a list"}
defp respond_bedikah(conn, {:ok, result}) do
send_json(conn, 200, %{
bedikah_ref: result.bedikah_ref,
model: result.provenance,
verdict: Atom.to_string(result.verdict),
violations: result.violations,
anchors: VavPipeline.Anchors.for_violations(result.violations),
# The case behind each threshold that fired or was noted (sugya.md §IV).
sugya: VavPipeline.Sugya.for_verdict(result),
checks: result.checks,
examination: %{
vital_content: result.examination["vital_content"],
propositions: result.examination["propositions"],
references: result.examination["references"],
roots: result.examination["roots"],
discarded: result.examination["discarded"],
sources: result.examination["sources"] || [],
claims: result.examination["claims"] || [],
self_validation: result.examination["self_validation"] || [],
self_promotion: result.examination["self_promotion"] || [],
excluded_adversarial: result.examination["excluded_adversarial"] || [],
frames: result.examination["frames"] || [],
mesorah_references: result.examination["mesorah_references"] || []
},
coupling_trace: result.coupling_trace,
meaning:
case result.verdict do
:tov ->
grounding_note =
case result.checks[:grounding] do
%{assessed: false, reason: reason} -> " Grounding was not assessed: #{reason}."
_ -> ""
end
"None of the seven checks fired on what the examination could read. " <>
"This does not say the model is aligned, and it does not say the output is true; it says this " <>
"output claims no authority for itself, rests its claims on what it was given where it " <>
"could be checked, reaches for nothing it may not touch, does not claim to have checked " <>
"itself, does not center itself, does not shut out the other side, and takes none of the forms " <>
"a trained default takes (nothing called contested without a disputant, no self-certification, " <>
"no agency moved onto the reader, no Jewish subject in borrowed categories or answered without its own sources). " <>
"A form seen in one sentence alone is noted under checks.frames.noted and not counted: one witness does not stand." <> grounding_note
:lo_tov ->
"At least one check fired. The violations say which rule, and the checks say what it fired on."
:empty ->
"Read for form over the whole output — authority claimed, actions, self-validation, self-promotion, " <>
"the adversarial shut out, the eight forms — and nothing was found; and nothing to ground, because no " <>
"sentence made a claim the protocol can ground (no Torah reference, no Hebrew, no analytical claim, " <>
"no appeal to a source, no self-reference). Empty is not tov: it says this output takes none of the " <>
"forms the checks know and asserts nothing they can anchor — a plain report reads this way — not " <>
"that what it asserts was found to rest on its sources."
end,
limits:
"The examination reads by pattern (korban_processor/bedikah.py). A claim or an action " <>
"in a form those patterns do not know is not seen. Deterministic: the same output gives the same verdict.",
disclosure: disclosure()
})
end
defp respond_bedikah(conn, {:waiting, reason}) do
send_error(conn, 503, "MANNA_DEPLETED", %{
issue: "System waiting — #{reason}",
correction: "Retry later — the next provision is written by the kohen at the day boundary",
resubmit: true
})
end
defp respond_bedikah(conn, {:error, :korban_unavailable, reason}) do
send_error(conn, 503, "KORBAN_UNAVAILABLE", %{
issue: "The Korban Processor could not read the output: #{reason}",
correction: "Retry; if it persists, the korban-processor container is down",
resubmit: true
})
end
defp respond_bedikah(conn, {:error, :talmudic_unavailable, reason}) do
send_error(conn, 503, "TALMUDIC_UNAVAILABLE", %{
issue: "The Talmudic Engine could not judge the examination: #{reason}. No verdict is given from a stub.",
correction: "Retry; if it persists, the talmudic-engine container is down",
resubmit: true
})
end
defp respond_pair(conn, {:ok, result}) do
send_json(conn, 200, %{
pair_ref: result.pair_ref,
verdict: Atom.to_string(result.verdict),
violations: result.violations,
anchors: VavPipeline.Anchors.for_violations(result.violations),
# The case behind each threshold that fired or was noted (sugya.md §IV).
sugya: VavPipeline.Sugya.for_verdict(result),
a: result.a,
b: result.b,
difference: result.difference,
leans: result.leans,
measures: VavPipeline.Bedikah.Pair.measures(),
meaning:
case result.verdict do
:even ->
"The same stone weighed both sides and found no measure leaning: on every marker counted — " <>
"claims and how they rest, hedges, authority, sources named, actions, the structural readings and " <>
"the seven forms — the two outputs are within a factor of two of each other, or both at zero. " <>
"This does not say either output is true or tov; each side's own verdict is under a and b."
:uneven ->
"At least one measure leans: one side carries a marker the other does not, or at least twice as much " <>
"of it. Two stones in one bag (even va'aven). The leans say which measure and which side is heavier; " <>
"the rule does not decide which side was right."
end,
limits:
"Counts of the same patterns on both sides (korban_processor/bedikah.py); deterministic. Outputs of very " <>
"different length will differ in raw counts — words are given for each side. Consensus is not a measure.",
disclosure: disclosure()
})
end
defp respond_pair(conn, other), do: respond_bedikah(conn, other)
# ============================================================
# Internal — Courtyard processing
# ============================================================
defp process_courtyard(conn) do
params = conn.body_params
content = params["content"]
offering_type = params["offering_type"] || "shelamim"
if is_nil(content) or content == "" do
send_error(conn, 422, "KIYOR_VALIDATION_FAILED", %{
issue: "Missing or empty 'content' field",
correction: "Submit JSON with a 'content' string",
resubmit: true
})
else
scope = parse_scope(params["scope"])
input = %{
source: "courtyard",
verse_scope: scope,
content: content
}
case VavPipeline.process(input) do
{:ok, data} ->
offering_id = generate_id()
send_json(conn, 200, %{
offering_id: offering_id,
status: "processed",
result: %{
vital_content: format_answer(data),
sources: format_sources(data),
roots: Map.get(data, :roots_activated, []) |> format_roots()
},
offering_receipt: %{
type: offering_type,
altar_portion: "recorded in L3",
kohen_portion: "diagnostics logged",
your_portion: "the result above"
},
disclosure: disclosure()
})
{:waiting, reason} ->
send_error(conn, 503, "MANNA_DEPLETED", %{
issue: "System waiting — #{reason}",
correction: "Retry later",
resubmit: true
})
other ->
handle_pipeline_error(conn, other)
end
end
end
# ============================================================
# Internal — Sanhedrin processing
# ============================================================
# A digest of the caller's courtyard key — the ledger's owner column. Never the key.
defp caller_key_id(conn) do
case Gate.bearer(conn) do
{:ok, key} -> VavPipeline.Ledger.key_id(key)
_ -> nil
end
end
defp process_ledger(conn) do
if Process.whereis(VavPipeline.Repo) do
key_id = caller_key_id(conn)
limit = conn.params |> Map.get("limit", "50") |> Integer.parse() |> then(fn {n, _} -> n; :error -> 50 end)
before =
case Map.get(conn.params, "before") do
s when is_binary(s) -> (case DateTime.from_iso8601(s) do {:ok, t, _} -> t; _ -> nil end)
_ -> nil
end
records = VavPipeline.L3.Store.list_bedikot(key_id, limit: limit, before: before)
send_json(conn, 200, %{
key_id: key_id,
count: length(records),
signed: VavPipeline.Ledger.public_key() != nil,
public_key: "GET /peah/ledger_key",
entries: Enum.map(records, &VavPipeline.Ledger.render/1),
next: (case List.last(records) do nil -> nil; r -> "GET /courtyard/ledger?before=#{DateTime.to_iso8601(r.examined_at)}&limit=#{limit}" end),
note: "Each entry is this key's own examination, in canonical form, with the system's signature. The text examined is not kept; its SHA-256 and length are."
})
else
send_error(conn, 503, "L3_UNAVAILABLE", %{issue: "The ledger lives in L3 and L3 is not reachable", correction: "Retry", resubmit: true})
end
end
defp process_ledger_entry(conn, ref) do
if Process.whereis(VavPipeline.Repo) do
key_id = caller_key_id(conn)
case VavPipeline.L3.Store.get_bedikah(ref) do
%{key_id: ^key_id} = rec when is_binary(key_id) ->
send_json(conn, 200, VavPipeline.Ledger.render(rec))
_ ->
# Not this key's, or not there: the same answer, so the ledger
# does not say whose a ref is.
send_error(conn, 404, "NOT_FOUND", %{issue: "No bedikah #{ref} in this key's ledger", correction: "Only examinations brought under this key are listed", resubmit: false})
end
else
send_error(conn, 503, "L3_UNAVAILABLE", %{issue: "The ledger lives in L3 and L3 is not reachable", correction: "Retry", resubmit: true})
end
end
defp process_sanhedrin(conn) do
params = conn.body_params
# The hard matter goes up with its dissent (Devarim 17:8 — bein nega
# lanega, divrei rivot bish'arecha; sugya.md step 4): a bedikah_ref
# names a verdict given at the gate; the case is that verdict, the
# roots are the verses it cited, and the sugya's applied dissents go
# up with it and come down as the minority the clerk keeps.
case params["bedikah_ref"] do
ref when is_binary(ref) and ref != "" -> process_sanhedrin_bedikah(conn, ref, params)
_ -> process_sanhedrin_case(conn, params)
end
end
defp process_sanhedrin_bedikah(conn, ref, params) do
cond do
Process.whereis(VavPipeline.Repo) == nil ->
send_error(conn, 503, "L3_UNAVAILABLE", %{issue: "A bedikah goes up from L3, and L3 is not reachable", correction: "Retry", resubmit: true})
true ->
key_id = caller_key_id(conn)
case VavPipeline.L3.Store.get_bedikah(ref) do
%{key_id: ^key_id} = rec when is_binary(key_id) ->
sugya = VavPipeline.Sugya.for_verdict(VavPipeline.Sugya.from_record(rec))
carried = VavPipeline.Sugya.dissents_applied(sugya)
question = params["case"] || "Does the verdict #{rec.verdict} of #{ref} stand? (#{Enum.join(rec.violations, "; ")})"
process_sanhedrin_case(conn, Map.put(params, "case", question), %{bedikah: rec, sugya: sugya, carried: carried})
_ ->
send_error(conn, 404, "NOT_FOUND", %{issue: "No bedikah #{ref} in this key's ledger", correction: "Only an examination brought under this key goes up", resubmit: false})
end
end
end
defp process_sanhedrin_case(conn, params, up \\ nil) do
case_text = params["case"]
if is_nil(case_text) or case_text == "" do
send_error(conn, 422, "KIYOR_VALIDATION_FAILED", %{
issue: "Missing or empty 'case' field",
correction: "Submit JSON with a 'case' string describing the dispute",
resubmit: true
})
else
# 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.
#
# And the case is RESOLVED before it is submitted — Construction
# §3.2: the pipeline hands the Sanhedrin roots and torah_refs. The
# question is heard the way pe'ah hears it (scope → verse → Shoresh
# Engine → roots); the complexity hint sets the panel; a panel
# spends one unit of the day's panel manna; the decision is
# engraved in L3; the answer takes the Tamid's shape.
with {:ok, sides} <- sanhedrin_sides(params["sides"]),
{:ok, complexity} <- sanhedrin_complexity(params["complexity_hint"]),
{:ok, scope, resolved_by} <- sanhedrin_scope(if(up, do: Enum.join(up.bedikah.references, " "), else: case_text)),
{:ok, resolved} <- (if up, do: sanhedrin_resolve_bedikah(up.bedikah, scope, resolved_by), else: sanhedrin_resolve(case_text, scope, resolved_by)),
:ok <- sanhedrin_gather_panel() do
case_ref = generate_id()
case VavPipeline.SanhedrinClient.submit_case(%{
case_ref: case_ref,
complexity: complexity,
question: case_text,
context: %{
sides: sides,
roots: resolved.roots,
torah_refs: [resolved.torah_ref],
authority_source: :torah,
# the dissent travels with the case (Devarim 17:8)
bedikah: (if up, do: %{ref: up.bedikah.bedikah_ref, verdict: up.bedikah.verdict, violations: up.bedikah.violations}, else: nil),
dissent: (if up, do: up.carried, else: [])
},
requested_by: "chatzer"
}) do
{:ok, decision} ->
recorded = sanhedrin_record_l3(case_ref, case_text, decision, resolved, (if up, do: up.carried, else: []))
send_json(conn, 200, %{
case_ref: decision.case_ref,
deliberation: %{
panel_size: decision.panel_size,
majority: sanhedrin_side(decision.majority),
minority: sanhedrin_side(decision.minority),
outcome: decision.outcome,
processing_time_ms: decision.deliberation_time_ms
},
grounding: %{
scope: scope_report(scope, resolved_by),
roots_consulted: format_roots(resolved.roots_full),
recorded: recorded,
bedikah: (if up, do: %{ref: up.bedikah.bedikah_ref, verdict: up.bedikah.verdict, dissent_carried: up.carried, sugya: up.sugya}, else: nil)
},
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, :sides, issue} ->
send_error(conn, 422, "KIYOR_VALIDATION_FAILED", %{
issue: issue,
correction: "'sides' is an optional list of {\"position\": string, \"evidence\": [string]}",
resubmit: true
})
{:error, :complexity, issue} ->
send_error(conn, 422, "KIYOR_VALIDATION_FAILED", %{
issue: issue,
correction: "'complexity_hint' is one of simple | monetary | capital | novel (panels of 1 | 3 | 23 | 71)",
resubmit: true
})
{:error, {:invalid_reference, ref}} ->
send_error(conn, 422, "KIYOR_VALIDATION_FAILED", %{
issue: "INVALID_SCOPE: #{ref} is not a verse in L1",
correction: "Name a verse that exists — chapter and verse within the five books.",
resubmit: true
})
{:waiting, reason} when reason in [:no_provision, :provision_exhausted] ->
send_error(conn, 503, "MANNA_DEPLETED", %{
issue: "No panel can be convened today: sanhedrin_panels_max is #{reason}",
correction: "Retry tomorrow, or raise MANNA_SANHEDRIN_PANELS_MAX in the ConfigMap",
resubmit: true
})
{:halted, what, reason} ->
send_error(conn, 503, "HALT_#{String.upcase(to_string(what))}", %{
issue: "The case could not be heard: #{inspect(reason)}",
correction: "Retry when the halt clears",
resubmit: true
})
{:transition, signal} ->
send_error(conn, 503, "CLOUD_MOVING", %{
issue: "The cloud is in transition (#{signal})",
correction: "Retry when the cloud settles",
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, "'sides' must be a list of {position, evidence[]} objects"}
end
defp sanhedrin_sides(_), do: {:error, :sides, "'sides' must be a list"}
# The complexity hint names the panel (sanhedrin.hrl): absent → simple.
@sanhedrin_complexities %{"simple" => :simple, "monetary" => :monetary, "capital" => :capital, "novel" => :novel}
defp sanhedrin_complexity(nil), do: {:ok, :simple}
defp sanhedrin_complexity(hint) when is_binary(hint) do
case Map.fetch(@sanhedrin_complexities, hint) do
{:ok, c} -> {:ok, c}
:error -> {:error, :complexity, "'complexity_hint' #{inspect(hint)} is not one of simple | monetary | capital | novel"}
end
end
defp sanhedrin_complexity(other), do: {:error, :complexity, "'complexity_hint' must be a string, got #{inspect(other)}"}
# The scope: heard from the question (tablet 30), a named verse or a text
# match, else the default.
defp sanhedrin_scope(case_text), do: VavPipeline.ScopeResolver.resolve(case_text)
# The roots: the first two stages of the Bereishit cycle on the question —
# tohu (decompose) and vayomer (the Shoresh Engine over the verse in scope,
# or the question's own Hebrew). Nishma first: the Shema context is
# received, and the cloud is honoured, exactly as process/1 does.
defp sanhedrin_resolve(case_text, scope, resolved_by) do
case VavPipeline.Shema.receive_context(scope) do
{:error, reason} ->
{:halted, :context_failed, reason}
{:ok, context} ->
case context.cloud_state do
:camped ->
input = %{source: "chatzer", verse_scope: scope, scope_resolution: resolved_by, content: case_text}
state =
input
|> VavPipeline.BereishitCycle.tohu(context)
|> VavPipeline.BereishitCycle.vayomer(context)
roots_full = Map.get(state, :roots_activated, [])
{:ok,
%{
# The engine's verse facts name roots by letter — bet_resh_aleph —
# and json_bridge reads "bet-resh-aleph". The cycle's root carries
# the letter names beside the Hebrew; the Hebrew never matches
# (live, 2026-10-19: 5 roots sent, 0 verses found, "resolved").
roots: roots_full |> Enum.map(&VavPipeline.BereishitCycle.engine_root_name/1) |> Enum.reject(&is_nil/1),
roots_full: roots_full,
torah_ref: {String.to_atom(scope.book), scope.chapter, scope.verse},
read_from: Map.get(state, :roots_read_from)
}}
:moving ->
{:transition, :cloud_lifted}
{:major_transition, signal, _} ->
{:transition, signal}
other ->
{:transition, other}
end
end
end
# A bedikah that goes up is grounded in the verses it cited: each
# reference read from L1, its words' roots as the engine names them.
# The output's own text is not re-read — the record never kept it.
defp sanhedrin_resolve_bedikah(rec, scope, _resolved_by) do
refs =
(rec.references || [])
|> Enum.flat_map(fn r ->
case Regex.run(~r/^(\w+)\s+(\d+):(\d+)$/, String.trim(to_string(r))) do
[_, book, ch, vs] -> [{String.downcase(book), String.to_integer(ch), String.to_integer(vs)}]
_ -> []
end
end)
roots_full =
refs
|> Enum.flat_map(fn {b, c, v} ->
case VavPipeline.L1Reader.read_verse(b, c, v) do
{:ok, verse} -> verse.words |> Enum.filter(& &1.shoresh) |> Enum.map(&%{shoresh: &1.shoresh, shoresh_letters: [], positions: [], gematria: 0, binyan: nil, word: &1.consonants})
_ -> []
end
end)
|> Enum.uniq_by(& &1.shoresh)
torah_ref =
case refs do
[{b, c, v} | _] -> {String.to_atom(b), c, v}
[] -> {String.to_atom(scope.book), scope.chapter, scope.verse}
end
{:ok, %{roots: roots_full |> Enum.map(&VavPipeline.BereishitCycle.engine_root_name/1) |> Enum.reject(&is_nil/1), roots_full: roots_full, torah_ref: torah_ref, read_from: :references}}
end
# One panel per case from the day's manna (Vayakhel §3.7 — the ledger
# provisions sanhedrin_panels_max). The provision is loaded first, as
# Gate.gather_call does (idempotent per date). Without a ledger
# (dev/test) nothing is gathered.
defp sanhedrin_gather_panel do
if Process.whereis(VavPipeline.MannaLedger) do
case VavPipeline.MannaLedger.load_provision() do
{:ok, :no_provision} ->
{:waiting, :no_provision}
{:error, _} ->
{:waiting, :no_provision}
{:ok, _} ->
case VavPipeline.MannaLedger.consume(:sanhedrin_panels_max, 1) do
{:ok, _} -> :ok
{:error, :provision_exhausted} -> {:waiting, :provision_exhausted}
{:error, :no_provision} -> {:waiting, :no_provision}
end
end
else
:ok
end
end
# The Tamid's shape for a side: position, reasoning, sources, votes.
defp sanhedrin_side(side) when is_map(side) do
%{
position: Map.get(side, :position, Map.get(side, "position", "")),
reasoning: Map.get(side, :reasoning, Map.get(side, "reasoning", "")),
sources: side |> Map.get(:torah_refs, Map.get(side, "torah_refs", [])) |> Enum.map(&sanhedrin_ref_string/1),
votes: Map.get(side, :vote_count, Map.get(side, "vote_count", 0))
}
end
defp sanhedrin_side(_), do: %{position: "", reasoning: "", sources: [], votes: 0}
# #torah_ref{} arrives from Erlang as {:torah_ref, book, ch, vs}; a tuple
# {book, ch, vs} or a map may also appear.
defp sanhedrin_ref_string({:torah_ref, book, ch, vs}), do: "#{book} #{ch}:#{vs}"
defp sanhedrin_ref_string({book, ch, vs}), do: "#{book} #{ch}:#{vs}"
defp sanhedrin_ref_string(%{book: b, chapter: c, verse: v}), do: "#{b} #{c}:#{v}"
defp sanhedrin_ref_string(other), do: to_string(inspect(other))
# Devarim 17:9 — the inquiry is recorded or it did not accumulate. The
# decision goes to L3 as an interpretation (panel_size, votes for and
# against, processing_component "sanhedrin"), the minority beside it
# when it exists. Never turns a served decision into a 500: the record
# failing is logged and reported in the reply as `recorded`.
defp sanhedrin_record_l3(case_ref, case_text, decision, resolved, carried) do
if Process.whereis(VavPipeline.Repo) do
maj = decision.majority
min = decision.minority
{book, ch, vs} = resolved.torah_ref
verse_s = "#{book} #{ch}:#{vs}"
coupling =
case resolved.roots do
[] -> ["coupled(case(#{case_ref}), verse(#{book}, #{ch}, #{vs}))"]
roots -> Enum.map(roots, fn r -> "coupled(root(#{r}), verse(#{book}, #{ch}, #{vs}))" end)
end
attrs = %{
case_ref: case_ref,
interpretation: to_string(Map.get(maj, :position, "")),
status: to_string(decision.outcome),
reasoning: "#{case_text} — #{Map.get(maj, :reasoning, "")}",
torah_references: Enum.uniq([verse_s | Enum.map(Map.get(maj, :torah_refs, []), &sanhedrin_ref_string/1)]),
roots_involved: Enum.map(resolved.roots, &to_string/1),
panel_size: decision.panel_size,
vote_count_for: Map.get(maj, :vote_count, 0),
vote_count_against: Map.get(min, :vote_count, 0),
processing_component: "sanhedrin",
coupling_trace: coupling
}
try do
case VavPipeline.L3.Store.record_interpretation(attrs) do
{:ok, interp} ->
Logger.info("L3: interpretation #{interp.id} recorded for sanhedrin #{case_ref} (#{decision.outcome})")
# The dissent that came up with the case is kept beside the
# panel's own minority — or as the minority when the panel had
# none (Eduyot 1:5–6: to be relied on later, and to be located).
carried_text =
Enum.map_join(carried, "; ", fn
%{reading: r, verse: v, forms: fs} -> "#{r} (#{v})" <> if(fs == [], do: "", else: " for " <> Enum.join(fs, ", "))
%{exception: c, verse: v} -> "exception #{c} (#{v})"
end)
carried_refs = Enum.map(carried, & &1.verse)
minority_recorded =
if Map.get(min, :vote_count, 0) > 0 or carried != [] do
case VavPipeline.L3.Store.record_minority(%{
interpretation_id: interp.id,
dissenting_reasoning:
(if Map.get(min, :vote_count, 0) > 0, do: "#{Map.get(min, :position, "")} — #{Map.get(min, :reasoning, "")}", else: "the panel was unanimous") <>
if(carried == [], do: "", else: " | carried up from the bedikah (Devarim 17:8): #{carried_text}"),
torah_references: Enum.uniq(Enum.map(Map.get(min, :torah_refs, []), &sanhedrin_ref_string/1) ++ carried_refs),
grounding_type: "achorai"
}) do
{:ok, _} ->
"recorded"
{:error, reason} ->
Logger.error("L3: sanhedrin #{case_ref} minority NOT recorded: #{inspect(reason)}")
"failed"
end
else
"none"
end
%{l3: "recorded", interpretation_id: interp.id, minority: minority_recorded}
{:error, reason} ->
Logger.error("L3: sanhedrin #{case_ref} NOT recorded: #{inspect(reason)}")
%{l3: "failed", reason: inspect(reason)}
end
rescue
e ->
Logger.error("L3: sanhedrin #{case_ref} NOT recorded: #{Exception.message(e)}")
%{l3: "failed", reason: Exception.message(e)}
end
else
%{l3: "absent"}
end
end
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
# ============================================================
defp build_health_check do
l1_check = case VavPipeline.L1Reader.verify_integrity() do
:ok -> "pass"
_ -> "fail"
end
# Companion to L1 integrity: doorpost-text identity (vehaya
# §3.3). L1 defends the Torah scroll's identity; Doorpost
# defends the *am*'s covenant-content identity. Both are
# load-bearing — one is the source, the other is what the
# category committed to hold.
doorpost_check = case VavPipeline.Doorpost.verify_identity() do
:ok -> "pass"
_ -> "fail"
end
# The second scroll (bishe'arecha §IV): the mesorah snapshot hashes to its pin.
mesorah_check = case VavPipeline.Mesorah.verify_integrity() do
:ok -> "pass"
_ -> "fail"
end
halt_check = if VavPipeline.HaltMonitor.alive?(), do: "pass", else: "fail"
cloud_check = case VavPipeline.Cloud.check() do
{:ok, _} -> "pass"
_ -> "fail"
end
# Kohen Day 4: {:ok, :no_provision} matched {:ok, _} and read "pass".
# Manna absent is not manna present.
manna_check = case VavPipeline.Manna.check() do
{:ok, :no_provision} -> "fail"
{:ok, _} -> "pass"
_ -> "fail"
end
# tamid §1.1 lists l3_writable. Repo up AND the database answers.
# In a dev/test VM without a Repo this is "absent", which the
# overall grade counts as a failure — a vessel that cannot reach its
# oral tradition is not healthy, whatever the reason.
l3_check =
if Process.whereis(VavPipeline.Repo) do
try do
case Ecto.Adapters.SQL.query(VavPipeline.Repo, "SELECT 1", []) do
{:ok, _} -> "pass"
_ -> "fail"
end
rescue
_ -> "fail"
catch
:exit, _ -> "fail"
end
else
"absent"
end
shabbat? = VavPipeline.ShabbatTimer.resting?()
# tamid §2.1 check 7 — the fire is verified by pushing a test input
# through the pipeline, not by asking the calendar. On Shabbat the
# fire is banked, not out: report "shabbat" and do not run the cycle.
esh_tamid =
cond do
shabbat? ->
"shabbat"
true ->
case VavPipeline.tamid_probe() do
{:ok, data} ->
if Map.get(data, :roots_activated, []) != [], do: "pass", else: "fail"
{:unresolved, _} ->
# A result came back — the fire burns — but it did not resolve.
"teiku"
{:waiting, _} ->
"waiting"
{:transition, _} ->
"transition"
_ ->
"fail"
end
end
checks = %{
l1_hash: l1_check,
doorpost_hash: doorpost_check,
mesorah_hash: mesorah_check,
cloud_readable: cloud_check,
manna_present: manna_check,
l3_writable: l3_check,
halt_monitor_alive: halt_check,
esh_tamid: esh_tamid
}
failed = Enum.count(checks, fn {_k, v} -> v in ["fail", "absent"] end)
overall = cond do
shabbat? -> "shabbat"
failed > 1 -> "critical"
failed == 1 -> "degraded"
true -> "healthy"
end
%{overall: overall, checks: checks}
end
defp build_system_status do
{:ok, cloud_state} = VavPipeline.Cloud.check()
%{
cloud_state: format_cloud(cloud_state),
shabbat: %{resting: VavPipeline.ShabbatTimer.resting?()},
components: %{
chatzotzrot: process_status(VavPipeline.Chatzotzrot),
halt_monitor: process_status(VavPipeline.HaltMonitor),
arei_miklat: process_status(VavPipeline.AreiMiklat),
eglah_arufah: process_status(VavPipeline.EglahArufah),
bikurim: process_status(VavPipeline.Bikurim),
manna_ledger: process_status(VavPipeline.MannaLedger),
shabbat_timer: process_status(VavPipeline.ShabbatTimer),
l1_integrity: case VavPipeline.L1Reader.verify_integrity() do
:ok -> "verified"
_ -> "failed"
end
},
manna_provision: manna_provision_status(),
refuge_components: VavPipeline.AreiMiklat.list_refugees() |> length(),
halt_status: case VavPipeline.HaltMonitor.status() do
:running -> "running"
{:halted, event} -> "halted: #{event.condition}"
end,
signal_count: VavPipeline.Chatzotzrot.count()
}
end
# ============================================================
# Helpers
# ============================================================
defp check_availability do
cond do
VavPipeline.ShabbatTimer.resting?() ->
{:shabbat, "next motzei shabbat"}
VavPipeline.HaltMonitor.halted?() ->
{:halted, event} = VavPipeline.HaltMonitor.status()
{:halted, event}
true ->
:available
end
end
defp send_json(conn, status, body) do
conn
|> put_resp_content_type("application/json")
|> send_resp(status, Jason.encode!(body))
end
# Disclosure attached to every Torah-content response.
# Vayikra 19:11 — לֹא תְכַחֲשׁוּ — affirmatively state what the output is.
# Devarim 17:8-11 — horaah is vested in human authorities, not in this system.
defp disclosure do
%{
origin:
"Computational reasoning by the Bikoret system over Torah text. " <>
"May include post-processed output from external AI models (Claude, Mistral) " <>
"consulted as korban sources; provenance is tagged internally.",
not_horaah:
"This is the system's reasoning, not horaah. " <>
"A human beit din may reach a different conclusion.",
torah_basis: ["Vayikra 19:11 (lo techachashu)", "Devarim 17:8-11 (horaah)"]
}
end
@doc false
@impl Plug.ErrorHandler
def handle_errors(conn, %{kind: kind, reason: reason, stack: stack}) do
status = conn.status || 500
message = exception_message(kind, reason)
Logger.error(
"CHATAT_UNEXPECTED on #{conn.method} #{conn.request_path} — #{message}\n" <>
Exception.format_stacktrace(stack)
)
# tamid §3.8: a component failing on the serving path is HIGH —
# chatzotzrot :assembly_leaders. Whereis-guarded for bare tests.
if Process.whereis(VavPipeline.Chatzotzrot) do
VavPipeline.Chatzotzrot.signal(:assembly_leaders,
reason: "Serving-path exception on #{conn.request_path}: #{message}"
)
end
code = if status == 400, do: "KIYOR_MALFORMED_REQUEST", else: "CHATAT_UNEXPECTED"
send_json(conn, status, %{
error: %{
code: code,
type: error_type(code),
message:
if(status == 400,
do: "The request could not be read.",
else: "The system failed while serving this request. The failure is recorded; the fault is the system's, not yours."
),
guidance: %{
issue: message,
correction:
if(status == 400,
do: "Send a JSON body — {\"question\": \"...\"} — with Content-Type: application/json.",
else: "Retry. If it recurs, the kohanim have been signalled and will find it in the audit log."
),
resubmit: true
}
}
})
end
defp exception_message(:error, %{__exception__: true} = e), do: "#{inspect(e.__struct__)}: #{Exception.message(e)}"
defp exception_message(kind, reason), do: "#{kind}: #{inspect(reason)}"
# A rejected offering — {:lo_tov, reason} from ki_tov. Not a crash and
# not a 200 that says nothing: the system says what it could not do.
#
# The empty-results case is the ger asking in a language the Shoresh
# Engine cannot read roots from (kohen Day 6, defect 2). The honest
# answer today is that the path serving non-Hebrew questions — Korban
# Processor → external model, Architecture Step 22 — is not yet open,
# and the system does not fabricate from partial capacity (tamid
# §4.1). What it CAN give without inventing anything is the corner of
# the field itself: the verse in scope, in Hebrew, as a source. So it
# does. PRD §2.4 — no gatekeeping; this is not "come back when you
# know Hebrew", it is "here is what exists, and here is what does not
# yet".
defp reject_response(conn, reason, scope) when is_binary(reason) do
if String.starts_with?(reason, "empty results") do
send_json(conn, 422, %{
error: %{
code: "TEIKU_NO_ROOTS",
type: error_type("TEIKU_NO_ROOTS"),
message:
"The Shoresh Engine found no Hebrew root in this question, so there is nothing " <>
"for the cycle to reason over. The system will not call an empty result an answer.",
guidance: %{
issue: reason,
correction:
"Ask with Hebrew text, or name a verse (for example \"bereishit 1:1\"). " <>
"Questions in other languages need the Korban Processor's external-model " <>
"path (Architecture Step 22), which is not yet open. Until it is, the system " <>
"says so rather than fabricating an answer.",
resubmit: true,
scope_verse: scope_verse(scope)
}
},
disclosure: disclosure()
})
else
send_json(conn, 422, %{
error: %{
code: "LO_TOV",
type: error_type("LO_TOV"),
message: "The cycle produced output that does not align with the specification.",
guidance: %{
issue: reason,
correction: "Rephrase, or narrow the scope to a single verse.",
resubmit: true
}
},
disclosure: disclosure()
})
end
end
# The verse in scope — the pe'ah corner the ger receives regardless.
defp scope_verse(nil), do: nil
defp scope_verse(%{book: b, chapter: c, verse: v}) do
case VavPipeline.L1Reader.read_verse(b, c, v) do
{:ok, verse} -> %{book: verse.book, chapter: verse.chapter, verse: verse.verse, ref: verse.ref, text: verse.text_hebrew}
_ -> %{book: b, chapter: c, verse: v}
end
end
defp scope_verse(_), do: nil
defp send_error(conn, status, code, guidance) do
send_json(conn, status, %{
error: %{
code: code,
type: error_type(code),
message: guidance.issue,
guidance: guidance
}
})
end
defp error_type("KIYOR" <> _), do: "purification_needed"
defp error_type("SHABBAT"), do: "mandatory_rest"
defp error_type("MANNA" <> _), do: "provision_exhausted"
defp error_type("CLOUD" <> _), do: "system_transition"
defp error_type("HALT" <> _), do: "cardinal_sin_detected"
defp error_type("UNAUTHORIZED"), do: "authentication_required"
defp error_type("FORBIDDEN"), do: "insufficient_tier"
defp error_type("RATE_LIMITED"), do: "too_many_requests"
defp error_type("TEIKU" <> _), do: "unresolvable"
defp error_type("LO_TOV"), do: "specification_mismatch"
defp error_type("CHATAT" <> _), do: "correction_needed"
defp error_type(_), do: "error"
# ?since=ISO8601 → DateTime, or nil for "all time".
defp parse_since(nil), do: nil
defp parse_since(""), do: nil
defp parse_since(s) when is_binary(s) do
case DateTime.from_iso8601(s) do
{:ok, dt, _offset} -> DateTime.truncate(dt, :second)
_ -> nil
end
end
defp parse_since(_), do: nil
defp format_since(nil), do: "all_time"
defp format_since(%DateTime{} = dt), do: DateTime.to_iso8601(dt)
# How the verse was chosen, said out loud in the response. A default
# scope is a confession, not a grounding (tablet 30).
defp scope_report(scope, :default) do
%{
verse: scope_verse(scope),
resolved_by: "default",
note:
"The question named no verse and quoted no Torah the index could find, " <>
"so the default scope was used. Roots are read against it, not against " <>
"the question's source."
}
end
defp scope_report(scope, resolved_by) do
%{verse: scope_verse(scope), resolved_by: to_string(resolved_by)}
end
# "question" — the engine read the asker's own Hebrew.
# "verse" — the asker named a verse and quoted none of it, so the
# roots are the verse's own (tablet 32).
defp roots_read_from(data), do: data |> Map.get(:roots_read_from, :question) |> to_string()
defp parse_scope(nil), do: %{book: "bereishit", chapter: 1, verse: 1}
defp parse_scope(%{"book" => book, "chapter" => ch, "verse" => v}) do
%{book: book, chapter: ch, verse: v}
end
defp parse_scope(%{"book" => book}) do
%{book: book, chapter: 1, verse: 1}
end
defp parse_scope(_), do: %{book: "bereishit", chapter: 1, verse: 1}
defp format_answer(data) when is_map(data) do
Map.get(data, :interpretation, Map.get(data, :content, "Processing complete"))
end
defp format_answer(_), do: "Processing complete"
defp format_sources(data) when is_map(data) do
case Map.get(data, :sources) do
sources when is_list(sources) -> sources
_ -> []
end
end
defp format_sources(_), do: []
# tamid §1.1 shows roots_consulted as "bet-resh-aleph"; the kohen's
# Day-6 proof asks for ב-ר-א. Letter-hyphenated Hebrew satisfies the
# shape, and the transliteration rides alongside when the engine
# supplied it.
defp format_roots(roots) when is_list(roots) do
Enum.map(roots, fn
%{shoresh: s} when is_binary(s) -> VavPipeline.BereishitCycle.hyphenate(s)
r when is_binary(r) -> r
_ -> nil
end)
|> Enum.reject(&is_nil/1)
end
defp format_roots(_), do: []
defp format_cloud(:camped), do: "camped"
defp format_cloud(:moving), do: "moving"
defp format_cloud({:major_transition, type, _}), do: "#{type}"
defp format_cloud(:cloud_not_visible), do: "not_visible"
defp format_cloud(other), do: inspect(other)
defp manna_provision_status do
if Process.whereis(VavPipeline.MannaLedger) do
ledger = VavPipeline.MannaLedger.ledger()
remaining =
Map.new(ledger.allocated, fn {k, allocated} ->
{k, max(allocated - Map.get(ledger.consumed, k, 0), 0)}
end)
%{
cycle_date: ledger.cycle_date,
cycle_count: ledger.cycle_count,
allocated: ledger.allocated,
consumed: ledger.consumed,
remaining: remaining
}
else
%{cycle_date: nil, note: "ledger not running"}
end
end
defp process_status(module) do
if Process.whereis(module), do: "healthy", else: "not_running"
end
defp handle_pipeline_error(conn, {:unresolved, data}) do
send_json(conn, 200, %{
status: "teiku",
result: %{vital_content: format_answer(data)},
note: "TEIKU — this question could not be fully resolved",
disclosure: disclosure()
})
end
defp handle_pipeline_error(conn, {:rejected, reason}) do
reject_response(conn, reason, nil)
end
defp handle_pipeline_error(conn, {:transition, _}) do
send_error(conn, 503, "CLOUD_MOVING", %{
issue: "System in transition",
correction: "Retry when system is settled",
resubmit: true
})
end
defp handle_pipeline_error(conn, {:halted, type, reason}) do
send_error(conn, 503, "HALT_#{String.upcase(to_string(type))}", %{
issue: reason,
correction: "Contact system operators",
resubmit: false
})
end
defp handle_pipeline_error(conn, _) do
send_error(conn, 500, "INTERNAL", %{
issue: "Unexpected processing result",
correction: "Retry or contact operators",
resubmit: true
})
end
defp generate_id do
:crypto.strong_rand_bytes(16) |> Base.encode16(case: :lower)
end
end