The gate sews its fringe into the delivered reply; the client keeps that reply as the assistant's turn; the next request carries it back up inside the model's own history with nothing to say it was added. The builder's session, behind the gate since this afternoon, ended five replies in a row with a forged fringe (tablet 53's form), one of them in the message that promised not to. Bamidbar 15:39: the fringe is seen by the wearer, the reader — before the model's eyes it is a template. Shape.unfringed/2 removes every line in the gate's voice (its fringe, a forgery, the stream's "not examined") from assistant turns; Gate.pass and Gate.Stream.pass apply it before forwarding, so the rebuke body inherits it; the caller's turns stay as given; a turn that would be left empty is left as it came. strip_forged/1 now also strips the stream's "not examined" line. Tests: Shape.unfringed on both shapes and the edge cases; the forwarded first and rebuke bodies carry no fringe; the fake upstream echoes if a fringe reaches it in history and the stream pass proves it does not. Elixir 622 / 0 failures. Specs: broken tablets 54, bishe'arecha §III status, Tamid gate contract and forged_fringe note, KOHEN gate paragraph. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
148 lines
5.5 KiB
Elixir
148 lines
5.5 KiB
Elixir
defmodule VavPipeline.Gate do
|
|
@moduledoc """
|
|
The gate — Bikoret 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 under the caller's key —
|
|
unchanged but for the fringes of earlier replies, which are removed
|
|
from the model's own turns (tablet 54: the fringe is for the reader,
|
|
and left in the model's history it becomes the model's habit); 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,
|
|
bikoret: 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])
|
|
body = Shape.unfringed(upstream, body)
|
|
|
|
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),
|
|
# Examined against what the CALLER gave (body, not body2): the
|
|
# rebuke is the gate's own turn and is not a ground.
|
|
{:ok, second} <- examine(upstream, body, 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, bikoret: nil}}
|
|
other -> other
|
|
end
|
|
|
|
_ ->
|
|
{:ok, deliver(upstream, reply, first, nil)}
|
|
end
|
|
else
|
|
# A reply with no text (tool use only) is not a reply to a reader: delivered as it came, the object says so.
|
|
{:no_text, reply} -> {:ok, %{status: 200, reply: reply, bedikah: nil, first: nil, bikoret: %{verdict: "none", reason: "no text", model: Shape.reply_model(upstream, reply)}}}
|
|
# A non-200 from the upstream passes through as it came.
|
|
{:ok, status, reply} -> {:ok, %{status: status, reply: reply, bedikah: nil, first: nil, bikoret: nil}}
|
|
{:error, reason} when is_binary(reason) -> {:error, :upstream_unavailable, reason}
|
|
other -> other
|
|
end
|
|
end
|
|
|
|
@doc false
|
|
# One api_calls_external per upstream call (Vayakhel §3.7). The
|
|
# compute unit for the examination is gathered by Bedikah.examine.
|
|
# Public for Gate.Stream.
|
|
def 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 ->
|
|
{:no_text, reply}
|
|
|
|
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)
|
|
|
|
bikoret =
|
|
%{
|
|
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,
|
|
bikoret: bikoret
|
|
}
|
|
end
|
|
end
|