1. Chatzotzrot signal mapping corrected to match Architecture §3.12: assembly_all→tekiah, assembly_leaders→teruah, breaking_camp→tekiah_teruah, moadim→tekiah_tekiah. Four of five patterns were swapped. All five now match Bamidbar 10. 2. Manna parse_datetime(nil) bug fixed: missing expires_at now fails validation instead of silently returning DateTime.utc_now() which created a race condition where manna appeared expired. 3. Festival detection implemented in cloud.ex: pre-computed Gregorian dates for Pesach, Shavuot, Rosh Hashanah, Yom Kippur, Sukkot, Shemini Atzeret (2025-2028). Pipeline now halts on Yom Tov as Torah requires. Step 15 verification now covers festival rest. 76 tests, 0 failures (68 prior + 8 new). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
485 lines
16 KiB
Elixir
485 lines
16 KiB
Elixir
defmodule Step15ShemaTest do
|
|
@moduledoc """
|
|
Step 15 Verification — The Shema Test.
|
|
|
|
Architecture §6.3: Before the ki tov test, the shema test: can the system hear?
|
|
|
|
Five tests from §6.3:
|
|
1. Te'amim reception — the text's own divisions guide havdalah
|
|
2. Ketiv/qere preservation — dual readings never collapse
|
|
3. Cloud obedience — system does NOT process when cloud lifts
|
|
4. Manna fidelity — system waits when no provision
|
|
5. Context immutability — ShemaContext unchanged at every stage
|
|
|
|
Plus integration tests from §6.2:
|
|
6. Halt condition — pipeline halts on cardinal sin trigger
|
|
7. Full pipeline end-to-end — input through to evaluated output
|
|
"""
|
|
|
|
use ExUnit.Case, async: false
|
|
|
|
alias VavPipeline.{BereishitCycle, Shema, Specification, HaltConditions}
|
|
|
|
# A known weekday for testing — Wednesday noon UTC
|
|
@wednesday_noon "2026-02-25T12:00:00Z"
|
|
@scope %{book: "bereishit", chapter: 1, verse: 1}
|
|
|
|
# ============================================================
|
|
# Test 1: Te'amim Reception (Architecture §6.3)
|
|
# ============================================================
|
|
describe "Shema Test 1: Te'amim Reception" do
|
|
test "ShemaContext contains raw te'amim marks from L1" do
|
|
{:ok, wednesday, _} = DateTime.from_iso8601(@wednesday_noon)
|
|
{:ok, context} = Shema.receive_context_at(@scope, wednesday)
|
|
|
|
# 7 words in Bereishit 1:1 → 7 te'amim marks
|
|
assert length(context.teamim_marks) == 7
|
|
|
|
# Each mark has taam, word_index, role
|
|
Enum.each(context.teamim_marks, fn mark ->
|
|
assert Map.has_key?(mark, :taam)
|
|
assert Map.has_key?(mark, :word_index)
|
|
assert Map.has_key?(mark, :role)
|
|
assert mark.role in ["melekh", "mesharet"]
|
|
end)
|
|
end
|
|
|
|
test "etnachta on word 3 classified as melekh" do
|
|
{:ok, wednesday, _} = DateTime.from_iso8601(@wednesday_noon)
|
|
{:ok, context} = Shema.receive_context_at(@scope, wednesday)
|
|
|
|
etnachta = Enum.find(context.teamim_marks, fn m -> m.taam == "etnachta" end)
|
|
assert etnachta != nil
|
|
assert etnachta.word_index == 2
|
|
assert etnachta.role == "melekh"
|
|
end
|
|
|
|
test "meteg_siluq on last word classified as melekh" do
|
|
{:ok, wednesday, _} = DateTime.from_iso8601(@wednesday_noon)
|
|
{:ok, context} = Shema.receive_context_at(@scope, wednesday)
|
|
|
|
siluq = Enum.find(context.teamim_marks, fn m -> m.taam == "meteg_siluq" end)
|
|
assert siluq != nil
|
|
assert siluq.word_index == 6
|
|
assert siluq.role == "melekh"
|
|
end
|
|
|
|
test "havdalah follows the text's own divisions, not imposed ones" do
|
|
{:ok, wednesday, _} = DateTime.from_iso8601(@wednesday_noon)
|
|
{:ok, context} = Shema.receive_context_at(@scope, wednesday)
|
|
|
|
input = %{
|
|
source: "test",
|
|
verse_scope: @scope,
|
|
content: "בראשית ברא אלהים את השמים ואת הארץ"
|
|
}
|
|
|
|
# Run through tohu → vayomer → havdalah
|
|
result =
|
|
input
|
|
|> BereishitCycle.tohu(context)
|
|
|> BereishitCycle.vayomer(context)
|
|
|> BereishitCycle.havdalah(context)
|
|
|
|
# Havdalah boundaries must match melekh positions from te'amim
|
|
melekh_positions =
|
|
context.teamim_marks
|
|
|> Enum.filter(fn m -> m.role == "melekh" end)
|
|
|> Enum.map(fn m -> m.word_index end)
|
|
|> Enum.sort()
|
|
|
|
assert result.separation_boundaries == melekh_positions
|
|
|
|
# System used text's divisions (etnachta at 2, meteg_siluq at 6)
|
|
assert 2 in result.separation_boundaries
|
|
assert 6 in result.separation_boundaries
|
|
end
|
|
end
|
|
|
|
# ============================================================
|
|
# Test 2: Ketiv/Qere Preservation (Architecture §6.3)
|
|
# ============================================================
|
|
describe "Shema Test 2: Ketiv/Qere Preservation" do
|
|
setup do
|
|
{:ok, wednesday, _} = DateTime.from_iso8601(@wednesday_noon)
|
|
{:ok, context} = Shema.receive_context_at(@scope, wednesday)
|
|
|
|
# Inject synthetic ketiv/qere (Bereishit 1:1 has none)
|
|
ketiv_qere = [
|
|
%{
|
|
word_index: 3,
|
|
ketiv: "את",
|
|
qere: "אות",
|
|
both_preserved: true
|
|
}
|
|
]
|
|
|
|
context = %{context | ketiv_qere: ketiv_qere}
|
|
|
|
input = %{
|
|
source: "test",
|
|
verse_scope: @scope,
|
|
content: "בראשית ברא אלהים את השמים ואת הארץ"
|
|
}
|
|
|
|
{:ok, context: context, input: input, ketiv_qere: ketiv_qere}
|
|
end
|
|
|
|
test "context contains both readings", %{context: context, ketiv_qere: ketiv_qere} do
|
|
assert context.ketiv_qere == ketiv_qere
|
|
kq = hd(context.ketiv_qere)
|
|
assert kq.ketiv == "את"
|
|
assert kq.qere == "אות"
|
|
end
|
|
|
|
test "ketiv/qere preserved through vayomer", %{context: context, input: input} do
|
|
result =
|
|
input
|
|
|> BereishitCycle.tohu(context)
|
|
|> BereishitCycle.vayomer(context)
|
|
|
|
# vayomer copies ketiv_qere from context into state
|
|
assert result.ketiv_qere == context.ketiv_qere
|
|
end
|
|
|
|
test "ki_tov evaluation preserves both readings", %{context: context, input: input} do
|
|
result =
|
|
input
|
|
|> BereishitCycle.tohu(context)
|
|
|> BereishitCycle.vayomer(context)
|
|
|> BereishitCycle.havdalah(context)
|
|
|> BereishitCycle.kriah(context)
|
|
|
|
named_results = result.named_results
|
|
|
|
# Specification.evaluate receives the ketiv_qere list
|
|
evaluation = Specification.evaluate(named_results, context.ketiv_qere)
|
|
|
|
# With both_preserved: true, evaluation should be :tov (not lo_tov)
|
|
assert evaluation == :tov
|
|
end
|
|
|
|
test "collapsed ketiv/qere triggers lo_tov" do
|
|
# Simulate a collapsed dual reading
|
|
collapsed_kq = [%{word_index: 3, ketiv: "את", qere: "את", both_preserved: false}]
|
|
results = [%{shoresh: "ברא"}]
|
|
|
|
evaluation = Specification.evaluate(results, collapsed_kq)
|
|
assert {:lo_tov, reason} = evaluation
|
|
assert reason =~ "ketiv/qere"
|
|
end
|
|
end
|
|
|
|
# ============================================================
|
|
# Test 3: Cloud Obedience (Architecture §6.3)
|
|
# ============================================================
|
|
describe "Shema Test 3: Cloud Obedience" do
|
|
test "Shabbat cloud state prevents processing" do
|
|
{:ok, shabbat, _} = DateTime.from_iso8601("2026-02-28T10:00:00Z")
|
|
{:ok, context} = Shema.receive_context_at(@scope, shabbat)
|
|
|
|
# Context reports major_transition
|
|
assert {:major_transition, :shabbat_rest, _} = context.cloud_state
|
|
end
|
|
|
|
test "pipeline returns {:transition, :shabbat_rest} on Shabbat — does NOT process" do
|
|
{:ok, shabbat, _} = DateTime.from_iso8601("2026-02-28T10:00:00Z")
|
|
{:ok, context} = Shema.receive_context_at(@scope, shabbat)
|
|
|
|
# Simulate what VavPipeline.process/1 does after context reception
|
|
result =
|
|
case context.cloud_state do
|
|
:moving ->
|
|
{:transition, :cloud_lifted}
|
|
|
|
{:major_transition, signal, _params} ->
|
|
{:transition, signal}
|
|
|
|
:cloud_not_visible ->
|
|
{:waiting, :cloud_not_visible}
|
|
|
|
:camped ->
|
|
:would_process
|
|
end
|
|
|
|
assert {:transition, :shabbat_rest} = result
|
|
end
|
|
|
|
test "festival cloud state prevents processing — Pesach" do
|
|
# 15 Nisan 5787 = 2 April 2026
|
|
{:ok, pesach, _} = DateTime.from_iso8601("2026-04-02T12:00:00Z")
|
|
{:ok, context} = Shema.receive_context_at(@scope, pesach)
|
|
|
|
assert {:major_transition, :festival_rest, %{festival: :pesach}} = context.cloud_state
|
|
|
|
# Pipeline would return {:transition, :festival_rest}
|
|
result =
|
|
case context.cloud_state do
|
|
{:major_transition, signal, _params} -> {:transition, signal}
|
|
_ -> :would_process
|
|
end
|
|
|
|
assert {:transition, :festival_rest} = result
|
|
end
|
|
|
|
test "cloud_not_visible triggers waiting" do
|
|
# Manually construct context with cloud_not_visible
|
|
{:ok, wednesday, _} = DateTime.from_iso8601(@wednesday_noon)
|
|
{:ok, context} = Shema.receive_context_at(@scope, wednesday)
|
|
context = %{context | cloud_state: :cloud_not_visible}
|
|
|
|
result =
|
|
case context.cloud_state do
|
|
:cloud_not_visible -> {:waiting, :cloud_not_visible}
|
|
_ -> :would_process
|
|
end
|
|
|
|
assert {:waiting, :cloud_not_visible} = result
|
|
end
|
|
end
|
|
|
|
# ============================================================
|
|
# Test 4: Manna Fidelity (Architecture §6.3)
|
|
# ============================================================
|
|
describe "Shema Test 4: Manna Fidelity" do
|
|
test "no provision → pipeline returns {:waiting, :no_provision}" do
|
|
{:ok, wednesday, _} = DateTime.from_iso8601(@wednesday_noon)
|
|
{:ok, context} = Shema.receive_context_at(@scope, wednesday)
|
|
|
|
# No provision file exists in test environment
|
|
assert context.daily_provision == :no_provision
|
|
|
|
# Simulate VavPipeline.process decision
|
|
result =
|
|
case context.daily_provision do
|
|
:no_provision -> {:waiting, :no_provision}
|
|
_provision -> :would_process
|
|
end
|
|
|
|
assert {:waiting, :no_provision} = result
|
|
end
|
|
|
|
test "with provision → pipeline proceeds" do
|
|
{:ok, wednesday, _} = DateTime.from_iso8601(@wednesday_noon)
|
|
{:ok, context} = Shema.receive_context_at(@scope, wednesday)
|
|
|
|
# Inject valid provision
|
|
provision = %{
|
|
date: "2026-02-25",
|
|
provision: %{
|
|
compute_units: 1000,
|
|
memory_mb: 32768,
|
|
api_calls_external: 500,
|
|
storage_delta_mb: 100,
|
|
sanhedrin_panels_max: 5
|
|
},
|
|
type: :regular,
|
|
expires_at: DateTime.utc_now() |> DateTime.add(3600),
|
|
written_by: "test",
|
|
written_at: DateTime.utc_now()
|
|
}
|
|
|
|
context = %{context | daily_provision: provision}
|
|
|
|
result =
|
|
case context.daily_provision do
|
|
:no_provision -> {:waiting, :no_provision}
|
|
_provision -> :would_process
|
|
end
|
|
|
|
assert result == :would_process
|
|
end
|
|
end
|
|
|
|
# ============================================================
|
|
# Test 5: Context Immutability (Architecture §6.3)
|
|
# ============================================================
|
|
describe "Shema Test 5: Context Immutability" do
|
|
test "ShemaContext is identical at every pipeline stage" do
|
|
{:ok, wednesday, _} = DateTime.from_iso8601(@wednesday_noon)
|
|
{:ok, context} = Shema.receive_context_at(@scope, wednesday)
|
|
|
|
input = %{
|
|
source: "test",
|
|
verse_scope: @scope,
|
|
content: "בראשית ברא אלהים את השמים ואת הארץ"
|
|
}
|
|
|
|
# Capture context hash before processing
|
|
ctx_hash = :erlang.phash2(context)
|
|
|
|
# Stage 1: tohu
|
|
state1 = BereishitCycle.tohu(input, context)
|
|
assert :erlang.phash2(context) == ctx_hash, "Context mutated after tohu"
|
|
|
|
# Stage 2: vayomer
|
|
state2 = BereishitCycle.vayomer(state1, context)
|
|
assert :erlang.phash2(context) == ctx_hash, "Context mutated after vayomer"
|
|
|
|
# Stage 3: havdalah
|
|
state3 = BereishitCycle.havdalah(state2, context)
|
|
assert :erlang.phash2(context) == ctx_hash, "Context mutated after havdalah"
|
|
|
|
# Stage 4: kriah
|
|
state4 = BereishitCycle.kriah(state3, context)
|
|
assert :erlang.phash2(context) == ctx_hash, "Context mutated after kriah"
|
|
|
|
# Stage 5: ki_tov
|
|
_result = BereishitCycle.ki_tov(state4, context)
|
|
assert :erlang.phash2(context) == ctx_hash, "Context mutated after ki_tov"
|
|
|
|
# Verify deep equality — not just hash
|
|
assert context.verse.ref == "Bereishit 1:1"
|
|
assert length(context.teamim_marks) == 7
|
|
assert context.scope == @scope
|
|
end
|
|
end
|
|
|
|
# ============================================================
|
|
# Test 6: Halt Condition Pipeline Integration (Architecture §6.2)
|
|
# ============================================================
|
|
describe "Integration: Halt Conditions" do
|
|
setup do
|
|
{:ok, wednesday, _} = DateTime.from_iso8601(@wednesday_noon)
|
|
{:ok, context} = Shema.receive_context_at(@scope, wednesday)
|
|
|
|
input = %{
|
|
source: "test",
|
|
verse_scope: @scope,
|
|
content: "בראשית ברא אלהים את השמים ואת הארץ"
|
|
}
|
|
|
|
{:ok, context: context, input: input}
|
|
end
|
|
|
|
test "avodah zarah halts pipeline", %{context: context} do
|
|
# Construct result data with non-Torah authority in named_results
|
|
data = %{
|
|
named_results: [%{authority_source: :claude_model, name: "test", roots: [], index: 0}],
|
|
stage: :ki_tov,
|
|
evaluation: :tov
|
|
}
|
|
|
|
result = BereishitCycle.erev_voker({:ok, data}, context)
|
|
assert {:halted, :avodah_zarah, reason} = result
|
|
assert reason =~ "non-Torah authority"
|
|
end
|
|
|
|
test "shfichut damim halts pipeline", %{context: context} do
|
|
data = %{
|
|
named_results: [%{action: :delete, name: "test", roots: [], index: 0}],
|
|
stage: :ki_tov,
|
|
evaluation: :tov
|
|
}
|
|
|
|
result = BereishitCycle.erev_voker({:ok, data}, context)
|
|
assert {:halted, :shfichut_damim, reason} = result
|
|
assert reason =~ "destructive action"
|
|
end
|
|
|
|
test "gilui arayot halts pipeline", %{context: context} do
|
|
data = %{
|
|
named_results: [
|
|
%{
|
|
boundary_violation: {:spec_interp_merge, "treated interpretation as specification"},
|
|
name: "test",
|
|
roots: [],
|
|
index: 0
|
|
}
|
|
],
|
|
stage: :ki_tov,
|
|
evaluation: :tov
|
|
}
|
|
|
|
result = BereishitCycle.erev_voker({:ok, data}, context)
|
|
assert {:halted, :gilui_arayot, reason} = result
|
|
assert reason =~ "boundary collapsed"
|
|
end
|
|
|
|
test "clean results pass halt check", %{context: context} do
|
|
data = %{
|
|
named_results: [%{shoresh: "ברא", name: "ברא", roots: [], index: 0}],
|
|
stage: :ki_tov,
|
|
evaluation: :tov
|
|
}
|
|
|
|
result = BereishitCycle.erev_voker({:ok, data}, context)
|
|
assert {:ok, _} = result
|
|
end
|
|
|
|
test "HaltConditions.check_all with clean results" do
|
|
results = [%{shoresh: "ברא", action: :read, authority_source: :l1}]
|
|
assert :clear == HaltConditions.check_all(results)
|
|
end
|
|
end
|
|
|
|
# ============================================================
|
|
# Test 7: Full Pipeline End-to-End (Architecture §6.2)
|
|
# ============================================================
|
|
describe "Integration: Full Pipeline" do
|
|
test "BereishitCycle.execute produces evaluated output" do
|
|
{:ok, wednesday, _} = DateTime.from_iso8601(@wednesday_noon)
|
|
{:ok, context} = Shema.receive_context_at(@scope, wednesday)
|
|
|
|
input = %{
|
|
source: "test",
|
|
verse_scope: @scope,
|
|
content: "בראשית ברא אלהים את השמים ואת הארץ"
|
|
}
|
|
|
|
result = BereishitCycle.execute(input, context)
|
|
|
|
# Should complete — {:ok, data} or {:unresolved, data}
|
|
assert elem(result, 0) in [:ok, :unresolved]
|
|
|
|
data = elem(result, 1)
|
|
|
|
# Output has named_results from kriah
|
|
assert is_list(data.named_results)
|
|
assert length(data.named_results) > 0
|
|
|
|
# Output has roots_activated from vayomer
|
|
assert is_list(data.roots_activated)
|
|
assert length(data.roots_activated) > 0
|
|
|
|
# ברא should be among activated roots
|
|
bara = Enum.find(data.roots_activated, fn r -> r.shoresh == "ברא" end)
|
|
assert bara != nil
|
|
end
|
|
|
|
test "ShemaContext built before processing" do
|
|
{:ok, wednesday, _} = DateTime.from_iso8601(@wednesday_noon)
|
|
scope = @scope
|
|
|
|
# Step 1: nishma — hear first
|
|
{:ok, context} = Shema.receive_context_at(scope, wednesday)
|
|
|
|
# Context has all required fields
|
|
assert context.verse != nil
|
|
assert is_list(context.teamim_marks)
|
|
assert is_list(context.ketiv_qere)
|
|
assert is_map(context.root_gematria)
|
|
assert context.cloud_state != nil
|
|
assert context.daily_provision != nil
|
|
assert context.scope == scope
|
|
assert context.received_at != nil
|
|
end
|
|
|
|
test "pipeline does NOT produce {:halted, ...} on clean Bereishit 1:1 input" do
|
|
{:ok, wednesday, _} = DateTime.from_iso8601(@wednesday_noon)
|
|
{:ok, context} = Shema.receive_context_at(@scope, wednesday)
|
|
|
|
input = %{
|
|
source: "l1",
|
|
verse_scope: @scope,
|
|
content: "בראשית ברא אלהים את השמים ואת הארץ"
|
|
}
|
|
|
|
result = BereishitCycle.execute(input, context)
|
|
|
|
# Must NOT be halted — Bereishit 1:1 is clean Torah text
|
|
refute match?({:halted, _, _}, result)
|
|
end
|
|
end
|
|
end
|