Files
bikoret/vav_pipeline/lib/vav_pipeline/shema.ex
T
Scott KarpandClaude Opus 4.7 0c4f764024 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>
2026-04-24 18:58:14 -04:00

105 lines
3.5 KiB
Elixir

defmodule VavPipeline.Shema do
@moduledoc """
The Shema context builder.
Architecture §3.9, §4 Step 9:
Reads from L1 for te'amim/Torah governance.
Reads calendar for Shabbat/festivals.
Reads provision file for manna.
The ShemaContext is IMMUTABLE once built. It is passed through
every stage of the pipeline. Processing happens WITHIN the context,
never modifying it.
Construction spec §4.4: shema_context_cache_ttl_ms: 0
— always read fresh. No caching.
"""
alias VavPipeline.{L1Reader, Cloud, Manna}
@type shema_context :: %{
# From L1
verse: L1Reader.verse(),
teamim_marks: [map()],
ketiv_qere: [map()],
root_gematria: 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)
cloud_state: Cloud.cloud_state(),
# From manna
daily_provision: Manna.manna() | :no_provision,
# Metadata
scope: VavPipeline.scope(),
received_at: DateTime.t()
}
@doc """
Receive context for a verse scope.
This is RECEPTION, not processing. The system hears before it speaks.
Reads L1 (te'amim, roots, ketiv/qere), checks cloud (Torah + calendar),
checks manna (provision).
Returns {:ok, context} or {:error, reason}.
"""
@spec receive_context(VavPipeline.scope()) :: {:ok, shema_context()} | {:error, String.t()}
def receive_context(scope) do
now = DateTime.utc_now()
with {:ok, verse} <- L1Reader.read_verse(scope.book, scope.chapter, scope.verse),
{:ok, cloud_state} <- Cloud.check_at(now),
{:ok, manna} <- Manna.check_at(now) do
context = %{
# L1 data — read fresh, never cached
verse: verse,
teamim_marks: L1Reader.extract_teamim(verse),
ketiv_qere: L1Reader.extract_ketiv_qere(verse),
root_gematria: L1Reader.extract_roots(verse),
smikhut: L1Reader.extract_smikhut(verse),
voice_profile: L1Reader.extract_voice(verse),
# Cloud state — derived from Torah (L1 integrity + calendar)
cloud_state: cloud_state,
# Manna — daily provision
daily_provision: manna,
# Metadata
scope: scope,
received_at: now
}
{:ok, context}
end
end
@doc """
Build context with explicit time. Testable variant.
"""
@spec receive_context_at(VavPipeline.scope(), DateTime.t()) ::
{:ok, shema_context()} | {:error, String.t()}
def receive_context_at(scope, now) do
with {:ok, verse} <- L1Reader.read_verse(scope.book, scope.chapter, scope.verse),
{:ok, cloud_state} <- Cloud.check_at(now),
{:ok, manna} <- Manna.check_at(now) do
context = %{
verse: verse,
teamim_marks: L1Reader.extract_teamim(verse),
ketiv_qere: L1Reader.extract_ketiv_qere(verse),
root_gematria: L1Reader.extract_roots(verse),
smikhut: L1Reader.extract_smikhut(verse),
voice_profile: L1Reader.extract_voice(verse),
cloud_state: cloud_state,
daily_provision: manna,
scope: scope,
received_at: now
}
{:ok, context}
end
end
end