engine: the verse facts are the whole scroll, derived from L1
torah_text.pl held 32 hand-written verses from Phase 1 — "the reasoning layer's view of the specification". With the cycle and the Sanhedrin now consulting the engine on every question (2026-10-19), that view showed: any root it knew lit the same handful, and bereishit 1:1 and devarim 6:4 were read in the same eleven verses. - scripts/build_engine_verses.py writes talmudic_engine/src/specification/l1_verses.pl from l1/torah: every verse, each word's shoresh as the engine names it (Hebrew → letter names, finals folded), the consonantal text as gloss, and the SHA-256 of the L1 files read in the header. `--check` exits 1 when the facts lag the scroll; build_and_push.sh runs it beside the Doorpost check. - torah_text.pl includes the generated file; the hand facts are gone. L1's roots are authoritative where the hand facts differed (Israel שרה, the Name הוה). - interpret.pl: over 5,853 verses a common root lights hundreds, so the reading ranks verses by how many of the case's roots each carries and keeps the top 12 in canonical order; the coupling trace names only the verses the reading cites. Local, over the full scroll: bereishit 1:1 → itself, Devarim 4:32, Bereishit 2:4 …; devarim 6:4 → itself, Shemot 3:18, Devarim 4:1 …; shabbat roots → Bereishit 2:3, Shemot 20:8, 20:11, 31:13. interpret 15 ms, challenge 10 ms (now finds a kushya for the Shema), resolve 24 ms. Prolog 7 suites green (middot 2.9 s — pairwise scans over the scroll). 5,853 verses loaded, 49,542 root occurrences. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
4e0eb2f983
commit
861cd4e64e
5 files changed
+6023
-91
No files matched your search
@@ -63,6 +63,11 @@ if [[ "${DOORPOST_SKIP:-0}" != "1" ]]; then
|
||||
python3 -I scripts/doorpost_check.py || exit 1
|
||||
fi
|
||||
|
||||
# The engine's verse facts are derived from L1 (scripts/build_engine_verses.py);
|
||||
# an image whose facts lag the scroll reasons over a scroll that is not
|
||||
# the one the pipeline reads. Regenerate and commit, never skip.
|
||||
python3 -I scripts/build_engine_verses.py --check || exit 1
|
||||
|
||||
if ! docker buildx version >/dev/null 2>&1; then
|
||||
echo "ERROR: docker buildx not available — install Docker Desktop or buildx plugin" >&2
|
||||
exit 1
|
||||
|
||||
Executable
+111
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env python3
|
||||
"""build_engine_verses.py — the Talmudic Engine's verse facts, derived from L1.
|
||||
|
||||
Architecture §3.4: verse(Book, Chapter, Verse, HebrewRoots, Gloss) is "the
|
||||
reasoning layer's view of the specification — not a replacement for L1".
|
||||
Until 2026-10-19 that view was 32 hand-written verses, so any root the
|
||||
engine knew lit the same handful and every reading cited the same list.
|
||||
This script writes talmudic_engine/src/specification/l1_verses.pl from
|
||||
l1/torah — every verse, its roots as the engine names them
|
||||
(Hebrew shoresh → letter names, finals folded: ברא → bet_resh_aleph), the
|
||||
consonantal text as the gloss — and records the SHA-256 of the L1 files it
|
||||
read in the header, so the facts can be checked against the scroll.
|
||||
|
||||
scripts/build_engine_verses.py # write the facts
|
||||
scripts/build_engine_verses.py --check # exit 1 if the facts do not match L1 as it stands
|
||||
|
||||
Devarim 4:2 — nothing added, nothing taken: the engine reads the scroll's
|
||||
roots, not the builder's memory of them.
|
||||
"""
|
||||
import argparse, hashlib, json, os, sys
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
L1 = os.path.join(ROOT, "l1", "torah")
|
||||
OUT = os.path.join(ROOT, "talmudic_engine", "src", "specification", "l1_verses.pl")
|
||||
BOOKS = ["bereishit", "shemot", "vayikra", "bamidbar", "devarim"]
|
||||
|
||||
NAMES = {
|
||||
"א": "aleph", "ב": "bet", "ג": "gimel", "ד": "dalet", "ה": "he", "ו": "vav", "ז": "zayin",
|
||||
"ח": "chet", "ט": "tet", "י": "yod", "כ": "kaf", "ך": "kaf", "ל": "lamed", "מ": "mem", "ם": "mem",
|
||||
"נ": "nun", "ן": "nun", "ס": "samekh", "ע": "ayin", "פ": "pe", "ף": "pe", "צ": "tsadi", "ץ": "tsadi",
|
||||
"ק": "qof", "ר": "resh", "ש": "shin", "ת": "tav",
|
||||
}
|
||||
|
||||
|
||||
def root_atom(shoresh):
|
||||
if not shoresh:
|
||||
return None
|
||||
names = [NAMES.get(c) for c in shoresh]
|
||||
if not names or any(n is None for n in names):
|
||||
return None
|
||||
return "_".join(names)
|
||||
|
||||
|
||||
def quote(s):
|
||||
return "'" + s.replace("\\", "\\\\").replace("'", "\\'") + "'"
|
||||
|
||||
|
||||
def verse_files():
|
||||
out = []
|
||||
for book in BOOKS:
|
||||
bdir = os.path.join(L1, book)
|
||||
for ch in sorted((d for d in os.listdir(bdir) if d.isdigit()), key=int):
|
||||
cdir = os.path.join(bdir, ch)
|
||||
for f in sorted((f for f in os.listdir(cdir) if f.endswith(".json") and f[:-5].isdigit()), key=lambda x: int(x[:-5])):
|
||||
out.append((book, int(ch), int(f.split(".")[0]), os.path.join(cdir, f)))
|
||||
return out
|
||||
|
||||
|
||||
def build():
|
||||
h = hashlib.sha256()
|
||||
facts = []
|
||||
nverses = nroots = 0
|
||||
for book, ch, vs, path in verse_files():
|
||||
data = open(path, "rb").read()
|
||||
h.update(f"{book}/{ch}/{vs}\n".encode())
|
||||
h.update(data)
|
||||
d = json.loads(data)
|
||||
roots = []
|
||||
for w in d.get("words", []):
|
||||
a = root_atom(w.get("shoresh"))
|
||||
if a and a not in roots:
|
||||
roots.append(a)
|
||||
gloss = " ".join(w.get("consonants", "") for w in d.get("words", []) if w.get("consonants"))
|
||||
facts.append(f"verse({book}, {ch}, {vs}, [{', '.join(roots)}], {quote(gloss)}).")
|
||||
nverses += 1
|
||||
nroots += len(roots)
|
||||
header = f"""%% l1_verses.pl — GENERATED by scripts/build_engine_verses.py from l1/torah.
|
||||
%% Do not edit: regenerate. verse(Book, Chapter, Verse, Roots, Gloss) for
|
||||
%% every verse of L1; Roots are the engine's letter-name atoms derived from
|
||||
%% each word's shoresh (finals folded), once per verse in word order; Gloss
|
||||
%% is the consonantal text. Included by torah_text.pl.
|
||||
%%
|
||||
%% l1_sha256: {h.hexdigest()}
|
||||
%% verses: {nverses} root_occurrences: {nroots}
|
||||
"""
|
||||
return header + "\n".join(facts) + "\n", h.hexdigest(), nverses
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--check", action="store_true")
|
||||
a = ap.parse_args()
|
||||
text, digest, n = build()
|
||||
if a.check:
|
||||
try:
|
||||
current = open(OUT, encoding="utf-8").read()
|
||||
except FileNotFoundError:
|
||||
print(f"engine verses: {OUT} missing — run scripts/build_engine_verses.py", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if current != text:
|
||||
print(f"engine verses: {OUT} does not match L1 (l1_sha256 now {digest[:12]}…) — regenerate", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
print(f"engine verses: match L1 ({n} verses, l1_sha256 {digest[:12]}…)")
|
||||
return
|
||||
with open(OUT, "w", encoding="utf-8") as f:
|
||||
f.write(text)
|
||||
print(f"wrote {OUT}: {n} verses, l1_sha256 {digest[:12]}…")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user