engine: a word's root is what L1 records for it (tablet 33)

The Shoresh Engine never opened L1 to find a root. extractRoots called
every word of two or three letters a root and dropped every longer one:
את → א-ת-ה (padded with a He the text never wrote), ואת → ו-א-ת,
בכל → ב-כ-ל; ויאמר, ואהבת, אלהים → nothing. Handed all of Devarim 6:5
it found no א-ה-ב. Binyan was "Qal" for everything, nouns included.

shoresh-engine:
- Shoresh.Roots: an index over all of L1 (5853 verses, built once at
  server start, <1 s). A word resolves, in order: from the verse in
  scope (its own shoresh, with the binyan read from the verb's
  morphology; else its lemma's shoresh — לבבך → לבב); else the way Torah
  most often reads that surface form (its commonest lemma decides, so
  אשר the particle has no root); else a root the asker named, if Torah
  knows it. A word in the verse that L1 gives no root has none, whatever
  a homograph elsewhere carries. Unknown words yield nothing.
- Roots are rendered as L1 records them: two letters stay two letters.
- binyan is null unless a verb stands in the scoped verse.
- L1 is found via L1_MOUNT_POINT, not the literal l1/ relative to cwd —
  which is why parse_tree was null on the pod.
- Adds "surface" and "derivation" to each root (verse|lemma|torah|
  named_root). 22 new HSpec examples; 55 examples, 0 failures.

vav_pipeline:
- normalize_root places a lemma-derived root at the word it was heard
  from (L1 has no shoresh on לבבך to search by).
- A binyan is printed only where there is one; "binyan unknown" on a
  noun claimed a question that does not exist.
- test/tablet33_engine_roots_test.exs: the new shape through the router.
- liveness_test: clear a halt leaked by halt_monitor_test (the seed
  0/191243 flake, present since before tablet 30).
  461 tests, 0 failures on seeds 0 42 191243.

Verified before push with BOTH built images, the real engine binary and
a local PostgreSQL 16 (SSL): Devarim 6:5 → א-ה-ב (Qal, word 1), י-ה-ו,
א-ל-ה, כ-ל (words 5,7,9), ל-ב-ב, נ-פ-ש, מ-א-ד; 4 interpretations
written to L3, 0 L3_WRITE_FAILED, health healthy.

NOT repaired here: the quality of L1's own shoresh field, which the
engine is now faithful to — ויהי is recorded as יהי, שמים as שמ, יהוה
as יהו, הבאתי as בא. That is the quarry's tablet (scripts/build_l1.py)
and it changes the L1 hash; a decision, not a patch.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Scott KarpandClaude Fable 5.1 committed 2026-10-04 17:28:08 +00:00
1 parent 83fa5ac46b
commit 6d3411fcfa
7 files changed
+683 -69

No files matched your search

+3
View File
@@ -22,6 +22,9 @@ defmodule VavPipeline.LivenessTest do
setup do
# Make sure no provision exists — the exact state of a fresh pod.
File.rm(VavPipeline.Manna.provision_file())
# ...and no halt. halt_monitor_test leaves the monitor halted on some
# seeds (0, 191243), and a fresh pod is not halted.
if HaltMonitor.halted?(), do: HaltMonitor.resolve("liveness test setup")
:ok
end
@@ -0,0 +1,131 @@
defmodule VavPipeline.Tablet33EngineRootsTest do
@moduledoc """
Tablet 33 (2026-10-04) — the Shoresh Engine now hears roots from L1
(shoresh-engine `Shoresh.Roots`), and its answers changed shape in three
ways the cycle must carry faithfully:
* a root may be two letters (`כל`), exactly as L1 records it — no
padded He;
* `binyan` is null for a noun, and for any word not in the verse;
* a root heard through a word's lemma (`לבבך` → `לבב`) arrives with the
`surface` word it was heard from, because L1 records no shoresh on
the suffixed noun and the verse cannot be searched by shoresh.
The engine's own behaviour is held by its HSpec suite (55 examples).
This file holds the pipeline's reading of it, with an engine-shaped
stand-in under the real port name answering as the rebuilt engine
answered for Devarim 6:5 on 2026-10-04.
"""
use ExUnit.Case, async: false
import Plug.Test
import Plug.Conn
alias VavPipeline.{HaltMonitor, MannaLedger}
alias VavPipeline.BigdeiKodesh.Router
@opts Router.init([])
defmodule EnginePort do
use GenServer
def start, do: GenServer.start(__MODULE__, :ok, name: VavPipeline.ShoreshEnginePort)
@impl true
def init(:ok), do: {:ok, :ok}
@impl true
def handle_call({:request, _json_line}, _from, s) do
roots = [
root(["aleph", "he", "bet"], "אהב", 8, "Qal", "ואהבת", "verse"),
root(["kaf", "lamed"], "כל", 50, nil, "בכל", "verse"),
root(["lamed", "bet", "bet"], "לבב", 34, nil, "לבבך", "lemma")
]
{:reply, {:ok, %{"roots_activated" => roots, "parse_tree" => nil, "dual_readings" => []}}, s}
end
defp root(letters, hebrew, gematria, binyan, surface, derivation) do
%{
"shoresh" => letters,
"shoresh_hebrew" => hebrew,
"gematria_value" => gematria,
"binyan" => binyan,
"domain" => "unknown",
"torah_refs" => [],
"neighbors" => [],
"gematria_neighbors" => [],
"surface" => surface,
"derivation" => derivation
}
end
end
setup do
if HaltMonitor.halted?(), do: HaltMonitor.resolve("tablet33 test setup")
write_provision(50)
assert Process.whereis(VavPipeline.ShoreshEnginePort) == nil
{:ok, pid} = EnginePort.start()
on_exit(fn -> if Process.alive?(pid), do: GenServer.stop(pid) end)
:ok
end
test "Devarim 6:5 — the engine's roots are placed in the verse and said plainly" do
conn = post_ask("devarim 6:5")
body = Jason.decode!(conn.resp_body)
answer = body["answer"]
assert conn.status == 200, "#{conn.status} #{conn.resp_body}"
# A verb in the verse: binyan and position.
assert answer =~ "א-ה-ב (gematria 8, Qal, word 1)"
# A two-letter root stays two letters, and stands where L1 says: בכל
# is word 5, ובכל words 7 and 9.
assert answer =~ "כ-ל (gematria 50, word 5, 7, 9)"
# Heard through the lemma; L1 gives לבבך no shoresh, so only the
# surface word can place it. Word 6 — not "not in this verse".
assert answer =~ "ל-ב-ב (gematria 34, word 6)"
refute answer =~ "not in this verse"
refute answer =~ "binyan unknown"
assert body["processing"]["roots_consulted"] == ["א-ה-ב", "כ-ל", "ל-ב-ב"]
end
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" => "tablet33_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