Files
bikoret/vav_pipeline/test/tablet32_reference_test.exs
Scott KarpandClaude Fable 5.1 83fa5ac46b cycle: one root, once — a verse that repeats a word (tablet 32a)
Reading roots from the verse (tablet 32) met a verse that carries אור
twice: the real engine answers per word and Bereishit 1:3 came back
with א-ו-ר listed twice, each already carrying positions 4 and 6.
uniq_by shoresh after normalization. Found by running the built image
against the real shoresh-engine binary, not by the suite.

460 tests, 0 failures on seeds 0 42 99.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 16:54:38 +00:00

197 lines
7.0 KiB
Elixir

defmodule VavPipeline.Tablet32ReferenceTest do
@moduledoc """
Tablet 32 (2026-10-04) — `devarim 6:5` on the live pod: 422
TEIKU_NO_ROOTS. The scope was heard (tablet 30), but the Shoresh Engine
activates roots from the text it is handed, it was handed the question,
and a bare reference has no Hebrew in it.
`tablet30_scope_test` asserted "an explicit reference is honoured" and
passed — on the L1 fallback, which reads the scoped verse and never
looks at the question. The Day-6 pattern, again: green here, broken
where the engine is real.
So this file does not run the fallback. Like `day6_serving_test`, it
registers a process under the REAL name `VavPipeline.ShoreshEnginePort`
that behaves the way the engine behaves — roots only from Hebrew it is
given — and it reports back exactly what text it was given.
"""
use ExUnit.Case, async: false
import Plug.Test
import Plug.Conn
alias VavPipeline.{HaltMonitor, L1Reader, MannaLedger}
alias VavPipeline.BigdeiKodesh.Router
@opts Router.init([])
# ============================================================
# An engine-shaped stand-in that tells the test what it was handed
# ============================================================
defmodule SpyPort do
use GenServer
def start(test_pid),
do: GenServer.start(__MODULE__, test_pid, name: VavPipeline.ShoreshEnginePort)
@impl true
def init(test_pid), do: {:ok, test_pid}
@impl true
def handle_call({:request, json_line}, _from, test_pid) do
text = json_line |> Jason.decode!() |> get_in(["input", "text"]) || ""
send(test_pid, {:engine_received, text})
roots =
cond do
# Devarim 6:5 opens וְאָהַבְתָּ — the engine would find א-ה-ב.
String.contains?(text, "ואהבת") -> [root(["aleph", "he", "bet"], "אהב", 8)]
# A verse with a repeated word: the engine answers per word.
String.contains?(text, "אור ויהי אור") -> List.duplicate(root(["aleph", "vav", "resh"], "אור", 207), 2)
String.match?(text, ~r/[\x{05D0}-\x{05EA}]/u) -> [root(["bet", "resh", "aleph"], "ברא", 203)]
true -> []
end
{:reply, {:ok, %{"roots_activated" => roots, "parse_tree" => nil, "dual_readings" => []}},
test_pid}
end
# The engine's root shape as the pod delivers it: string keys, no `position`.
defp root(letters, hebrew, gematria) do
%{
"shoresh" => letters,
"shoresh_hebrew" => hebrew,
"gematria_value" => gematria,
"binyan" => "Qal",
"domain" => "unknown",
"torah_refs" => [],
"neighbors" => [],
"gematria_neighbors" => []
}
end
end
setup do
if HaltMonitor.halted?(), do: HaltMonitor.resolve("tablet32 test setup")
write_provision(50)
# This file is about the real-engine path. If a real engine is
# registered on this host there is nothing to stand in for.
assert Process.whereis(VavPipeline.ShoreshEnginePort) == nil,
"a real ShoreshEnginePort is registered; this test needs to spy on the port"
{:ok, pid} = SpyPort.start(self())
on_exit(fn -> if Process.alive?(pid), do: GenServer.stop(pid) end)
:ok
end
describe "a named verse, no Hebrew in the question" do
test "the engine is handed the verse's own words, and the ger is answered" do
conn = post_ask("devarim 6:5")
body = Jason.decode!(conn.resp_body)
{:ok, verse} = L1Reader.read_verse("devarim", 6, 5)
verse_text = Enum.map_join(verse.words, " ", & &1.consonants)
assert_received {:engine_received, ^verse_text}
assert verse_text =~ "ואהבת"
assert conn.status == 200, "#{conn.status} #{conn.resp_body}"
assert [%{"ref" => "Devarim 6:5"}] = body["sources"]
assert body["processing"]["scope"]["resolved_by"] == "verse_reference"
assert body["processing"]["scope"]["roots_read_from"] == "verse"
assert body["processing"]["roots_consulted"] != []
assert body["answer"] =~ "Devarim 6:5"
end
test "a word the verse carries twice is one root, not two" do
conn = post_ask("bereishit 1:3")
body = Jason.decode!(conn.resp_body)
assert conn.status == 200
assert length(body["processing"]["roots_consulted"]) == 1
end
test "English around the reference changes nothing" do
conn = post_ask("What does Deuteronomy 6:5 command?")
body = Jason.decode!(conn.resp_body)
assert_received {:engine_received, text}
assert text =~ "ואהבת"
assert conn.status == 200
assert body["processing"]["scope"]["roots_read_from"] == "verse"
end
end
describe "what must NOT be handed the verse" do
test "no reference, no Hebrew: the default scope earns no reading — still a teiku" do
conn = post_ask("Test query 1")
body = Jason.decode!(conn.resp_body)
assert_received {:engine_received, "Test query 1"}
assert conn.status == 422
assert body["error"]["code"] == "TEIKU_NO_ROOTS"
end
test "quoted Torah: the engine reads the asker's own words" do
conn = post_ask("ויאמר אלהים יהי אור")
body = Jason.decode!(conn.resp_body)
assert_received {:engine_received, "ויאמר אלהים יהי אור"}
assert conn.status == 200
assert body["processing"]["scope"]["resolved_by"] == "text_match"
assert body["processing"]["scope"]["roots_read_from"] == "question"
end
test "a reference WITH Hebrew: the asker's Hebrew is what is read" do
q = "devarim 6:5 — ואהבת"
conn = post_ask(q)
body = Jason.decode!(conn.resp_body)
assert_received {:engine_received, ^q}
assert conn.status == 200
assert body["processing"]["scope"]["resolved_by"] == "verse_reference"
assert body["processing"]["scope"]["roots_read_from"] == "question"
end
end
# ------------------------------------------------------------
# Helpers (shape from day6_serving_test.exs / tablet30_scope_test.exs)
# ------------------------------------------------------------
defp write_provision(compute_units) do
MannaLedger.reset()
on_exit(fn -> MannaLedger.reset() end)
today = Date.utc_today() |> Date.to_iso8601()
expires = DateTime.utc_now() |> DateTime.add(6 * 3600) |> DateTime.to_iso8601()
provision = %{
"date" => today,
"provision" => %{
"compute_units" => compute_units,
"memory_mb" => 512,
"api_calls_external" => 5,
"storage_delta_mb" => 2,
"sanhedrin_panels_max" => 2
},
"type" => "regular",
"expires_at" => expires,
"written_by" => "tablet32_test",
"written_at" => DateTime.utc_now() |> DateTime.to_iso8601()
}
path = VavPipeline.Manna.provision_file()
File.mkdir_p!(Path.dirname(path))
File.write!(path, Jason.encode!(provision))
on_exit(fn -> File.rm(path) end)
path
end
defp post_ask(question) do
conn(:post, "/peah/ask", Jason.encode!(%{question: question}))
|> put_req_header("content-type", "application/json")
|> Router.call(@opts)
end
end