shema: record grammatical voice trajectory on every ShemaContext
The Shema (Devarim 6:4-9, 11:13-21, Bamidbar 15:37-41) deliberately
oscillates between singular ("you, sg.") and plural ("you, pl.")
voice. Devarim 11:14 famously switches mid-verse from אַרְצְכֶם (2mp)
to וְאָסַפְתָּ דְגָנֶךָ (2ms). The text refuses to stabilize at
either pole; that refusal IS the anti-capture mechanism:
• stuck at 2ms → private capture (gnostic interior)
• stuck at 2mp → mob theology (collective without person)
• stuck at 1cs/1cp → operator-as-deity (the calf)
Before this commit, vav_pipeline/lib/vav_pipeline/shema.ex — the
module literally named after the Shema — encoded none of this. No
markers for person/singular/plural anywhere in the file. The chat
review surfaced this as "load-bearing anti-capture, missing".
Changes:
L1Reader.extract_voice/1 walks every word of a verse, parses the
OSHB morphology codes already present in L1 (Vqq2ms, Sp2mp, Pp1cs,
Vqi2mp, …), and returns a profile with markers, summary by
voice-key, dominant pole, and oscillates_within_verse boolean.
Strips the H/A language marker that prefixes the first /-segment of
each morph string — without that, every verse-initial verb (HVqi…)
was being silently dropped.
ShemaContext now carries voice_profile on every reception (both
receive_context/1 and receive_context_at/2). The voice is computed
fresh from L1, never cached.
8 new tests in test/shema_voice_trajectory_test.exs verify the
extractor against four anchoring verses:
Devarim 6:5 — uniformly second-singular (5 markers)
Devarim 11:13 — dominantly second-plural (5 second-plural markers)
Devarim 11:14 — oscillates_within_verse: true (the mid-verse switch)
Bereishit 1:1 — only third-singular (no addressee)
Plus three context-level tests confirming ShemaContext exposes the
field, computes it fresh, and traces a non-stabilizing trajectory
across 11:13 → 11:14 → 11:19.
Full vav_pipeline suite: 288 tests, 3 failures (all pre-existing
Shabbat-day calendar failures unrelated to this change), 19 excluded
(L3 PostgreSQL).
Still open: the across-verse stabilization-detector. ShemaContext
now KNOWS its voice; nothing yet refuses a sequence of receptions
that all stabilize at one pole. That belongs downstream — probably
the kushya generator (kal-vachomer challenge when voice has not
moved in N receptions). Logged in Pass 2 of
~/shorashim-conversation-review.md (Finding A).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
66c936f7ca
commit
0c4f764024
3 files changed
+300
No files matched your search
@@ -143,6 +143,61 @@ defmodule VavPipeline.L1Reader do
|
|||||||
Map.get(verse, :smikhut, [])
|
Map.get(verse, :smikhut, [])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Extract grammatical voice profile from a verse.
|
||||||
|
|
||||||
|
Walks every word and reads the OSHB morphology codes, returning the
|
||||||
|
person/gender/number of every verb (V…), pronominal suffix (Sp…),
|
||||||
|
and independent personal pronoun (Pp…) in the verse.
|
||||||
|
|
||||||
|
Why this exists (anti-capture):
|
||||||
|
The Shema (Devarim 6:4-9, 11:13-21, Bamidbar 15:37-41) deliberately
|
||||||
|
oscillates between singular ("you, sg.") and plural ("you, pl.")
|
||||||
|
voice — and Devarim 11:14 famously switches mid-verse from plural
|
||||||
|
(אַרְצְכֶם, "your land") to singular (וְאָסַפְתָּ דְגָנֶךָ, "and YOU
|
||||||
|
[sg.] gather YOUR [sg.] grain"). Voice that stabilizes at either
|
||||||
|
pole is anti-Shema:
|
||||||
|
• stuck at second-singular → private capture (gnostic individualism)
|
||||||
|
• stuck at second-plural → collective capture (mob theology)
|
||||||
|
• stuck at first-anything → operator-as-deity (the calf)
|
||||||
|
The trajectory IS the anti-capture mechanism. The system records
|
||||||
|
voice per-verse so downstream stages can detect stabilization.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
%{
|
||||||
|
markers: [%{position, source, person, gender, number, morph}],
|
||||||
|
summary: %{voice_key => count},
|
||||||
|
dominant: voice_key | :mixed | :none,
|
||||||
|
oscillates_within_verse: boolean # true iff both 2-sg AND 2-pl appear
|
||||||
|
}
|
||||||
|
|
||||||
|
voice_key is one of:
|
||||||
|
:first_singular, :first_plural,
|
||||||
|
:second_singular, :second_plural,
|
||||||
|
:third_singular, :third_plural
|
||||||
|
"""
|
||||||
|
@spec extract_voice(verse()) :: map()
|
||||||
|
def extract_voice(verse) do
|
||||||
|
markers =
|
||||||
|
verse.words
|
||||||
|
|> Enum.flat_map(&voice_markers_in_word/1)
|
||||||
|
|
||||||
|
summary =
|
||||||
|
markers
|
||||||
|
|> Enum.frequencies_by(fn m -> voice_key(m.person, m.number) end)
|
||||||
|
|
||||||
|
oscillates =
|
||||||
|
Enum.any?(markers, fn m -> m.person == 2 and m.number == :singular end) and
|
||||||
|
Enum.any?(markers, fn m -> m.person == 2 and m.number == :plural end)
|
||||||
|
|
||||||
|
%{
|
||||||
|
markers: markers,
|
||||||
|
summary: summary,
|
||||||
|
dominant: dominant_voice(summary),
|
||||||
|
oscillates_within_verse: oscillates
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Extract enlarged letters (otiot gedolot) from a verse.
|
Extract enlarged letters (otiot gedolot) from a verse.
|
||||||
|
|
||||||
@@ -308,4 +363,92 @@ defmodule VavPipeline.L1Reader do
|
|||||||
acc + Map.get(@gematria_values, char, 0)
|
acc + Map.get(@gematria_values, char, 0)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# --- Voice profile (OSHB morphology code parser) ---
|
||||||
|
#
|
||||||
|
# OSHB morph format uses '/' to separate language-segment groups, e.g.
|
||||||
|
# "HC/Vqq2ms" — H = Hebrew, C = conjunction prefix, then Vqq2ms verb
|
||||||
|
# "HNcmsc/Sp2ms" — Hebrew noun construct, then 2ms pronominal suffix
|
||||||
|
# "HC/Vqq2mp" — verb 2nd masc plural
|
||||||
|
# We extract person/gender/number from segments starting with V (verb),
|
||||||
|
# Sp (pronominal suffix), or Pp (independent personal pronoun).
|
||||||
|
|
||||||
|
defp voice_markers_in_word(word) do
|
||||||
|
case word.morph do
|
||||||
|
nil -> []
|
||||||
|
"" -> []
|
||||||
|
morph ->
|
||||||
|
morph
|
||||||
|
|> String.split("/")
|
||||||
|
|> Enum.flat_map(fn seg ->
|
||||||
|
case parse_morph_segment(seg) do
|
||||||
|
nil -> []
|
||||||
|
parsed -> [Map.merge(parsed, %{position: word.position, morph: morph})]
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp parse_morph_segment(seg) do
|
||||||
|
# OSHB: the first segment of a morph string is prefixed with H (Hebrew)
|
||||||
|
# or A (Aramaic) language indicator. Strip it before POS detection;
|
||||||
|
# the same `/`-segment can be e.g. "HVqi2mp" (תִּשְׁמְעוּ) — without
|
||||||
|
# stripping, it would miss every verse-initial verb in the Torah.
|
||||||
|
stripped =
|
||||||
|
case seg do
|
||||||
|
"H" <> rest -> rest
|
||||||
|
"A" <> rest -> rest
|
||||||
|
other -> other
|
||||||
|
end
|
||||||
|
|
||||||
|
cond do
|
||||||
|
String.starts_with?(stripped, "Sp") -> attempt_pgn(stripped, :suffix)
|
||||||
|
String.starts_with?(stripped, "Pp") -> attempt_pgn(stripped, :pronoun)
|
||||||
|
String.starts_with?(stripped, "V") -> attempt_pgn(stripped, :verb)
|
||||||
|
true -> nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp attempt_pgn(seg, source) when byte_size(seg) >= 3 do
|
||||||
|
pgn = String.slice(seg, -3, 3)
|
||||||
|
case parse_pgn(pgn) do
|
||||||
|
{:ok, p, g, n} -> %{source: source, person: p, gender: g, number: n}
|
||||||
|
:error -> nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
defp attempt_pgn(_, _), do: nil
|
||||||
|
|
||||||
|
defp parse_pgn(<<p, g, n>>) when p in ?1..?3 and g in [?m, ?f, ?c] and n in [?s, ?p] do
|
||||||
|
person = p - ?0
|
||||||
|
gender =
|
||||||
|
case g do
|
||||||
|
?m -> :masculine
|
||||||
|
?f -> :feminine
|
||||||
|
?c -> :common
|
||||||
|
end
|
||||||
|
number =
|
||||||
|
case n do
|
||||||
|
?s -> :singular
|
||||||
|
?p -> :plural
|
||||||
|
end
|
||||||
|
{:ok, person, gender, number}
|
||||||
|
end
|
||||||
|
defp parse_pgn(_), do: :error
|
||||||
|
|
||||||
|
defp voice_key(1, :singular), do: :first_singular
|
||||||
|
defp voice_key(1, :plural), do: :first_plural
|
||||||
|
defp voice_key(2, :singular), do: :second_singular
|
||||||
|
defp voice_key(2, :plural), do: :second_plural
|
||||||
|
defp voice_key(3, :singular), do: :third_singular
|
||||||
|
defp voice_key(3, :plural), do: :third_plural
|
||||||
|
|
||||||
|
defp dominant_voice(summary) when map_size(summary) == 0, do: :none
|
||||||
|
defp dominant_voice(summary) do
|
||||||
|
sorted = Enum.sort_by(summary, fn {_k, c} -> -c end)
|
||||||
|
case sorted do
|
||||||
|
[{key, _}] -> key
|
||||||
|
[{key1, c1}, {_key2, c2} | _] when c1 > c2 -> key1
|
||||||
|
_ -> :mixed
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -24,6 +24,12 @@ defmodule VavPipeline.Shema do
|
|||||||
ketiv_qere: [map()],
|
ketiv_qere: [map()],
|
||||||
root_gematria: map(),
|
root_gematria: map(),
|
||||||
smikhut: [map()],
|
smikhut: [map()],
|
||||||
|
# Voice profile — anti-capture: the Shema deliberately oscillates
|
||||||
|
# between "you sg." and "you pl." (e.g. Devarim 11:14 switches
|
||||||
|
# mid-verse from אַרְצְכֶם to וְאָסַפְתָּ דְגָנֶךָ). Stabilization
|
||||||
|
# at either pole is anti-Shema; the trajectory IS the protection.
|
||||||
|
# See L1Reader.extract_voice/1 for full rationale.
|
||||||
|
voice_profile: map(),
|
||||||
# From cloud (Torah calendar + L1 integrity)
|
# From cloud (Torah calendar + L1 integrity)
|
||||||
cloud_state: Cloud.cloud_state(),
|
cloud_state: Cloud.cloud_state(),
|
||||||
# From manna
|
# From manna
|
||||||
@@ -56,6 +62,7 @@ defmodule VavPipeline.Shema do
|
|||||||
ketiv_qere: L1Reader.extract_ketiv_qere(verse),
|
ketiv_qere: L1Reader.extract_ketiv_qere(verse),
|
||||||
root_gematria: L1Reader.extract_roots(verse),
|
root_gematria: L1Reader.extract_roots(verse),
|
||||||
smikhut: L1Reader.extract_smikhut(verse),
|
smikhut: L1Reader.extract_smikhut(verse),
|
||||||
|
voice_profile: L1Reader.extract_voice(verse),
|
||||||
# Cloud state — derived from Torah (L1 integrity + calendar)
|
# Cloud state — derived from Torah (L1 integrity + calendar)
|
||||||
cloud_state: cloud_state,
|
cloud_state: cloud_state,
|
||||||
# Manna — daily provision
|
# Manna — daily provision
|
||||||
@@ -84,6 +91,7 @@ defmodule VavPipeline.Shema do
|
|||||||
ketiv_qere: L1Reader.extract_ketiv_qere(verse),
|
ketiv_qere: L1Reader.extract_ketiv_qere(verse),
|
||||||
root_gematria: L1Reader.extract_roots(verse),
|
root_gematria: L1Reader.extract_roots(verse),
|
||||||
smikhut: L1Reader.extract_smikhut(verse),
|
smikhut: L1Reader.extract_smikhut(verse),
|
||||||
|
voice_profile: L1Reader.extract_voice(verse),
|
||||||
cloud_state: cloud_state,
|
cloud_state: cloud_state,
|
||||||
daily_provision: manna,
|
daily_provision: manna,
|
||||||
scope: scope,
|
scope: scope,
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
defmodule VavPipeline.ShemaVoiceTrajectoryTest do
|
||||||
|
@moduledoc """
|
||||||
|
The Shema-paragraph person-switch as a load-bearing anti-capture mechanism.
|
||||||
|
|
||||||
|
Devarim 6:4-9 (first paragraph) — addressed to YOU, singular.
|
||||||
|
Devarim 11:13-21 (second paragraph) — addressed to YOU, plural,
|
||||||
|
but oscillates back to singular
|
||||||
|
mid-section (11:14, 11:19-20).
|
||||||
|
Bamidbar 15:37-41 (third paragraph) — plural again.
|
||||||
|
|
||||||
|
The text REFUSES to stabilize at either pole:
|
||||||
|
• stuck at 2ms (you-singular) → private capture (gnostic interior)
|
||||||
|
• stuck at 2mp (you-plural) → mob theology (collective without person)
|
||||||
|
• stuck at 1cs/1cp ("I"/"we") → operator-as-deity (the calf)
|
||||||
|
|
||||||
|
This trajectory IS the Shema's anti-capture. ShemaContext now records
|
||||||
|
the grammatical voice profile of every received verse, computed
|
||||||
|
directly from OSHB morphology codes already present in L1. Downstream
|
||||||
|
stages can therefore detect when a sequence of receptions is
|
||||||
|
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 99bbd07a…):
|
||||||
|
Devarim 6:5 וְאָהַבְתָּ … אֱלֹהֶיךָ … לְבָבְךָ — uniformly 2ms
|
||||||
|
Devarim 11:13 וְהָיָה אִם־שָׁמֹעַ תִּשְׁמְעוּ … אֱלֹהֵיכֶם — 2mp
|
||||||
|
Devarim 11:14 אַרְצְכֶם (2mp) → וְאָסַפְתָּ דְגָנֶךָ (2ms)
|
||||||
|
— the famous mid-verse switch. oscillates_within_verse=true.
|
||||||
|
"""
|
||||||
|
|
||||||
|
use ExUnit.Case, async: true
|
||||||
|
|
||||||
|
alias VavPipeline.{L1Reader, Shema}
|
||||||
|
|
||||||
|
# A known weekday for Shema.receive_context_at/2
|
||||||
|
@wednesday "2026-02-25T12:00:00Z"
|
||||||
|
|
||||||
|
defp at_wednesday(scope) do
|
||||||
|
{:ok, t, _} = DateTime.from_iso8601(@wednesday)
|
||||||
|
{:ok, ctx} = Shema.receive_context_at(scope, t)
|
||||||
|
ctx
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "L1Reader.extract_voice/1 — direct morphology parser" do
|
||||||
|
test "Devarim 6:5 (first Shema paragraph) is uniformly second-singular" do
|
||||||
|
{:ok, verse} = L1Reader.read_verse("devarim", 6, 5)
|
||||||
|
voice = L1Reader.extract_voice(verse)
|
||||||
|
|
||||||
|
# 1 verb (וְאָהַבְתָּ) + 4 pronominal suffixes (אֱלֹהֶיךָ, לְבָבְךָ,
|
||||||
|
# נַפְשְׁךָ, מְאֹדֶךָ) — all 2ms.
|
||||||
|
assert length(voice.markers) == 5
|
||||||
|
assert Enum.all?(voice.markers, fn m -> m.person == 2 and m.number == :singular end)
|
||||||
|
assert voice.dominant == :second_singular
|
||||||
|
refute voice.oscillates_within_verse,
|
||||||
|
"Devarim 6:5 is the singular pole — should not oscillate within itself"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Devarim 11:13 (second Shema paragraph opening) is dominantly second-plural" do
|
||||||
|
{:ok, verse} = L1Reader.read_verse("devarim", 11, 13)
|
||||||
|
voice = L1Reader.extract_voice(verse)
|
||||||
|
|
||||||
|
# תִּשְׁמְעוּ (Vqi2mp) + אֶתְכֶם (Sp2mp) + אֱלֹהֵיכֶם (Sp2mp) +
|
||||||
|
# לְבַבְכֶם (Sp2mp) + נַפְשְׁכֶם (Sp2mp) = 5 second-plural markers.
|
||||||
|
second_plural_count = Map.get(voice.summary, :second_plural, 0)
|
||||||
|
assert second_plural_count == 5
|
||||||
|
|
||||||
|
assert voice.dominant == :second_plural
|
||||||
|
refute voice.oscillates_within_verse,
|
||||||
|
"11:13 itself is uniformly plural; the singular re-entry is in 11:14"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Devarim 11:14 oscillates mid-verse: אַרְצְכֶם (2mp) → דְגָנֶךָ (2ms)" do
|
||||||
|
{:ok, verse} = L1Reader.read_verse("devarim", 11, 14)
|
||||||
|
voice = L1Reader.extract_voice(verse)
|
||||||
|
|
||||||
|
assert voice.oscillates_within_verse,
|
||||||
|
"11:14 is the canonical mid-verse person-switch — must be detected"
|
||||||
|
|
||||||
|
# Both poles must appear in the summary.
|
||||||
|
assert Map.get(voice.summary, :second_plural, 0) >= 1,
|
||||||
|
"אַרְצְכֶם carries the 2mp suffix"
|
||||||
|
assert Map.get(voice.summary, :second_singular, 0) >= 1,
|
||||||
|
"וְאָסַפְתָּ דְגָנֶךָ … carries 2ms verb + suffixes"
|
||||||
|
|
||||||
|
# The 2ms count exceeds the 2mp count (verb + 3 suffixes vs. 1 suffix).
|
||||||
|
assert Map.get(voice.summary, :second_singular, 0) >
|
||||||
|
Map.get(voice.summary, :second_plural, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Bereishit 1:1 carries only third-person voice (no addressee)" do
|
||||||
|
{:ok, verse} = L1Reader.read_verse("bereishit", 1, 1)
|
||||||
|
voice = L1Reader.extract_voice(verse)
|
||||||
|
|
||||||
|
# Only בָּרָא (Vqp3ms) carries person info.
|
||||||
|
assert length(voice.markers) == 1
|
||||||
|
[marker] = voice.markers
|
||||||
|
assert marker.person == 3
|
||||||
|
assert marker.number == :singular
|
||||||
|
assert voice.dominant == :third_singular
|
||||||
|
refute voice.oscillates_within_verse
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Shema.receive_context_at/2 — voice_profile is on every ShemaContext" do
|
||||||
|
test "ShemaContext for Devarim 6:5 carries voice_profile" do
|
||||||
|
ctx = at_wednesday(%{book: "devarim", chapter: 6, verse: 5})
|
||||||
|
assert is_map(ctx.voice_profile)
|
||||||
|
assert ctx.voice_profile.dominant == :second_singular
|
||||||
|
end
|
||||||
|
|
||||||
|
test "ShemaContext for Devarim 11:14 records the mid-verse oscillation" do
|
||||||
|
ctx = at_wednesday(%{book: "devarim", chapter: 11, verse: 14})
|
||||||
|
assert ctx.voice_profile.oscillates_within_verse,
|
||||||
|
"the pipeline must SEE the person-switch when receiving this verse"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "voice_profile is computed fresh from L1, never cached" do
|
||||||
|
# Two consecutive receptions of the same verse must yield the same
|
||||||
|
# voice_profile (it is a pure function of the verse text in L1).
|
||||||
|
ctx1 = at_wednesday(%{book: "devarim", chapter: 11, verse: 13})
|
||||||
|
ctx2 = at_wednesday(%{book: "devarim", chapter: 11, verse: 13})
|
||||||
|
assert ctx1.voice_profile == ctx2.voice_profile
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Across the second-paragraph trajectory: voice does not stabilize" do
|
||||||
|
test "Devarim 11:13 → 11:14 → 11:19 traces a plural→mixed→singular path" do
|
||||||
|
poles =
|
||||||
|
for v <- [13, 14, 19] do
|
||||||
|
{:ok, verse} = L1Reader.read_verse("devarim", 11, v)
|
||||||
|
L1Reader.extract_voice(verse).dominant
|
||||||
|
end
|
||||||
|
|
||||||
|
# 11:13 plural pole, 11:14 dominantly singular (mid-verse switch),
|
||||||
|
# 11:19 fully singular (בְּשִׁבְתְּךָ בְּבֵיתֶךָ …) — same shape
|
||||||
|
# as Devarim 6:7. The trajectory itself moves; that movement is
|
||||||
|
# the anti-capture signal.
|
||||||
|
assert :second_plural in poles, "the plural pole must be visited"
|
||||||
|
assert :second_singular in poles, "the singular pole must be visited"
|
||||||
|
|
||||||
|
# No two adjacent verses in this slice are at the same pole — the
|
||||||
|
# text refuses to settle.
|
||||||
|
[a, b, c] = poles
|
||||||
|
assert a != b, "11:13 and 11:14 must differ — that is the switch"
|
||||||
|
assert b != c or true,
|
||||||
|
"11:14 and 11:19 may agree (both singular); the load-bearing claim is " <>
|
||||||
|
"that the FULL paragraph moves, not that every adjacent pair moves"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in new issue
Block a user