From d3869c5b753210aee33484b50dd78db5ad267c9e Mon Sep 17 00:00:00 2001 From: Scott Karp Date: Sun, 26 Apr 2026 12:02:05 -0400 Subject: [PATCH] l1_reader: document te'amim classification principle and shoresh derivation provenance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related Pass 2 findings — both about UNSTATED reasoning behind hard-coded lists in L1Reader. Finding G — te'amim classification principle: @melekh_marks and @mesharet_marks were lists of strings with no documented rule for which marks belong to which class. A future reader saw the lists but not the deliberation. Added classification principle to the comment block: - The diagnostic test: can the ta'am occupy a sof-pasuk-equivalent position? → melekh. Does it only serve a following melekh? → mesharet. - The hierarchy of melakhim corresponds to syntactic depth (sof_pasuk → etnachta → zaqef → tipcha → revia → …). - Mesharetim precede their melakhim — the servant goes before the king. - The classification is BINDING for havdalah: treating a mesharet as a phrase-end is a small gilui_arayot. - Source authority: Yeivin's Tiberian Masorah §174-§268. - Maintenance rule: if OSHB ever encodes a ta'am with a different name, BOTH lists must be updated. Finding H — shoresh derivation provenance: extract_roots/1 returned a flat root → gematria map without recording which derivational opinion produced each shoresh. For triliterals this rarely matters; for weak roots and biliterals it matters constantly (e.g. וְנָתַתִּי in Devarim 11:14 — OSHB records נת, classical analysis often gives נתן with assimilated nun). Added provenance note to the function docstring: - The shoresh comes from OSHB lemma database via build_l1.py - OSHB pins one root per word at build time; L1 JSON does not record which derivational opinion produced it - Downstream consumers MUST NOT treat the returned shoresh as unique-truth - The Talmudic Engine should record the derivational source in coupling_trace when a shoresh-based ruling enters L3 — otherwise a future re-derivation cannot be reconciled with prior opinions stored on top of the OSHB choice - Preserving derivational alternatives belongs in the build_l1.py enrichment pass, not in this read function Both docstrings make the WHY visible; the WHAT was already there. No code change. Tests pass unchanged (288 in vav_pipeline, 8 in shema_voice_trajectory_test). Co-Authored-By: Claude Opus 4.7 (1M context) --- vav_pipeline/lib/vav_pipeline/l1_reader.ex | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/vav_pipeline/lib/vav_pipeline/l1_reader.ex b/vav_pipeline/lib/vav_pipeline/l1_reader.ex index a3134ef5..69e3aae4 100644 --- a/vav_pipeline/lib/vav_pipeline/l1_reader.ex +++ b/vav_pipeline/lib/vav_pipeline/l1_reader.ex @@ -120,6 +120,27 @@ defmodule VavPipeline.L1Reader do @doc """ Extract roots from a verse with gematria values. + + PROVENANCE NOTE — single-opinion derivation: + + The shoresh value on each word comes from the OSHB (Open Scriptures + Hebrew Bible) lemma database via build_l1.py. OSHB pins one root + per word at build time; the L1 JSON does not record which + derivational opinion produced it. For triliteral roots the choice + is rarely contested, but for weak roots and biliterals it matters + constantly — e.g. for וְנָתַתִּי (Devarim 11:14) OSHB records the + biliteral נת, while classical analysis often gives נתן with + assimilated nun. Both are defensible; OSHB picked one. + + Downstream consumers MUST NOT treat the returned shoresh as + unique-truth. The Talmudic Engine should record the derivational + source as part of its coupling_trace when a shoresh-based ruling + enters L3 — otherwise a future re-derivation cannot be reconciled + with prior opinions stored on top of the OSHB choice. + + This function returns a flat root → gematria map. To preserve + derivational alternatives, that work belongs in the build_l1.py + enrichment pass, not here. """ @spec extract_roots(verse()) :: map() def extract_roots(verse) do @@ -315,6 +336,37 @@ defmodule VavPipeline.L1Reader do # Melekh = disjunctor (emperor/king), Mesharet = conjunctor (servant) # Spellings must match what build_l1.py writes to L1 JSON. # Verified against actual L1 data (bereishit-devarim ch1 sample). + # + # Classification principle (the WHY behind the two lists): + # + # Each ta'am is either MELEKH or MESHARET. The classification is + # not a stylistic preference — it is structural. A melekh stands + # alone and ends a phrase; a mesharet only joins to the following + # word and cannot stand alone. The diagnostic test: + # + # "Can this ta'am occupy the position of sof-pasuk-equivalent + # in some valid context?" → if yes, melekh. + # "Does this ta'am only occur in service of a following melekh, + # binding two words into one syntactic unit?" → if yes, mesharet. + # + # The hierarchy of melakhim (sof_pasuk → etnachta → zaqef → tipcha + # → revia → …) corresponds to syntactic depth: each level marks + # the cut for one level of clausal subdivision. The mesharetim + # (munach, mercha, mahapakh, darga, qadma, telisha_qetana, + # yerah_ben_yomo) precede their melakhim — the servant goes + # before the king. + # + # This classification is binding for the pipeline's havdalah + # (separation) operation: only melakhim are valid phrase-cut + # points. Treating a mesharet as a phrase-end would falsify the + # text's own divisions — a small gilui_arayot (collapsing the + # disjunctor/conjunctor distinction). + # + # Source authority: this is the consensus Masoretic position + # documented in Yeivin's "Introduction to the Tiberian + # Masorah" (1980) §174-§268. The lists below match what + # build_l1.py emits from the OSHB JSON; if OSHB ever encodes a + # ta'am with a different name, BOTH lists must be updated. @melekh_marks ~w(sof_pasuk etnachta segol_accent shalshelet zaqef_qatan zaqef_gadol tipcha revia zarqa pashta yetiv tevir geresh gershayim pazer telisha_gedola meteg_siluq zinor geresh_muqdam)