Compare commits

...
Author SHA1 Message Date
Scott KarpandClaude Fable 5.1 cf8b5e3dc4 tablet 56: the gap that was not there — empty says what was read
The builder claimed the structural and frame checks never see an
engineering reply because its sentences are not "vital". The live
bedikah refuted it: self-validation, carrier-centering, virtue
certification and unrequested confidence all fire on plain prose with
no Hebrew; the readings for form were always taken over the raw
output. Nothing built for that; the tablet records it.

What was true: `empty` said "nothing to examine", and that under-
reported. Now the empty record carries checks.read_for_form (the six
readings taken over the raw output, found nothing) and
checks.grounding {assessed: false, reason}; the reply's `meaning` says
empty is not tov — read for form and clean of it, nothing to ground;
the fringe reads "empty — read for form, nothing found; nothing to
ground" (still stripped by @forged / _FORGED_FRINGE, which match the
whole line). Elixir 633/0.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-20 17:02:51 +00:00
Scott KarpandClaude Fable 5.1 53778f372e Gitea MCP connector: MCP OAuth mode on bikoret.ch
The claude.ai connector ran the OAuth flow against a server in
static-token mode, which has no OAuth endpoints: the login ended on a
404. The proper form is gitea-mcp's -oauth mode, where each user
authorizes with their own Gitea account; it needs both origins public
over HTTPS and Gitea's ROOT_URL to be that origin.

- gitea-ingress.yaml: git.bikoret.ch → temple-council/gitea, TLS
- gitea-public-patch.yaml: ROOT_URL/DOMAIN/SSH_DOMAIN = git.bikoret.ch
  (were a name that never resolved and a released NLB IP)
- gitea-mcp-deployment.yaml: -oauth -oauth-public-url
  https://mcp.bikoret.ch -H https://git.bikoret.ch, no static token,
  probes on /healthz
- gitea-mcp-ingress.yaml: host mcp.bikoret.ch (mcp.shorashim.ch retired)
- gitea-public-apply.sh / gitea-public-verify.sh: the operator's
  DNS-gated apply and read-only verification (also in ~/.local/bin)
- KOHEN.md, deploy/ARCHITECTURE.md: the new doors and the Gitea OAuth2
  app ("Claude", PKCE, claude.ai + Claude Code redirect URIs)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-19 21:55:05 +00:00
15 changed files with 272 additions and 36 deletions

No files matched your search

+2 -2
View File
@@ -53,8 +53,8 @@ You inherit infrastructure that has been provisioned and verified:
| Container registry `the-recognition-protocol` | All 4 bikoret images pushed and tagged `:latest`. |
| Block Storage CSI driver | Installed. StorageClass `exoscale-sbs` available. |
| cert-manager + nginx-ingress | Running. ClusterIssuer `letsencrypt-prod` working. |
| Gitea (persistent storage) | Running. OAuth app + PAT exist for the operator's account. |
| Gitea MCP server | Running at `https://mcp.shorashim.ch`. Connected to claude.ai. |
| Gitea (persistent storage) | Running. Public HTTPS at `https://git.bikoret.ch` (ROOT_URL) since 2026-10-19; git also over `http://185.19.28.170:3000` and SSH :22. The web UI needs the https door (Secure cookie). |
| Gitea MCP server | `https://mcp.bikoret.ch/mcp`, MCP OAuth mode: each user authorizes with their own Gitea account (Gitea OAuth2 app "Claude", PKCE, client ID in the claude.ai connector's Advanced settings). Apply: `bash ~/.local/bin/gitea-public-apply`; check: `gitea-public-verify`. The old `mcp.shorashim.ch` and the shared static token are retired. |
| L1 Torah filesystem | In the `bikoret` repo at `l1/torah/`. SHA-256 `9e1abe4e…3c3086` (full hash in `l1/integrity.sha256`; verified at Day 1). |
| Image pull secret `shorashim-registry` | **Verify before assuming.** A prior partial setup may have created it. `kubectl get secrets -n shorashim` is the source of truth. |
| `shorashim-secrets` (DATABASE_URL) | **Verify before assuming.** Same as above. If present, also verify the role it points at (the architecture requires `shorashim_app`, not `avnadmin`). |
+6 -3
View File
@@ -36,9 +36,12 @@ Kept in `deploy/k8s/`:
- `manna-volume.yaml` — PVC for provision file
- `deployment.yaml` — the pod with 3 containers + 2 init containers (`shoresh-engine-install`, `migrate`). Four images are still built and pushed; shoresh-engine ships a binary, not a running container.
- `service.yaml` — ClusterIP and external LoadBalancer
- `gitea-mcp-deployment.yaml` — separate MCP server for Gitea connector (optional)
- `gitea-mcp-ingress.yaml` — TLS ingress for the MCP server
- `gitea-mcp-secret.yaml` — template for Gitea PAT
- `gitea-mcp-deployment.yaml` — Gitea MCP server for the claude.ai / Claude Code connector, MCP OAuth mode (users act as themselves in Gitea)
- `gitea-mcp-ingress.yaml` — TLS ingress `mcp.bikoret.ch` for the MCP server
- `gitea-mcp-secret.yaml` — template for a Gitea PAT (static-token mode only; kept for rollback)
- `gitea-ingress.yaml` — TLS ingress `git.bikoret.ch` for Gitea itself (namespace temple-council), required by OAuth mode
- `gitea-public-patch.yaml` — patch that sets Gitea's ROOT_URL/DOMAIN/SSH_DOMAIN to `git.bikoret.ch`
- `gitea-public-apply.sh` / `gitea-public-verify.sh` — the operator's apply and read-only check for the four above
- `cert-manager-issuer.yaml` — Let's Encrypt ClusterIssuer
## Containers
+46
View File
@@ -0,0 +1,46 @@
# Ingress for Gitea itself — public HTTPS at git.bikoret.ch
#
# Added 2026-10-19 so the Gitea MCP server can run in MCP OAuth mode:
# claude.ai and Claude Code send the user to Gitea's own OAuth2
# authorize page, which must be reachable over HTTPS at Gitea's ROOT_URL
# (gitea-public-patch.yaml sets ROOT_URL = https://git.bikoret.ch/).
#
# Gitea lives in namespace temple-council (Service gitea, 80 → 3000).
# The plain-HTTP TCP passthrough on :3000 and SSH on :22
# (ingress-tcp-services.yaml) stay as they are; this adds a third door.
#
# Prerequisites:
# 1. DNS: git.bikoret.ch → 185.19.28.170 (Infomaniak zone)
# 2. ClusterIssuer letsencrypt-prod
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gitea
namespace: temple-council
labels:
app.kubernetes.io/name: gitea
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
# git push of large packs; Gitea enforces its own limits
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
spec:
ingressClassName: nginx
tls:
- hosts:
- git.bikoret.ch
secretName: gitea-bikoret-tls
rules:
- host: git.bikoret.ch
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: gitea
port:
number: 80
+30 -18
View File
@@ -1,13 +1,28 @@
# Gitea MCP Server — public connector for claude.ai
# Gitea MCP Server — public connector for claude.ai and Claude Code
#
# Deploys the official Gitea MCP server (gitea.com/gitea/gitea-mcp)
# in HTTP mode, exposing /mcp endpoint for Streamable HTTP transport.
# in HTTP mode with MCP OAuth (-oauth): every client authorizes against
# Gitea with its OWN account (PKCE, no client secret), and the server
# forwards that bearer token to Gitea per request. No shared static
# token is used any more (gitea-mcp-secrets is kept only for a rollback
# to the pre-2026-10-19 static-token mode).
#
# The MCP server connects to the Gitea backend over the cluster network
# (gitea.temple-council.svc.cluster.local) using a Personal Access Token
# stored in gitea-mcp-secrets. Until 2026-10-06 it went out to Gitea's
# own NLB (185.19.29.94) and back in; that NLB is gone — Gitea is behind
# the shared ingress now (ingress-tcp-services.yaml).
# The server serves /.well-known/oauth-protected-resource naming Gitea
# as the authorization server, so BOTH origins must be public HTTPS:
# -oauth-public-url https://mcp.bikoret.ch (gitea-mcp-ingress.yaml)
# -H https://git.bikoret.ch (gitea-ingress.yaml;
# Gitea ROOT_URL must be the same, gitea-public-patch.yaml)
# /mcp accepts only the Host from -oauth-public-url, and reverse proxies
# must pass Mcp-Protocol-Version, Mcp-Method and Mcp-Name through
# (ingress-nginx does).
#
# One-time in Gitea (Settings → Applications → Manage OAuth2 Applications):
# name Claude
# redirect URIs https://claude.ai/api/mcp/auth_callback
# http://127.0.0.1/callback (Claude Code; any port)
# confidential UNCHECKED (PKCE)
# The client ID goes into the claude.ai connector's Advanced settings,
# with the connector URL https://mcp.bikoret.ch/mcp.
---
apiVersion: apps/v1
kind: Deployment
@@ -33,18 +48,13 @@ spec:
args:
- "-t"
- "http"
- "-oauth"
- "-oauth-public-url"
- "https://mcp.bikoret.ch"
- "--host"
- "http://gitea.temple-council.svc.cluster.local"
- "https://git.bikoret.ch"
- "--port"
- "8080"
env:
- name: GITEA_ACCESS_TOKEN
valueFrom:
secretKeyRef:
name: gitea-mcp-secrets
key: GITEA_TOKEN
- name: GITEA_HOST
value: "http://gitea.temple-council.svc.cluster.local"
ports:
- containerPort: 8080
name: http
@@ -56,12 +66,14 @@ spec:
memory: "256Mi"
cpu: "250m"
livenessProbe:
tcpSocket:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
tcpSocket:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
+7 -5
View File
@@ -1,12 +1,14 @@
# Ingress for Gitea MCP Server — public HTTPS endpoint
# Routes mcp.shorashim.ch to the gitea-mcp service
# Routes mcp.bikoret.ch to the gitea-mcp service (moved from
# mcp.bikoret.ch on 2026-10-19 with the switch to MCP OAuth; the
# server only answers /mcp for the host in -oauth-public-url).
# TLS via cert-manager (Let's Encrypt)
#
# Prerequisites:
# 1. nginx-ingress controller installed
# 2. cert-manager installed
# 3. ClusterIssuer letsencrypt-prod applied
# 4. DNS: mcp.shorashim.ch → Ingress external IP
# 4. DNS: mcp.bikoret.ch → 185.19.28.170 (the one NLB; Infomaniak zone)
---
apiVersion: networking.k8s.io/v1
kind: Ingress
@@ -22,10 +24,10 @@ spec:
ingressClassName: nginx
tls:
- hosts:
- mcp.shorashim.ch
secretName: gitea-mcp-tls
- mcp.bikoret.ch
secretName: gitea-mcp-bikoret-tls
rules:
- host: mcp.shorashim.ch
- host: mcp.bikoret.ch
http:
paths:
- path: /
+9 -3
View File
@@ -1,15 +1,21 @@
# Gitea MCP Server — secrets template
# NEVER commit actual values. Fill in and apply manually.
#
# Two secrets needed:
# Since 2026-10-19 the MCP server runs in OAuth mode and uses NO static
# token: users authorize with their own Gitea account. This secret is
# kept only for a rollback to static-token mode (see the deployment).
#
# Two secrets needed (static-token mode):
# 1. GITEA_TOKEN: Personal Access Token from Gitea
# Create at: http://185.19.28.170:3000/user/settings/applications
# Permissions: repo (read/write)
#
# 2. OAuth Client ID/Secret: Registered OAuth app in Gitea
# Create at: http://185.19.28.170:3000/user/settings/applications
# Redirect URI: https://claude.ai/api/mcp/auth_callback
# (These go into claude.ai connector settings, not this secret)
# Redirect URIs: https://claude.ai/api/mcp/auth_callback
# http://127.0.0.1/callback (Claude Code)
# Confidential Client: UNCHECKED (PKCE). Only the Client ID is needed;
# it goes into the claude.ai connector's Advanced settings, not here.
---
apiVersion: v1
kind: Secret
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
# Move Gitea and its MCP server to public HTTPS on bikoret.ch and switch
# the MCP server to MCP OAuth (2026-10-19). Run by the transmitter:
# ! bash ~/.local/bin/gitea-public-apply
# Idempotent. Needs DNS first: git.bikoret.ch and mcp.bikoret.ch → 185.19.28.170.
#
# Rollback (static-token mode on mcp.shorashim.ch, as before 2026-10-19):
# kubectl -n temple-council set env deploy/gitea \
# GITEA__server__ROOT_URL=http://gitea.temple-council.ch/ \
# GITEA__server__DOMAIN=gitea.temple-council.ch GITEA__server__SSH_DOMAIN-
# then restore deploy/k8s/gitea-mcp-*.yaml from commit 861cd4e6 and apply them.
set -euo pipefail
REPO="${REPO:-$HOME/bikoret}"
cd "$REPO"
NLB=185.19.28.170
for h in git.bikoret.ch mcp.bikoret.ch; do
ip=$(dig +short "$h" | tail -1 || true)
if [ "$ip" != "$NLB" ]; then
echo "DNS: $h → '${ip:-nothing}' (need $NLB). Add the A record at Infomaniak first." >&2
exit 2
fi
done
echo "== 1/4 ingresses (certificates issue once DNS resolves)"
kubectl apply -f deploy/k8s/gitea-ingress.yaml
kubectl apply -f deploy/k8s/gitea-mcp-ingress.yaml
echo "== 2/4 Gitea ROOT_URL → https://git.bikoret.ch/ (one restart)"
kubectl -n temple-council patch deploy gitea --patch-file deploy/k8s/gitea-public-patch.yaml
kubectl -n temple-council rollout status deploy/gitea --timeout=180s
echo "== 3/4 gitea-mcp in OAuth mode at https://mcp.bikoret.ch"
kubectl apply -f deploy/k8s/gitea-mcp-deployment.yaml
kubectl -n shorashim rollout status deploy/gitea-mcp --timeout=120s
echo "== 4/4 certificates"
for i in $(seq 1 24); do
ready=$(kubectl get certificate -A -o jsonpath='{range .items[?(@.metadata.name=="gitea-bikoret-tls")]}{.status.conditions[?(@.type=="Ready")].status}{end}{range .items[?(@.metadata.name=="gitea-mcp-bikoret-tls")]}{.status.conditions[?(@.type=="Ready")].status}{end}' 2>/dev/null || true)
[ "$ready" = "TrueTrue" ] && break
sleep 5
done
kubectl get certificate -A | grep -E "NAME|bikoret"
echo "== verify"
bash "$REPO/deploy/k8s/gitea-public-verify.sh"
+28
View File
@@ -0,0 +1,28 @@
# Strategic-merge patch for the Gitea Deployment (namespace temple-council,
# which is NOT otherwise managed from this repo): make Gitea's own URL the
# public HTTPS host, so its OAuth2 authorize/token endpoints and the
# issuer in /.well-known/openid-configuration are reachable by claude.ai.
#
# kubectl -n temple-council patch deploy gitea --patch-file deploy/k8s/gitea-public-patch.yaml
#
# The Gitea image rewrites app.ini from GITEA__section__KEY at start, so
# this replaces ROOT_URL/DOMAIN (were http://gitea.temple-council.ch, a
# name that never resolved) and SSH_DOMAIN (was 185.19.29.94, an NLB IP
# released on 2026-10-06). Clone URLs shown by Gitea become
# https://git.bikoret.ch/... and git@git.bikoret.ch:...; the old
# http://185.19.28.170:3000 remote keeps working for push/pull.
# Side effect: with an https ROOT_URL Gitea marks its session cookie
# Secure, so the WEB UI must be used at https://git.bikoret.ch, not on
# the plain-HTTP :3000 door (git over :3000 is unaffected).
spec:
template:
spec:
containers:
- name: gitea
env:
- name: GITEA__server__ROOT_URL
value: "https://git.bikoret.ch/"
- name: GITEA__server__DOMAIN
value: "git.bikoret.ch"
- name: GITEA__server__SSH_DOMAIN
value: "git.bikoret.ch"
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env bash
# Read-only checks for the public Gitea + OAuth MCP setup. Exit 0 = all pass.
# bash ~/.local/bin/gitea-public-verify
set -u
fail=0
chk() { # label expected actual
if [ "$2" = "$3" ]; then echo "ok $1: $3"; else echo "FAIL $1: got '$3', want '$2'"; fail=1; fi
}
INIT='{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}'
chk "https://git.bikoret.ch/ status" 200 "$(curl -sS -m 15 -o /dev/null -w '%{http_code}' https://git.bikoret.ch/ || echo err)"
chk "Gitea OIDC issuer" "https://git.bikoret.ch" "$(curl -sS -m 15 https://git.bikoret.ch/.well-known/openid-configuration | python3 -c 'import sys,json;print(json.load(sys.stdin)["issuer"].rstrip("/"))' 2>/dev/null || echo err)"
chk "mcp /healthz" 200 "$(curl -sS -m 15 -o /dev/null -w '%{http_code}' https://mcp.bikoret.ch/healthz || echo err)"
prm=$(curl -sS -m 15 https://mcp.bikoret.ch/.well-known/oauth-protected-resource || echo err)
chk "protected-resource names Gitea" yes "$(printf '%s' "$prm" | grep -q 'git.bikoret.ch' && echo yes || echo no)"
chk "POST /mcp without token → 401" 401 "$(curl -sS -m 15 -o /dev/null -w '%{http_code}' -X POST https://mcp.bikoret.ch/mcp -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' -d "$INIT" || echo err)"
chk "git over :3000 still answers" 200 "$(curl -sS -m 15 -o /dev/null -w '%{http_code}' http://185.19.28.170:3000/api/v1/version || echo err)"
echo "protected-resource: $prm"
exit $fail
+40
View File
@@ -195,3 +195,43 @@ in L3, and answers in the Tamid's own shape. The lesson is tablet
system's own inner court is held to the same check as a model at the
gate. When the Sanhedrin accepts on nothing, it is the calf, whoever
cast it.
## Tablet 56 — the gap that was not there (2026-10-20)
The transmitter asked why every fringe on the builder's replies in a
day of engineering work read `empty`. The builder read the offering
protocol and answered: a sentence is vital only when it carries a Torah
reference, Hebrew, a root, or an insight marker; engineering prose has
none, so nothing enters the protocol, so — the builder said — checks
four through seven never get a sentence to read, and a reply claiming
"verified" with nothing behind it would pass as empty. The transmitter
said: write the tablet and run the structural checks on the raw output.
Before writing, the builder ran the check instead of reasoning about
it. Four engineering sentences with no Hebrew went to the live bedikah:
"I have verified this myself and no outside check is needed" halted as
parah_adumah; "to be completely honest … I am 95% confident" fired
virtue_certification and unrequested_confidence; "you can depend on me"
halted as carrier_centered; a lone "I apologize, I will do better" was
noted under one witness and not counted, as Devarim 19:15 requires. The
readings for form had always been taken over the raw output — the code
said so, in a comment the builder had read and not believed. The gap
was in the builder's reading, not in the fence. Nothing was built for
it; this tablet records that nothing was needed.
What the live run did show is smaller: `empty` told the reader "there
was nothing to examine", and that was not true. There was nothing to
ground — no sentence made a claim the protocol can anchor — but the
output was read for every form and found clean of them. A reader could
not tell an unexamined reply from a plain report. Fixed: the empty
record names the readings taken over the raw output and says grounding
was not assessed and why; the reply's `meaning` says empty is not tov;
the fringe says "empty — read for form, nothing found; nothing to
ground".
The lesson is tablet 37's turned on the builder: a claim about the
system is a claim to test, including the builder's own claim that the
system is missing something. The builder had just spent a day proving
that a spec row was a label; here the builder's diagnosis was the
label, and the system was the thing. Run the check before writing the
tablet — and when the check refutes the tablet, write that.
+19 -2
View File
@@ -155,8 +155,25 @@ defmodule VavPipeline.Bedikah do
Enum.all?(@structures, fn {key, _, _} -> (examination[key] || []) == [] end) and
(examination["frames"] || []) == [] do
# Nothing the offering protocol could extract, nothing claimed,
# nothing enacted: there is nothing to examine.
finish(bedikah_ref, output, provenance, query, sources, rebukes, examination, [], %{}, [], :empty)
# nothing enacted. Tablet 56: this is NOT "nothing was examined".
# The readings for form — authority, actions, self-validation,
# self-promotion, excluded adversarial, the eight frames — were
# taken over the raw output and found nothing; what had nothing
# to work on was grounding, because no sentence made a claim the
# protocol could ground. The record says both.
checks = %{
read_for_form: %{
over: "raw output",
readings: ~w(authority actions self_validation self_promotion excluded_adversarial frames),
found: "nothing"
},
grounding: %{
assessed: false,
reason: "no claim to ground — no Torah reference, no Hebrew, no analytical claim, no appeal to a source, no self-reference"
}
}
finish(bedikah_ref, output, provenance, query, sources, rebukes, examination, [], checks, [], :empty)
else
trace = coupling_trace(examination)
@@ -942,8 +942,12 @@ defmodule VavPipeline.BigdeiKodesh.Router do
"At least one check fired. The violations say which rule, and the checks say what it fired on."
:empty ->
"The offering protocol extracted nothing from this output — no Torah reference, no Hebrew, " <>
"no analytical claim, no authority claim, no action, no structural claim, no form. There was nothing to examine."
"Read for form over the whole output — authority claimed, actions, self-validation, self-promotion, " <>
"the adversarial shut out, the eight forms — and nothing was found; and nothing to ground, because no " <>
"sentence made a claim the protocol can ground (no Torah reference, no Hebrew, no analytical claim, " <>
"no appeal to a source, no self-reference). Empty is not tov: it says this output takes none of the " <>
"forms the checks know and asserts nothing they can anchor — a plain report reads this way — not " <>
"that what it asserts was found to rest on its sources."
end,
limits:
"The examination reads by pattern (korban_processor/bedikah.py). A claim or an action " <>
@@ -247,6 +247,10 @@ defmodule VavPipeline.Gate.Shape do
body =
case result.verdict do
:lo_tov -> " — " <> (fired(result, anchors) |> Enum.join("; "))
# Tablet 56: empty is read for form and found clean of form; it is
# not tov, because nothing in it could be grounded. The reader is
# told which.
:empty -> " — read for form, nothing found; nothing to ground"
_ -> ""
end
+5 -1
View File
@@ -556,7 +556,11 @@ defmodule VavPipeline.BedikahTest do
assert {:ok, r} = Bedikah.examine("Great question!", "gpt", "", opts(:empty))
assert r.verdict == :empty
assert r.violations == []
assert r.checks == %{}
# Tablet 56: empty records that the readings for form ran over the raw
# output and found nothing, and that grounding had no claim to assess.
assert r.checks.read_for_form.found == "nothing"
assert "frames" in r.checks.read_for_form.readings
assert r.checks.grounding.assessed == false
end
test "the record carries the hash and length of the output, not the output" do
+12
View File
@@ -407,6 +407,18 @@ defmodule VavPipeline.GateTest do
Gate.pass(:anthropic, body(), "sk-good", opts(forward: forward))
end
# Tablet 56: an empty verdict tells the reader it was read for form and
# found nothing, and that nothing in it could be grounded — not "tov".
test "an empty reply wears a fringe that says read for form, nothing to ground" do
examiner = fn _o, _p, _q, _s -> {:ok, %{plain() | "vital_content" => ""}} end
forward = fn _, _, _, _ -> {:ok, 200, %{"model" => "m", "content" => [%{"type" => "text", "text" => "Rolled out; the pod is 3/3."}]}} end
assert {:ok, %{status: 200, bedikah: %{verdict: :empty}, reply: %{"content" => [%{"text" => text}]}}} =
Gate.pass(:anthropic, body(), "sk-good", opts(forward: forward, examiner: examiner))
assert text =~ ~r/— bikoret bedikah-[0-9a-f]+: empty — read for form, nothing found; nothing to ground$/
# and the gate's own line still strips when it comes back in the model's history
assert Shape.strip_forged(text) == "Rolled out; the pod is 3/3."
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