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>
149 lines
5.5 KiB
Elixir
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
|