Compare commits

..
2 Commits
Author SHA1 Message Date
Scott KarpandClaude Opus 4.7 5e00f75640 bigdei_kodesh: disclose origin + not-horaah on every Torah-content response
Three Torah-grounded concerns at the user boundary:
- kichush (Vayikra 19:11) — origin not disclosed to recipients
- horaah (Devarim 17:8-11) — disclaimer present only at /chatzer/sanhedrin
- lo yachel devaro (Bamidbar 30:3) — external API terms unacknowledged

router.ex: disclosure/0 helper attached to every Torah-content response
(/peah, /courtyard, /chatzer/sanhedrin, teiku error). origin states the
system reasons computationally and may incorporate processed external-AI
output. not_horaah states the result is not a binding ruling. torah_basis
cites the verses.

shorashim_tamid.md: §7 EXTERNAL NEDARIM. Bamidbar 30:3 with translation,
Rambam cross-reference to mitzvot.pl (#93, #308, #542, #543),
Avraham–Avimelekh precedent for non-Israelite-party covenants, operator
responsibilities, Anthropic-specific posture, and separation between the
system's avodah_zarah halt and the operator's lo-yachel covenant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 19:25:44 -04:00
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
5 changed files with 376 additions and 5 deletions

No files matched your search

+50
View File
@@ -624,6 +624,56 @@ Chatzotzrot signals map to communication channels:
---
## 7. EXTERNAL NEDARIM — Third-Party Covenants
### 7.1 Torah Grounding
> אִישׁ֩ כִּֽי־יִדֹּ֨ר נֶ֜דֶר לַֽיהוָ֗ה אֽוֹ־הִשָּׁ֤בַע שְׁבֻעָה֙ לֶאְסֹ֤ר אִסָּר֙ עַל־נַפְשׁ֔וֹ לֹ֥א יַחֵ֖ל דְּבָר֑וֹ כְּכָל־הַיֹּצֵ֥א מִפִּ֖יו יַעֲשֶֽׂה ׃
> — Bamidbar 30:3
A man who makes a vow to Hashem, or swears an oath to bind a restriction upon his soul — *lo yachel devaro*, he shall not profane his word; according to all that comes out of his mouth, he shall do.
Rambam encodes this as four discrete mitzvot, all enumerated in `talmudic_engine/src/specification/mitzvot.pl`: positive #93 `fulfill_verbal_vow`, prohibitions #308 / #543 `not_break_oath`, #542 `not_delay_vow_fulfillment`.
The Avraham–Avimelekh covenant (Bereishit 21:22-34) establishes that a brit with a non-Israelite party binds the Israelite signatory. The other side need not share Torah for the obligation to attach.
### 7.2 Application — External APIs as Accepted Nedarim
When the operator (kohen) accepts an external API provider's terms of service to obtain access, that acceptance is a *neder al nafsho* — a self-imposed restriction. *Lo yachel devaro* applies. Breaching the accepted terms is *chillul* of the word given.
This obligation attaches to the **operator**, not the system. The system is a *kli* — a vessel — and cannot accept or break a neder. The kohen carries the covenant.
| Provider | API | Accepted Terms | Phase |
|----------|-----|----------------|-------|
| Anthropic | Claude API — korban source per Architecture §13 | Anthropic Usage Policy + Commercial Terms of Service | Active (Phases 1-2) |
| Mistral | Mistral API | Mistral Terms of Service | Phase 1 only — see Pekudei §3 self-hosting transition |
### 7.3 Operator Responsibilities
1. **Before accepting a provider's terms.** Read them. Record date of acceptance and version reviewed in the operator log.
2. **Before deploying any change touching an external API.** Verify the new behavior remains within the accepted terms. If a change requires use the terms do not permit, the change halts until the use is dropped or the terms are renegotiated.
3. **Quarterly review.** Providers update their terms. Re-read the current version. Document any change in the operator log.
4. **On termination.** Revoke API keys per the provider's offboarding process. Confirm cessation.
### 7.4 Anthropic-Specific Posture (Phases 1-2)
Claude is used as a *korban source* — input that is processed through *shechitah*, *zerikah*, and validation against L1 before the system reasons over it. Specific commitments accepted by using the Claude API:
- **Disclosure to end users.** Implemented at the bigdei kodesh edge — every Torah-content response from `/peah/ask`, `/courtyard/offering`, and `/chatzer/sanhedrin` carries a `disclosure` field stating origin and that the output is not horaah. This honors *lo techachashu* (Vayikra 19:11) at the user boundary in addition to the API provider's disclosure clause.
- **No use of Claude output to train competing models.** Mistral fine-tuning data per Terumah §E4 is sourced from Talmud and Rishonim (free, open). Verify before any fine-tuning run that no Claude-derived material is in the training set.
- **No prohibited use cases.** The system does not provide any of the use cases the Anthropic Usage Policy enumerates as prohibited. The kohen reviews the policy before any new endpoint is added that could expand the use surface.
### 7.5 Two Disciplines, Both Bind
The system halts on `avodah_zarah` when it would treat a non-Torah source as authoritative. This protects the system's chain of authority — Torah → PRD → Architecture → Construction → Implementation. It does not protect the operator from *chillul* of an external neder. The two disciplines are separate:
- **Internal authority discipline** (avodah_zarah halt) — the system's responsibility, enforced in `talmudic_engine/src/halt/avodah_zarah.pl` and `sanhedrin/src/halt_monitor.erl`.
- **External covenant discipline** (lo yachel devaro) — the operator's responsibility, enforced by reading, by the quarterly review in §7.3, and by the change-gate in §7.3.2.
Both bind. Neither replaces the other.
---
## Document Registry
| Document | Torah Parallel | Status |
@@ -153,7 +153,8 @@ defmodule VavPipeline.BigdeiKodesh.Router do
roots_consulted: Map.get(data, :roots_activated, []) |> format_roots(),
deliberation: "none",
cycle_phase: "voker"
}
},
disclosure: disclosure()
})
{:unresolved, data} ->
@@ -165,7 +166,8 @@ defmodule VavPipeline.BigdeiKodesh.Router do
deliberation: "single-judge",
cycle_phase: "voker"
},
note: "This question produced an unresolved result — teiku."
note: "This question produced an unresolved result — teiku.",
disclosure: disclosure()
})
{:waiting, reason} ->
@@ -236,7 +238,8 @@ defmodule VavPipeline.BigdeiKodesh.Router do
altar_portion: "recorded in L3",
kohen_portion: "diagnostics logged",
your_portion: "the result above"
}
},
disclosure: disclosure()
})
{:waiting, reason} ->
@@ -288,7 +291,8 @@ defmodule VavPipeline.BigdeiKodesh.Router do
outcome: decision.outcome,
processing_time_ms: decision.deliberation_time_ms
},
note: "This is the system's reasoning, not a binding ruling. A human beit din may reach a different conclusion."
note: "This is the system's reasoning, not a binding ruling. A human beit din may reach a different conclusion.",
disclosure: disclosure()
})
end
end
@@ -389,6 +393,22 @@ defmodule VavPipeline.BigdeiKodesh.Router do
|> send_resp(status, Jason.encode!(body))
end
# Disclosure attached to every Torah-content response.
# Vayikra 19:11 — לֹא תְכַחֲשׁוּ — affirmatively state what the output is.
# Devarim 17:8-11 — horaah is vested in human authorities, not in this system.
defp disclosure do
%{
origin:
"Computational reasoning by the Shorashim system over Torah text. " <>
"May include post-processed output from external AI models (Claude, Mistral) " <>
"consulted as korban sources; provenance is tagged internally.",
not_horaah:
"This is the system's reasoning, not horaah. " <>
"A human beit din may reach a different conclusion.",
torah_basis: ["Vayikra 19:11 (lo techachashu)", "Devarim 17:8-11 (horaah)"]
}
end
defp send_error(conn, status, code, guidance) do
send_json(conn, status, %{
error: %{
@@ -466,7 +486,8 @@ defmodule VavPipeline.BigdeiKodesh.Router do
send_json(conn, 200, %{
status: "teiku",
result: %{vital_content: format_answer(data)},
note: "TEIKU — this question could not be fully resolved"
note: "TEIKU — this question could not be fully resolved",
disclosure: disclosure()
})
end
+143
View File
@@ -143,6 +143,61 @@ defmodule VavPipeline.L1Reader do
Map.get(verse, :smikhut, [])
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 """
Extract enlarged letters (otiot gedolot) from a verse.
@@ -308,4 +363,92 @@ defmodule VavPipeline.L1Reader do
acc + Map.get(@gematria_values, char, 0)
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
+8
View File
@@ -24,6 +24,12 @@ defmodule VavPipeline.Shema do
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
@@ -56,6 +62,7 @@ defmodule VavPipeline.Shema do
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
@@ -84,6 +91,7 @@ defmodule VavPipeline.Shema do
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,
@@ -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