Shemot 28:2 — V'asita vigdei kodesh l'Aharon achicha, l'khavod ul'tifaret. The builder makes the garments. The kohen wears them. Four access tiers (Operations spec §1.1): - Pe'ah (GET/POST /peah/ask) — public, no auth, no tracking. The stranger is safe to approach. - Courtyard (POST /courtyard/offering) — Bearer token auth, offering types: shelamim, olah, chatat, minchah - Chatzer (POST /chatzer/sanhedrin) — Sanhedrin deliberation, returns majority/minority with preserved dissent - Kodesh (GET /kodesh/health, /kodesh/status) — operator interface, health checks for K8s liveness/readiness probes Error format follows chatat protocol (Operations spec §1.2): KIYOR_* (422), SHABBAT (503), MANNA_DEPLETED (503), CLOUD_MOVING (503), HALT_* (503), UNAUTHORIZED (401) Implementation: - Plug.Router with Bandit HTTP server - Added to supervision tree via maybe_add_http_server - Checks system availability (Shabbat, halt) before processing - Health check reads L1 integrity, cloud, manna, halt monitor - Status shows all component states, signal count, refuge list - 16 API tests covering all four tiers + error format All 235 Elixir + 19 L3 + 195 Prolog + 33 Haskell = 463+ tests, 0 failures Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
501 lines
14 KiB
Elixir
501 lines
14 KiB
Elixir
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
|
|
|
|
plug Plug.Logger, log: :info
|
|
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: "Shorashim — 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
|
|
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
|
|
|
|
# === COURTYARD — Authenticated ===
|
|
|
|
post "/courtyard/offering" do
|
|
case get_req_header(conn, "authorization") do
|
|
["Bearer " <> _token] ->
|
|
process_courtyard(conn)
|
|
|
|
_ ->
|
|
send_error(conn, 401, "UNAUTHORIZED", %{
|
|
issue: "Missing or invalid Authorization header",
|
|
correction: "Include 'Authorization: Bearer <api-key>' header",
|
|
resubmit: true
|
|
})
|
|
end
|
|
end
|
|
|
|
# === CHATZER — Full Access (Sanhedrin) ===
|
|
|
|
post "/chatzer/sanhedrin" do
|
|
case get_req_header(conn, "authorization") do
|
|
["Bearer " <> _token] ->
|
|
process_sanhedrin(conn)
|
|
|
|
_ ->
|
|
send_error(conn, 401, "UNAUTHORIZED", %{
|
|
issue: "Missing or invalid Authorization header",
|
|
correction: "Include 'Authorization: Bearer <api-key>' header",
|
|
resubmit: true
|
|
})
|
|
end
|
|
end
|
|
|
|
# === KODESH — Operator Interface ===
|
|
|
|
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/status" do
|
|
status = build_system_status()
|
|
send_json(conn, 200, status)
|
|
end
|
|
|
|
# === CATCH-ALL ===
|
|
|
|
match _ do
|
|
send_error(conn, 404, "NOT_FOUND", %{
|
|
issue: "Unknown endpoint: #{conn.request_path}",
|
|
correction: "Available: /peah/ask, /courtyard/offering, /chatzer/sanhedrin, /kodesh/health",
|
|
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
|
|
scope = extract_scope_from_question(question)
|
|
|
|
input = %{
|
|
source: "peah",
|
|
verse_scope: scope,
|
|
content: question
|
|
}
|
|
|
|
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"
|
|
}
|
|
})
|
|
|
|
{: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"
|
|
},
|
|
note: "This question produced an unresolved result — teiku."
|
|
})
|
|
|
|
{:waiting, reason} ->
|
|
code = if reason == :no_provision, do: "MANNA_DEPLETED", else: "CLOUD_MOVING"
|
|
|
|
send_error(conn, 503, code, %{
|
|
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
|
|
end
|
|
|
|
# ============================================================
|
|
# 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"
|
|
}
|
|
})
|
|
|
|
{: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
|
|
# ============================================================
|
|
|
|
defp process_sanhedrin(conn) do
|
|
params = conn.body_params
|
|
|
|
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
|
|
case_ref = generate_id()
|
|
|
|
{:ok, decision} =
|
|
VavPipeline.SanhedrinClient.submit_case(%{
|
|
case_ref: case_ref,
|
|
complexity: :simple,
|
|
question: case_text,
|
|
context: params["sides"] || %{},
|
|
requested_by: "chatzer"
|
|
})
|
|
|
|
send_json(conn, 200, %{
|
|
case_ref: decision.case_ref,
|
|
deliberation: %{
|
|
panel_size: decision.panel_size,
|
|
majority: decision.majority,
|
|
minority: decision.minority,
|
|
outcome: decision.outcome,
|
|
processing_time_ms: decision.deliberation_time_ms
|
|
},
|
|
note: "This is the system's reasoning, not a binding ruling. A human beit din may reach a different conclusion."
|
|
})
|
|
end
|
|
end
|
|
|
|
# ============================================================
|
|
# Internal — Health and Status
|
|
# ============================================================
|
|
|
|
defp build_health_check do
|
|
l1_check = case VavPipeline.L1Reader.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
|
|
|
|
manna_check = case VavPipeline.Manna.check() do
|
|
{:ok, _} -> "pass"
|
|
_ -> "fail"
|
|
end
|
|
|
|
shabbat_check = if VavPipeline.ShabbatTimer.resting?(), do: "shabbat", else: "pass"
|
|
|
|
checks = %{
|
|
l1_hash: l1_check,
|
|
cloud_readable: cloud_check,
|
|
manna_present: manna_check,
|
|
halt_monitor_alive: halt_check,
|
|
esh_tamid: shabbat_check
|
|
}
|
|
|
|
failed = Enum.count(checks, fn {_k, v} -> v == "fail" end)
|
|
|
|
overall = cond do
|
|
shabbat_check == "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
|
|
},
|
|
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
|
|
|
|
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(_), do: "error"
|
|
|
|
defp extract_scope_from_question(_question) do
|
|
# Default scope for pe'ah — Bereishit 1:1
|
|
# In production, this would parse the question for verse references
|
|
%{book: "bereishit", chapter: 1, verse: 1}
|
|
end
|
|
|
|
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: []
|
|
|
|
defp format_roots(roots) when is_list(roots) do
|
|
Enum.map(roots, fn
|
|
%{shoresh: s} -> 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 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"
|
|
})
|
|
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
|