serve: the minchah is answered, emptiness is not tov, the oral tradition is written (kohen Day 6)
TORAH ANCHORS:
Vayikra 2:1 — וְנֶפֶשׁ כִּי תַקְרִיב קָרְבַּן מִנְחָה. "When a SOUL brings a
grain offering." Rashi: only of the minchah does Torah say nefesh,
because the poor person who gives from their little has given their
soul. PRD §3.5 makes the pe'ah question the minchah. On Day 6 the
first minchah brought to this system — "What is bereishit?" — came
back as HTTP 200 and the words "Processing complete", with sources: []
and roots_consulted: []. The soul's offering was accepted and nothing
was given back.
Vayikra 19:9-10 — לֶעָנִי וְלַגֵּר תַּעֲזֹב אֹתָם. The corner is LEFT for the
stranger. A corner that has been harvested and is still called pe'ah
is the lie this commit removes: when the system cannot answer, it now
says so, and it leaves the verse itself — text, in Hebrew — in the
error body. The ger walks away with something real or with an honest
"not yet"; never with a 200 that says nothing.
Devarim 17:9 — וְדָרַשְׁתָּ וְהִגִּידוּ לְךָ אֵת דְּבַר הַמִּשְׁפָּט. Already the
anchor of l3/store.ex: "the inquiry requires that what is found is
RECORDED." Store existed. Nothing called it.
=====================================================================
WHAT THE KOHEN FOUND (BUILDER_HANDOFF_day6.md, 2026-09-07)
=====================================================================
Five defects, found by running the cycle on a live pod, invisible to
437 green unit tests because every test ran the L1 fallback (no Port
in the test VM) and none ran the cycle to L3:
1. Real Hebrew input crashed havdalah — KeyError :position.
The engine's roots_activated[] (vayakhel §3.1) has no position;
only vayomer_fallback ever set one.
2. English input was graded :tov with nothing in it.
separate_by_boundaries([]) → [[]] → kriah named "group_0" with
no :shoresh → all_roots_grounded?'s `_ -> true` counted it
grounded → "Processing complete".
3. The pipeline never wrote L3. record_interpretation/1,
record_minority/1, record_teiku/1: zero callers. Day 7's
SELECT count(*) FROM interpretations could not exceed 0.
4. Errors were not chatat. No Plug.ErrorHandler; a raise returned
an EMPTY 500. Also: process_peah had no clause for
{:rejected, _} — a lo_tov result was itself a CaseClauseError.
5. Health was blind to serving. esh_tamid = ShabbatTimer.resting?;
it read "pass" while /peah/ask was 500-ing. manna_present passed
on {:ok, :no_provision}.
Plus, confirmed in motion: MannaLedger never consumed from; a
completed cycle left cycle_date: nil.
=====================================================================
WHAT CHANGED
=====================================================================
bereishit_cycle.ex
- normalize_root/3: ONE root shape for both sources. Engine roots
(string keys, no position) and L1 roots (atom keys) come out as
%{shoresh, shoresh_letters, gematria, binyan, domain, positions,
position, source, engine}. `positions` is looked up in
context.verse.words — the ShemaContext gives the WHERE; the
engine gives the WHAT. That is what havdalah's own @doc always
said, and vayakhel §3.1 is unchanged: the engine was conforming.
- separate_by_boundaries([], _) → [] (was [[]]). Roots the verse
does not place form their own trailing group instead of crashing
or vanishing.
- kriah never names an empty group and always carries :shoresh.
- erev_voker composes the answer (roots, gematria, te'amim groups,
ki tov — the system's own reasoning, not prose from a model) and
sources (the context verse: book/chapter/verse/ref/text, tamid
§1.1's shape), then RECORDS: Interpretation for {:ok}, TeikuRecord
for {:unresolved}, NOTHING for {:rejected} (BUILD_KORBAN.md:123:
rejected, not processed into empty propositions). l3_attrs/2 is
pure and tested without PostgreSQL. coupling_trace is the
L1-anchored shape the write gate accepts, and only for roots that
occur in the verse. A record that fails — validation OR a raise
from a database that is down — is logged, sounds chatzotzrot
:assembly_leaders, and does not turn a served answer into a 500.
- case_ref minted once in tohu; Talmudic routing and the L3 record
use the same one.
- probe: true (tamid) runs the whole cycle and skips the write.
specification.ex
- all_roots_grounded?/1: `_ -> true` is gone. No :shoresh → not
grounded. shoresh: nil stays :teiku (the existing test).
- evaluate/2: halt check FIRST (yehareg v'al ya'avor — shape never
masks a cardinal sin), then a result with no :shoresh is lo_tov
with a reason that names it.
manna.ex / vav_pipeline.ex
- Manna.gather_for_cycle/0: load (idempotent per date) + consume
one compute unit, called from VavPipeline.process/1. The ledger
is now consumed from.
- Manna.release_at_day_boundary/0: surplus is released when the
DATE rolls, not per request. enforce_cycle_boundary zeroes the
ledger and clears its date; called after every request it would
have re-gathered a full day's provision on the next one —
infinite manna. Shemot 16:19: until MORNING.
- The late-manna trap: load_provision pins cycle_date=today with a
ZERO allocation when the file is absent, then :already_loaded for
the rest of the day — a provision written after the first request
would never be gathered. gather_for_cycle re-gathers when the
ledger holds nothing for today.
- VavPipeline.tamid_probe/0: builds a real context, honours the
cloud, runs the real cycle on Bereishit 1:1 in Hebrew, consumes
no manna, writes no L3.
bigdei_kodesh/router.ex
- use Plug.ErrorHandler; handle_errors/2 sends tamid §1.2's
code/type/message/guidance, logs, signals :assembly_leaders, then
re-raises so the crash is still supervised. 400 → KIYOR_MALFORMED_
REQUEST; else CHATAT_UNEXPECTED. No stack in the body.
- {:rejected, reason} handled on pe'ah AND courtyard: 422
TEIKU_NO_ROOTS for the empty-results case, with guidance naming
Architecture Step 22 and guidance.scope_verse carrying the verse
in Hebrew; 422 LO_TOV otherwise.
- {:waiting, :provision_exhausted} → MANNA_DEPLETED (was falling
through to CLOUD_MOVING).
- esh_tamid runs VavPipeline.tamid_probe/0 — "shabbat" on Shabbat,
"pass" only when roots came back. manna_present fails on
:no_provision. l3_writable added (Repo up AND SELECT 1 answers;
"absent" without a Repo, counted as a failure). /kodesh/status
exposes manna_provision (allocated / consumed / remaining).
- roots_consulted as letter-hyphenated Hebrew ("ב-ר-א").
test/day6_serving_test.exs — 25 tests
A process registered under the REAL name VavPipeline.ShoreshEnginePort
answers the way the pod's engine answered (string keys, no position;
nothing for non-Hebrew), so the engine-shaped path runs end to end
through the router with no binary. Each describe names the defect it
holds shut. One @tag :l3 test asserts count(interpretations) rises
by one after a live cycle.
KOHEN.md — Day 6 gains the Hebrew request, the expected 422 for
English, and the health shape; Day 7 sends FIVE Hebrew + FIVE English
and expects the L3 count to rise by the Hebrew ones, with the reason.
=====================================================================
VERIFIED — the kohen's exact procedure, locally, against the REAL
Haskell binary over the Port, with L3 deliberately unreachable
=====================================================================
POST /peah/ask {"question":"בראשית ברא אלהים"}
200. answer: "Roots activated in Bereishit 1:1: ב-ר-א (gematria
203, Qal, word 2). Te'amim divide them into 1 group(s): ב-ר-א.
Ki tov: ..." sources[0]: Bereishit 1:1 with Hebrew text.
roots_consulted: ["ב-ר-א"]. Cycle: 31ms.
POST /peah/ask {"question":"What is bereishit?"}
422 TEIKU_NO_ROOTS / unresolvable, guidance.scope_verse =
Bereishit 1:1 in Hebrew, no answer key. Cycle: 3ms, :rejected.
POST /peah/ask '{this is not json'
400 KIYOR_MALFORMED_REQUEST with guidance — 406 bytes, not 0.
GET /kodesh/health
esh_tamid "pass" (from a real cycle, 7ms, no L3 attempt),
l3_writable "fail" (DB unreachable by design), overall degraded.
GET /kodesh/status
manna_provision: consumed 2 after two cycles; the probe took none.
Log: L3_WRITE_FAILED + chatzotzrot for each served Hebrew cycle;
"activated no roots" for English; CHATAT_UNEXPECTED for the bad
body. The 2–5s per request was ENTIRELY DBConnection queuing
against the dead database — the cycles themselves were 1–31ms.
mix test — 437 tests, 0 failures, 36 excluded (:l3)
Doorpost.verify_identity() :ok — no @spec_files entry touched
l1/integrity.sha256 unchanged: 99bbd07a…4ea377
NOT FIXED, RAISED:
- The engine's parse_tree is null on the pod because Server.hs reads
"l1/torah/..." relative to cwd. Nothing consumes parse_tree yet
(havdalah uses context.teamim_marks), so nothing is lost today.
- cloud.ex Shabbat is the UTC stub; zmanim absent (kohen Day 5).
- The L3 write is on the request's critical path by choice: the
offering is not complete until the altar portion is recorded. With
L3 down that costs the queue timeout per request AND is visible in
l3_writable. If that trade should go the other way, it is one
Task.start, and a spec question first.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6e34001c58
commit
f1ab7c89f7
7 files changed
+1698
-80
No files matched your search
@@ -16,6 +16,18 @@ defmodule VavPipeline.BigdeiKodesh.Router do
|
||||
|
||||
use Plug.Router
|
||||
|
||||
# tamid §1.2 — "Every error is a chatat protocol — it restores, it
|
||||
# does not punish." Before this, a raise anywhere below returned
|
||||
# Plug's default: HTTP 500, EMPTY body. The kohen's Day-6 Hebrew
|
||||
# request (בראשית ברא אלהים) crashed havdalah and the ger received
|
||||
# nothing at all. Plug.ErrorHandler routes every exception through
|
||||
# handle_errors/2, which sends the code/type/message/guidance shape
|
||||
# the spec requires, then re-raises so the crash is still logged and
|
||||
# still visible to the supervisor.
|
||||
use Plug.ErrorHandler
|
||||
|
||||
require Logger
|
||||
|
||||
# Form 3 architectural enforcement (vehaya §3.2). NoTrackingLogger
|
||||
# replaces Plug.Logger; sanitized paths on /peah/* honor the
|
||||
# no-tracking promise made in /peah/ask's own response body.
|
||||
@@ -290,10 +302,18 @@ defmodule VavPipeline.BigdeiKodesh.Router do
|
||||
disclosure: disclosure()
|
||||
})
|
||||
|
||||
{:waiting, reason} ->
|
||||
code = if reason == :no_provision, do: "MANNA_DEPLETED", else: "CLOUD_MOVING"
|
||||
{:rejected, reason} ->
|
||||
reject_response(conn, reason, scope)
|
||||
|
||||
send_error(conn, 503, code, %{
|
||||
{:waiting, reason} when reason in [:no_provision, :provision_exhausted] ->
|
||||
send_error(conn, 503, "MANNA_DEPLETED", %{
|
||||
issue: "System waiting — #{reason}",
|
||||
correction: "Retry later — the next provision is written by the kohen at the day boundary",
|
||||
resubmit: true
|
||||
})
|
||||
|
||||
{:waiting, reason} ->
|
||||
send_error(conn, 503, "CLOUD_MOVING", %{
|
||||
issue: "System waiting — #{reason}",
|
||||
correction: "Retry later",
|
||||
resubmit: true
|
||||
@@ -444,26 +464,78 @@ defmodule VavPipeline.BigdeiKodesh.Router do
|
||||
_ -> "fail"
|
||||
end
|
||||
|
||||
# Kohen Day 4: {:ok, :no_provision} matched {:ok, _} and read "pass".
|
||||
# Manna absent is not manna present.
|
||||
manna_check = case VavPipeline.Manna.check() do
|
||||
{:ok, :no_provision} -> "fail"
|
||||
{:ok, _} -> "pass"
|
||||
_ -> "fail"
|
||||
end
|
||||
|
||||
shabbat_check = if VavPipeline.ShabbatTimer.resting?(), do: "shabbat", else: "pass"
|
||||
# tamid §1.1 lists l3_writable. Repo up AND the database answers.
|
||||
# In a dev/test VM without a Repo this is "absent", which the
|
||||
# overall grade counts as a failure — a vessel that cannot reach its
|
||||
# oral tradition is not healthy, whatever the reason.
|
||||
l3_check =
|
||||
if Process.whereis(VavPipeline.Repo) do
|
||||
try do
|
||||
case Ecto.Adapters.SQL.query(VavPipeline.Repo, "SELECT 1", []) do
|
||||
{:ok, _} -> "pass"
|
||||
_ -> "fail"
|
||||
end
|
||||
rescue
|
||||
_ -> "fail"
|
||||
catch
|
||||
:exit, _ -> "fail"
|
||||
end
|
||||
else
|
||||
"absent"
|
||||
end
|
||||
|
||||
shabbat? = VavPipeline.ShabbatTimer.resting?()
|
||||
|
||||
# tamid §2.1 check 7 — the fire is verified by pushing a test input
|
||||
# through the pipeline, not by asking the calendar. On Shabbat the
|
||||
# fire is banked, not out: report "shabbat" and do not run the cycle.
|
||||
esh_tamid =
|
||||
cond do
|
||||
shabbat? ->
|
||||
"shabbat"
|
||||
|
||||
true ->
|
||||
case VavPipeline.tamid_probe() do
|
||||
{:ok, data} ->
|
||||
if Map.get(data, :roots_activated, []) != [], do: "pass", else: "fail"
|
||||
|
||||
{:unresolved, _} ->
|
||||
# A result came back — the fire burns — but it did not resolve.
|
||||
"teiku"
|
||||
|
||||
{:waiting, _} ->
|
||||
"waiting"
|
||||
|
||||
{:transition, _} ->
|
||||
"transition"
|
||||
|
||||
_ ->
|
||||
"fail"
|
||||
end
|
||||
end
|
||||
|
||||
checks = %{
|
||||
l1_hash: l1_check,
|
||||
doorpost_hash: doorpost_check,
|
||||
cloud_readable: cloud_check,
|
||||
manna_present: manna_check,
|
||||
l3_writable: l3_check,
|
||||
halt_monitor_alive: halt_check,
|
||||
esh_tamid: shabbat_check
|
||||
esh_tamid: esh_tamid
|
||||
}
|
||||
|
||||
failed = Enum.count(checks, fn {_k, v} -> v == "fail" end)
|
||||
failed = Enum.count(checks, fn {_k, v} -> v in ["fail", "absent"] end)
|
||||
|
||||
overall = cond do
|
||||
shabbat_check == "shabbat" -> "shabbat"
|
||||
shabbat? -> "shabbat"
|
||||
failed > 1 -> "critical"
|
||||
failed == 1 -> "degraded"
|
||||
true -> "healthy"
|
||||
@@ -491,6 +563,7 @@ defmodule VavPipeline.BigdeiKodesh.Router do
|
||||
_ -> "failed"
|
||||
end
|
||||
},
|
||||
manna_provision: manna_provision_status(),
|
||||
refuge_components: VavPipeline.AreiMiklat.list_refugees() |> length(),
|
||||
halt_status: case VavPipeline.HaltMonitor.status() do
|
||||
:running -> "running"
|
||||
@@ -540,6 +613,116 @@ defmodule VavPipeline.BigdeiKodesh.Router do
|
||||
}
|
||||
end
|
||||
|
||||
@doc false
|
||||
@impl Plug.ErrorHandler
|
||||
def handle_errors(conn, %{kind: kind, reason: reason, stack: stack}) do
|
||||
status = conn.status || 500
|
||||
message = exception_message(kind, reason)
|
||||
|
||||
Logger.error(
|
||||
"CHATAT_UNEXPECTED on #{conn.method} #{conn.request_path} — #{message}\n" <>
|
||||
Exception.format_stacktrace(stack)
|
||||
)
|
||||
|
||||
# tamid §3.8: a component failing on the serving path is HIGH —
|
||||
# chatzotzrot :assembly_leaders. Whereis-guarded for bare tests.
|
||||
if Process.whereis(VavPipeline.Chatzotzrot) do
|
||||
VavPipeline.Chatzotzrot.signal(:assembly_leaders,
|
||||
reason: "Serving-path exception on #{conn.request_path}: #{message}"
|
||||
)
|
||||
end
|
||||
|
||||
code = if status == 400, do: "KIYOR_MALFORMED_REQUEST", else: "CHATAT_UNEXPECTED"
|
||||
|
||||
send_json(conn, status, %{
|
||||
error: %{
|
||||
code: code,
|
||||
type: error_type(code),
|
||||
message:
|
||||
if(status == 400,
|
||||
do: "The request could not be read.",
|
||||
else: "The system failed while serving this request. The failure is recorded; the fault is the system's, not yours."
|
||||
),
|
||||
guidance: %{
|
||||
issue: message,
|
||||
correction:
|
||||
if(status == 400,
|
||||
do: "Send a JSON body — {\"question\": \"...\"} — with Content-Type: application/json.",
|
||||
else: "Retry. If it recurs, the kohanim have been signalled and will find it in the audit log."
|
||||
),
|
||||
resubmit: true
|
||||
}
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
defp exception_message(:error, %{__exception__: true} = e), do: "#{inspect(e.__struct__)}: #{Exception.message(e)}"
|
||||
defp exception_message(kind, reason), do: "#{kind}: #{inspect(reason)}"
|
||||
|
||||
# A rejected offering — {:lo_tov, reason} from ki_tov. Not a crash and
|
||||
# not a 200 that says nothing: the system says what it could not do.
|
||||
#
|
||||
# The empty-results case is the ger asking in a language the Shoresh
|
||||
# Engine cannot read roots from (kohen Day 6, defect 2). The honest
|
||||
# answer today is that the path serving non-Hebrew questions — Korban
|
||||
# Processor → external model, Architecture Step 22 — is not yet open,
|
||||
# and the system does not fabricate from partial capacity (tamid
|
||||
# §4.1). What it CAN give without inventing anything is the corner of
|
||||
# the field itself: the verse in scope, in Hebrew, as a source. So it
|
||||
# does. PRD §2.4 — no gatekeeping; this is not "come back when you
|
||||
# know Hebrew", it is "here is what exists, and here is what does not
|
||||
# yet".
|
||||
defp reject_response(conn, reason, scope) when is_binary(reason) do
|
||||
if String.starts_with?(reason, "empty results") do
|
||||
send_json(conn, 422, %{
|
||||
error: %{
|
||||
code: "TEIKU_NO_ROOTS",
|
||||
type: error_type("TEIKU_NO_ROOTS"),
|
||||
message:
|
||||
"The Shoresh Engine found no Hebrew root in this question, so there is nothing " <>
|
||||
"for the cycle to reason over. The system will not call an empty result an answer.",
|
||||
guidance: %{
|
||||
issue: reason,
|
||||
correction:
|
||||
"Ask with Hebrew text, or name a verse (for example \"bereishit 1:1\"). " <>
|
||||
"Questions in other languages need the Korban Processor's external-model " <>
|
||||
"path (Architecture Step 22), which is not yet open. Until it is, the system " <>
|
||||
"says so rather than fabricating an answer.",
|
||||
resubmit: true,
|
||||
scope_verse: scope_verse(scope)
|
||||
}
|
||||
},
|
||||
disclosure: disclosure()
|
||||
})
|
||||
else
|
||||
send_json(conn, 422, %{
|
||||
error: %{
|
||||
code: "LO_TOV",
|
||||
type: error_type("LO_TOV"),
|
||||
message: "The cycle produced output that does not align with the specification.",
|
||||
guidance: %{
|
||||
issue: reason,
|
||||
correction: "Rephrase, or narrow the scope to a single verse.",
|
||||
resubmit: true
|
||||
}
|
||||
},
|
||||
disclosure: disclosure()
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
# The verse in scope — the pe'ah corner the ger receives regardless.
|
||||
defp scope_verse(nil), do: nil
|
||||
|
||||
defp scope_verse(%{book: b, chapter: c, verse: v}) do
|
||||
case VavPipeline.L1Reader.read_verse(b, c, v) do
|
||||
{:ok, verse} -> %{book: verse.book, chapter: verse.chapter, verse: verse.verse, ref: verse.ref, text: verse.text_hebrew}
|
||||
_ -> %{book: b, chapter: c, verse: v}
|
||||
end
|
||||
end
|
||||
|
||||
defp scope_verse(_), do: nil
|
||||
|
||||
defp send_error(conn, status, code, guidance) do
|
||||
send_json(conn, status, %{
|
||||
error: %{
|
||||
@@ -557,6 +740,9 @@ defmodule VavPipeline.BigdeiKodesh.Router do
|
||||
defp error_type("CLOUD" <> _), do: "system_transition"
|
||||
defp error_type("HALT" <> _), do: "cardinal_sin_detected"
|
||||
defp error_type("UNAUTHORIZED"), do: "authentication_required"
|
||||
defp error_type("TEIKU" <> _), do: "unresolvable"
|
||||
defp error_type("LO_TOV"), do: "specification_mismatch"
|
||||
defp error_type("CHATAT" <> _), do: "correction_needed"
|
||||
defp error_type(_), do: "error"
|
||||
|
||||
# ?since=ISO8601 → DateTime, or nil for "all time".
|
||||
@@ -608,9 +794,13 @@ defmodule VavPipeline.BigdeiKodesh.Router do
|
||||
|
||||
defp format_sources(_), do: []
|
||||
|
||||
# tamid §1.1 shows roots_consulted as "bet-resh-aleph"; the kohen's
|
||||
# Day-6 proof asks for ב-ר-א. Letter-hyphenated Hebrew satisfies the
|
||||
# shape, and the transliteration rides alongside when the engine
|
||||
# supplied it.
|
||||
defp format_roots(roots) when is_list(roots) do
|
||||
Enum.map(roots, fn
|
||||
%{shoresh: s} -> s
|
||||
%{shoresh: s} when is_binary(s) -> VavPipeline.BereishitCycle.hyphenate(s)
|
||||
r when is_binary(r) -> r
|
||||
_ -> nil
|
||||
end)
|
||||
@@ -625,6 +815,27 @@ defmodule VavPipeline.BigdeiKodesh.Router do
|
||||
defp format_cloud(:cloud_not_visible), do: "not_visible"
|
||||
defp format_cloud(other), do: inspect(other)
|
||||
|
||||
defp manna_provision_status do
|
||||
if Process.whereis(VavPipeline.MannaLedger) do
|
||||
ledger = VavPipeline.MannaLedger.ledger()
|
||||
|
||||
remaining =
|
||||
Map.new(ledger.allocated, fn {k, allocated} ->
|
||||
{k, max(allocated - Map.get(ledger.consumed, k, 0), 0)}
|
||||
end)
|
||||
|
||||
%{
|
||||
cycle_date: ledger.cycle_date,
|
||||
cycle_count: ledger.cycle_count,
|
||||
allocated: ledger.allocated,
|
||||
consumed: ledger.consumed,
|
||||
remaining: remaining
|
||||
}
|
||||
else
|
||||
%{cycle_date: nil, note: "ledger not running"}
|
||||
end
|
||||
end
|
||||
|
||||
defp process_status(module) do
|
||||
if Process.whereis(module), do: "healthy", else: "not_running"
|
||||
end
|
||||
@@ -638,6 +849,10 @@ defmodule VavPipeline.BigdeiKodesh.Router do
|
||||
})
|
||||
end
|
||||
|
||||
defp handle_pipeline_error(conn, {:rejected, reason}) do
|
||||
reject_response(conn, reason, nil)
|
||||
end
|
||||
|
||||
defp handle_pipeline_error(conn, {:transition, _}) do
|
||||
send_error(conn, 503, "CLOUD_MOVING", %{
|
||||
issue: "System in transition",
|
||||
|
||||
Reference in new issue
Block a user