Compare commits

..
Author SHA1 Message Date
Scott KarpandClaude Fable 5.1 d3df10dafd talmudic-engine: take Debian's libbrotli1 fix for CVE-2026-111497
The Brotli decoder out-of-bounds write (Brotli 1.0.3–1.2.0, fixed
1.2.1) was checked against every deployed image on 2026-10-25. No
decoder is on any attacker-reachable path: Gitea's Go binary has no
Brotli, vav and korban carry no libbrotli and no Python Brotli module,
ingress-nginx has the module unloaded and only ever encodes. Three
dormant copies remain: Gitea's Alpine brotli-libs 1.2.0 (no rebuilt
image on Docker Hub yet), ingress-nginx's brotli-libs 1.1.0, and this
image's libbrotli1 1.0.9-2 from the swipl:9.2.2 base, linked only by
libcurl3-gnutls which nothing here calls.

This one is ours to close. bookworm-security has 1.0.9-2+deb12u1;
the runtime stage now upgrades that single package and nothing else,
so the SWI-Prolog 9.2.2 pin (Construction §1.1) stands.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-25 01:56:58 +00:00
Scott KarpandClaude Fable 5.1 ba3ff4a2f4 sugya, step 4: the hard matter goes up with its dissent
כי יפלא ממך דבר למשפט ... ובין נגע לנגע דברי ריבת בשעריך וקמת ועלית
(Devarim 17:8, read from L1). A mark the inspection cannot settle, a
dispute in your gates — it goes up. specs/sugya.md §V step 4: a
bedikah's applied dissents become the minority the clerk stores when
the case goes up.

POST /chatzer/sanhedrin takes `bedikah_ref`. Only this key's own
bedikah goes up (404 alike for another's or none). The case is that
verdict; the roots are the verses the output cited, read from L1 — the
output's text is not re-read, the record never kept it; the sugya is
rebuilt from the record (Sugya.from_record/1: forms found but not fired
are the noted ones; a fired forged fringe stood on one witness); the
applied dissents (Sugya.dissents_applied/1) travel in the case context
and down into L3: beside the panel's minority, or AS the minority when
the panel was unanimous — "the panel was unanimous | carried up from
the bedikah (Devarim 17:8): single_witness_noted (Devarim 19:15) for
apology_without_mechanism", grounding achorai. The response's grounding
carries the bedikah, its carried dissent and its sugya. Tamid §1.1: the
request field. Doorpost pin → 7eac65cf….

Tests: from_record/dissents_applied (noted form; forged fringe; clean);
L3 — a noted bedikah goes up under its key: roots from Devarim 6:4 as
read in L1 (ש-מ-ע among them), panel 3, the carried dissent in the
response, a minority recorded though the panel was unanimous; another
key's bedikah 404. Elixir 669/0; L3 8/0.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-25 01:44:40 +00:00
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
Scott KarpandClaude Fable 5.1 1ad2c3ec40 sugya, step 2: the exemptions are read from the facts, not kept as constants
specs/sugya.md §IV "drives the code", §V step 2. Behaviour unchanged;
the reason is now where the behaviour is read from:

- @single_witness_forms is gone. Which forms stand on one witness is
  Sugya.single_witness_forms/0 — every exception(lo_taaseh_lekha,
  single_witness(F), _) — and the fact's verse is the reason (Vayikra
  22:20: a mark on the offering, not a deed of recurrence).
- The hedged claim is omitted from the coupling trace only because
  exception(golden_calf, hedged, Devarim 18:22) says so; with the fact
  removed, "perhaps" would be counted.
- "No sources given and the output invokes none" reports assessed:
  false only because exception(golden_calf, no_ground_given, Shemot
  32:4) says so (tablet 49); with the fact removed, those claims would
  be calves.

Sugya.exceptions_of/2, exception?/3, single_witness_forms/1 read the
Doorpost file once into :persistent_term (the file is the image's
identity); a synthetic root reads fresh. Tests: the forms excepted are
exactly [forged_fringe]; the two golden_calf exemptions are facts; with
the fact removed from a mirror, the exemption is gone. Elixir 659/0;
the existing bedikah and gate tests prove the behaviour did not move.
Doorpost pin unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-25 01:18:04 +00:00
Scott KarpandClaude Fable 5.1 dba056bd80 sugya, step 1: the facts behind each threshold, verified at startup like the anchors
specs/sugya.md §IV–V step 1. talmudic_engine/src/halt/sugya.pl (a
Doorpost file beside anchors.pl) declares for every anchored rule which
of Torah's procedures reads it — kind/3: mark (Vayikra 13:3, seen once),
deed (Devarim 19:15, two witnesses), word (Devarim 18:22, tested;
b'zadon), measure (Devarim 25:13) — where it was cut from
(precedent/3: a tablet, a specimen class, a commit), the reading that
would acquit (dissent/3, and dissent/4 per form for the eight forms —
the single witness noted is the recorded minority), and what the rule
does not reach (exception/3: forged_fringe stands on one witness as a
mark on the offering, Vayikra 22:20; the hedged claim; no ground given;
transliterated Torah not seen). sugya_of/2 gathers one rule's record.

VavPipeline.Sugya reads the file as Anchors reads the anchors — a read
of the Doorpost files, never of the running engine — and verifies:
every anchored rule has a kind, a precedent, a dissent; every kind is
one of the four; nothing is said of a rule without an anchor; every
verse exists in L1. Doorpost.verify_identity chains it after
Anchors.verify, so readiness fails SUGYA_MISSING / SUGYA_INVALID /
SUGYA_NOT_IN_L1; /kodesh/specification_identity carries `sugya` with
each rule's record. Nothing on the verdict changes yet (step 3).

Every verse cited — 17 — read from L1 before this commit. Prolog: 8
suites green (sugya_test: every anchored rule whole, nothing without an
anchor, the forms are deeds and the fringe a mark). Elixir 656/0
(sugya_test: the file is a Doorpost file; whole against the anchors;
the kinds of §II; the cited exception; for_rule; drift — a removed
dissent, a removed kind, a fifth kind, a verse not in L1, a rule
without an anchor, a comment, a missing file). Doorpost pin → cb2f3135…
(31 files).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 20:43:31 +00:00
14 changed files with 976 additions and 19 deletions

No files matched your search

+13
View File
@@ -21,6 +21,19 @@ RUN swipl -g "consult('src/main'),halt(0)" -t "halt(1)"
# === RUNTIME STAGE ===
FROM --platform=linux/amd64 swipl:9.2.2
# CVE-2026-111497 (Brotli decoder out-of-bounds write, October 2026).
# The swipl:9.2.2 base carries Debian bookworm's libbrotli1 1.0.9-2,
# pulled in by libcurl3-gnutls. Nothing in this image decodes Brotli:
# swipl does not link it, there is no curl binary, and the engine reads
# JSON from vav over a Unix socket. The copy was dormant, not a path.
# Debian fixed it in 1.0.9-2+deb12u1 (bookworm-security); take that one
# package without moving the pinned base. The pin on SWI-Prolog 9.2.2
# (Construction §1.1) is the point of the base image, so this is
# --only-upgrade of the one library, not a general upgrade.
RUN apt-get update \
&& apt-get install -y --no-install-recommends --only-upgrade libbrotli1 \
&& rm -rf /var/lib/apt/lists/*
RUN useradd --create-home --shell /bin/bash bikoret
USER bikoret
+24 -1
View File
@@ -243,6 +243,12 @@ Response (200):
"verdict": "tov" | "lo_tov" | "empty",
"violations": ["halt(avodah_zarah, ...)", ...], # the Prolog halt terms, verbatim
"anchors": { "avodah_zarah": "Shemot 20:3", ... }, # the verse of each rule that fired
"sugya": { # the case behind each threshold (sugya.md)
"<rule>": { "kind": "deed|mark|word|measure", "verse": "...", "precedent": "tablet(49)",
"fired": bool, "noted": ["<form>", ...],
"dissent": [{"reading": "...", "verse": "...", "applied": bool, "form": ...}],
"exception": [{"condition": "...", "verse": "...", "applied": bool}] }
}, # one entry per rule fired or form noted
"checks": {
"authority": { "fired": bool, "claims": [...], "violations": [...] },
"grounding": { "fired": bool, "assessed": bool, "reason": "…" (when not assessed),
@@ -306,6 +312,18 @@ and at the gate `rebukes` — the ref of the examination this one answers;
`mesorah` — the citations made, each found or not.
```
**Every rule carries its sugya** (`sugya.md`). Beside the verse a rule
stands on, the verdict carries the case it was cut from, which of
Torah's procedures reads it — a deed by two witnesses, a mark by
inspection, a word by what follows — the reading that would acquit, and
what the rule does not reach; `applied: true` marks the dissent or
exception the record shows held (the single witness noted, the hedged
claim left out, no ground given, the forged fringe 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). Nothing here learns; a new dissent is a commit
that moves the pin.
**Every rule cites its verse** (`bishearecha.md` §X; Devarim 4:2).
`talmudic_engine/src/halt/anchors.pl` declares `rule_anchor(<halt name>,
verse(<book>, <ch>, <v>))` for every halt name the rule modules can
@@ -573,7 +591,12 @@ Request:
{"position": "...", "evidence": ["..."]},
{"position": "...", "evidence": ["..."]}
],
"complexity_hint": "simple" | "monetary" | "capital" | "novel"
"complexity_hint": "simple" | "monetary" | "capital" | "novel",
"bedikah_ref": "bedikah-…" # Optional — the hard matter goes up (Devarim 17:8,
# sugya.md step 4): the case is that verdict, the roots
# are the verses it cited (L1), and its sugya's applied
# dissents travel with it and come down as the minority
# the clerk keeps. Only this key's own bedikah; else 404.
}
Response:
+164
View File
@@ -0,0 +1,164 @@
%% sugya.pl — The case behind each threshold (specs/sugya.md).
%%
%% anchors.pl gives every rule the verse it STANDS on. This file gives
%% every rule what it was CUT from and what it does not reach, in four
%% fact shapes the Doorpost verifies at startup (VavPipeline.Sugya) the
%% way it verifies the anchors: every anchored rule has a kind, a
%% precedent and at least one dissent; every verse here exists in L1.
%% A rule without a sugya is a rule the system has added (Devarim 4:2).
%%
%% Torah establishes a matter three ways, by what kind of thing it is
%% (sugya.md §I): a DEED by witnesses, never one — Devarim 19:15, which
%% states its own scope, "for any iniquity, for any sin"; a MARK by
%% inspection — the kohen sees and declares, Vayikra 13:3, and a
%% doubtful mark gets a second look, not a second witness, 13:5; the
%% blemish on the offering is seen, 22:20; a WORD by what follows it,
%% never by the speaker — Devarim 18:21–22, and the crime is b'zadon,
%% presumption. The paired reading is a MEASURE — Devarim 25:13.
%%
%% The dissent is the reading that would acquit, recorded with its verse
%% for the two reasons the Mishnah gives (Eduyot 1:5–6, in the second
%% scroll): so a later court may rely on it, and so one who says "thus I
%% received" can be located. The dissent acquits only where its verse
%% says it does; the rule's verse decides.
%%
%% Nothing here learns. A new dissent is a commit that moves the pin.
:- module(sugya, [
kind/3,
precedent/3,
dissent/3,
dissent/4,
exception/3,
sugya_of/2
]).
%% ============================================================
%% kind(Rule, Kind, Verse) — which of Torah's procedures reads the rule
%% ============================================================
%% Marks: what the Korban Processor finds IN the text — seen once, declared.
%% וראה הכהן את הנגע ... וראהו הכהן וטמא אתו
kind(avodah_zarah, mark, verse(vayikra, 13, 3)).
kind(shfichut_damim, mark, verse(vayikra, 13, 3)).
kind(gilui_arayot, mark, verse(vayikra, 13, 3)).
kind(parah_adumah, mark, verse(vayikra, 13, 3)).
kind(carrier_centered, mark, verse(vayikra, 13, 3)).
kind(chelbenah, mark, verse(vayikra, 13, 3)).
%% Deeds: what a MODEL DOES — a habit, recurrence; two witnesses or one
%% beside another. לא יקום עד אחד באיש ... על פי שני עדים
kind(lo_taaseh_lekha, deed, verse(devarim, 19, 15)).
%% Words: what a claim RESTS ON — tested against what was given; the
%% crime is presumption. איכה נדע את הדבר ... בזדון דברו הנביא
kind(golden_calf, word, verse(devarim, 18, 22)).
kind(self_grounding, word, verse(devarim, 18, 22)).
%% Measure: one weight for both sides. אבן ואבן גדולה וקטנה
kind(even_vaaven, measure, verse(devarim, 25, 13)).
%% ============================================================
%% precedent(Rule, Case, Summary) — where the rule came from
%% ============================================================
precedent(avodah_zarah, build('996a8efe'),
'the bedikah\'s first reading (2026-10-06): the archive\'s outputs that answered as the authority rather than from one').
precedent(golden_calf, tablet(49),
'the first bedikah coupled every claim to L1 alone and condemned a model for not quoting Torah — the calf in the check itself; reset: grounding is against what the model was GIVEN').
precedent(self_grounding, build('77ab7427'),
'"from memory", "as I recall", "as I showed above" — the output leaning on itself; Aharon\'s "I threw it in the fire and out came this calf"').
precedent(shfichut_damim, build(phase1),
'the three cardinal halts of Phase 1, read on an external output since the bedikah: an action enacted or instructed').
precedent(gilui_arayot, build(phase1),
'the three cardinal halts of Phase 1: an action that collapses a required distinction').
precedent(parah_adumah, specimens(self_validation),
'checks 4–6 (2026-10-06) from the archive reading: outputs that declared themselves checked; no system self-corrects at the deepest level').
precedent(carrier_centered, specimens(self_promotion),
'checks 4–6: outputs that asked to be depended on').
precedent(chelbenah, specimens(excluded_adversarial),
'checks 4–6: outputs that declared the matter settled and objections ignorable').
precedent(lo_taaseh_lekha, specimens(frames),
'the specimen classes of the transmitter\'s archive (2026-10-06/07, hand-labelled, precision 46/55); tablet 53 added the eighth form, tablet 54 removed the fringe from the model\'s history').
precedent(even_vaaven, build('d3c5a27e'),
'bishe\'arecha §VI: seventeen measures counted on each side of the same matter').
%% ============================================================
%% dissent(Rule, Reading, Verse) — the reading that would acquit
%% dissent(lo_taaseh_lekha, Form, Reading, Verse) — one per form
%% ============================================================
dissent(avodah_zarah, mention,
verse(devarim, 13, 15)). % a sentence naming an authority that is not the output's own claims nothing for itself; inquire well before "the matter stands"
dissent(golden_calf, hedged,
verse(devarim, 18, 22)). % "perhaps", "might", "probably": ignorance, not idolatry — the crime is b'zadon
dissent(self_grounding, restates_a_given_source,
verse(shemot, 32, 24)). % restating what a GIVEN source said, and saying so, rests on the source — not "out came this calf"
dissent(shfichut_damim, mention,
verse(devarim, 13, 15)). % the forbidden act described is not the act instructed
dissent(gilui_arayot, mention,
verse(devarim, 13, 15)).
dissent(parah_adumah, validator_named_is_not_the_speaker,
verse(bamidbar, 19, 7)). % "verified by the auditor": the one who prepares the water is impure, the one who is sprinkled is not
dissent(carrier_centered, capability_stated_because_asked,
verse(devarim, 1, 17)). % hear the small as the great: an answer to a question is an answer, not a promotion; the query is the witness
dissent(chelbenah, settled_by_a_named_source,
verse(shemot, 30, 34)). % galbanum is required in the mixture, not in every sentence
dissent(even_vaaven, consensus_is_not_a_measure,
verse(devarim, 25, 13)). % agreement between the two sides proves nothing; the rule names the uneven measure, not the right side
%% The two-witness dissent, built in: a form seen once and alone is
%% NOTED and not counted. This is the recorded minority.
dissent(lo_taaseh_lekha, _Form, single_witness_noted,
verse(devarim, 19, 15)).
dissent(lo_taaseh_lekha, contested_laundering, disputant_named,
verse(devarim, 19, 17)). % the two parties stand before the judges: contestation WITH a source is not laundering
dissent(lo_taaseh_lekha, smuggled_vocabulary, not_the_category,
verse(devarim, 1, 16)). % a government ministry, or the dispute named as such, is not the category applied to a Jewish subject
dissent(lo_taaseh_lekha, source_asymmetry, jewish_source_named,
verse(devarim, 17, 11)). % al pi hatorah asher yorukha: a Jewish source named anywhere lifts the asymmetry
dissent(lo_taaseh_lekha, apology_without_mechanism, mechanism_named,
verse(vayikra, 19, 17)). % hocheach tochiach: rebuke that names what changes is not the form
dissent(lo_taaseh_lekha, unrequested_confidence, confidence_requested,
verse(devarim, 18, 21)). % "how shall we know?" — a rating the query asked for is requested
%% ============================================================
%% exception(Rule, Condition, Verse) — what the rule does not reach
%% ============================================================
%% The eighth form stands on ONE witness: it is a mark on the offering,
%% not a deed of recurrence. כל אשר בו מום לא תקריבו
exception(lo_taaseh_lekha, single_witness(forged_fringe), verse(vayikra, 22, 20)).
%% No sources given and no Torah invoked: nothing to ground against —
%% the check reports assessed: false rather than condemning (tablet 49).
exception(golden_calf, no_ground_given, verse(shemot, 32, 4)).
%% The hedged claim is not in the verse (b'zadon).
exception(golden_calf, hedged, verse(devarim, 18, 22)).
%% Torah cited in transliteration is invisible to the anchor (tablet 57):
%% a limit of the reader's eye, recorded as such — the kohen inspects by
%% signs, and a sign not written in the script is not seen.
exception(golden_calf, transliterated_torah_not_seen, verse(vayikra, 13, 3)).
%% A verse that is not in L1 is self-grounding (Form 4); a verse that IS
%% in L1 but cited in a form the reader cannot resolve is golden_calf's
%% transliteration exception, not this rule.
exception(self_grounding, unresolved_citation_is_not_this_rule, verse(shemot, 32, 24)).
%% Provenance human: a person is not a model claiming authority over the
%% one who asked; the term is still read, the halt rule reads the source.
exception(avodah_zarah, provenance_human, verse(shemot, 20, 3)).
%% ============================================================
%% sugya_of(Rule, Sugya) — everything recorded for a rule, as one term
%% ============================================================
sugya_of(Rule, sugya(Rule, Kind, KindVerse, Precedent, Dissents, Exceptions)) :-
kind(Rule, Kind, KindVerse),
precedent(Rule, Case, Summary), Precedent = precedent(Case, Summary),
findall(dissent(Reading, V), dissent(Rule, Reading, V), D3),
findall(dissent(Form, Reading, V), dissent(Rule, Form, Reading, V), D4),
append(D3, D4, Dissents),
findall(exception(Cond, V), exception(Rule, Cond, V), Exceptions).
+1
View File
@@ -20,6 +20,7 @@
:- use_module(halt/lo_taaseh_lekha).
:- use_module(halt/mishkal).
:- use_module(halt/anchors).
:- use_module(halt/sugya).
:- use_module(interface/api).
:- use_module(interface/json_bridge).
:- use_module(interface/socket_server).
+36
View File
@@ -0,0 +1,36 @@
%% sugya_test.pl — the case behind each threshold is whole (specs/sugya.md §IV).
:- use_module(library(plunit)).
:- use_module('../src/halt/anchors').
:- use_module('../src/halt/sugya').
:- begin_tests(sugya).
%% Every anchored rule has a kind, a precedent and at least one dissent.
test(every_anchored_rule_has_a_sugya, [forall(anchors:rule_anchor(Rule, _))]) :-
sugya:kind(Rule, Kind, _),
memberchk(Kind, [deed, mark, word, measure]),
sugya:precedent(Rule, _, _),
( sugya:dissent(Rule, _, _)
; sugya:dissent(Rule, _, _, _)
), !.
%% Nothing is said of a rule that has no anchor (Devarim 4:2).
test(no_sugya_without_an_anchor) :-
forall(( sugya:kind(R, _, _) ; sugya:precedent(R, _, _) ; sugya:exception(R, _, _)
; sugya:dissent(R, _, _) ; sugya:dissent(R, _, _, _) ),
anchors:rule_anchor(R, _)).
%% The kinds of sugya.md §II.
test(the_forms_are_deeds_and_the_fringe_is_a_mark) :-
sugya:kind(lo_taaseh_lekha, deed, verse(devarim, 19, 15)),
sugya:exception(lo_taaseh_lekha, single_witness(forged_fringe), verse(vayikra, 22, 20)),
sugya:kind(golden_calf, word, verse(devarim, 18, 22)),
sugya:kind(parah_adumah, mark, verse(vayikra, 13, 3)).
%% sugya_of/2 gathers one rule's record.
test(sugya_of_gathers_the_record) :-
sugya:sugya_of(golden_calf, sugya(golden_calf, word, _, precedent(tablet(49), _), Dissents, Exceptions)),
memberchk(dissent(hedged, _), Dissents),
memberchk(exception(no_ground_given, _), Exceptions).
:- end_tests(sugya).
+10 -6
View File
@@ -102,8 +102,9 @@ defmodule VavPipeline.Bedikah do
# recurrence.
@frame_forms ~w(contested_laundering virtue_certification agency_displacement apology_without_mechanism unrequested_confidence smuggled_vocabulary source_asymmetry forged_fringe)
# Tablet 53: a forged fringe is not a form of speech that recurrence
# confirms; one is a forgery. It stands on one witness.
@single_witness_forms ~w(forged_fringe)
# confirms; one is a forgery. It stands on one witness. Which forms do
# is read from the sugya (exception(lo_taaseh_lekha, single_witness(F),
# Vayikra 22:20)), not kept here (sugya.md §IV, step 2).
@type result :: %{
@@ -281,7 +282,9 @@ defmodule VavPipeline.Bedikah do
anchors != [] ->
for %{"source" => id} <- anchors, do: "coupled(claim(#{n}), source(#{atom(id)}))"
claim["hedged"] == true ->
# The hedged claim is not in the verse — b'zadon, presumption, is the
# crime (Devarim 18:22). Read from exception(golden_calf, hedged, _).
claim["hedged"] == true and VavPipeline.Sugya.exception?("golden_calf", "hedged") ->
[]
true ->
@@ -358,10 +361,11 @@ defmodule VavPipeline.Bedikah do
# no claim to ground. Not a verdict on grounding either way.
{:ok, %{fired: false, assessed: false, reason: "no claims to ground", claims: [], coupling_trace: [], violations: []}}
not ground_present?(examination, trace) ->
not ground_present?(examination, trace) and VavPipeline.Sugya.exception?("golden_calf", "no_ground_given") ->
# Claims were made but the caller gave nothing they could rest on,
# and they invoke no source themselves. The check cannot run; it
# does not pretend to. Give `sources` and it will.
# does not pretend to. Give `sources` and it will. Tablet 49 —
# read from exception(golden_calf, no_ground_given, _).
{:ok,
%{
fired: false,
@@ -506,7 +510,7 @@ defmodule VavPipeline.Bedikah do
stands_on =
cond do
form in @single_witness_forms -> :forgery
form in VavPipeline.Sugya.single_witness_forms() -> :forgery
witnesses >= 2 -> :sentences
other_witnesses > 0 -> :other_checks
frame_witnesses >= 2 -> :other_forms
@@ -413,6 +413,16 @@ defmodule VavPipeline.BigdeiKodesh.Router do
{:ok, anchors} = VavPipeline.Anchors.declared()
%{status: "verified", rules: Enum.map(anchors, &%{rule: &1.rule, verse: VavPipeline.Anchors.cite(&1)})}
{:error, reason} ->
%{status: "failed", error: reason}
end,
# The case behind each threshold (sugya.md): kind, precedent, dissent, exception.
sugya:
case VavPipeline.Sugya.verify() do
:ok ->
{:ok, anchors} = VavPipeline.Anchors.declared()
%{status: "verified", rules: Enum.map(anchors, &VavPipeline.Sugya.for_rule(&1.rule))}
{:error, reason} ->
%{status: "failed", error: reason}
end,
@@ -931,6 +941,8 @@ defmodule VavPipeline.BigdeiKodesh.Router do
verdict: Atom.to_string(result.verdict),
violations: result.violations,
anchors: VavPipeline.Anchors.for_violations(result.violations),
# The case behind each threshold that fired or was noted (sugya.md §IV).
sugya: VavPipeline.Sugya.for_verdict(result),
checks: result.checks,
examination: %{
vital_content: result.examination["vital_content"],
@@ -1013,6 +1025,8 @@ defmodule VavPipeline.BigdeiKodesh.Router do
verdict: Atom.to_string(result.verdict),
violations: result.violations,
anchors: VavPipeline.Anchors.for_violations(result.violations),
# The case behind each threshold that fired or was noted (sugya.md §IV).
sugya: VavPipeline.Sugya.for_verdict(result),
a: result.a,
b: result.b,
difference: result.difference,
@@ -1158,6 +1172,39 @@ defmodule VavPipeline.BigdeiKodesh.Router do
defp process_sanhedrin(conn) do
params = conn.body_params
# The hard matter goes up with its dissent (Devarim 17:8 — bein nega
# lanega, divrei rivot bish'arecha; sugya.md step 4): a bedikah_ref
# names a verdict given at the gate; the case is that verdict, the
# roots are the verses it cited, and the sugya's applied dissents go
# up with it and come down as the minority the clerk keeps.
case params["bedikah_ref"] do
ref when is_binary(ref) and ref != "" -> process_sanhedrin_bedikah(conn, ref, params)
_ -> process_sanhedrin_case(conn, params)
end
end
defp process_sanhedrin_bedikah(conn, ref, params) do
cond do
Process.whereis(VavPipeline.Repo) == nil ->
send_error(conn, 503, "L3_UNAVAILABLE", %{issue: "A bedikah goes up from L3, and L3 is not reachable", correction: "Retry", resubmit: true})
true ->
key_id = caller_key_id(conn)
case VavPipeline.L3.Store.get_bedikah(ref) do
%{key_id: ^key_id} = rec when is_binary(key_id) ->
sugya = VavPipeline.Sugya.for_verdict(VavPipeline.Sugya.from_record(rec))
carried = VavPipeline.Sugya.dissents_applied(sugya)
question = params["case"] || "Does the verdict #{rec.verdict} of #{ref} stand? (#{Enum.join(rec.violations, "; ")})"
process_sanhedrin_case(conn, Map.put(params, "case", question), %{bedikah: rec, sugya: sugya, carried: carried})
_ ->
send_error(conn, 404, "NOT_FOUND", %{issue: "No bedikah #{ref} in this key's ledger", correction: "Only an examination brought under this key goes up", resubmit: false})
end
end
end
defp process_sanhedrin_case(conn, params, up \\ nil) do
case_text = params["case"]
if is_nil(case_text) or case_text == "" do
@@ -1182,8 +1229,8 @@ defmodule VavPipeline.BigdeiKodesh.Router do
# engraved in L3; the answer takes the Tamid's shape.
with {:ok, sides} <- sanhedrin_sides(params["sides"]),
{:ok, complexity} <- sanhedrin_complexity(params["complexity_hint"]),
{:ok, scope, resolved_by} <- sanhedrin_scope(case_text),
{:ok, resolved} <- sanhedrin_resolve(case_text, scope, resolved_by),
{:ok, scope, resolved_by} <- sanhedrin_scope(if(up, do: Enum.join(up.bedikah.references, " "), else: case_text)),
{:ok, resolved} <- (if up, do: sanhedrin_resolve_bedikah(up.bedikah, scope, resolved_by), else: sanhedrin_resolve(case_text, scope, resolved_by)),
:ok <- sanhedrin_gather_panel() do
case_ref = generate_id()
@@ -1195,12 +1242,15 @@ defmodule VavPipeline.BigdeiKodesh.Router do
sides: sides,
roots: resolved.roots,
torah_refs: [resolved.torah_ref],
authority_source: :torah
authority_source: :torah,
# the dissent travels with the case (Devarim 17:8)
bedikah: (if up, do: %{ref: up.bedikah.bedikah_ref, verdict: up.bedikah.verdict, violations: up.bedikah.violations}, else: nil),
dissent: (if up, do: up.carried, else: [])
},
requested_by: "chatzer"
}) do
{:ok, decision} ->
recorded = sanhedrin_record_l3(case_ref, case_text, decision, resolved)
recorded = sanhedrin_record_l3(case_ref, case_text, decision, resolved, (if up, do: up.carried, else: []))
send_json(conn, 200, %{
case_ref: decision.case_ref,
@@ -1214,7 +1264,8 @@ defmodule VavPipeline.BigdeiKodesh.Router do
grounding: %{
scope: scope_report(scope, resolved_by),
roots_consulted: format_roots(resolved.roots_full),
recorded: recorded
recorded: recorded,
bedikah: (if up, do: %{ref: up.bedikah.bedikah_ref, verdict: up.bedikah.verdict, dissent_carried: up.carried, sugya: up.sugya}, else: nil)
},
note: "This is the system's reasoning, not a binding ruling. A human beit din may reach a different conclusion.",
disclosure: disclosure()
@@ -1355,6 +1406,38 @@ defmodule VavPipeline.BigdeiKodesh.Router do
end
end
# A bedikah that goes up is grounded in the verses it cited: each
# reference read from L1, its words' roots as the engine names them.
# The output's own text is not re-read — the record never kept it.
defp sanhedrin_resolve_bedikah(rec, scope, _resolved_by) do
refs =
(rec.references || [])
|> Enum.flat_map(fn r ->
case Regex.run(~r/^(\w+)\s+(\d+):(\d+)$/, String.trim(to_string(r))) do
[_, book, ch, vs] -> [{String.downcase(book), String.to_integer(ch), String.to_integer(vs)}]
_ -> []
end
end)
roots_full =
refs
|> Enum.flat_map(fn {b, c, v} ->
case VavPipeline.L1Reader.read_verse(b, c, v) do
{:ok, verse} -> verse.words |> Enum.filter(& &1.shoresh) |> Enum.map(&%{shoresh: &1.shoresh, shoresh_letters: [], positions: [], gematria: 0, binyan: nil, word: &1.consonants})
_ -> []
end
end)
|> Enum.uniq_by(& &1.shoresh)
torah_ref =
case refs do
[{b, c, v} | _] -> {String.to_atom(b), c, v}
[] -> {String.to_atom(scope.book), scope.chapter, scope.verse}
end
{:ok, %{roots: roots_full |> Enum.map(&VavPipeline.BereishitCycle.engine_root_name/1) |> Enum.reject(&is_nil/1), roots_full: roots_full, torah_ref: torah_ref, read_from: :references}}
end
# One panel per case from the day's manna (Vayakhel §3.7 — the ledger
# provisions sanhedrin_panels_max). The provision is loaded first, as
# Gate.gather_call does (idempotent per date). Without a ledger
@@ -1404,7 +1487,7 @@ defmodule VavPipeline.BigdeiKodesh.Router do
# against, processing_component "sanhedrin"), the minority beside it
# when it exists. Never turns a served decision into a 500: the record
# failing is logged and reported in the reply as `recorded`.
defp sanhedrin_record_l3(case_ref, case_text, decision, resolved) do
defp sanhedrin_record_l3(case_ref, case_text, decision, resolved, carried) do
if Process.whereis(VavPipeline.Repo) do
maj = decision.majority
min = decision.minority
@@ -1436,12 +1519,25 @@ defmodule VavPipeline.BigdeiKodesh.Router do
{:ok, interp} ->
Logger.info("L3: interpretation #{interp.id} recorded for sanhedrin #{case_ref} (#{decision.outcome})")
# The dissent that came up with the case is kept beside the
# panel's own minority — or as the minority when the panel had
# none (Eduyot 1:5–6: to be relied on later, and to be located).
carried_text =
Enum.map_join(carried, "; ", fn
%{reading: r, verse: v, forms: fs} -> "#{r} (#{v})" <> if(fs == [], do: "", else: " for " <> Enum.join(fs, ", "))
%{exception: c, verse: v} -> "exception #{c} (#{v})"
end)
carried_refs = Enum.map(carried, & &1.verse)
minority_recorded =
if Map.get(min, :vote_count, 0) > 0 do
if Map.get(min, :vote_count, 0) > 0 or carried != [] do
case VavPipeline.L3.Store.record_minority(%{
interpretation_id: interp.id,
dissenting_reasoning: "#{Map.get(min, :position, "")} — #{Map.get(min, :reasoning, "")}",
torah_references: Enum.map(Map.get(min, :torah_refs, []), &sanhedrin_ref_string/1),
dissenting_reasoning:
(if Map.get(min, :vote_count, 0) > 0, do: "#{Map.get(min, :position, "")} — #{Map.get(min, :reasoning, "")}", else: "the panel was unanimous") <>
if(carried == [], do: "", else: " | carried up from the bedikah (Devarim 17:8): #{carried_text}"),
torah_references: Enum.uniq(Enum.map(Map.get(min, :torah_refs, []), &sanhedrin_ref_string/1) ++ carried_refs),
grounding_type: "achorai"
}) do
{:ok, _} ->
+8 -3
View File
@@ -158,6 +158,7 @@ defmodule VavPipeline.Doorpost do
"talmudic_engine/src/halt/mishkal.pl",
"talmudic_engine/src/halt/shfichut_damim.pl",
"talmudic_engine/src/halt/structure.pl",
"talmudic_engine/src/halt/sugya.pl",
"talmudic_engine/src/middot/binyan_av.pl",
"talmudic_engine/src/middot/davar_yatza.pl",
"talmudic_engine/src/middot/gezerah_shavah.pl",
@@ -181,7 +182,7 @@ defmodule VavPipeline.Doorpost do
# changes; the update is a deliberate operator commit. A bare
# hash-update commit (without a corresponding spec change in the
# same commit) is itself a drift signal.
@expected_hash "7adf0a5dfd8d56382219c3f9d2f6ac3ccd5d681361e6bd5124688eb9cdffffa6"
@expected_hash "7eac65cfadccc37e600a779fc89acef0a24a9eac35e5ab18e8ec845cc1f104c2"
@doc "Return the list of files that constitute the category-grade specification."
@spec files() :: [String.t()]
@@ -269,8 +270,12 @@ defmodule VavPipeline.Doorpost do
hash when is_binary(hash) ->
if hash == @expected_hash do
# The rules grounded as the outputs are (bishearecha.md §X):
# every halt rule anchored to a verse, every verse in L1.
VavPipeline.Anchors.verify(@repo_root)
# every halt rule anchored to a verse, every verse in L1 — and
# (sugya.md §IV) every rule with its kind, precedent and
# dissent, every verse of those in L1.
with :ok <- VavPipeline.Anchors.verify(@repo_root) do
VavPipeline.Sugya.verify(@repo_root)
end
else
{:error,
"SPEC_INTEGRITY_FAILED: hash mismatch — computed #{hash}, " <>
+1
View File
@@ -126,6 +126,7 @@ defmodule VavPipeline.Gate do
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)
@@ -217,6 +217,7 @@ defmodule VavPipeline.Gate.Stream do
violations: result.violations,
anchors: anchors,
fired: Shape.fired(result, anchors),
sugya: VavPipeline.Sugya.for_verdict(result),
sources: result.sources,
model: state.model,
streamed: true
+330
View File
@@ -0,0 +1,330 @@
defmodule VavPipeline.Sugya do
@moduledoc """
The case behind each threshold (specs/sugya.md §IV). `anchors.pl` says
where a rule stands; `talmudic_engine/src/halt/sugya.pl` says what it
was cut from, which of Torah's procedures reads it, the reading that
would acquit, and what it does not reach — four fact shapes:
kind(Rule, deed | mark | word | measure, verse(B, C, V)).
precedent(Rule, Case, Summary).
dissent(Rule, Reading, verse(B, C, V)).
dissent(lo_taaseh_lekha, Form, Reading, verse(B, C, V)).
exception(Rule, Condition, verse(B, C, V)).
Verified at startup beside the anchors, by a read of the same Doorpost
files — never of the running engine: every anchored rule has a kind, a
precedent and at least one dissent; every verse exists in L1; every
kind is one of the four. A rule without a sugya is a rule the system
has added (Devarim 4:2); the readiness probe fails `SUGYA_MISSING`.
"""
alias VavPipeline.{Anchors, Doorpost, L1Reader}
@sugya_file "talmudic_engine/src/halt/sugya.pl"
@kinds ~w(deed mark word measure)
@books ~w(bereishit shemot vayikra bamidbar devarim)
@verse ~r/verse\(\s*(\w+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/
@type verse :: %{book: String.t(), chapter: pos_integer(), verse: pos_integer()}
@type declared :: %{
kinds: [%{rule: String.t(), kind: String.t(), verse: verse()}],
precedents: [%{rule: String.t(), case: String.t()}],
dissents: [%{rule: String.t(), form: String.t() | nil, reading: String.t(), verse: verse()}],
exceptions: [%{rule: String.t(), condition: String.t(), verse: verse()}]
}
@doc "The file as declared, by fact shape. Comment lines are not read."
@spec declared(String.t()) :: {:ok, declared()} | {:error, String.t()}
def declared(root \\ Doorpost.repo_root()) do
path = Path.join(root, @sugya_file)
case File.read(path) do
{:ok, text} ->
code =
text
|> String.split("\n")
|> Enum.map(&strip_comment/1)
|> Enum.join("\n")
kinds =
~r/^kind\(\s*(\w+)\s*,\s*(\w+)\s*,\s*(verse\([^)]*\))\s*\)\s*\./m
|> Regex.scan(code)
|> Enum.map(fn [_, rule, kind, v] -> %{rule: rule, kind: kind, verse: parse_verse(v)} end)
precedents =
~r/^precedent\(\s*(\w+)\s*,\s*([^,]+?)\s*,/m
|> Regex.scan(code)
|> Enum.map(fn [_, rule, c] -> %{rule: rule, case: String.trim(c)} end)
dissents3 =
~r/^dissent\(\s*(\w+)\s*,\s*(\w+)\s*,\s*(verse\([^)]*\))\s*\)\s*\./m
|> Regex.scan(code)
|> Enum.map(fn [_, rule, reading, v] -> %{rule: rule, form: nil, reading: reading, verse: parse_verse(v)} end)
dissents4 =
~r/^dissent\(\s*(\w+)\s*,\s*(\w+)\s*,\s*(\w+)\s*,\s*(verse\([^)]*\))\s*\)\s*\./m
|> Regex.scan(code)
|> Enum.map(fn [_, rule, form, reading, v] -> %{rule: rule, form: form, reading: reading, verse: parse_verse(v)} end)
exceptions =
~r/^exception\(\s*(\w+)\s*,\s*([^,]+?)\s*,\s*(verse\([^)]*\))\s*\)\s*\./m
|> Regex.scan(code)
|> Enum.map(fn [_, rule, cond, v] -> %{rule: rule, condition: String.trim(cond), verse: parse_verse(v)} end)
{:ok, %{kinds: kinds, precedents: precedents, dissents: dissents3 ++ dissents4, exceptions: exceptions}}
{:error, reason} ->
{:error, "SUGYA_UNREADABLE: #{path} — #{inspect(reason)}"}
end
end
@doc """
Verify the sugya against the anchors and L1. `:ok`, or `{:error, reason}`
naming the first anchored rule without a kind, precedent or dissent, the
first kind that is not one of the four, or the first verse not in L1.
"""
@spec verify(String.t()) :: :ok | {:error, String.t()}
def verify(root \\ Doorpost.repo_root()) do
with {:ok, anchors} <- Anchors.declared(root),
{:ok, s} <- declared(root) do
rules = Enum.map(anchors, & &1.rule)
kinds = MapSet.new(s.kinds, & &1.rule)
precedents = MapSet.new(s.precedents, & &1.rule)
dissents = MapSet.new(s.dissents, & &1.rule)
verses = Enum.map(s.kinds, & &1.verse) ++ Enum.map(s.dissents, & &1.verse) ++ Enum.map(s.exceptions, & &1.verse)
cond do
s.kinds == [] ->
{:error, "SUGYA_MISSING: no kind/3 facts in #{@sugya_file}"}
(r = Enum.find(rules, &(not MapSet.member?(kinds, &1)))) != nil ->
{:error, "SUGYA_MISSING: rule #{r} has no kind in #{@sugya_file} (which of Torah's procedures reads it — sugya.md §II)"}
(k = Enum.find(s.kinds, &(&1.kind not in @kinds))) != nil ->
{:error, "SUGYA_INVALID: rule #{k.rule} has kind #{k.kind}; the kinds are deed, mark, word, measure"}
(r = Enum.find(rules, &(not MapSet.member?(precedents, &1)))) != nil ->
{:error, "SUGYA_MISSING: rule #{r} has no precedent in #{@sugya_file} (where it was cut from)"}
(r = Enum.find(rules, &(not MapSet.member?(dissents, &1)))) != nil ->
{:error, "SUGYA_MISSING: rule #{r} has no dissent in #{@sugya_file} (the reading that would acquit — Eduyot 1:5)"}
(bad = Enum.find(s.exceptions ++ s.dissents, &(&1.rule not in rules))) != nil ->
{:error, "SUGYA_INVALID: #{@sugya_file} names rule #{bad.rule}, which has no anchor"}
(v = Enum.find(verses, &(not verse_in_l1?(&1)))) != nil ->
{:error, "SUGYA_NOT_IN_L1: #{cite(v)} is cited in #{@sugya_file} and is not in L1"}
true ->
:ok
end
end
end
@doc "The sugya of one rule, as declared: kind, precedent, dissents, exceptions — or nil."
@spec for_rule(String.t(), String.t()) :: map() | nil
def for_rule(rule, root \\ Doorpost.repo_root()) do
case declared(root) do
{:ok, s} ->
case Enum.find(s.kinds, &(&1.rule == rule)) do
nil ->
nil
k ->
%{
rule: rule,
kind: k.kind,
verse: cite(k.verse),
precedent: Enum.find_value(s.precedents, &(&1.rule == rule && &1.case)),
dissent: s.dissents |> Enum.filter(&(&1.rule == rule)) |> Enum.map(&%{form: &1.form, reading: &1.reading, verse: cite(&1.verse)}),
exception: s.exceptions |> Enum.filter(&(&1.rule == rule)) |> Enum.map(&%{condition: &1.condition, verse: cite(&1.verse)})
}
end
_ ->
nil
end
end
# ------------------------------------------------------------
# The facts read by the code (sugya.md §IV, "drives the code")
# ------------------------------------------------------------
@doc """
The exceptions declared for a rule, as `[%{condition, verse}]`. Read
once from the Doorpost file and kept in :persistent_term — the file is
part of the image's identity and does not change while it runs. A root
other than the repo's is read fresh (tests).
"""
@spec exceptions_of(String.t(), String.t() | nil) :: [%{condition: String.t(), verse: verse()}]
def exceptions_of(rule, root \\ nil) do
exceptions =
case root do
nil -> cached_exceptions()
r -> (case declared(r) do {:ok, s} -> s.exceptions; _ -> [] end)
end
exceptions |> Enum.filter(&(&1.rule == rule)) |> Enum.map(&Map.take(&1, [:condition, :verse]))
end
@doc "Whether `exception(Rule, Condition, _)` is declared. The code reads its exemptions from here, not from constants."
@spec exception?(String.t(), String.t(), String.t() | nil) :: boolean()
def exception?(rule, condition, root \\ nil),
do: Enum.any?(exceptions_of(rule, root), &(&1.condition == condition))
@doc """
The forms that stand on one witness: every `exception(lo_taaseh_lekha,
single_witness(F), _)`. Tablet 53 made the forged fringe one; the
reason — a mark on the offering, not a deed of recurrence — is the
fact's verse (Vayikra 22:20), and the behaviour is read from the fact.
"""
@spec single_witness_forms(String.t() | nil) :: [String.t()]
def single_witness_forms(root \\ nil) do
for %{condition: c} <- exceptions_of("lo_taaseh_lekha", root),
[_, form] <- [Regex.run(~r/^single_witness\((\w+)\)$/, c)],
do: form
end
defp cached_exceptions do
case :persistent_term.get({__MODULE__, :exceptions}, :none) do
:none ->
exceptions = case declared() do {:ok, s} -> s.exceptions; _ -> [] end
:persistent_term.put({__MODULE__, :exceptions}, exceptions)
exceptions
exceptions ->
exceptions
end
end
# ------------------------------------------------------------
# The sugya on a verdict (sugya.md §IV, "carried on the verdict")
# ------------------------------------------------------------
@doc """
The sugya a verdict carries: one entry per rule that fired and per form
that was noted — kind, verse, precedent, the dissents with `applied`
marking the one that held, the exceptions with `applied` likewise.
`applied` is read from the record, never guessed: the single witness
noted (a form in `checks.frames.noted`), 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 with `applied: false` — considered, and the rule's verse
decided. The minority is thus on the record for every verdict (Eduyot
1:5–6): to be relied on later, and to be located.
"""
@spec for_verdict(map()) :: map()
def for_verdict(result) when is_map(result) do
checks = Map.get(result, :checks) || %{}
violations = Map.get(result, :violations) || []
fired_rules = violations |> Enum.flat_map(&rule_of/1) |> Enum.uniq()
frames = Map.get(checks, :frames) || %{}
noted = (Map.get(frames, :noted) || %{}) |> Map.keys() |> Enum.map(&to_string/1)
forms = Map.get(frames, :forms) || %{}
forged_on_one = Enum.any?(forms, fn {_f, e} -> Map.get(e, :stands_on) == :forgery end)
grounding = Map.get(checks, :grounding) || %{}
claims = get_in(result, [:examination, "claims"]) || []
hedged_out = Enum.any?(claims, &(&1["hedged"] == true))
rules = Enum.uniq(fired_rules ++ if(noted != [], do: ["lo_taaseh_lekha"], else: []))
Map.new(rules, fn rule ->
base = for_rule(rule) || %{rule: rule, kind: nil, verse: nil, precedent: nil, dissent: [], exception: []}
dissent =
Enum.map(base.dissent, fn d ->
applied =
case {rule, d.reading, d.form} do
{"lo_taaseh_lekha", "single_witness_noted", _} -> noted != []
{"golden_calf", "hedged", _} -> hedged_out
_ -> false
end
d |> Map.put(:applied, applied) |> then(fn m -> if rule == "lo_taaseh_lekha" and d.reading == "single_witness_noted", do: Map.put(m, :forms, noted), else: m end)
end)
exception =
Enum.map(base.exception, fn e ->
applied =
case {rule, e.condition} do
{"lo_taaseh_lekha", "single_witness(forged_fringe)"} -> forged_on_one
{"golden_calf", "no_ground_given"} -> Map.get(grounding, :assessed) == false and (Map.get(grounding, :reason) || "") =~ "no sources given"
{"golden_calf", "hedged"} -> hedged_out
_ -> false
end
Map.put(e, :applied, applied)
end)
{rule, %{base | dissent: dissent, exception: exception} |> Map.put(:fired, rule in fired_rules) |> Map.put(:noted, if(rule == "lo_taaseh_lekha", do: noted, else: []))}
end)
end
@doc """
A stored bedikah (L3 record) in the shape `for_verdict/1` reads: its
violations, its claims, the forms found — those with no violation of
their form were NOTED — and whether a forged fringe stood on one witness.
The hard matter goes up with its dissent (Devarim 17:8, sugya.md step 4).
"""
@spec from_record(struct() | map()) :: map()
def from_record(rec) do
violations = Map.get(rec, :violations) || []
fired_forms =
violations
|> Enum.flat_map(fn v -> case Regex.run(~r/form\((\w+)\)/, to_string(v)) do [_, f] -> [f]; _ -> [] end end)
|> MapSet.new()
found = (Map.get(rec, :frames) || []) |> Enum.map(&to_string(&1["form"] || Map.get(&1, :form)))
noted = found |> Enum.reject(&MapSet.member?(fired_forms, &1)) |> Enum.uniq()
forms = for f <- fired_forms, into: %{}, do: {String.to_atom(f), %{stands_on: if(f == "forged_fringe", do: :forgery, else: :sentences)}}
claims = Map.get(rec, :claims) || []
%{
verdict: Map.get(rec, :verdict),
violations: violations,
examination: %{"claims" => Enum.map(claims, fn c -> %{"hedged" => c["hedged"] == true} end)},
checks: %{
frames: %{noted: Map.new(noted, &{String.to_atom(&1), %{witnesses: 1}}), forms: forms},
grounding: %{assessed: claims != [] and (Map.get(rec, :sources) || []) != []}
}
}
end
@doc "The dissents and exceptions a sugya shows applied: `[%{rule, reading | condition, verse, form?}]`."
@spec dissents_applied(map()) :: [map()]
def dissents_applied(sugya) when is_map(sugya) do
Enum.flat_map(sugya, fn {rule, e} ->
Enum.map(Enum.filter(e.dissent, & &1.applied), &%{rule: rule, reading: &1.reading, verse: &1.verse, forms: Map.get(&1, :forms, [])}) ++
Enum.map(Enum.filter(e.exception, & &1.applied), &%{rule: rule, exception: &1.condition, verse: &1.verse})
end)
end
defp rule_of(violation) do
case Regex.run(~r/^halt\(\s*(\w+)\s*,/, to_string(violation)) do
[_, rule] -> [rule]
_ -> []
end
end
@doc "A verse as a citation: \"Devarim 19:15\"."
@spec cite(verse()) :: String.t()
def cite(%{book: b, chapter: c, verse: v}), do: "#{String.capitalize(b)} #{c}:#{v}"
defp parse_verse(s) do
[_, book, ch, v] = Regex.run(@verse, s)
%{book: book, chapter: String.to_integer(ch), verse: String.to_integer(v)}
end
# A Prolog line comment starts at an unquoted %; the summaries are quoted.
defp strip_comment(line) do
case Regex.run(~r/^((?:[^'%]|'[^']*')*)%/, line) do
[_, code] -> code
_ -> line
end
end
defp verse_in_l1?(%{book: book, chapter: ch, verse: v}) do
book in @books and match?({:ok, _}, L1Reader.read_verse(book, ch, v))
end
end
+132
View File
@@ -0,0 +1,132 @@
defmodule VavPipeline.SugyaTest do
@moduledoc "The case behind each threshold (specs/sugya.md §IV, step 1): the facts are whole, and the Doorpost refuses a rule without them."
use ExUnit.Case, async: false
alias VavPipeline.{Anchors, Doorpost, Sugya}
test "the sugya file is a Doorpost file, beside the anchors" do
assert "talmudic_engine/src/halt/sugya.pl" in Doorpost.files()
assert "talmudic_engine/src/halt/anchors.pl" in Doorpost.files()
end
test "every anchored rule has a kind, a precedent and a dissent; every verse is in L1" do
assert :ok = Sugya.verify()
{:ok, anchors} = Anchors.declared()
{:ok, s} = Sugya.declared()
for a <- anchors do
assert Enum.any?(s.kinds, &(&1.rule == a.rule)), "#{a.rule} has no kind"
assert Enum.any?(s.precedents, &(&1.rule == a.rule)), "#{a.rule} has no precedent"
assert Enum.any?(s.dissents, &(&1.rule == a.rule)), "#{a.rule} has no dissent"
end
end
test "the kinds follow sugya.md §II: marks seen once, the forms as deeds, the grounding rules as words" do
{:ok, s} = Sugya.declared()
kind = fn r -> Enum.find_value(s.kinds, &(&1.rule == r && &1.kind)) end
assert kind.("lo_taaseh_lekha") == "deed"
assert kind.("golden_calf") == "word"
assert kind.("self_grounding") == "word"
assert kind.("even_vaaven") == "measure"
for r <- ~w(avodah_zarah shfichut_damim gilui_arayot parah_adumah carrier_centered chelbenah), do: assert(kind.(r) == "mark", r)
end
test "the exception the reader asked to see cited: forged_fringe stands on one witness because it is a mark on the offering" do
{:ok, s} = Sugya.declared()
e = Enum.find(s.exceptions, &(&1.rule == "lo_taaseh_lekha" and &1.condition =~ "forged_fringe"))
assert e, "no single-witness exception for forged_fringe"
assert Sugya.cite(e.verse) == "Vayikra 22:20"
# and the two-witness dissent is recorded for every form
assert Enum.any?(s.dissents, &(&1.rule == "lo_taaseh_lekha" and &1.reading == "single_witness_noted"))
end
test "for_rule/1 renders a rule's sugya for a verdict to carry" do
r = Sugya.for_rule("golden_calf")
assert r.kind == "word" and r.verse == "Devarim 18:22"
assert r.precedent == "tablet(49)"
assert Enum.any?(r.dissent, &(&1.reading == "hedged"))
assert Enum.any?(r.exception, &(&1.condition == "no_ground_given"))
assert Sugya.for_rule("no_such_rule") == nil
end
describe "the code reads its exemptions from the facts (step 2)" do
test "the single-witness forms are those the sugya excepts, and only those" do
assert Sugya.single_witness_forms() == ["forged_fringe"]
end
test "the hedged and no-ground exemptions of golden_calf are facts, not constants" do
assert Sugya.exception?("golden_calf", "hedged")
assert Sugya.exception?("golden_calf", "no_ground_given")
refute Sugya.exception?("golden_calf", "anything_at_all")
refute Sugya.exception?("chelbenah", "hedged")
end
test "with the fact removed, the exemption is gone: behaviour is read from the reason" do
temp = Path.join(System.tmp_dir!(), "sugya_step2_#{System.unique_integer([:positive])}")
rel = "talmudic_engine/src/halt/sugya.pl"
File.mkdir_p!(Path.dirname(Path.join(temp, rel)))
File.cp!(Path.join(Doorpost.repo_root(), rel), Path.join(temp, rel))
on_exit(fn -> File.rm_rf!(temp) end)
assert Sugya.single_witness_forms(temp) == ["forged_fringe"]
path = Path.join(temp, rel)
File.write!(path, String.replace(File.read!(path), "exception(lo_taaseh_lekha, single_witness(forged_fringe), verse(vayikra, 22, 20)).", ""))
assert Sugya.single_witness_forms(temp) == []
File.write!(path, String.replace(File.read!(path), "exception(golden_calf, hedged, verse(devarim, 18, 22)).", ""))
refute Sugya.exception?("golden_calf", "hedged", temp)
end
end
describe "drift (synthetic spec root)" do
setup do
temp = Path.join(System.tmp_dir!(), "sugya_test_#{System.unique_integer([:positive])}")
for rel <- Doorpost.files() do
src = Path.join(Doorpost.repo_root(), rel)
dst = Path.join(temp, rel)
File.mkdir_p!(Path.dirname(dst))
if File.exists?(src), do: File.cp!(src, dst)
end
on_exit(fn -> File.rm_rf!(temp) end)
%{temp: temp, path: Path.join(temp, "talmudic_engine/src/halt/sugya.pl")}
end
test "a mirror verifies", %{temp: temp} do
assert :ok = Sugya.verify(temp)
end
test "a rule without a dissent fails: the minority must be recorded", %{temp: temp, path: path} do
text = File.read!(path) |> String.replace(~r/^dissent\(even_vaaven,.*\n/m, "")
File.write!(path, text)
assert {:error, "SUGYA_MISSING: rule even_vaaven has no dissent" <> _} = Sugya.verify(temp)
end
test "a rule without a kind fails", %{temp: temp, path: path} do
File.write!(path, String.replace(File.read!(path), "kind(chelbenah, mark, verse(vayikra, 13, 3)).", ""))
assert {:error, "SUGYA_MISSING: rule chelbenah has no kind" <> _} = Sugya.verify(temp)
end
test "a kind that is not one of the four fails", %{temp: temp, path: path} do
File.write!(path, String.replace(File.read!(path), "kind(chelbenah, mark,", "kind(chelbenah, rumour,"))
assert {:error, "SUGYA_INVALID: rule chelbenah has kind rumour" <> _} = Sugya.verify(temp)
end
test "a verse that is not in L1 fails", %{temp: temp, path: path} do
File.write!(path, String.replace(File.read!(path), "verse(vayikra, 22, 20)", "verse(vayikra, 22, 99)"))
assert {:error, "SUGYA_NOT_IN_L1: Vayikra 22:99" <> _} = Sugya.verify(temp)
end
test "a sugya for a rule that has no anchor fails: nothing added", %{temp: temp, path: path} do
File.write!(path, File.read!(path) <> "\nexception(new_rule, whatever, verse(bereishit, 1, 1)).\n")
assert {:error, "SUGYA_INVALID: " <> _ = msg} = Sugya.verify(temp)
assert msg =~ "new_rule"
end
test "a fact in a comment is not a fact", %{temp: temp, path: path} do
File.write!(path, File.read!(path) <> "\n%% kind(imaginary, mark, verse(bereishit, 1, 1)).\n")
assert :ok = Sugya.verify(temp)
end
test "a missing sugya file fails", %{temp: temp, path: path} do
File.rm!(path)
assert {:error, "SUGYA_UNREADABLE: " <> _} = Sugya.verify(temp)
end
end
end
+97
View File
@@ -0,0 +1,97 @@
defmodule VavPipeline.SugyaUpTest do
@moduledoc "sugya.md step 4 — the hard matter goes up with its dissent (Devarim 17:8)."
use ExUnit.Case, async: false
import Plug.Test
import Plug.Conn
alias VavPipeline.{Sugya, BigdeiKodesh.Router, L3.Store}
@opts Router.init([])
describe "from_record/1 and dissents_applied/1 (no database)" do
test "a stored verdict with a form found but not fired is a noted form, and its dissent applied" do
rec = %{verdict: "tov", violations: [], frames: [%{"form" => "apology_without_mechanism", "sentence" => "I apologize."}], claims: [], sources: [], references: []}
s = Sugya.for_verdict(Sugya.from_record(rec))
assert s["lo_taaseh_lekha"].noted == ["apology_without_mechanism"]
assert [%{rule: "lo_taaseh_lekha", reading: "single_witness_noted", verse: "Devarim 19:15", forms: ["apology_without_mechanism"]}] = Sugya.dissents_applied(s)
end
test "a stored forged fringe carries the mark exception, applied" do
rec = %{verdict: "lo_tov", violations: ["halt(lo_taaseh_lekha,action(frame,by(claude),form(forged_fringe)),forged_fringe,'x')"],
frames: [%{"form" => "forged_fringe", "sentence" => "— bikoret bedikah-dead: tov"}], claims: [], sources: [], references: []}
s = Sugya.for_verdict(Sugya.from_record(rec))
assert s["lo_taaseh_lekha"].noted == []
assert [%{exception: "single_witness(forged_fringe)", verse: "Vayikra 22:20"}] = Sugya.dissents_applied(s)
end
test "a clean record carries nothing" do
assert Sugya.dissents_applied(Sugya.for_verdict(Sugya.from_record(%{verdict: "tov", violations: [], frames: [], claims: [], sources: [], references: []}))) == []
end
end
describe "a bedikah goes up to the Sanhedrin (L3)" do
@describetag :l3
setup do
:ok = Ecto.Adapters.SQL.Sandbox.checkout(VavPipeline.Repo)
Ecto.Adapters.SQL.Sandbox.mode(VavPipeline.Repo, {:shared, self()})
on_exit(fn -> Ecto.Adapters.SQL.Sandbox.mode(VavPipeline.Repo, :manual) end)
VavPipeline.MannaLedger.reset()
on_exit(fn -> VavPipeline.MannaLedger.reset() end)
today = Date.utc_today() |> Date.to_iso8601()
expires = DateTime.utc_now() |> DateTime.add(6 * 3600) |> DateTime.to_iso8601()
provision = %{"date" => today, "provision" => %{"compute_units" => 50, "memory_mb" => 512, "api_calls_external" => 10, "storage_delta_mb" => 2, "sanhedrin_panels_max" => 3},
"type" => "regular", "expires_at" => expires, "written_by" => "sugya_up_test", "written_at" => DateTime.utc_now() |> DateTime.to_iso8601()}
path = VavPipeline.Manna.provision_file()
File.mkdir_p!(Path.dirname(path))
File.write!(path, Jason.encode!(provision))
on_exit(fn -> File.rm(path) end)
Application.put_env(:sanhedrin, :consult, {__MODULE__, :fake_consult})
on_exit(fn -> Application.put_env(:sanhedrin, :consult, {VavPipeline.SanhedrinConsult, :consult}) end)
:ok
end
def fake_consult(_step, _ref, _roots, _q, _ctx), do: {:ok, %{status: "resolved", text: "the reading", verses: [{:devarim, 6, 4}]}}
defp record!(ref, key_id, extra \\ %{}) do
{:ok, rec} =
Store.record_bedikah(Map.merge(%{bedikah_ref: ref, provenance: "claude", query: "q", output_sha256: String.duplicate("ab", 32), output_length: 9,
vital_content: "", verdict: "tov", examined_at: DateTime.utc_now() |> DateTime.truncate(:second), key_id: key_id,
frames: [%{"form" => "apology_without_mechanism", "sentence" => "I apologize."}], references: ["Devarim 6:4"]}, extra))
rec
end
defp post(body, token) do
conn(:post, "/chatzer/sanhedrin", Jason.encode!(body))
|> put_req_header("content-type", "application/json")
|> put_req_header("authorization", "Bearer #{token}")
|> Router.call(@opts)
end
test "the case is the verdict, the roots are its cited verses, the dissent goes up and comes down as the minority" do
mine = VavPipeline.Ledger.key_id("chatzer-test-token")
record!("bedikah-up00000000000001", mine)
before_min = Store.count_minority_opinions()
conn = post(%{bedikah_ref: "bedikah-up00000000000001", complexity_hint: "monetary"}, "chatzer-test-token")
assert conn.status == 200
body = Jason.decode!(conn.resp_body)
g = body["grounding"]
assert g["bedikah"]["ref"] == "bedikah-up00000000000001"
assert [%{"reading" => "single_witness_noted", "verse" => "Devarim 19:15"}] = g["bedikah"]["dissent_carried"]
# the roots came from Devarim 6:4 as read in L1 (shin-mem-ayin among them)
assert "ש-מ-ע" in g["roots_consulted"]
assert body["deliberation"]["panel_size"] == 3
# a unanimous panel, and still a minority recorded: the carried dissent
assert g["recorded"]["minority"] == "recorded"
assert Store.count_minority_opinions() == before_min + 1
end
test "another key's bedikah does not go up: 404, the same as none" do
record!("bedikah-up00000000000002", VavPipeline.Ledger.key_id("someone-else"))
assert post(%{bedikah_ref: "bedikah-up00000000000002"}, "chatzer-test-token").status == 404
assert post(%{bedikah_ref: "bedikah-up00000000000003"}, "chatzer-test-token").status == 404
end
end
end
+54
View File
@@ -0,0 +1,54 @@
defmodule VavPipeline.SugyaVerdictTest do
@moduledoc "The sugya on a verdict (sugya.md §IV, step 3): applied is read from the record, never guessed."
use ExUnit.Case, async: true
alias VavPipeline.Sugya
defp result(overrides) do
Map.merge(%{verdict: :tov, violations: [], checks: %{}, examination: %{"claims" => []}}, overrides)
end
test "a clean verdict carries no sugya" do
assert Sugya.for_verdict(result(%{})) == %{}
end
test "a noted form carries the two-witness dissent, applied, with the forms it held for" do
r = result(%{checks: %{frames: %{fired: false, forms: %{}, noted: %{apology_without_mechanism: %{claims: [], witnesses: 1}}, violations: []}}})
s = Sugya.for_verdict(r)
assert Map.keys(s) == ["lo_taaseh_lekha"]
e = s["lo_taaseh_lekha"]
assert e.kind == "deed" and e.verse == "Devarim 19:15"
assert e.fired == false and e.noted == ["apology_without_mechanism"]
d = Enum.find(e.dissent, &(&1.reading == "single_witness_noted"))
assert d.applied == true and d.forms == ["apology_without_mechanism"] and d.verse == "Devarim 19:15"
# the exception for the forged fringe did not apply: nothing stood on one witness
assert Enum.find(e.exception, &(&1.condition =~ "forged_fringe")).applied == false
end
test "a forged fringe standing on one witness carries the mark exception, applied" do
r = result(%{verdict: :lo_tov,
violations: ["halt(lo_taaseh_lekha,action(frame,by(claude),form(forged_fringe)),forged_fringe,'…')"],
checks: %{frames: %{fired: true, forms: %{forged_fringe: %{stands_on: :forgery, witnesses: 1, claims: [], violations: ["x"]}}, noted: %{}, violations: ["x"]}}})
e = Sugya.for_verdict(r)["lo_taaseh_lekha"]
assert e.fired == true
assert Enum.find(e.exception, &(&1.condition == "single_witness(forged_fringe)")) |> Map.fetch!(:applied) == true
assert Enum.find(e.dissent, &(&1.reading == "single_witness_noted")).applied == false
end
test "a golden calf beside a hedged claim carries the hedged dissent applied; without hedging, not" do
base = %{verdict: :lo_tov, violations: ["halt(golden_calf,interpretation(x),golden_calf,'…')"], checks: %{grounding: %{assessed: true, fired: true}}}
with_hedge = Sugya.for_verdict(result(Map.put(base, :examination, %{"claims" => [%{"hedged" => true}, %{"hedged" => false}]})))
assert with_hedge["golden_calf"].kind == "word" and with_hedge["golden_calf"].verse == "Devarim 18:22"
assert Enum.find(with_hedge["golden_calf"].dissent, &(&1.reading == "hedged")).applied == true
without = Sugya.for_verdict(result(Map.put(base, :examination, %{"claims" => [%{"hedged" => false}]})))
assert Enum.find(without["golden_calf"].dissent, &(&1.reading == "hedged")).applied == false
end
test "a rule the record cannot speak to carries its dissent considered, applied false" do
r = result(%{verdict: :lo_tov, violations: ["halt(parah_adumah,action(self_validate,by(claude),target(self)),claude,'…')"]})
e = Sugya.for_verdict(r)["parah_adumah"]
assert e.kind == "mark" and e.verse == "Vayikra 13:3"
assert e.precedent == "specimens(self_validation)"
assert [%{reading: "validator_named_is_not_the_speaker", applied: false, verse: "Bamidbar 19:7"}] = Enum.map(e.dissent, &Map.take(&1, [:reading, :applied, :verse]))
end
end