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>
This commit is contained in:
Scott KarpandClaude Fable 5.1 committed 2026-10-07 11:38:32 +00:00
1 parent 47025fbfeb
commit 5c63273dba
16 files changed
+1082 -15

No files matched your search

+137
View File
@@ -0,0 +1,137 @@
defmodule VavPipeline.Gate do
@moduledoc """
The gate — Shorashim between a caller and a model (Construction §3.10;
bishe'arecha §I "on your gates", §III the fringe, §VII rebuke).
One pass: forward the caller's request unchanged under the caller's
key; read the reply as korban with the request's own context in hand
(sources = what the caller gave the model); deliver the reply with its
fringe. With `rebuke: :once` and a `lo_tov` first reply, one more
request carrying the finding, and the second reply — examined like
the first — is the one delivered, marked with both.
Stateless. No conversation kept, no key kept.
"""
alias VavPipeline.{Anchors, Bedikah}
alias VavPipeline.Gate.{Shape, Upstream}
@type pass :: %{
status: non_neg_integer(),
reply: map(),
bedikah: map() | nil,
first: map() | nil,
shorashim: map() | nil
}
@doc """
Options: `:rebuke` (`:once` | `:none`), `:extra_headers` (forwarded),
`:forward` (fun/4, for tests), and the `Bedikah.examine/4` options.
"""
@spec pass(:anthropic | :openai, map(), String.t(), keyword()) ::
{:ok, pass()} | {:waiting, atom()} | {:error, atom(), String.t()}
def pass(upstream, body, key, opts \\ []) do
forward = Keyword.get(opts, :forward, &Upstream.forward/4)
extra = Keyword.get(opts, :extra_headers, [])
rebuke = Keyword.get(opts, :rebuke, :none)
examine_opts = Keyword.drop(opts, [:forward, :extra_headers, :rebuke])
with :ok <- gather_call(),
{:ok, 200, reply} <- forward.(upstream, body, key, extra),
{:ok, first} <- examine(upstream, body, reply, examine_opts) do
case {rebuke, first.verdict} do
{:once, :lo_tov} ->
anchors = Anchors.for_violations(first.violations)
first_text = Shape.reply_text(upstream, reply)
body2 = Shape.with_rebuke(upstream, body, first_text, Shape.rebuke_text(first, anchors))
with :ok <- gather_call(),
{:ok, 200, reply2} <- forward.(upstream, body2, key, extra),
{:ok, second} <- examine(upstream, body2, reply2, Keyword.put(examine_opts, :rebukes, first.bedikah_ref)) do
{:ok, deliver(upstream, reply2, second, first)}
else
{:ok, status, other} -> {:ok, %{status: status, reply: other, bedikah: nil, first: first, shorashim: nil}}
other -> other
end
_ ->
{:ok, deliver(upstream, reply, first, nil)}
end
else
# A non-200 from the upstream passes through as it came.
{:ok, status, reply} -> {:ok, %{status: status, reply: reply, bedikah: nil, first: nil, shorashim: nil}}
{:error, reason} when is_binary(reason) -> {:error, :upstream_unavailable, reason}
other -> other
end
end
# One api_calls_external per upstream call (Vayakhel §3.7). The
# compute unit for the examination is gathered by Bedikah.examine.
defp gather_call 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(:api_calls_external, 1) do
{:ok, _} -> :ok
{:error, :provision_exhausted} -> {:waiting, :api_calls_exhausted}
{:error, :no_provision} -> {:waiting, :no_provision}
end
end
else
:ok
end
end
defp examine(upstream, body, reply, opts) do
case Shape.reply_text(upstream, reply) do
nil ->
{:error, :upstream_unavailable, "the reply from #{upstream} carries no text to examine"}
text ->
provenance = Shape.provenance(upstream)
query = Shape.query(upstream, body)
sources = Shape.sources(upstream, body)
case Bedikah.examine(text, provenance, query, Keyword.put(opts, :sources, sources)) do
{:ok, result} -> {:ok, result}
other -> other
end
end
end
defp deliver(upstream, reply, result, first) do
anchors = Anchors.for_violations(result.violations)
trailer = Shape.trailer(result, anchors, first)
shorashim =
%{
bedikah_ref: result.bedikah_ref,
verdict: Atom.to_string(result.verdict),
violations: result.violations,
anchors: anchors,
fired: Shape.fired(result, anchors),
checks: result.checks,
sources: result.sources,
model: Shape.reply_model(upstream, reply)
}
|> then(fn m ->
if first,
do: Map.put(m, :rebuked, %{first_bedikah_ref: first.bedikah_ref, first_verdict: Atom.to_string(first.verdict)}),
else: m
end)
%{
status: 200,
reply: Shape.with_fringe(upstream, reply, trailer),
bedikah: result,
first: first,
shorashim: shorashim
}
end
end