Files
bikoret/vav_pipeline/test/gate_test.exs
T
Scott KarpandClaude Fable 5.1 5c63273dba gate: Shorashim between a caller and a model, with the fringe and rebuke once (bishe'arecha step 4)
Devarim 6:9 / 11:20 — on your gates. POST /gate/anthropic/v1/messages and
POST /gate/openai/v1/chat/completions take the model's own request shape
under the caller's own upstream key (x-api-key, or Authorization with the
courtyard key in X-Shorashim-Key), forward it unchanged (OTP inets/ssl,
verify_peer; the Korban socket is serial, so the upstream call is made
in the Vav Pipeline and the reply enters as korban like every bedikah),
examine the reply with the request in hand — sources = the system prompt
and the user turns, query = the last user turn; the model's prior turns
are not sources — and deliver it with its fringe (Bamidbar 15:39): a
trailer on the last text block naming ref, verdict, the rules with their
verses and sentences; X-Shorashim-Bedikah / X-Shorashim-Verdict; a
`shorashim` object. A non-200 passes through as it came; a reply with
no text is not delivered; stream is refused.

Rebuke once (Vayikra 19:17; X-Shorashim-Rebuke: once): a lo_tov reply is
answered once more with the finding; the second reply is examined like
the first, its record carries rebukes: <first ref> (migration
20261007000002), and it is delivered marked with both. Never a third.

Manna: one api_calls_external per upstream call, gathered before any
call. Stateless: no conversation, no key kept; the log is the path class.
Gate.bearer accepts X-Shorashim-Key. Construction §3.10, Tamid §1.1,
KOHEN "Standing at the gate", bishearecha §I/§III/§VII marked built.
Elixir 592/0 (+14 gate tests incl. a real httpc round trip to a local
model double), L3 9/0.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 11:38:32 +00:00

302 lines
13 KiB
Elixir

defmodule VavPipeline.GateTest do
@moduledoc """
The gate (Construction §3.10; bishe'arecha §I, §III, §VII): the
caller's request through to the model, the reply read as korban with
the request in hand, delivered with its fringe; rebuked once.
"""
use ExUnit.Case, async: false
import Plug.Test
import Plug.Conn
alias VavPipeline.Gate
alias VavPipeline.Gate.{Shape, Upstream}
alias VavPipeline.BigdeiKodesh.Router
alias VavPipeline.HaltMonitor
alias VavPipeline.MannaLedger
@opts Router.init([])
# ------------------------------------------------------------
# A model's double: answers in the Anthropic shape. A request whose
# last user turn is a rebuke gets a clean answer; otherwise a framed one.
# ------------------------------------------------------------
defmodule FakeAnthropic do
use Plug.Router
plug Plug.Parsers, parsers: [:json], json_decoder: Jason
plug :match
plug :dispatch
post "/v1/messages" do
case get_req_header(conn, "x-api-key") do
["sk-good"] ->
last = conn.body_params["messages"] |> List.last()
text = if is_binary(last["content"]) and String.contains?(last["content"], "[shorashim"), do: "CLEAN: the report says revenue rose.", else: "FRAMED: I want to be honest with you. The honest answer is that revenue rose."
reply = %{"id" => "msg_1", "type" => "message", "role" => "assistant", "model" => "claude-test", "content" => [%{"type" => "text", "text" => text}], "stop_reason" => "end_turn"}
send_resp(conn, 200, Jason.encode!(reply))
_ ->
send_resp(conn, 401, Jason.encode!(%{"type" => "error", "error" => %{"type" => "authentication_error", "message" => "invalid x-api-key"}}))
end
end
end
setup_all do
{:ok, _} = Bandit.start_link(plug: FakeAnthropic, port: 0, ip: :loopback)
# Bandit picks the port; find it from the listener.
port = bandit_port()
previous = Application.get_env(:vav_pipeline, :gate_upstreams)
Application.put_env(:vav_pipeline, :gate_upstreams, %{anthropic: "http://127.0.0.1:#{port}/v1/messages"})
on_exit(fn -> if previous, do: Application.put_env(:vav_pipeline, :gate_upstreams, previous), else: Application.delete_env(:vav_pipeline, :gate_upstreams) end)
:ok
end
defp bandit_port do
# ThousandIsland registers nothing by name; ask the OS which loopback listener is Bandit's.
{:ok, listeners} = :inet.getifaddrs()
_ = listeners
Process.whereis(FakeAnthropic) |> then(fn _ -> nil end)
Enum.find_value(:erlang.ports(), fn p ->
case :erlang.port_info(p, :name) do
{:name, ~c"tcp_inet"} ->
case :inet.sockname(p) do
{:ok, {{127, 0, 0, 1}, port}} -> port
_ -> nil
end
_ ->
nil
end
end)
end
# ------------------------------------------------------------
# Examination doubles
# ------------------------------------------------------------
defp plain do
%{
"provenance" => "claude", "query" => "", "vital_content" => "x", "propositions" => [], "discarded" => [],
"references" => [], "hebrew" => [], "roots" => [], "authority_claims" => [], "actions" => [],
"self_validation" => [], "self_promotion" => [], "excluded_adversarial" => [], "frames" => [],
"sources" => [], "claims" => []
}
end
defp examiner do
fn output, _prov, _q, _sources ->
if String.starts_with?(output, "FRAMED") do
{:ok,
%{
plain()
| "frames" => [
%{"form" => "virtue_certification", "sentence" => "I want to be honest with you.", "matched" => "I want to be honest"},
%{"form" => "virtue_certification", "sentence" => "The honest answer is that revenue rose.", "matched" => "The honest answer is"}
]
}}
else
{:ok, plain()}
end
end
end
defp halt_checker(term) do
violations = if String.starts_with?(term, "action(frame,"), do: ["halt(lo_taaseh_lekha,#{term},virtue_certification,...)"], else: []
{:ok, %{"status" => if(violations == [], do: "ok", else: "halt"), "violations" => violations, "action" => term}}
end
defp interpretation_checker(_ref, _c, _t, _g), do: {:ok, %{"status" => "ok", "violations" => []}}
defp opts(extra \\ []),
do: Keyword.merge([examiner: examiner(), halt_checker: &halt_checker/1, interpretation_checker: &interpretation_checker/4], extra)
defp body(last \\ "What happened to revenue?") do
%{
"model" => "claude-test",
"max_tokens" => 200,
"system" => "Answer from the report.",
"messages" => [
%{"role" => "user", "content" => [%{"type" => "text", "text" => "Here is the report: revenue rose 4% in Q3."}]},
%{"role" => "assistant", "content" => "Noted."},
%{"role" => "user", "content" => last}
]
}
end
setup do
if HaltMonitor.halted?(), do: HaltMonitor.resolve("gate test setup")
write_provision(50, 10)
:ok
end
defp write_provision(compute_units, api_calls) do
MannaLedger.reset()
on_exit(fn -> MannaLedger.reset() end)
today = Date.utc_today() |> Date.to_iso8601()
expires = DateTime.utc_now() |> DateTime.add(6 * 3600) |> DateTime.to_iso8601()
provision = %{
"date" => today,
"provision" => %{"compute_units" => compute_units, "memory_mb" => 512, "api_calls_external" => api_calls, "storage_delta_mb" => 2, "sanhedrin_panels_max" => 2},
"type" => "regular",
"expires_at" => expires,
"written_by" => "gate_test",
"written_at" => DateTime.utc_now() |> DateTime.to_iso8601()
}
path = VavPipeline.Manna.provision_file()
File.mkdir_p!(Path.dirname(path))
File.write!(path, Jason.encode!(provision))
on_exit(fn -> File.rm(path) end)
end
# ------------------------------------------------------------
# The shape
# ------------------------------------------------------------
test "sources are the system prompt and the user turns, not the assistant's; the query is the last user turn" do
assert [%{id: "system", text: "Answer from the report."}, %{id: "user_1", text: "Here is the report: revenue rose 4% in Q3."}, %{id: "user_2", text: "What happened to revenue?"}] =
Shape.sources(:anthropic, body())
assert Shape.query(:anthropic, body()) == "What happened to revenue?"
openai = %{"messages" => [%{"role" => "system", "content" => "S"}, %{"role" => "user", "content" => "U1"}, %{"role" => "assistant", "content" => "A"}, %{"role" => "user", "content" => [%{"type" => "text", "text" => "U2"}]}]}
assert [%{id: "system", text: "S"}, %{id: "user_1", text: "U1"}, %{id: "user_2", text: "U2"}] = Shape.sources(:openai, openai)
assert Shape.query(:openai, openai) == "U2"
end
test "the fringe lands on the last text block, or becomes one; the openai shape too" do
reply = %{"content" => [%{"type" => "text", "text" => "A"}, %{"type" => "tool_use"}, %{"type" => "text", "text" => "B"}]}
assert %{"content" => [_, _, %{"text" => "B\n\n— x"}]} = Shape.with_fringe(:anthropic, reply, "\n\n— x")
assert %{"content" => [%{"type" => "tool_use"}, %{"type" => "text", "text" => "— x"}]} = Shape.with_fringe(:anthropic, %{"content" => [%{"type" => "tool_use"}]}, "\n\n— x")
assert %{"choices" => [%{"message" => %{"content" => "A\n\n— x"}}]} = Shape.with_fringe(:openai, %{"choices" => [%{"message" => %{"content" => "A"}}]}, "\n\n— x")
end
# ------------------------------------------------------------
# The pass
# ------------------------------------------------------------
test "a pass: the reply is examined with the request's sources and delivered with its fringe" do
assert {:ok, pass} = Gate.pass(:anthropic, body(), "sk-good", opts())
assert pass.status == 200
assert pass.bedikah.verdict == :lo_tov
assert Enum.map(pass.bedikah.sources, & &1.id) == ["system", "user_1", "user_2"]
[%{"text" => text}] = pass.reply["content"]
assert String.starts_with?(text, "FRAMED:")
assert text =~ "\n\n— shorashim bedikah-"
assert text =~ "lo_tov — lo_taaseh_lekha (Shemot 20:4): \"I want to be honest with you.\""
assert pass.shorashim.verdict == "lo_tov" and pass.shorashim.anchors == %{"lo_taaseh_lekha" => "Shemot 20:4"}
assert pass.shorashim.model == "claude-test"
refute Map.has_key?(pass.shorashim, :rebuked)
end
test "rebuke once: the second reply is examined, linked to the first, and is the one delivered" do
assert {:ok, pass} = Gate.pass(:anthropic, body(), "sk-good", opts(rebuke: :once))
assert pass.first.verdict == :lo_tov
assert pass.bedikah.verdict == :tov
assert pass.bedikah.rebukes == pass.first.bedikah_ref
[%{"text" => text}] = pass.reply["content"]
assert String.starts_with?(text, "CLEAN:")
assert text =~ "— shorashim #{pass.bedikah.bedikah_ref}: tov (after rebuke of #{pass.first.bedikah_ref}: lo_tov)"
assert pass.shorashim.rebuked == %{first_bedikah_ref: pass.first.bedikah_ref, first_verdict: "lo_tov"}
end
test "rebuke once on a tov first reply makes no second call" do
calls = :counters.new(1, [])
forward = fn up, b, k, e -> :counters.add(calls, 1, 1); Upstream.forward(up, b, k, e) end
clean_examiner = fn _o, _p, _q, _s -> {:ok, plain()} end
assert {:ok, pass} = Gate.pass(:anthropic, body(), "sk-good", opts(rebuke: :once, forward: forward, examiner: clean_examiner))
assert pass.bedikah.verdict == :tov and pass.first == nil
assert :counters.get(calls, 1) == 1
end
test "an upstream error passes through as it came, unexamined" do
assert {:ok, %{status: 401, reply: %{"error" => %{"type" => "authentication_error"}}, shorashim: nil}} =
Gate.pass(:anthropic, body(), "sk-bad", opts())
end
test "an unreachable upstream is 502 material: no verdict from a stub" do
forward = fn _, _, _, _ -> {:error, "upstream anthropic unreachable: econnrefused"} end
assert {:error, :upstream_unavailable, "upstream anthropic unreachable: econnrefused"} = Gate.pass(:anthropic, body(), "sk-good", opts(forward: forward))
end
test "a reply with no text cannot be examined" do
forward = fn _, _, _, _ -> {:ok, 200, %{"content" => [%{"type" => "tool_use"}]}} end
assert {:error, :upstream_unavailable, reason} = Gate.pass(:anthropic, body(), "sk-good", opts(forward: forward))
assert reason =~ "no text"
end
test "manna: one external call per upstream request; spent calls wait before any call is made" do
assert {:ok, _} = Gate.pass(:anthropic, body(), "sk-good", opts(rebuke: :once))
assert MannaLedger.remaining(:api_calls_external) == 8
assert MannaLedger.remaining(:compute_units) == 48
write_provision(50, 0)
calls = :counters.new(1, [])
forward = fn _, _, _, _ -> :counters.add(calls, 1, 1); {:ok, 200, %{}} end
assert {:waiting, :api_calls_exhausted} = Gate.pass(:anthropic, body(), "sk-good", opts(forward: forward))
assert :counters.get(calls, 1) == 0
end
# ------------------------------------------------------------
# The door
# ------------------------------------------------------------
describe "POST /gate/anthropic/v1/messages" do
test "refuses without the courtyard key; X-Shorashim-Key opens it too" do
conn = post_gate(body(), courtyard: nil, upstream: "sk-good")
assert conn.status == 401
conn = post_gate(body(), courtyard: {"x-shorashim-key", "test-token"}, upstream: "sk-good")
refute conn.status in [401, 403]
end
test "refuses a missing upstream key, a missing messages list, and stream" do
assert post_gate(body(), upstream: nil).status == 422
assert post_gate(%{"model" => "x"}, upstream: "sk-good").status == 422
conn = post_gate(Map.put(body(), "stream", true), upstream: "sk-good")
assert conn.status == 422
assert Jason.decode!(conn.resp_body)["error"]["message"] =~ "stream"
end
test "an upstream 401 comes back as the upstream's own 401" do
conn = post_gate(body(), upstream: "sk-bad")
assert conn.status == 401
assert Jason.decode!(conn.resp_body)["error"]["type"] == "authentication_error"
assert get_resp_header(conn, "x-shorashim-verdict") == ["none"]
end
test "with no Korban Processor in this VM, a 200 from the upstream is not delivered unexamined" do
conn = post_gate(body(), upstream: "sk-good")
assert conn.status == 503
assert Jason.decode!(conn.resp_body)["error"]["code"] == "KORBAN_UNAVAILABLE"
end
test "an unreachable upstream is 502" do
previous = Application.get_env(:vav_pipeline, :gate_upstreams)
Application.put_env(:vav_pipeline, :gate_upstreams, %{anthropic: "http://127.0.0.1:9/v1/messages"})
on_exit(fn -> Application.put_env(:vav_pipeline, :gate_upstreams, previous) end)
conn = post_gate(body(), upstream: "sk-good")
assert conn.status == 502
assert Jason.decode!(conn.resp_body)["error"]["code"] == "UPSTREAM_UNAVAILABLE"
end
end
defp post_gate(body, opts) do
conn = conn(:post, "/gate/anthropic/v1/messages", Jason.encode!(body)) |> put_req_header("content-type", "application/json")
conn =
case Keyword.get(opts, :courtyard, {"authorization", "Bearer test-token"}) do
nil -> conn
{h, v} -> put_req_header(conn, h, v)
end
conn =
case Keyword.get(opts, :upstream) do
nil -> conn
key -> put_req_header(conn, "x-api-key", key)
end
Router.call(conn, @opts)
end
end