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>
197 lines
7.0 KiB
Elixir
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
|