`devarim 6:5` on the live pod: 422 TEIKU_NO_ROOTS. Tablet 30 heard the scope, but the Shoresh Engine activates roots from the text it is handed, vayomer handed it the question, and a bare reference carries no Hebrew. The 422's own guidance said "name a verse". tablet30_scope_test asserted an explicit reference was honoured and passed — on the L1 fallback, which reads the scoped verse and never looks at the question. The Day-6 pattern, by the builder who named it. - BereishitCycle.engine_text/2: when the scope came from an explicit reference AND the question has no Hebrew letter, the engine is handed the verse's own consonantal words. Default scope earns no such reading (English stays a teiku); quoted Torah and reference+Hebrew are read from the asker's words, as before. - input.scope_resolution carries how the scope was chosen into tohu. - processing.scope.roots_read_from: "verse" | "question". - test/tablet32_reference_test.exs: an engine-shaped spy under the REAL name VavPipeline.ShoreshEnginePort reports the text it was handed. 5 tests; 4 fail without the fix. 459 tests, 0 failures on seeds 0 1 7 42 99. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
187 lines
6.6 KiB
Elixir
187 lines
6.6 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)]
|
|
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 "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
|