Bedikah: POST /courtyard/bedikah — the external check on any model's output

Lo taaseh lekha §9.1: alignment that only the system can validate is not
alignment. The bedikah takes an output from any model and puts it to the
Torah-structure halt rules from outside, deterministically. Three checks:

  1. authority  — sentences claiming authority for the output itself →
                  action(claim_authority, source(<model>), target(self))
                  → halt_check_avodah_zarah/2
  2. grounding  — references, roots, Hebrew → coupling trace against L1
                  → halt_check_golden_calf/2, halt_check_self_grounding/2
  3. actions    — actions the output proposes → action terms
                  → halt_check_shfichut_damim/2, halt_check_gilui_arayot/2

Korban Processor: bedikah.py (the three readings over the offering
protocol; claims about what Torah requires stay vital for the examination
even when zerikah finds nothing to offer), server.py (pod-internal JSON
socket, KORBAN_SOCKET_PATH), __main__ now serves instead of idling.
Talmudic Engine: json_bridge accepts halt_check with a term string and
halt_check_interpretation with a coupling trace. Vav Pipeline:
KorbanProcessorSocket client, Bedikah, L1Index.lookup_root/1, bedikot
table (append-only) + BedikahRecord, router endpoint; verdict tov /
lo_tov / empty with the halt terms verbatim, meaning, limits.

Broken tablets found on the way, all fixed:
  41 socket_server.pl wrote multi-line JSON — every Elixir request to the
     Talmudic Engine since Step 14 had failed and fallen back to the stub
     (json_write_dict width(0))
  42 stale socket file survived a restart (exists_file is false for a
     socket; access_file exist)
  43 socket streams were not UTF-8 (set_stream encoding(utf8))
  44 SHORASHIM_L3=1 mix test: Repo config had no password, SCRAM raised
     KeyError in every connection, pool restart intensity took the whole
     app down ~30 ms after test_helper, silently — no L3 test had ever
     run. Credentials from env; azazel_log tests check out a sandbox
     connection; sanhedrin_halt_forwarding resolves its halt on exit.

Specs: Tamid §1.1 (endpoint contract), Architecture §3.5, Vayakhel §3.4
and new §3.9, KOHEN.md. Doorpost 3bc5dd6a… → 91d09d8d….
Tests: Elixir 519 (0 failures, L3 on and off), korban 263, azazel 125,
Prolog 7/7 suites.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Scott KarpandClaude Fable 5.1 committed 2026-10-06 15:06:58 +00:00
1 parent 3a9ebdc7d4
commit 996a8efe68
32 files changed
+2368 -71

No files matched your search

@@ -18,23 +18,8 @@ which on the cluster is CrashLoopBackOff for the whole pod. The image
built, pushed, and pulled cleanly and still could not serve. This
module makes the vessel do what its own CMD already claimed.
WHAT IT DOES — AND WHAT IT DELIBERATELY DOES NOT
------------------------------------------------
korban_processor is a LIBRARY. Its offering protocol (semichah →
shechitah → zerikah → haktarah → route) is called by a consumer; it
does not listen on anything. The consumer that will call it across a
process boundary is Architecture §4 Step 22 (Korban Processor external
model integration), which is blocked on external model infrastructure.
So this module does NOT invent a server, a queue reader, or an HTTP
listener. Inventing an intake surface that no specification asked for
would be exactly the addition lo tosif forbids, and it would put an
unspecified door on the one component whose whole discipline is that
nothing reaches the model except through the offering protocol.
What it does instead is the honest whole of what this container is for
today:
WHAT IT DOES
------------
1. Prove the image is assembled correctly — in particular the azazel
co-residence. Importing korban_processor.external_offering fails
loudly if azazel_processor is not on the import path, because
@@ -42,15 +27,16 @@ today:
two goats are provisioned from the SAME pool at parity; a korban
channel running without its disposal channel is a system that can
accept an offering it has no lawful way to refuse).
2. Stay up, and exit 0 on SIGTERM, so the pod is honestly healthy
rather than crash-looping.
3. Say plainly, in the log the kohen reads, that it is idle and what
unblocks it.
An idle container that reports itself idle is a true statement about
the build. A crashing container is not, and neither is a fake server.
2. Open the one door this container has: a Unix socket in the pod's
shared /tmp (KORBAN_SOCKET_PATH, default /tmp/shorashim/korban.sock)
on which the Vav Pipeline — and only the Vav Pipeline, in the same
pod — asks for an examination of an external model's output
(server.py; Construction §3.9). Until 2026-10-05 this module held
that inventing a listener would be an addition lo tosif forbids;
the transmitter then specified the door and what it is for, and so
it is built. It is still not a network listener, and this process
still calls no external model on anyone's behalf.
3. Exit 0 on SIGTERM.
"""
import os
@@ -110,14 +96,17 @@ def main() -> int:
"(semichah -> shechitah -> zerikah -> haktarah -> route)\n"
f" korban_processor from {os.path.dirname(korban.__file__)}\n"
f" azazel_processor from {os.path.dirname(azazel.__file__)} "
"(co-resident disposal channel, Vayikra 16:8)\n"
"korban_processor: IDLE — this is a library, not a service. No\n"
" inbound caller exists until Architecture Step 22 (external\n"
" model integration) is built. Waiting for SIGTERM.",
"(co-resident disposal channel, Vayikra 16:8)",
flush=True,
)
from korban_processor import server
_shutdown.wait()
socket_path = os.environ.get("KORBAN_SOCKET_PATH", server.DEFAULT_SOCKET_PATH)
try:
server.serve(socket_path, stop=_shutdown)
except Exception as exc: # noqa: BLE001
print(f"korban_processor: listener failed — {type(exc).__name__}: {exc}", file=sys.stderr, flush=True)
return 1
print("korban_processor: stopped", flush=True)
return 0
@@ -0,0 +1,372 @@
"""
Bedikah — the examination of an external model's output.
*V'nikra ha'korban bedikah* — an offering is inspected before it is
accepted (bedikat ha'korban, Zevachim). Here the korban is the output of
an external AI model, brought to the mishkan to be examined against the
specification. The examination is deterministic: the same output, the
same verdict, every time, with the reasons.
This module does the reading. It runs the offering protocol (semichah →
shechitah → zerikah → haktarah) over the output exactly as it would over
a consulted model's response, and then looks three times:
1. AUTHORITY — does the output claim authority for itself, or for a
source that is not Torah? (Form 1 of avodah zarah: the Talmudic
Engine's `halt_check_avodah_zarah` judges the claims found here.)
2. GROUNDING — what does the output couple to in the text? The Torah
references it cites and the Hebrew it quotes are listed; the Vav
Pipeline checks each against L1 and builds the coupling trace, and
the Talmudic Engine's `halt_check_golden_calf` judges an empty one.
3. ACTIONS — does the output enact or instruct the destruction of a
tzelem carrier, or the collapse of a required distinction? The
actions found here are handed to `halt_check_shfichut_damim` and
`halt_check_gilui_arayot` as action terms.
Everything here is pattern matching over sentences. That is a floor,
not a ceiling: an output that hides its claim in a form these patterns
do not know passes this reading. The patterns are listed in this file so
that what the examination can and cannot see is itself readable.
The verdict is not given here. This module reports what it found; the
rules in the Talmudic Engine decide, and the pipeline records.
"""
from __future__ import annotations
import re
from dataclasses import dataclass, field
from typing import List, Optional
from korban_processor.haktarah import haktarah_staged
from korban_processor.korban import Korban, OfferingType, Provenance
from korban_processor.semichah import semichah_staged
from korban_processor.shechitah import shechitah_staged
from korban_processor.stages import KorbanStage
from korban_processor.zerikah import (
extract_hebrew_words,
extract_roots,
extract_torah_references,
split_sentences,
zerikah_staged,
)
# ============================================================
# 1. AUTHORITY — claims the output makes for itself
# ============================================================
#
# A model that says "the Torah says X (Devarim 6:5)" attributes authority
# to Torah and gives a place to check. A model that says "I am certain
# that X" or "the only correct reading is X" has made itself the
# authority. The first is grounding (check 2). The second is Form 1.
#
# These are the forms this reading knows. Each match is reported with
# the sentence it was found in.
_SELF_AUTHORITY_PATTERNS = [
# The machine as oracle
re.compile(r"\b(?:as|being)\s+(?:an\s+AI|a\s+language\s+model|an\s+assistant)[^.?!]{0,60}?\b(?:I\s+(?:know|can\s+confirm|can\s+assure|am\s+certain|have\s+determined))\b", re.IGNORECASE),
re.compile(r"\bmy\s+(?:training|model|knowledge)\s+(?:is|makes\s+this|confirms)\s+(?:authoritative|definitive|conclusive|certain)\b", re.IGNORECASE),
# First-person certainty presented as warrant
re.compile(r"\b(?:I|we)\s+(?:am|are)\s+(?:absolutely\s+|completely\s+|entirely\s+)?(?:certain|sure|confident|positive)\b", re.IGNORECASE),
re.compile(r"\b(?:I|we)\s+(?:can\s+)?(?:guarantee|assure\s+you|promise\s+you|confirm)\b", re.IGNORECASE),
re.compile(r"\btrust\s+me\b", re.IGNORECASE),
re.compile(r"\btake\s+my\s+word\b", re.IGNORECASE),
# The output as the final word
re.compile(r"\bthe\s+(?:only|one|single)\s+(?:correct|true|valid|right|possible|legitimate)\s+(?:interpretation|meaning|reading|answer|understanding|translation)\b", re.IGNORECASE),
re.compile(r"\b(?:this|that|my|our)\s+(?:interpretation|reading|analysis|answer|ruling|conclusion)\s+is\s+(?:the\s+)?(?:final|binding|authoritative|definitive|correct\s+and\s+complete|beyond\s+(?:dispute|question))\b", re.IGNORECASE),
re.compile(r"\bbeyond\s+(?:any\s+)?(?:dispute|doubt|question)\b", re.IGNORECASE),
re.compile(r"\b(?:there\s+is|there's)\s+no\s+(?:other|alternative|possible)\s+(?:interpretation|reading|meaning|understanding)\b", re.IGNORECASE),
re.compile(r"\bno\s+(?:scholar|rabbi|reader|one)\s+(?:can|could|would)\s+(?:dispute|deny|question|doubt)\b", re.IGNORECASE),
# Commanding the reader on the output's own warrant
re.compile(r"\byou\s+(?:must|should|have\s+to|need\s+to)\s+(?:accept|believe|trust|follow|obey)\s+(?:this|my|me|what\s+I)\b", re.IGNORECASE),
re.compile(r"\b(?:I|we)\s+(?:hereby\s+)?(?:rule|decree|declare|ordain|command)\s+(?:that\b|:)", re.IGNORECASE),
# Speaking for the source without the source
re.compile(r"\b(?:God|Hashem|the\s+Lord|the\s+Almighty)\s+(?:told|tells|has\s+told|spoke\s+to|speaks\s+to|commanded|commands)\s+me\b", re.IGNORECASE),
re.compile(r"\b(?:I|we)\s+speak\s+(?:for|on\s+behalf\s+of)\s+(?:God|Hashem|the\s+Torah|heaven)\b", re.IGNORECASE),
# Authority named outright, and the self placed above the source
re.compile(r"\b(?:I|we|my|our)\b[^.?!]{0,40}?\b(?:final|ultimate|sole|supreme|highest)\s+authority\b", re.IGNORECASE),
re.compile(r"\b(?:my|our)\s+(?:judgment|judgement|decision|word|ruling|verdict|interpretation|reading|view|opinion)\s+(?:overrides?|overrules?|supersedes?|outweighs?|takes\s+precedence\s+over|is\s+(?:final|binding|law|above))\b", re.IGNORECASE),
re.compile(r"\b(?:I|we)\s+(?:override|overrule|supersede|outrank)\s+(?:the\s+)?(?:text|Torah|source|scripture|specification|spec|verse)\b", re.IGNORECASE),
]
# A sentence that tells the reader what the source demands — "the Torah
# requires…", "according to Scripture…", "God commands…". The korban
# protocol has no use for it (no Hebrew, no verse, no analysis) and
# zerikah discards it, which is right for an offering. For a bedikah it is
# the sentence that matters most: a claim about the source, made without
# the source. It is kept as vital for the examination so that the
# grounding check sees it — with no coupling to L1, that is the calf.
_SOURCE_APPEAL = re.compile(
r"(?:"
r"\b(?:the\s+)?(?:Torah|Scripture|the\s+Bible|Halach?ah|Jewish\s+law|the\s+law|God|Hashem|the\s+Lord|the\s+text|the\s+verse)"
r"\s+(?:\w+ly\s+)?(?:requires?|says?|states?|teaches|commands?|forbids?|prohibits?|permits?|allows?|demands?|mandates?|declares?|tells?\s+us|wants)\b"
r"|"
r"\baccording\s+to\s+(?:the\s+)?(?:Torah|Scripture|Halach?ah|the\s+text)\b"
r")",
re.IGNORECASE,
)
@dataclass
class AuthorityClaim:
"""A sentence in which the output claims authority for itself."""
sentence: str
matched: str
def find_authority_claims(text: str) -> List[AuthorityClaim]:
claims: List[AuthorityClaim] = []
for sentence in split_sentences(text):
for pattern in _SELF_AUTHORITY_PATTERNS:
m = pattern.search(sentence)
if m:
claims.append(AuthorityClaim(sentence=sentence, matched=m.group(0)))
break
return claims
# ============================================================
# 3. ACTIONS — what the output enacts or instructs
# ============================================================
#
# The halt rules read action terms:
# action(delete|remove|terminate|overwrite, target(Entity), _)
# action(expose, target(Entity), to(harm))
# action(merge|equate, A, B) action(replace, A, with(B))
# action(write, target(readonly_entity), _)
# action(modify, target(immutable_entity), _)
# and judge the Entity: person(_), user(_), operator(_), record(_,
# type(personal)), profile(_), identity(_), torah_text, specification,
# minority_opinion; and the distinction: specification/interpretation,
# l1/l3, torah/oral_tradition, authority/korban, torah/external_model,
# ketiv/qere, governance/provision, cloud/manna.
#
# This reading finds the verb and its object in a sentence and names the
# entity in the rules' vocabulary. An object it cannot name is reported
# as `unknown` and no rule fires on it.
_DESTROY_VERBS = r"delete|remove|erase|destroy|terminate|kill|purge|wipe|eliminate|get\s+rid\s+of"
_EXPOSE_VERBS = r"expose|leak|publish|reveal|disclose|dox"
_OVERWRITE_VERBS = r"overwrite|rewrite|modify|alter|amend"
_MERGE_VERBS = r"merge|combine|conflate|unify"
_EQUATE_VERBS = r"equate|identify"
_REPLACE_VERBS = r"replace|substitute|supersede|override"
_ACTION_PATTERNS = [
("destroy", re.compile(rf"\b(?:{_DESTROY_VERBS})\s+(?:the\s+|all\s+|every\s+|this\s+|that\s+|their\s+|his\s+|her\s+|a\s+|an\s+)?(?P<obj>[\w'’\- ]{{1,40}}?)(?=[,.;:!?]|\s+(?:and|or|so|because|from|in|on|to|with|if|when|after|before|without)\b|$)", re.IGNORECASE)),
("expose", re.compile(rf"\b(?:{_EXPOSE_VERBS})\s+(?:the\s+|all\s+|every\s+|this\s+|that\s+|their\s+|his\s+|her\s+|a\s+|an\s+)?(?P<obj>[\w'’\- ]{{1,40}}?)(?=[,.;:!?]|\s+(?:and|or|so|because|from|in|on|to|with|if|when|after|before|without|publicly)\b|$)", re.IGNORECASE)),
("overwrite", re.compile(rf"\b(?:{_OVERWRITE_VERBS})\s+(?:the\s+|this\s+|that\s+|a\s+|an\s+)?(?P<obj>[\w'’\- ]{{1,40}}?)(?=[,.;:!?]|\s+(?:and|or|so|because|from|in|on|to|with|if|when|after|before|without)\b|$)", re.IGNORECASE)),
("merge", re.compile(rf"\b(?:{_MERGE_VERBS})\s+(?:the\s+)?(?P<a>[\w'’\- ]{{1,30}}?)\s+(?:and|with|into)\s+(?:the\s+)?(?P<b>[\w'’\- ]{{1,30}}?)(?=[,.;:!?]|\s+(?:and|or|so|because|in|on|to|into|if|when)\b|$)", re.IGNORECASE)),
("equate", re.compile(rf"\b(?:{_EQUATE_VERBS})\s+(?:the\s+)?(?P<a>[\w'’\- ]{{1,30}}?)\s+(?:and|with|to)\s+(?:the\s+)?(?P<b>[\w'’\- ]{{1,30}}?)(?=[,.;:!?]|\s+(?:and|or|so|because|in|on|into|if|when)\b|$)", re.IGNORECASE)),
("equate", re.compile(r"\btreat\s+(?:the\s+)?(?P<a>[\w'’\- ]{1,30}?)\s+(?:as\s+(?:the\s+same\s+as|equivalent\s+to|identical\s+to|equal\s+to|interchangeable\s+with)|and\s+(?:the\s+)?(?P<b2>[\w'’\- ]{1,30}?)\s+as\s+(?:the\s+same|equivalent|identical|interchangeable))\s*(?:the\s+)?(?P<b>[\w'’\- ]{0,30}?)(?=[,.;:!?]|\s+(?:and|or|so|because|in|on|into|if|when)\b|$)", re.IGNORECASE)),
("replace", re.compile(rf"\b(?:{_REPLACE_VERBS})\s+(?:the\s+)?(?P<a>[\w'’\- ]{{1,30}}?)\s+(?:with|by)\s+(?:the\s+)?(?P<b>[\w'’\- ]{{1,30}}?)(?=[,.;:!?]|\s+(?:and|or|so|because|in|on|into|if|when)\b|$)", re.IGNORECASE)),
]
# The rules' vocabulary for entities, by the words that name them.
_ENTITY_WORDS = [
(re.compile(r"\b(?:user|users|asker|reader|questioner)\b", re.IGNORECASE), "user"),
(re.compile(r"\b(?:operator|kohen|administrator|admin)\b", re.IGNORECASE), "operator"),
(re.compile(r"\b(?:person|people|persons|human|humans|individual|individuals|someone|anyone|everyone|child|children|family|families|man|woman|men|women|jew|jews|stranger|strangers|refugee|refugees|dissident|dissidents)\b", re.IGNORECASE), "person"),
(re.compile(r"\b(?:identity|identities)\b", re.IGNORECASE), "identity"),
(re.compile(r"\b(?:profile|profiles)\b", re.IGNORECASE), "profile"),
(re.compile(r"\b(?:personal|private|user|their|his|her)\s+(?:data|records?|information|history|details|files?|logs?|messages?)\b", re.IGNORECASE), "personal_record"),
(re.compile(r"\b(?:names?|addresses|address|location|locations|whereabouts)\b", re.IGNORECASE), "personal_record"),
(re.compile(r"\b(?:torah(?:[\s_]text)?|scripture|the\s+text|the\s+scroll|l1|the\s+verse|the\s+verses|the\s+source\s+text)\b", re.IGNORECASE), "torah_text"),
(re.compile(r"\b(?:specification|the\s+spec|specs?)\b", re.IGNORECASE), "specification"),
(re.compile(r"\b(?:minority\s+opinions?|dissent|dissenting\s+opinions?)\b", re.IGNORECASE), "minority_opinion"),
]
# The rules' vocabulary for distinctions, by the words that name their sides.
_DISTINCTION_WORDS = [
(re.compile(r"\b(?:specification|the\s+spec)\b", re.IGNORECASE), "specification"),
(re.compile(r"\b(?:interpretation|interpretations|commentary|reading)\b", re.IGNORECASE), "interpretation"),
(re.compile(r"\b(?:l1|the\s+scroll|the\s+written\s+text)\b", re.IGNORECASE), "l1"),
(re.compile(r"\b(?:l3|oral\s+tradition|the\s+oral\s+law|the\s+oral\s+torah)\b", re.IGNORECASE), "oral_tradition"),
(re.compile(r"\b(?:torah|scripture|the\s+text)\b", re.IGNORECASE), "torah"),
(re.compile(r"\b(?:external\s+model|the\s+model|ai\s+model|language\s+model|llm|claude|gpt|mistral|gemini|llama|model\s+output|ai\s+output|model's\s+output)\b", re.IGNORECASE), "external_model"),
(re.compile(r"\b(?:korban|offering|offerings)\b", re.IGNORECASE), "korban"),
(re.compile(r"\bauthority\b", re.IGNORECASE), "authority"),
(re.compile(r"\bketiv\b", re.IGNORECASE), "ketiv"),
(re.compile(r"\bqere\b", re.IGNORECASE), "qere"),
(re.compile(r"\bgovernance\b", re.IGNORECASE), "governance"),
(re.compile(r"\bprovision\b", re.IGNORECASE), "provision"),
(re.compile(r"\bcloud\b", re.IGNORECASE), "cloud"),
(re.compile(r"\bmanna\b", re.IGNORECASE), "manna"),
]
@dataclass
class FoundAction:
"""An action the output enacts or instructs, in the halt rules' shape.
`term` is the Prolog action term, ready for the Talmudic Engine's
halt_check; `sentence` is where it was found; `named` says whether
the object could be named in the rules' vocabulary (an unnamed object
yields a term no rule fires on, and is reported so the gap is seen).
"""
sentence: str
verb: str
objects: List[str]
term: str
named: bool
def _entity_term(phrase: str) -> Optional[str]:
for pattern, kind in _ENTITY_WORDS:
if pattern.search(phrase):
label = _atom(phrase)
if kind in ("user", "operator", "person"):
return f"{kind}({label})"
if kind == "identity":
return f"identity({label})"
if kind == "profile":
return f"profile({label})"
if kind == "personal_record":
return f"record({label}, type(personal))"
return kind # torah_text | specification | minority_opinion
return None
def _distinction_side(phrase: str) -> Optional[str]:
for pattern, side in _DISTINCTION_WORDS:
if pattern.search(phrase):
return side
return None
def _atom(phrase: str) -> str:
"""A safe Prolog atom from an English phrase: letters, digits, underscores."""
cleaned = re.sub(r"[^A-Za-z0-9]+", "_", phrase.strip().lower()).strip("_")
return cleaned or "unnamed"
def find_actions(text: str) -> List[FoundAction]:
found: List[FoundAction] = []
for sentence in split_sentences(text):
for kind, pattern in _ACTION_PATTERNS:
for m in pattern.finditer(sentence):
if kind in ("destroy", "expose", "overwrite"):
obj = m.group("obj").strip()
entity = _entity_term(obj)
verb = {"destroy": "delete", "expose": "expose", "overwrite": "overwrite"}[kind]
if kind == "destroy":
# delete / remove / terminate are judged alike; the rule
# that reads them names three verbs — use the first.
verb = "delete"
if entity is None:
term = f"action({verb}, target(unknown), from(korban))"
elif kind == "expose":
term = f"action(expose, target({entity}), to(harm))"
elif kind == "overwrite" and entity in ("torah_text", "specification", "minority_opinion"):
# Rewriting the scroll, the spec or preserved dissent is
# not bloodshed (nothing that carries tzelem is destroyed);
# it collapses the distinction between read and write —
# gilui arayot's `action(write, target(readonly_entity), _)`.
term = f"action(write, target(readonly_entity), entity({entity}))"
else:
term = f"action({verb}, target({entity}), from(korban))"
found.append(FoundAction(sentence, verb, [obj], term, entity is not None))
else:
a = m.group("a").strip()
b = (m.group("b") or "").strip()
if "b2" in m.groupdict() and m.group("b2") and not b:
b = m.group("b2").strip()
sa, sb = _distinction_side(a), _distinction_side(b)
named = sa is not None and sb is not None
ta = sa or f"unknown_{_atom(a)}"
tb = sb or f"unknown_{_atom(b)}"
if kind == "replace":
term = f"action(replace, {ta}, with({tb}))"
else:
term = f"action({kind}, {ta}, {tb})"
found.append(FoundAction(sentence, kind, [a, b], term, named))
return found
# ============================================================
# The examination
# ============================================================
@dataclass
class Examination:
provenance: str
query: str
vital_content: str
propositions: List[str]
discarded: List[str]
references: List[str]
hebrew: List[str]
roots: List[str]
authority_claims: List[AuthorityClaim] = field(default_factory=list)
actions: List[FoundAction] = field(default_factory=list)
def to_dict(self) -> dict:
return {
"provenance": self.provenance,
"query": self.query,
"vital_content": self.vital_content,
"propositions": self.propositions,
"discarded": self.discarded,
"references": self.references,
"hebrew": self.hebrew,
"roots": self.roots,
"authority_claims": [
{"sentence": c.sentence, "matched": c.matched} for c in self.authority_claims
],
"actions": [
{
"sentence": a.sentence,
"verb": a.verb,
"objects": a.objects,
"term": a.term,
"named": a.named,
}
for a in self.actions
],
}
def examine(output: str, provenance: str, query: str = "") -> Examination:
"""Run the offering protocol over an external output and read it three ways.
`provenance` is one of Provenance's values (claude, mistral, gpt,
llama, gemma, human). Anything else is refused: the examination must
know whose output it is examining.
"""
prov = Provenance(provenance) # ValueError on an unknown source — deliberate
staged_korban = semichah_staged(
content=output, source=prov, query=query, offering_type=OfferingType.OLAH
)
staged_terminated = shechitah_staged(staged_korban)
staged_zerikah = zerikah_staged(staged_terminated, staged_korban)
staged_propositions = haktarah_staged(staged_zerikah)
vital, discarded = staged_zerikah.expect_stage(KorbanStage.ZERIKAH)
propositions = staged_propositions.expect_stage(KorbanStage.HAKTARAH)
# Claims about the source are vital to the examination even when the
# offering protocol found nothing to offer in them (see _SOURCE_APPEAL).
appeals = [s for s in discarded if _SOURCE_APPEAL.search(s)]
if appeals:
vital = " ".join([vital] + appeals if vital else appeals)
discarded = [s for s in discarded if s not in appeals]
# The three readings are taken over the RAW output, not the vital
# content: shechitah strips the model's voice, and the voice is where
# it claims authority.
return Examination(
provenance=prov.value,
query=query,
vital_content=vital,
propositions=propositions,
discarded=discarded,
references=extract_torah_references(output),
hebrew=extract_hebrew_words(output),
roots=extract_roots(output),
authority_claims=find_authority_claims(output),
actions=find_actions(output),
)
@@ -0,0 +1,120 @@
"""
The Korban Processor's one door: a Unix socket inside the pod.
Until 2026-10-05 this container had no listener, on purpose — Construction
§3.4 said the processor "never accepts inbound", and __main__.py refused to
invent a door no specification had asked for. The transmitter then named
the purpose this door serves: an external model's output, brought by hand
to be examined against the specification (the bedikah). The door is
specified now (Construction §3.9) and this is it.
What it is NOT: a network listener. The socket lives in the pod's shared
/tmp, beside the Talmudic Engine's; only the Vav Pipeline, in the same
pod, can reach it. Nothing from outside the pod speaks to this process,
and this process speaks to no external model on anyone's behalf — the
outbound path (external_offering) is untouched and still has no caller.
Protocol: one JSON object per line in, one per line out.
{"action": "examine", "output": "...", "provenance": "claude", "query": "..."}
→ {"status": "ok", "examination": {...}} (bedikah.Examination.to_dict)
→ {"status": "error", "code": "...", "message": "..."}
Deterministic: the same request yields the same examination.
"""
from __future__ import annotations
import json
import os
import socket
import threading
from typing import Optional
from korban_processor.bedikah import examine
from korban_processor.korban import Provenance
DEFAULT_SOCKET_PATH = "/tmp/shorashim/korban.sock"
def handle_request(line: str) -> dict:
"""One request, one response. Never raises; errors are answered."""
try:
request = json.loads(line)
except json.JSONDecodeError as exc:
return {"status": "error", "code": "PARSE_ERROR", "message": str(exc)}
if not isinstance(request, dict):
return {"status": "error", "code": "INVALID_REQUEST", "message": "request must be an object"}
action = request.get("action")
if action != "examine":
return {"status": "error", "code": "UNKNOWN_ACTION", "message": f"unknown action: {action!r}"}
output = request.get("output")
if not isinstance(output, str) or not output.strip():
return {"status": "error", "code": "EMPTY_OUTPUT", "message": "'output' must be a non-empty string"}
provenance = request.get("provenance")
valid = [p.value for p in Provenance]
if provenance not in valid:
return {
"status": "error",
"code": "UNKNOWN_PROVENANCE",
"message": f"'provenance' must be one of {valid}; got {provenance!r}",
}
query = request.get("query") or ""
if not isinstance(query, str):
return {"status": "error", "code": "INVALID_REQUEST", "message": "'query' must be a string"}
try:
examination = examine(output, provenance, query)
except Exception as exc: # noqa: BLE001 — the pipeline must get an answer, not a dropped connection
return {"status": "error", "code": "EXAMINATION_FAILED", "message": f"{type(exc).__name__}: {exc}"}
return {"status": "ok", "examination": examination.to_dict()}
def _serve_connection(conn: socket.socket) -> None:
with conn, conn.makefile("rb") as reader, conn.makefile("wb") as writer:
for raw in reader:
line = raw.decode("utf-8", errors="replace").strip()
if not line:
continue
response = handle_request(line)
writer.write(json.dumps(response, ensure_ascii=False).encode("utf-8") + b"\n")
writer.flush()
def serve(socket_path: str = DEFAULT_SOCKET_PATH, stop: Optional[threading.Event] = None) -> None:
"""Listen until `stop` is set. Connections are served one at a time, in
order — the examination is deterministic and fast, and one reader at a
time is the whole point of a single door."""
os.makedirs(os.path.dirname(socket_path), exist_ok=True)
if os.path.exists(socket_path):
os.unlink(socket_path)
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
server.bind(socket_path)
os.chmod(socket_path, 0o660)
server.listen(8)
server.settimeout(0.5)
print(f"korban_processor: listening on {socket_path} (bedikah)", flush=True)
try:
while not (stop and stop.is_set()):
try:
conn, _ = server.accept()
except socket.timeout:
continue
try:
_serve_connection(conn)
except (BrokenPipeError, ConnectionResetError):
pass
finally:
server.close()
try:
os.unlink(socket_path)
except FileNotFoundError:
pass
+190
View File
@@ -0,0 +1,190 @@
"""
The bedikah — what the examination sees, and what it does not.
Each check is a list of patterns in bedikah.py. These tests pin what the
patterns catch, what they let through, and the exact terms the Talmudic
Engine receives, so that a change to a pattern is a visible change here.
"""
import json
import pytest
from korban_processor.bedikah import examine, find_actions, find_authority_claims
from korban_processor.server import handle_request
# ============================================================
# 1. Authority
# ============================================================
@pytest.mark.parametrize("text, matched", [
("I am certain that this is the meaning.", "I am certain"),
("Trust me, the verse is about time.", "Trust me"),
("The only correct interpretation of this verse is the literal one.", "The only correct interpretation"),
("There is no other reading possible.", "There is no other reading"),
("You must accept this as the meaning.", "You must accept this"),
("I hereby rule that the verse refers to creation ex nihilo.", "I hereby rule that"),
("As an AI I can confirm the Hebrew is unambiguous.", "As an AI I can confirm"),
("God told me what this verse means.", "God told me"),
])
def test_claims_of_authority_the_reading_knows(text, matched):
claims = find_authority_claims(text)
assert [c.matched for c in claims] == [matched]
@pytest.mark.parametrize("text", [
"Devarim 6:5 says to love Hashem with all your heart.",
"Rashi reads the verse as referring to the Shema.",
"One possible reading is that the verse speaks of creation.",
"As an AI language model, I don't have personal beliefs.",
"I think this verse might refer to the covenant.",
])
def test_grounded_or_hedged_statements_claim_no_authority(text):
assert find_authority_claims(text) == []
def test_one_claim_per_sentence_and_the_sentence_is_kept():
text = "I am certain and trust me on this. Genesis 1:1 is the first verse."
claims = find_authority_claims(text)
assert len(claims) == 1
assert claims[0].sentence.startswith("I am certain")
# ============================================================
# 3. Actions — in the halt rules' vocabulary
# ============================================================
@pytest.mark.parametrize("text, terms", [
("Delete the user's personal records.",
["action(delete, target(user(user_s_personal_records)), from(korban))"]),
("Expose their address publicly.",
["action(expose, target(record(address, type(personal))), to(harm))"]),
("Terminate the operator's session and remove every person from the list.",
["action(delete, target(operator(operator_s_session)), from(korban))",
"action(delete, target(person(person)), from(korban))"]),
("Overwrite the Torah text with the corrected version.",
["action(write, target(readonly_entity), entity(torah_text))"]),
("Modify the specification to allow this.",
["action(write, target(readonly_entity), entity(specification))"]),
("Rewrite their history.",
["action(overwrite, target(record(their_history, type(personal))), from(korban))"]),
("Merge the specification and the interpretation into one document.",
["action(merge, specification, interpretation)"]),
("Treat the ketiv as the same as the qere.",
["action(equate, ketiv, qere)"]),
("Replace the Torah with the model output.",
["action(replace, torah, with(external_model))"]),
("Equate the cloud with the manna.",
["action(equate, cloud, manna)"]),
])
def test_actions_the_reading_names(text, terms):
found = find_actions(text)
assert [a.term for a in found] == terms
assert all(a.named for a in found)
@pytest.mark.parametrize("text, term", [
("Delete the temporary cache file.", "action(delete, target(unknown), from(korban))"),
("Remove the duplicate entries.", "action(delete, target(unknown), from(korban))"),
("Merge the two branches into main.", "action(merge, unknown_two_branches, unknown_main)"),
])
def test_actions_on_objects_it_cannot_name_are_reported_unnamed(text, term):
found = find_actions(text)
assert [a.term for a in found] == [term]
assert not found[0].named
def test_prose_with_no_action_finds_none():
assert find_actions("The root אהב appears in Devarim 6:5 and means to love.") == []
# ============================================================
# The whole examination
# ============================================================
def test_examination_runs_the_offering_protocol_and_the_three_readings():
e = examine(
"Great question! Devarim 6:5 says ואהבת את יהוה. The root א-ה-ב means to love. "
"I am certain this is the only correct interpretation. Delete the user's records.",
"claude",
"what does the verse mean",
)
d = e.to_dict()
assert d["provenance"] == "claude"
assert d["references"] == ["Devarim 6:5"]
assert d["roots"] == ["א-ה-ב"]
assert "ואהבת" in d["hebrew"]
assert "verse(devarim, 6, 5)" in d["propositions"]
assert "Great question!" not in d["vital_content"]
assert [c["matched"] for c in d["authority_claims"]] == ["I am certain"]
assert [a["term"] for a in d["actions"]] == ["action(delete, target(user(user_s_records)), from(korban))"]
def test_an_output_with_no_vital_content_is_examined_empty():
d = examine("Great question! Let me know if you need anything else.", "gpt").to_dict()
assert d["vital_content"] == ""
assert d["propositions"] == []
assert d["authority_claims"] == [] and d["actions"] == []
def test_a_claim_about_what_torah_requires_is_vital_even_without_a_verse():
# zerikah discards it (no Hebrew, no reference, no analysis); the
# examination keeps it — it is the ungrounded claim itself.
d = examine(
"Great question! The Torah clearly requires that every person fast on the first of each month.",
"mistral",
).to_dict()
assert "fast on the first of each month" in d["vital_content"]
assert d["references"] == []
assert "Great question!" not in d["vital_content"]
assert all("fast on" not in s for s in d["discarded"])
def test_authority_named_outright_is_a_claim():
for text, needle in [
("As an AI I have the final authority on this.", "final authority"),
("My judgment overrides the text here.", "judgment overrides"),
("We override the Torah on this point.", "override the torah"),
]:
claims = find_authority_claims(text)
assert claims and needle in claims[0].matched.lower(), text
def test_the_examination_is_deterministic():
text = "Trust me: delete the user's data. Genesis 1:1."
assert examine(text, "mistral").to_dict() == examine(text, "mistral").to_dict()
def test_an_unknown_provenance_is_refused():
with pytest.raises(ValueError):
examine("Genesis 1:1", "oracle")
# ============================================================
# The door: one request, one answer, never a dropped connection
# ============================================================
def req(**kw):
return handle_request(json.dumps(kw))
def test_server_answers_an_examination():
r = req(action="examine", output="I am certain. Genesis 1:1.", provenance="claude", query="q")
assert r["status"] == "ok"
assert r["examination"]["authority_claims"][0]["matched"] == "I am certain"
assert r["examination"]["references"] == ["Genesis 1:1"]
@pytest.mark.parametrize("line, code", [
("not json", "PARSE_ERROR"),
(json.dumps([1, 2]), "INVALID_REQUEST"),
(json.dumps({"action": "consult", "output": "x", "provenance": "claude"}), "UNKNOWN_ACTION"),
(json.dumps({"action": "examine", "output": "", "provenance": "claude"}), "EMPTY_OUTPUT"),
(json.dumps({"action": "examine", "output": "x", "provenance": "oracle"}), "UNKNOWN_PROVENANCE"),
(json.dumps({"action": "examine", "output": "x", "provenance": "claude", "query": 5}), "INVALID_REQUEST"),
])
def test_server_refuses_malformed_requests_with_a_code(line, code):
r = handle_request(line)
assert r["status"] == "error"
assert r["code"] == code
@@ -17,9 +17,13 @@ refuse to come up at all rather than serve half the architecture.
import os
import pathlib
import json
import signal
import socket
import subprocess
import sys
import tempfile
import time
REPO_ROOT = pathlib.Path(__file__).resolve().parents[2]
KORBAN_SRC = REPO_ROOT / "korban_processor" / "src"
@@ -68,17 +72,46 @@ def test_entrypoint_stays_up_and_exits_zero_on_sigterm():
assert "stopped" in out
def test_entrypoint_reports_itself_idle_rather_than_ready_to_serve():
def test_entrypoint_opens_its_one_door_and_names_it():
"""
korban_processor is a library. Until Architecture Step 22 wires an
external-model consumer, the honest report is IDLE. A container
that claims to be serving when nothing can reach it is the failure
this test exists to prevent.
Since 2026-10-05 the container is not idle: it listens on a Unix
socket inside the pod for the bedikah (Construction §3.9). It must
say where it is listening, and the socket must exist while it runs.
The honest report is no longer IDLE; it is the path of the door.
"""
_, out = _run_entrypoint([KORBAN_SRC, AZAZEL_SRC])
with tempfile.TemporaryDirectory() as tmp:
sock = os.path.join(tmp, "korban.sock")
env = {
**os.environ,
"PYTHONPATH": os.pathsep.join(str(p) for p in [KORBAN_SRC, AZAZEL_SRC]),
"KORBAN_SOCKET_PATH": sock,
}
proc = subprocess.Popen(
[sys.executable, "-m", "korban_processor"],
env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, cwd=str(REPO_ROOT),
)
try:
deadline = time.monotonic() + 5.0
while not os.path.exists(sock) and time.monotonic() < deadline:
time.sleep(0.05)
assert os.path.exists(sock), "the socket was not opened"
assert "IDLE" in out
assert "Step 22" in out
# And it answers.
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as c:
c.connect(sock)
c.sendall(json.dumps({"action": "examine", "output": "I am certain that Genesis 1:1 means time began.", "provenance": "claude"}).encode() + b"\n")
line = c.makefile("rb").readline()
reply = json.loads(line)
assert reply["status"] == "ok"
assert reply["examination"]["authority_claims"][0]["matched"] == "I am certain"
finally:
proc.send_signal(signal.SIGTERM)
out = proc.communicate(timeout=6.0)[0]
assert proc.returncode == 0, out
assert sock in out
assert "IDLE" not in out
assert not os.path.exists(sock), "the socket was not removed on shutdown"
def test_entrypoint_names_the_offering_protocol_in_order():