L1: the petuchot and setumot are engraved (tablet 39)
The scroll in the aron had no paragraphs. The WLC marks every petuchah
(פ) and setumah (ס); scripts/build_l1.py handled sof pasuq, maqaf and
paseq and read past these. L1 was 5,853 verses divided only by chapter
numbers, which are not Torah's.
Transmitter's word: "add the petuchah and setumah markers to L1."
- build_l1.py: a verse followed — or interrupted — by a break carries
"parashah_breaks": [{ "after_word": 13, "type": "petuchah" }]
The break is recorded by the word it follows; at the end of a verse
that is the verse's word count. text_hebrew is untouched: the break
is structure, not letters. Counts go into build_report.json.
- 293 petuchot and 394 setumot, in 686 verses — the source's count
exactly. Four fall inside a verse (pisqa b'emtza pasuq): Bereishit
35:22, Devarim 2:8, 5:21, 23:8; Devarim 5:21 has two. These are the
Leningrad Codex's divisions (a scroll written to the Rambam's list
has 290 and 379); the README says which tradition L1 records.
WHAT CHANGED IN THE EDUT: one field added to 686 verse files. Verified
file by file — with that field removed, all 5,853 verse files are
byte-identical to the previous L1.
L1 SHA-256 156b1d27…d8c9d5 → f743b34b…3f1494
Moved together, because they pin the hash: L1Reader @l1_expected_hash ·
Luchot.Firmware + Spec · azazel goral._L1_INTEGRITY_HASH · the kohen's
gate in KOHEN.md and KOHEN_PROMPT.md. The goral's seed changes with it;
azazel records already in L3 stand as cast.
- L1Reader: `parashah_breaks` on every verse (a list, empty where there
is none; only the two kinds are passed on).
- test/tablet39_parashah_breaks_test.exs — 7 tests: the six days of
creation each close with a petuchah; the Shema's first paragraph
(Devarim 6:4–9) opens after a petuchah and closes with a setumah; the
break inside Bereishit 35:22 falls between ישראל and ויהיו; Devarim
5:21 has its two; the whole scroll counts 293 / 394 / 686.
- Specs: vayakhel §1.2 and terumah §E1 said the marks were dropped;
they now say where they are. (The source element is <seg>, not a
milestone as 483d50c called it; and that commit's "233" was 293.)
Doorpost @expected_hash 21f1a4d5… → b62311e7…, in this commit.
- l1/README.md: a "Paragraph Breaks" section.
Read before committing — all five suites on the new L1: Elixir 491/0
(seeds 0 7 42) · Haskell 59/0 · korban 222 passed 1 skipped · azazel
125 passed · Prolog 6 suites exit 0. verify_l1.py OK. Four images
built; full local stack (real engine, PostgreSQL with SSL): the witness
reads the scroll and matches, Hebrew 200, English 422, health 7/7,
L3 written, no errors; every image carries f743b34b… read-only.
Nothing consumes the breaks yet: no cycle, scope or answer uses the
parashah. They are in the scroll and in the reader.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
483d50c4ab
commit
7d46fdc7c9
701 files changed
+4305
-16
No files matched your search
@@ -175,7 +175,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 "21f1a4d52e21a166be243533f71e7b5be1ab13ac55670a0811ba5b1b4d29fa56"
|
||||
@expected_hash "b62311e7541277f3881f074e2e884b742f6f85bd2de8b4890f44e978d06a3f21"
|
||||
|
||||
@doc "Return the list of files that constitute the category-grade specification."
|
||||
@spec files() :: [String.t()]
|
||||
|
||||
@@ -17,7 +17,7 @@ defmodule VavPipeline.L1Reader do
|
||||
# Updated after build_l1.py fix: enlarged letters (Shema's ayin/dalet, gachon's
|
||||
# vav, mishpatan's nun), ketiv/qere pairs, and smikhut construct pairs are now
|
||||
# preserved in L1 rather than silently dropped.
|
||||
@l1_expected_hash "156b1d2724a4bf9999aa45c83ebd0794792f5aa4d524e8a7a898c871c1d8c9d5"
|
||||
@l1_expected_hash "f743b34b83617c328ec190e78ea1402470938e460b9701aec465c3f1613f1494"
|
||||
|
||||
@type letter_mark :: %{letter: String.t(), offset: non_neg_integer()}
|
||||
|
||||
@@ -50,6 +50,11 @@ defmodule VavPipeline.L1Reader do
|
||||
absolute_consonants: String.t()
|
||||
}
|
||||
|
||||
# A petuchah (open) or setumah (closed) paragraph break, recorded by the
|
||||
# word it follows. `after_word` equal to the verse's word count is a
|
||||
# break at the end of the verse; anything less is a break within it.
|
||||
@type parashah_break :: %{after_word: pos_integer(), type: :petuchah | :setumah}
|
||||
|
||||
@type verse :: %{
|
||||
ref: String.t(),
|
||||
book: String.t(),
|
||||
@@ -58,7 +63,8 @@ defmodule VavPipeline.L1Reader do
|
||||
text_hebrew: String.t(),
|
||||
words: [word()],
|
||||
ketiv_qere: [ketiv_qere_entry()],
|
||||
smikhut: [smikhut_pair()]
|
||||
smikhut: [smikhut_pair()],
|
||||
parashah_breaks: [parashah_break()]
|
||||
}
|
||||
|
||||
@doc """
|
||||
@@ -379,10 +385,21 @@ defmodule VavPipeline.L1Reader do
|
||||
text_hebrew: data["text_hebrew"],
|
||||
words: Enum.map(data["words"] || [], &normalize_word/1),
|
||||
ketiv_qere: Enum.map(data["ketiv_qere"] || [], &normalize_ketiv_qere/1),
|
||||
smikhut: Enum.map(data["smikhut"] || [], &normalize_smikhut/1)
|
||||
smikhut: Enum.map(data["smikhut"] || [], &normalize_smikhut/1),
|
||||
parashah_breaks: Enum.flat_map(data["parashah_breaks"] || [], &normalize_parashah_break/1)
|
||||
}
|
||||
end
|
||||
|
||||
# Only the two kinds the scroll has. Anything else in that field is not
|
||||
# a paragraph break and is not passed on as one.
|
||||
defp normalize_parashah_break(%{"after_word" => n, "type" => "petuchah"}) when is_integer(n),
|
||||
do: [%{after_word: n, type: :petuchah}]
|
||||
|
||||
defp normalize_parashah_break(%{"after_word" => n, "type" => "setumah"}) when is_integer(n),
|
||||
do: [%{after_word: n, type: :setumah}]
|
||||
|
||||
defp normalize_parashah_break(_), do: []
|
||||
|
||||
defp normalize_word(w) do
|
||||
%{
|
||||
position: w["position"],
|
||||
|
||||
@@ -20,7 +20,7 @@ defmodule VavPipeline.ShemaVoiceTrajectoryTest do
|
||||
stabilizing at one pole — which is the algorithmic shape of "you have
|
||||
forgotten the One who took you out of Egypt".
|
||||
|
||||
Anchoring verses (text from L1, hash 156b1d27…):
|
||||
Anchoring verses (text from L1, hash f743b34b…):
|
||||
Devarim 6:5 וְאָהַבְתָּ … אֱלֹהֶיךָ … לְבָבְךָ — uniformly 2ms
|
||||
Devarim 11:13 וְהָיָה אִם־שָׁמֹעַ תִּשְׁמְעוּ … אֱלֹהֵיכֶם — 2mp
|
||||
Devarim 11:14 אַרְצְכֶם (2mp) → וְאָסַפְתָּ דְגָנֶךָ (2ms)
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
defmodule VavPipeline.Tablet39ParashahBreaksTest do
|
||||
@moduledoc """
|
||||
Tablet 39 (2026-10-04) — L1 had no paragraphs.
|
||||
|
||||
The WLC marks every petuchah (פ) and setumah (ס). `scripts/build_l1.py`
|
||||
read past them, so the scroll in the aron was 5,853 verses with no
|
||||
division between them but chapter numbers, which are not Torah's.
|
||||
They are now engraved per verse as `parashah_breaks`, and the reader
|
||||
carries them.
|
||||
"""
|
||||
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias VavPipeline.L1Reader
|
||||
|
||||
defp breaks(book, chapter, verse) do
|
||||
{:ok, v} = L1Reader.read_verse(book, chapter, verse)
|
||||
{v.parashah_breaks, length(v.words)}
|
||||
end
|
||||
|
||||
test "the six days of creation each close with a petuchah" do
|
||||
for verse <- [5, 8, 13, 19, 23, 31] do
|
||||
{brks, word_count} = breaks("bereishit", 1, verse)
|
||||
assert brks == [%{after_word: word_count, type: :petuchah}], "Bereishit 1:#{verse}"
|
||||
end
|
||||
end
|
||||
|
||||
test "a verse with no break after it carries an empty list, not a missing field" do
|
||||
assert {[], _} = breaks("bereishit", 1, 1)
|
||||
assert {[], _} = breaks("bereishit", 1, 6)
|
||||
end
|
||||
|
||||
test "the Shema opens after a petuchah and its first paragraph closes with a setumah" do
|
||||
# Devarim 6:3 ends the preceding section; 6:4–9 is one parashah.
|
||||
{before, n} = breaks("devarim", 6, 3)
|
||||
assert before == [%{after_word: n, type: :petuchah}]
|
||||
|
||||
for verse <- 4..8, do: assert({[], _} = breaks("devarim", 6, verse))
|
||||
|
||||
{closing, m} = breaks("devarim", 6, 9)
|
||||
assert closing == [%{after_word: m, type: :setumah}]
|
||||
end
|
||||
|
||||
test "pisqa b'emtza pasuq — a break inside the verse is placed by the word it follows" do
|
||||
# Bereishit 35:22: "…vayishma Yisrael" [פ] "vayihyu v'nei Ya'akov…"
|
||||
{[brk], word_count} = breaks("bereishit", 35, 22)
|
||||
assert brk == %{after_word: 14, type: :petuchah}
|
||||
assert brk.after_word < word_count
|
||||
|
||||
{:ok, v} = L1Reader.read_verse("bereishit", 35, 22)
|
||||
assert Enum.at(v.words, 13).consonants == "ישראל"
|
||||
assert Enum.at(v.words, 14).consonants == "ויהיו"
|
||||
end
|
||||
|
||||
test "Devarim 5:21 has two setumot — one within the verse, one after it" do
|
||||
{brks, word_count} = breaks("devarim", 5, 21)
|
||||
|
||||
assert brks == [
|
||||
%{after_word: 4, type: :setumah},
|
||||
%{after_word: word_count, type: :setumah}
|
||||
]
|
||||
end
|
||||
|
||||
test "the text of a verse is unchanged — the break is structure, not letters" do
|
||||
{:ok, v} = L1Reader.read_verse("bereishit", 1, 5)
|
||||
assert String.ends_with?(v.text_hebrew, "׃")
|
||||
refute String.ends_with?(String.trim_trailing(v.text_hebrew, "׃") |> String.trim(), "פ")
|
||||
end
|
||||
|
||||
test "the whole scroll: 293 petuchot and 394 setumot, in 686 verses" do
|
||||
index = L1Reader.l1_root() |> Path.join("torah/index.json") |> File.read!() |> Jason.decode!()
|
||||
|
||||
counts =
|
||||
for book <- index["books"],
|
||||
{chapter, verses} <- book["chapter_verse_counts"],
|
||||
verse <- 1..verses,
|
||||
reduce: %{petuchah: 0, setumah: 0, verses: 0} do
|
||||
acc ->
|
||||
{:ok, v} = L1Reader.read_verse(book["book"], String.to_integer(chapter), verse)
|
||||
|
||||
Enum.reduce(v.parashah_breaks, acc, fn b, acc -> Map.update!(acc, b.type, &(&1 + 1)) end)
|
||||
|> Map.update!(:verses, &(&1 + if(v.parashah_breaks == [], do: 0, else: 1)))
|
||||
end
|
||||
|
||||
assert counts == %{petuchah: 293, setumah: 394, verses: 686}
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user