Files
bikoret/vav_pipeline/lib/vav_pipeline/gate.ex
T
Scott KarpandClaude Fable 5.1 dc6316f1d7 sugya, step 3: the case behind each threshold is carried on every verdict
specs/sugya.md §IV "carried on the verdict", §V step 3. Sugya.for_verdict/1
renders, for every rule that fired and every form that was noted: kind,
verse, precedent, the dissents and exceptions — each with `applied`,
read from the record and never guessed: the single witness noted (and
for which forms), the hedged claims left out of the trace, grounding
not assessed for want of ground, a forged fringe standing on one
witness. A dissent the record cannot speak to is carried applied:
false — considered, and the rule's verse decided. The minority is on
every record (Eduyot 1:5–6): to be relied on later, and to be located.

Carried as `sugya` on the bedikah response, the pair's two sides, and
the gate's `bikoret` object (pass and stream). The fringe is unchanged
— it is for the reader, who asks for the sugya by reading the record.
Tamid §1.1: the `sugya` field in the response and the paragraph "Every
rule carries its sugya"; Doorpost pin → 658005c9…. Elixir 664/0
(sugya_verdict_test: clean → {}; noted apology → two-witness dissent
applied for that form; forged fringe → the mark exception applied;
golden calf with and without a hedged claim; a rule the record cannot
speak to → considered, applied false).

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

149 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),
sugya: VavPipeline.Sugya.for_verdict(result),
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