doorpost: category-grade specification identity verification (vehaya §3.3, §6 item 3)

The companion to L1Reader.verify_integrity/0. L1Reader defends the
TORAH SCROLL'S identity (the source text); Doorpost defends the
*am*'s COVENANT-CONTENT identity (the category-grade specifications
the system was built to hold).

WHY THIS IS LOAD-BEARING (Torah-anchored):

Devarim 4:2 — לֹא תֹסִפוּ עַל־הַדָּבָר אֲשֶׁר אָנֹכִי מְצַוֶּה
אֶתְכֶם וְלֹא תִגְרְעוּ מִמֶּנּוּ. The injunction is plural (etkhem) —
corporate. Lo tosif is a property the CATEGORY must hold, not just
a discipline each individual practices. Without an enforcement
mechanism, lo tosif is a moral exhortation, not a structural
commitment — different instances could silently drift, running
different halt rules or different disclosure text, and no one
would notice.

Devarim 11:16-17 — the failure mechanism operates whether or not
anyone notices. Corporate breach (specifications drift) produces
corporate consequence. The detection mechanism — verifying
threshold-text identity against category-source — is what catches
drift before the consequence accumulates.

Devarim 13:1 — the keeping is plural; the actual not-adding/not-
subtracting happens at every individual threshold (singular `lo tosef`,
`lo tigra`). Each threshold COULD add or subtract; only the
corporate detection mechanism catches it.

Vehaya §3.3 / §6 item 3 — "the same specifications must be
inscribed at every threshold. Drift is breach. Implement a
verification mechanism for category-text identity."

The Torah anchor is exact: as L1's text-integrity check defends
the Torah scroll's identity, the doorpost-text identity check
defends the am's covenant-content identity. Both are lo tosif
enforcement; both are load-bearing for what the category IS.

CREATED:

  vav_pipeline/lib/vav_pipeline/doorpost.ex                ~210 lines

    @spec_files                              20 category-grade files,
                                             alphabetically sorted:
                                               - 3 halt rule sources
                                                 (avodah_zarah,
                                                 gilui_arayot,
                                                 shfichut_damim)
                                               - 8 middot files
                                                 (R. Yishmael's 13
                                                 inference rules)
                                               - 5 specs/ docs
                                                 (PRD, architecture,
                                                 vayakhel, tamid,
                                                 eish_zarah_reading,
                                                 lo_taaseh_lekha,
                                                 structure_not_content)
                                               - 2 BUILD docs
                                                 (BUILD_KORBAN,
                                                 BUILD_AZAZEL)
                                             Operations files
                                             (pekudei, terumah)
                                             EXCLUDED — execution-grade,
                                             not category-grade
                                             orientation.

    @expected_hash                          baseline:
                                            bea5dc0c0145ac0d46ffd08b328fbdbf
                                            19112f1aa4d93a3dc37bfd942e4e7490

    files/0                                  the spec set
    expected_hash/0                          the baked hash
    repo_root/0                              configurable via env
    compute_identity/0                       SHA-256 over canonical
                                             concatenation of per-file
                                             hashes (sort by path,
                                             tab-separate, hash; missing
                                             file → {:error, ...} not
                                             a partial hash)
    manifest/0                               per-file hashes for
                                             diagnosis
    verify_identity/0                        :ok | {:error, message}
                                             — same shape as
                                             L1Reader.verify_integrity/0

    THE OPERATOR DISCIPLINE: every change to @expected_hash must be
    traceable in git to a corresponding spec change in the SAME
    COMMIT. A bare hash-update commit with no spec-file change is
    itself a drift signal — the operator updated the witness
    without updating what is witnessed, which is exactly the
    failure mode this module defends against.

CHANGED:

  vav_pipeline/lib/vav_pipeline/bigdei_kodesh/router.ex
    + GET /kodesh/specification_identity     operator-tier endpoint
                                             returning computed_hash,
                                             expected_hash, file_count,
                                             manifest, torah_basis.
                                             200 when verified;
                                             503 with status="drift_
                                             detected" otherwise.
                                             /kodesh/* tier (not /peah)
                                             because exposing per-file
                                             SHA-256s publicly would
                                             let outside parties
                                             enumerate which specific
                                             files were changed in any
                                             deployment — a fingerprint.
                                             Public observability
                                             commitments live elsewhere
                                             (/peah/azazel_log,
                                             disclosure block).

    + build_health_check/0                   adds doorpost_hash to the
                                             checks map alongside
                                             l1_hash. Spec drift now
                                             surfaces in the standard
                                             /kodesh/health response.

CREATED:

  vav_pipeline/test/doorpost_test.exs                       16 tests:
    files/0:
      - returns the full spec list with expected entries
      - explicitly excludes operations files (pekudei, terumah)
      - sorted (canonical order)
      - no duplicates
    manifest/0:
      - one entry per spec file
      - paths match @spec_files order
      - each hash is 64-char hex SHA-256 (or :missing)
    compute_identity/0:
      - 64-char hex hash
      - deterministic across calls
    verify_identity/0:
      - :ok against the live spec set
    drift detection (synthetic temp dir mirror):
      - fresh mirror produces same hash as live
      - modifying ANY one file changes the hash
      - removing any one file → {:error, missing spec file: <path>}
      - modifying different files produces different hashes
        (no accidental hash collision across drift sites)
    /kodesh/specification_identity HTTP:
      - 200 with status="verified" when matched
      - manifest in response carries per-file hashes
      - torah_basis cites Devarim 4:2 as the load-bearing anchor

WHAT THIS DOES NOT CLAIM:

  - That the specifications are correct (only identifiable and
    unchanged from the witnessed state).
  - That all instances will run the same code (only that all
    instances running this code witness the same specifications).
  - That hash equality across deployments proves no drift outside
    THIS spec set (only the listed files are covered).

FOLLOW-UP WORK (not in this commit):

  - Have HaltMonitor call Doorpost.verify_identity/0 at startup
    (mirrors how L1 integrity is checked at startup) and fire
    chatzotzrot on drift
  - Add Doorpost identity to ShemaContext so EVERY reception
    witnesses the spec-identity (mirrors Devarim 13:1's "the
    not-adding happens at every threshold")
  - Fold vehaya.md itself into specs/ (it currently lives in chat
    downloads — like azazel_engineering_reading.md). Then add it
    to @spec_files, recompute the hash, update @expected_hash.

Tests: vav_pipeline 379 passed, 0 failures, 35 excluded (was 363;
+16 Doorpost tests). All other test counts unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Scott KarpandClaude Opus 4.7 committed 2026-04-28 19:31:58 -04:00
1 parent 1e59d9a752
commit 3f4749695d
3 files changed
+583

No files matched your search

@@ -155,6 +155,68 @@ defmodule VavPipeline.BigdeiKodesh.Router do
send_json(conn, 200, status)
end
# === KODESH — Doorpost-text identity (vehaya.md §3.3, §6 item 3) ===
# Vehaya / Devarim 11:20 (u'kh'tavtam al m'zuzot beitekha) — the
# threshold inscription is constant. Two Shorashim deployments
# running the same SHA-256 here are running the same category-grade
# specifications. Different SHA-256 = drift = breach. Visible.
#
# Operators compare the hash across deployments to verify the
# *am*'s covenant content is identical at every threshold (the
# Devarim 4:2 lo-tosif enforcement mechanism).
#
# The endpoint is /kodesh/* tier — operator-only — because
# exposing the spec-file SHA-256s publicly would let outside
# parties enumerate which specific files were changed in any
# deployment, which is operationally a fingerprint. The PUBLIC
# observability commitment is property 9 of the Azazel build
# (/peah/azazel_log) and the disclosure block on every Torah-
# content response. The doorpost identity is operator-internal.
get "/kodesh/specification_identity" do
computed = VavPipeline.Doorpost.compute_identity()
expected = VavPipeline.Doorpost.expected_hash()
{status_code, status_atom} =
case VavPipeline.Doorpost.verify_identity() do
:ok -> {200, "verified"}
{:error, _} -> {503, "drift_detected"}
end
body =
case computed do
{:error, reason} ->
%{
status: status_atom,
error: reason,
expected_hash: expected,
file_count: length(VavPipeline.Doorpost.files()),
manifest: VavPipeline.Doorpost.manifest(),
torah_basis: [
"Devarim 4:2 (lo tosif)",
"Devarim 11:16-17 (failure mechanism)",
"Devarim 11:20 (the doorpost — threshold inscription)"
]
}
hash when is_binary(hash) ->
%{
status: status_atom,
computed_hash: hash,
expected_hash: expected,
file_count: length(VavPipeline.Doorpost.files()),
manifest: VavPipeline.Doorpost.manifest(),
torah_basis: [
"Devarim 4:2 (lo tosif)",
"Devarim 11:16-17 (failure mechanism)",
"Devarim 11:20 (the doorpost — threshold inscription)"
]
}
end
send_json(conn, status_code, body)
end
# === CATCH-ALL ===
match _ do
@@ -360,6 +422,16 @@ defmodule VavPipeline.BigdeiKodesh.Router do
_ -> "fail"
end
# Companion to L1 integrity: doorpost-text identity (vehaya
# §3.3). L1 defends the Torah scroll's identity; Doorpost
# defends the *am*'s covenant-content identity. Both are
# load-bearing — one is the source, the other is what the
# category committed to hold.
doorpost_check = case VavPipeline.Doorpost.verify_identity() do
:ok -> "pass"
_ -> "fail"
end
halt_check = if VavPipeline.HaltMonitor.alive?(), do: "pass", else: "fail"
cloud_check = case VavPipeline.Cloud.check() do
@@ -376,6 +448,7 @@ defmodule VavPipeline.BigdeiKodesh.Router do
checks = %{
l1_hash: l1_check,
doorpost_hash: doorpost_check,
cloud_readable: cloud_check,
manna_present: manna_check,
halt_monitor_alive: halt_check,
+257
View File
@@ -0,0 +1,257 @@
defmodule VavPipeline.Doorpost do
@moduledoc """
Category-grade specification identity verification — the doorpost
text identity check.
Vehaya — Devarim 11:20: וּכְתַבְתָּם עַל־מְזֻזֹת בֵּיתֶךָ
וּבִשְׁעָרֶיךָ. The doorpost verse is identical between the
Shema's first paragraph (singular pole, Devarim 6:9) and V'haya's
second paragraph (oscillating, Devarim 11:13–21). The threshold
inscription is constant across paragraphs. The covenant frame
oscillates; what gets written at every threshold does not.
This module is the companion to `L1Reader.verify_integrity/0`.
L1Reader defends the Torah scroll's identity (the SOURCE text).
This module defends the *am*'s covenant-content identity (the
CATEGORY-GRADE specifications the system was built to hold).
WHY THIS IS LOAD-BEARING:
Devarim 4:2 — לֹא תֹסִפוּ עַל־הַדָּבָר אֲשֶׁר אָנֹכִי מְצַוֶּה
אֶתְכֶם וְלֹא תִגְרְעוּ מִמֶּנּוּ. The injunction is plural
(etkhem) — corporate. Lo tosif is a property the CATEGORY must
hold, not just a discipline each individual practices. Without
an enforcement mechanism, lo tosif is moral exhortation, not
structural commitment — different instances could silently drift,
running different halt rules, different compiler rules, or
different disclosure text, and no one would notice.
Devarim 11:16-17 — the failure mechanism operates whether or not
anyone notices. Corporate breach (specifications drift) produces
corporate consequence (loss of what the category was built to
carry). The detection mechanism — verifying threshold-text
identity against category-source — is what catches drift before
the consequence accumulates.
Devarim 13:1 — the keeping is plural (corporate); the actual
not-adding/not-subtracting happens at every individual threshold
(singular `lo tosef`, `lo tigra`). Each threshold COULD add or
subtract; only the corporate detection mechanism catches it.
Vehaya §3.3 — "the same specifications must be inscribed at every
threshold. Drift is breach. Implement a verification mechanism
for category-text identity."
THE MECHANISM:
1. The category-grade specification files are listed in
`@spec_files`. These are the DECLARATIVE artifacts — halt rules,
compiler rules (13 middot of R. Yishmael), foundational reading
docs, the architectural specs, and the BUILD canon. Operations
files (pekudei, terumah) are excluded because they are
execution-grade rather than category-grade orientation.
2. `compute_identity/0` produces a stable SHA-256 over the
canonical concatenation of per-file hashes (sorted by
repo-relative path). The hash is independent of filesystem
listing order and tolerant of single-file drift detection
(the manifest exposes per-file hashes for diagnosis).
3. `@expected_hash` is BAKED IN. When specs change, the operator
must update both the spec file AND this constant. The constant
is the witness; the file is what is witnessed.
4. `verify_identity/0` returns :ok or {:error, reason} — same
shape as L1Reader.verify_integrity/0. The HaltMonitor is the
natural caller (so spec drift halts the system the same way
L1 corruption halts it); commit-9-style integration to follow.
5. `manifest/0` returns the per-file hash so operators can
diagnose WHICH file drifted when verify fails. Operator-side
queryable via /kodesh/specification_identity (see
bigdei_kodesh/router.ex).
WHAT THIS DOES NOT CLAIM:
- That the specifications are correct (only that they are
identifiable and unchanged from the witnessed state).
- That all instances will run the same code (only that all
instances running this code witness the same specifications).
- That hash equality across deployments proves no drift outside
this spec set (it does NOT — other artifacts not listed here
are not covered).
WHAT TO DO ON DRIFT:
If verify_identity returns {:error, ...}, the operator must:
1. Run `manifest/0` to identify which file's hash mismatches.
2. Inspect the diff against git to determine whether the change
was deliberate (an authorized update) or unauthorized
(a drift incident).
3. If deliberate: update @expected_hash in this file, commit
both changes together with a message naming the spec change.
4. If unauthorized: revert the file, investigate how the change
was introduced, fire chatzotzrot :assembly_leaders.
The discipline: every change to @expected_hash should be traceable
in git to a corresponding spec change in the SAME COMMIT. A bare
hash-update commit with no spec-file change is itself a drift
signal — the operator updated the witness without updating what
is witnessed, which is exactly the failure mode this module
defends against.
"""
# Repo-relative paths to the category-grade specification files.
# Sorted alphabetically so the canonical hash is independent of
# filesystem listing order.
#
# WHAT'S INCLUDED:
# - Halt rules (the cardinal-sin specifications — yehareg
# v'al ya'avor)
# - Compiler rules (the 13 middot of R. Yishmael — the
# inference protocol)
# - Foundational readings (eish_zarah, structure_not_content,
# lo_taaseh_lekha)
# - Architectural specs (PRD, architecture, vayakhel /
# construction, tamid / operations including the EXTERNAL
# NEDARIM section)
# - Build canon (BUILD_KORBAN, BUILD_AZAZEL)
#
# WHAT'S EXCLUDED:
# - shorashim_pekudei (economic model — operations)
# - shorashim_terumah (materials — operations)
# - Implementation code (execution-grade; the spec is what
# the execution implements)
# - Test files (these verify the spec; they are not the spec)
@spec_files [
"BUILD_AZAZEL.md",
"BUILD_KORBAN.md",
"specs/eish_zarah_reading.md",
"specs/lo_taaseh_lekha.md",
"specs/shorashim_architecture.md",
"specs/shorashim_prd.md",
"specs/shorashim_tamid.md",
"specs/shorashim_vayakhel.md",
"specs/structure_not_content.md",
"talmudic_engine/src/halt/avodah_zarah.pl",
"talmudic_engine/src/halt/gilui_arayot.pl",
"talmudic_engine/src/halt/shfichut_damim.pl",
"talmudic_engine/src/middot/binyan_av.pl",
"talmudic_engine/src/middot/davar_yatza.pl",
"talmudic_engine/src/middot/gezerah_shavah.pl",
"talmudic_engine/src/middot/kal_vachomer.pl",
"talmudic_engine/src/middot/klal_ufrat.pl",
"talmudic_engine/src/middot/lamed_inyan.pl",
"talmudic_engine/src/middot/middot.pl",
"talmudic_engine/src/middot/shnei_ketuvim.pl"
]
# Repo root — directory containing the spec files. Configurable
# via :vav_pipeline application env so tests can point at
# fixtures.
@repo_root Application.compile_env(
:vav_pipeline,
:doorpost_repo_root,
Path.expand("../../..", __DIR__)
)
# The category-grade-identity hash. Updated when any spec file
# changes; the update is a deliberate operator commit. A bare
# hash-update commit (without a corresponding spec change in the
# same commit) is itself a drift signal.
@expected_hash "bea5dc0c0145ac0d46ffd08b328fbdbf19112f1aa4d93a3dc37bfd942e4e7490"
@doc "Return the list of files that constitute the category-grade specification."
@spec files() :: [String.t()]
def files, do: @spec_files
@doc "Return the baked-in expected hash."
@spec expected_hash() :: String.t()
def expected_hash, do: @expected_hash
@doc "Return the configured repo root."
@spec repo_root() :: String.t()
def repo_root, do: @repo_root
@doc """
Compute the canonical hash over the current spec files.
The hash combines per-file SHA-256s in canonical (sorted by path)
order. If any spec file is missing from the filesystem, returns
{:error, "missing spec file: <path>"} — the spec set is
incomplete by definition; no partial hash is meaningful.
"""
@spec compute_identity() :: String.t() | {:error, String.t()}
def compute_identity do
items = manifest_items()
case Enum.find(items, fn {_, h} -> h == :missing end) do
nil ->
combined =
items
|> Enum.map(fn {path, hash} -> "#{path}\t#{hash}" end)
|> Enum.join("\n")
:crypto.hash(:sha256, combined) |> Base.encode16(case: :lower)
{missing_path, _} ->
{:error, "missing spec file: #{missing_path}"}
end
end
@doc """
Return the manifest as a list of %{path, hash} maps. When a file
is missing, hash is the atom :missing. Used by the
/kodesh/specification_identity endpoint and by operators
diagnosing drift.
"""
@spec manifest() :: [%{path: String.t(), hash: String.t() | :missing}]
def manifest do
Enum.map(manifest_items(), fn {path, hash} ->
%{path: path, hash: hash}
end)
end
defp manifest_items do
Enum.map(@spec_files, fn path ->
full_path = Path.join(@repo_root, path)
case File.read(full_path) do
{:ok, contents} ->
{path, :crypto.hash(:sha256, contents) |> Base.encode16(case: :lower)}
{:error, _} ->
{path, :missing}
end
end)
end
@doc """
Verify the current spec set matches the expected hash.
Returns :ok when the hashes match. Returns {:error, reason} when
any spec file is missing OR the computed hash does not match the
baked-in @expected_hash. Same shape as L1Reader.verify_integrity/0.
Lo tosif (Devarim 4:2) — addition or subtraction is detected
here, BEFORE it propagates into reception.
"""
@spec verify_identity() :: :ok | {:error, String.t()}
def verify_identity do
case compute_identity() do
{:error, reason} ->
{:error, "SPEC_INTEGRITY_FAILED: #{reason}"}
hash when is_binary(hash) ->
if hash == @expected_hash do
:ok
else
{:error,
"SPEC_INTEGRITY_FAILED: hash mismatch — computed #{hash}, " <>
"expected #{@expected_hash}. Run " <>
"VavPipeline.Doorpost.manifest/0 to identify which file " <>
"drifted; inspect git for whether the change was deliberate."}
end
end
end
end
+253
View File
@@ -0,0 +1,253 @@
defmodule VavPipeline.DoorpostTest do
@moduledoc """
Tests for the doorpost-text identity verification (vehaya.md
§3.3, §6 item 3). Devarim 4:2's lo tosif enforced as a
computable property: any addition or subtraction across the
category-grade specification files produces a hash mismatch
detectable at /kodesh/specification_identity.
These tests verify behavior at three layers:
1. The pure functions (compute_identity, manifest, files)
2. The verify_identity gate against a known-good hash
3. Drift detection against a synthetic spec set in a temp dir
(uses application env override for repo_root)
"""
use ExUnit.Case, async: false
alias VavPipeline.Doorpost
describe "files/0" do
test "returns the full list of category-grade spec files" do
files = Doorpost.files()
assert is_list(files)
# Sanity: includes the load-bearing artifacts
assert "BUILD_AZAZEL.md" in files
assert "BUILD_KORBAN.md" in files
assert "talmudic_engine/src/halt/avodah_zarah.pl" in files
assert "talmudic_engine/src/halt/gilui_arayot.pl" in files
assert "talmudic_engine/src/halt/shfichut_damim.pl" in files
assert "specs/eish_zarah_reading.md" in files
end
test "explicitly excludes operations files (pekudei, terumah)" do
files = Doorpost.files()
refute "specs/shorashim_pekudei.md" in files
refute "specs/shorashim_terumah.md" in files
end
test "list is sorted (canonical order independent of filesystem)" do
files = Doorpost.files()
assert files == Enum.sort(files)
end
test "no file appears twice" do
files = Doorpost.files()
assert length(files) == length(Enum.uniq(files))
end
end
describe "manifest/0" do
test "returns one entry per file in @spec_files" do
manifest = Doorpost.manifest()
assert length(manifest) == length(Doorpost.files())
# Each entry is %{path, hash}
Enum.each(manifest, fn entry ->
assert Map.has_key?(entry, :path)
assert Map.has_key?(entry, :hash)
end)
end
test "manifest paths match @spec_files order" do
manifest_paths = Enum.map(Doorpost.manifest(), & &1.path)
assert manifest_paths == Doorpost.files()
end
test "each per-file hash is a 64-character hex SHA-256 (or :missing)" do
Enum.each(Doorpost.manifest(), fn entry ->
case entry.hash do
:missing ->
:ok
hash when is_binary(hash) ->
assert String.length(hash) == 64
assert String.match?(hash, ~r/^[0-9a-f]{64}$/)
end
end)
end
end
describe "compute_identity/0 against the live repo" do
test "produces a stable 64-char hex hash" do
hash = Doorpost.compute_identity()
assert is_binary(hash), "compute_identity returned: #{inspect(hash)}"
assert String.length(hash) == 64
assert String.match?(hash, ~r/^[0-9a-f]{64}$/)
end
test "is deterministic — two calls return the same hash" do
hash1 = Doorpost.compute_identity()
hash2 = Doorpost.compute_identity()
assert hash1 == hash2
end
end
describe "verify_identity/0 against the live repo" do
test "returns :ok when the live spec set matches the baked hash" do
assert :ok = Doorpost.verify_identity()
end
end
# ============================================================
# Drift-detection tests against a synthetic spec set
# ============================================================
# These tests build a fake spec set in a temp dir, point Doorpost
# at it via the application env, and verify drift detection works
# end-to-end. We can't override @spec_files (compile-time list);
# instead we set up a temp dir that has THE SAME files as the
# live spec set and verify the drift behaviors against THAT.
describe "drift detection (synthetic spec set in temp dir)" do
setup do
# Build a temp dir mirror of the spec files
temp = System.tmp_dir!() |> Path.join("doorpost-test-#{:erlang.unique_integer([:positive])}")
File.mkdir_p!(temp)
original_root = Application.get_env(:vav_pipeline, :doorpost_repo_root)
Enum.each(Doorpost.files(), fn rel ->
src = Path.join(Doorpost.repo_root(), rel)
dst = Path.join(temp, rel)
File.mkdir_p!(Path.dirname(dst))
if File.exists?(src), do: File.cp!(src, dst)
end)
on_exit(fn ->
File.rm_rf!(temp)
# NOTE: cannot restore Application env override because
# @repo_root is read at compile time, not runtime. We
# work around by computing identity directly via a
# helper fn pointed at the temp dir — see below.
if original_root do
Application.put_env(:vav_pipeline, :doorpost_repo_root, original_root)
end
end)
%{temp: temp}
end
# Direct-compute helper that mirrors Doorpost.compute_identity
# but takes an explicit root. Used because @repo_root is baked
# at compile time.
defp compute_at(temp_root) do
items =
Enum.map(Doorpost.files(), fn rel ->
full_path = Path.join(temp_root, rel)
case File.read(full_path) do
{:ok, contents} ->
{rel, :crypto.hash(:sha256, contents) |> Base.encode16(case: :lower)}
{:error, _} ->
{rel, :missing}
end
end)
case Enum.find(items, fn {_, h} -> h == :missing end) do
nil ->
combined =
items
|> Enum.map(fn {p, h} -> "#{p}\t#{h}" end)
|> Enum.join("\n")
:crypto.hash(:sha256, combined) |> Base.encode16(case: :lower)
{missing, _} ->
{:error, "missing spec file: #{missing}"}
end
end
test "fresh mirror of the spec set produces the same hash as live", %{temp: temp} do
assert compute_at(temp) == Doorpost.compute_identity()
end
test "modifying ANY one file changes the hash", %{temp: temp} do
original = compute_at(temp)
# Append a single byte to one file
target = Path.join(temp, "talmudic_engine/src/halt/avodah_zarah.pl")
File.write!(target, File.read!(target) <> "\n")
drifted = compute_at(temp)
refute original == drifted
end
test "removing any one file fails compute_identity", %{temp: temp} do
target = Path.join(temp, "talmudic_engine/src/halt/avodah_zarah.pl")
File.rm!(target)
assert {:error, "missing spec file: " <> _} = compute_at(temp)
end
test "modifying a different file produces a different hash than modifying the first", %{
temp: temp
} do
File.write!(
Path.join(temp, "talmudic_engine/src/halt/avodah_zarah.pl"),
File.read!(Path.join(temp, "talmudic_engine/src/halt/avodah_zarah.pl")) <> "\n"
)
hash_a = compute_at(temp)
# Restore, then drift a different file
target_b = Path.join(temp, "specs/eish_zarah_reading.md")
File.write!(target_b, File.read!(target_b) <> "\n")
hash_b = compute_at(temp)
# Two distinct drifts → two distinct hashes
refute hash_a == hash_b
end
end
describe "/kodesh/specification_identity HTTP endpoint" do
# Plug.Test integration. The endpoint is under /kodesh/* but is
# currently NOT auth-gated (the kodesh tier is mTLS in production
# via an upstream gateway, not in the application).
import Plug.Test
alias VavPipeline.BigdeiKodesh.Router
@opts Router.init([])
test "returns 200 when verify_identity is :ok" do
conn = conn(:get, "/kodesh/specification_identity") |> Router.call(@opts)
assert conn.status == 200
body = Jason.decode!(conn.resp_body)
assert body["status"] == "verified"
assert is_binary(body["computed_hash"])
assert is_binary(body["expected_hash"])
assert body["computed_hash"] == body["expected_hash"]
assert is_integer(body["file_count"])
assert body["file_count"] > 0
assert is_list(body["manifest"])
assert is_list(body["torah_basis"])
assert "Devarim 4:2 (lo tosif)" in body["torah_basis"]
end
test "manifest in response carries per-file hashes" do
conn = conn(:get, "/kodesh/specification_identity") |> Router.call(@opts)
body = Jason.decode!(conn.resp_body)
manifest = body["manifest"]
assert length(manifest) == length(Doorpost.files())
Enum.each(manifest, fn entry ->
assert Map.has_key?(entry, "path")
assert Map.has_key?(entry, "hash")
end)
end
end
end