defmodule VavPipeline.Tablet30ScopeTest do @moduledoc """ Tablet 30 (kohen Day 7, 2026-10-04) — every Hebrew question was answered against Bereishit 1:1. `ויאמר אלהים יהי אור` (1:3) and `ויהי ערב ויהי בקר` (1:5) came back with `sources: [Bereishit 1:1]` and roots marked "not in this verse". `extract_scope_from_question/1` ignored its argument. These tests send the Day-7 offerings through the real router and demand that the verse in the answer is the verse in the question. """ use ExUnit.Case, async: false import Plug.Test import Plug.Conn alias VavPipeline.{HaltMonitor, L1Index, MannaLedger, ScopeResolver} alias VavPipeline.BigdeiKodesh.Router @opts Router.init([]) setup_all do # The index builds in handle_continue; the first call waits for it. {verses, words} = L1Index.stats() assert verses == 5853, "L1Index built #{verses} verses, expected the WLC 5853" assert words > 10_000 :ok end # ============================================================ # ScopeResolver — the three ways a question names its verse # ============================================================ describe "hebrew_tokens/1" do test "strips nikud and te'amim, splits maqaf, drops everything else" do assert ScopeResolver.hebrew_tokens("וַֽיְהִי־אֽוֹר ׃") == ["ויהי", "אור"] assert ScopeResolver.hebrew_tokens("What does בְּרֵאשִׁ֖ית mean?") == ["בראשית"] assert ScopeResolver.hebrew_tokens("Test query 3") == [] end end describe "resolve/1 — quoted Torah" do test "the Day-7 offerings land on their own verses" do assert {:ok, %{book: "bereishit", chapter: 1, verse: 1}, :text_match} = ScopeResolver.resolve("בראשית ברא אלהים") assert {:ok, %{book: "bereishit", chapter: 1, verse: 1}, :text_match} = ScopeResolver.resolve("את השמים ואת הארץ") assert {:ok, %{book: "bereishit", chapter: 1, verse: 3}, :text_match} = ScopeResolver.resolve("ויאמר אלהים יהי אור") assert {:ok, %{book: "bereishit", chapter: 1, verse: 5}, :text_match} = ScopeResolver.resolve("ויהי ערב ויהי בקר") assert {:ok, %{book: "bereishit", chapter: 1, verse: 1}, :text_match} = ScopeResolver.resolve("ברא אלהים") end test "pointed text resolves the same as bare consonants" do assert {:ok, %{book: "bereishit", chapter: 1, verse: 3}, :text_match} = ScopeResolver.resolve("וַיֹּ֥אמֶר אֱלֹהִ֖ים יְהִ֣י א֑וֹר") end test "reaches past Bereishit — the Shema" do assert {:ok, %{book: "devarim", chapter: 6, verse: 4}, :text_match} = ScopeResolver.resolve("שמע ישראל יהוה אלהינו יהוה אחד") end test "a phrase found in several verses goes to the earliest in Torah order" do # שמע ישראל opens Devarim 5:1 before it opens 6:4. assert {:ok, %{book: "devarim", chapter: 5, verse: 1}, :text_match} = ScopeResolver.resolve("שמע ישראל") end test "Hebrew the Torah does not contain falls to the default, and says so" do assert {:ok, %{book: "bereishit", chapter: 1, verse: 1}, :default} = ScopeResolver.resolve("מחשב") end end describe "resolve/1 — explicit reference" do test "transliterated, English and Hebrew book names, colon or dot" do assert {:ok, %{book: "bereishit", chapter: 1, verse: 3}, :verse_reference} = ScopeResolver.resolve("What happens in bereishit 1:3?") assert {:ok, %{book: "shemot", chapter: 3, verse: 14}, :verse_reference} = ScopeResolver.resolve("Exodus 3.14") assert {:ok, %{book: "devarim", chapter: 6, verse: 4}, :verse_reference} = ScopeResolver.resolve("דברים 6:4") end test "a reference outranks quoted text" do assert {:ok, %{book: "devarim", chapter: 6, verse: 4}, :verse_reference} = ScopeResolver.resolve("devarim 6:4 — בראשית ברא אלהים") end test "a verse L1 does not have is a shogeg error, not a silent default" do assert {:error, {:invalid_reference, "devarim 99:1"}} = ScopeResolver.resolve("devarim 99:1") end end describe "resolve/1 — nothing to go on" do test "English defaults, and the resolution is :default" do assert {:ok, %{book: "bereishit", chapter: 1, verse: 1}, :default} = ScopeResolver.resolve("What is bereishit?") end end # ============================================================ # Router — the verse in the answer is the verse in the question # ============================================================ describe "POST /peah/ask" do setup do # halt_monitor_test leaves the monitor halted on some seeds; a halted # vessel answers 503 before the resolver is reached. Clear it, as # liveness_test does, so this file measures scope and nothing else. if HaltMonitor.halted?(), do: HaltMonitor.resolve("tablet30 test setup") write_provision(50) :ok end test "Day-7 Hebrew offerings are sourced from their own verses" do for {q, ref} <- [ {"ויאמר אלהים יהי אור", "Bereishit 1:3"}, {"ויהי ערב ויהי בקר", "Bereishit 1:5"}, {"בראשית ברא אלהים", "Bereishit 1:1"} ] do conn = post_ask(q) body = Jason.decode!(conn.resp_body) assert conn.status == 200, "#{q}: #{conn.status} #{conn.resp_body}" assert [%{"ref" => ^ref} | _] = body["sources"], "#{q} sourced from #{inspect(body["sources"])}" assert body["processing"]["scope"]["resolved_by"] == "text_match" assert body["processing"]["scope"]["verse"]["ref"] == ref refute Map.has_key?(body["processing"]["scope"], "note") end end test "an explicit reference is honoured — the error text promised it" do conn = post_ask("bereishit 1:3") body = Jason.decode!(conn.resp_body) assert conn.status == 200 assert [%{"ref" => "Bereishit 1:3"}] = body["sources"] assert body["processing"]["scope"]["resolved_by"] == "verse_reference" end test "a verse that does not exist is a 422 chatat, with the correction" do conn = post_ask("bereishit 99:1") body = Jason.decode!(conn.resp_body) assert conn.status == 422 assert body["error"]["code"] == "KIYOR_VALIDATION_FAILED" assert body["error"]["guidance"]["issue"] =~ "INVALID_SCOPE" assert body["error"]["guidance"]["resubmit"] == true end test "a question with no verse in it confesses the default scope" do # In the test VM there is no Haskell Port, so the L1 fallback grounds # English against the default verse and answers 200 (day6_serving_test # holds the real-engine 422 shut). Whatever the status, the scope # report must say the verse was assumed, not heard. conn = post_ask("What is bereishit?") body = Jason.decode!(conn.resp_body) scope = case conn.status do 200 -> body["processing"]["scope"] 422 -> %{"resolved_by" => "default", "note" => "n/a", "verse" => body["error"]["guidance"]["scope_verse"]} end assert scope["resolved_by"] == "default" assert scope["verse"]["ref"] == "Bereishit 1:1" assert is_binary(scope["note"]) end end # ------------------------------------------------------------ # Helpers (shape from day6_serving_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" => "tablet30_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)) # Leave no provision behind: the Shema tests run at a fixed 2026-02-25 # and read a dated file as MANNA_STALE. 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