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:
Scott KarpandClaude Fable 5.1 committed 2026-10-04 22:34:16 +00:00
1 parent 483d50c4ab
commit 7d46fdc7c9
701 files changed
+4305 -16

No files matched your search

+1 -1
View File
@@ -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()]
+20 -3
View File
@@ -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