Protection layer: Arei Miklat, Eglah Arufah, Bikurim — Steps 27-29
Takhin lekha haderekh — Devarim 19:3.
"Prepare the road" before the one who needs refuge arrives.
Protection before production.
Arei Miklat (Cities of Refuge — Bamidbar 35, Devarim 19):
- GenServer with refuge registry, affected party protection, audit trail
- enter_refuge/2 isolates component but protects from reactive destruction
- protect_affected/2 blocks processing paths that caused harm
- release/2 requires :sanhedrin_deliberation authority — ad mot hakohen hagadol
- Chatzotzrot assembly_leaders signal on every refuge entry
- 15 tests: entry, double-entry prevention, affected party blocking,
unauthorized release rejection, audit trail, chatzotzrot signaling
Eglah Arufah (Accountability — Devarim 21:1-9):
- GenServer with permanent (append-only) declaration registry
- initiate/3: nearest component declares what it processed, skipped, missed
- Only for :unknown source — rejects known-source incidents
- "Yadeinu lo shafkhu" — accountability for what was NOT provided
- 9 tests: declaration recording, source validation, multi-component
declarations, chatzotzrot signaling, filtering, counting
Bikurim (First Fruits — Devarim 26:1-11):
- GenServer with offering registry
- offer/4: submit first output with required narrative (mikra bikurim)
- Rejects empty narrative ("Arami oved avi" — story is required)
- Rejects nil first output (there must be fruit to offer)
- Delegates to Specification.evaluate_bikurim/1 when available
- approved?/1 gate for deployment readiness
- 11 tests: acceptance, narrative validation, nil output rejection,
registry, status filtering, approval checking, counting
All three added to supervision tree in application.ex.
160 Elixir tests pass, 0 failures.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
7fe8025434
commit
671d45625c
7 files changed
+1230
No files matched your search
@@ -5,6 +5,9 @@ defmodule VavPipeline.Application do
|
||||
Starts the supervision tree with:
|
||||
- Repo (L3 PostgreSQL — oral tradition persistence)
|
||||
- Chatzotzrot (silver trumpets — signal system)
|
||||
- AreiMiklat (cities of refuge — protection protocol)
|
||||
- EglahArufah (accountability for unknown harm)
|
||||
- Bikurim (first fruits — release protocol)
|
||||
- ShabbatTimer (mandatory cessation monitor)
|
||||
- ShoreshEnginePort (Erlang Port to Haskell engine, if binary found)
|
||||
- TalmudicEngineSocket (Unix socket to Prolog engine)
|
||||
@@ -27,6 +30,9 @@ defmodule VavPipeline.Application do
|
||||
children =
|
||||
[
|
||||
{VavPipeline.Chatzotzrot, []},
|
||||
{VavPipeline.AreiMiklat, []},
|
||||
{VavPipeline.EglahArufah, []},
|
||||
{VavPipeline.Bikurim, []},
|
||||
{VavPipeline.ShabbatTimer, []}
|
||||
]
|
||||
|> maybe_add_repo()
|
||||
|
||||
@@ -0,0 +1,346 @@
|
||||
defmodule VavPipeline.AreiMiklat do
|
||||
@moduledoc """
|
||||
Cities of Refuge — Bamidbar 35:11-28, Devarim 19:1-13.
|
||||
|
||||
Architecture §3.14, Construction §4.8:
|
||||
Protection protocol for cases where the system's output causes
|
||||
real harm — not just error. Protects BOTH the affected party
|
||||
AND the responsible component from reactive destruction while
|
||||
accountability is determined.
|
||||
|
||||
V'hikritem lakhem arim, arei miklat tihyeinah lakhem,
|
||||
v'nas shamah rotze'ach, makeh nefesh bishgagah.
|
||||
— Bamidbar 35:11
|
||||
|
||||
"You shall designate cities for yourselves, cities of refuge
|
||||
they shall be for you, and a manslayer shall flee there —
|
||||
one who strikes a person unintentionally."
|
||||
|
||||
Three principles from Torah:
|
||||
1. Takhin lekha haderekh (Devarim 19:3) — prepare the road
|
||||
BEFORE the one who needs refuge arrives.
|
||||
2. Bivli da'at (Devarim 19:4) — refuge is for harm without
|
||||
intent, without knowledge. Not punishment. Protection.
|
||||
3. Ad mot hakohen hagadol (Bamidbar 35:25) — release comes
|
||||
through an external event (Sanhedrin judgment), not a timer.
|
||||
Neither the refugee nor the avenger controls release.
|
||||
|
||||
Protocol:
|
||||
1. Harm event detected → component enters refuge (isolated, protected)
|
||||
2. Affected party protected from further exposure
|
||||
3. Chatzotzrot signal alerts kohanim (operators)
|
||||
4. Sanhedrin deliberation determines release — not automatic, not arbitrary
|
||||
5. All refuge events permanently recorded (append-only audit)
|
||||
"""
|
||||
|
||||
use GenServer
|
||||
|
||||
require Logger
|
||||
|
||||
alias VavPipeline.Chatzotzrot
|
||||
|
||||
@type refuge_status :: :in_refuge | :released
|
||||
@type incident_type :: :unintentional_harm | :processing_error | :data_exposure | :integrity_breach
|
||||
|
||||
@type incident :: %{
|
||||
id: String.t(),
|
||||
type: incident_type(),
|
||||
description: String.t(),
|
||||
affected_party: String.t() | nil,
|
||||
processing_path: [String.t()],
|
||||
detected_at: DateTime.t()
|
||||
}
|
||||
|
||||
@type refuge_record :: %{
|
||||
component_id: String.t(),
|
||||
incident: incident(),
|
||||
status: refuge_status(),
|
||||
entered_at: DateTime.t(),
|
||||
released_at: DateTime.t() | nil,
|
||||
release_judgment: map() | nil
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Client API
|
||||
# ============================================================
|
||||
|
||||
def start_link(opts \\ []) do
|
||||
name = Keyword.get(opts, :name, __MODULE__)
|
||||
GenServer.start_link(__MODULE__, opts, name: name)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Enter refuge — a component that caused unintentional harm.
|
||||
|
||||
Bamidbar 35:11 — v'nas shamah rotze'ach, makeh nefesh bishgagah.
|
||||
|
||||
The component is isolated from normal operation but PROTECTED
|
||||
from reactive destruction. No one deletes a component under refuge.
|
||||
|
||||
Returns {:ok, refuge_record} or {:error, reason}.
|
||||
"""
|
||||
@spec enter_refuge(String.t(), incident()) :: {:ok, refuge_record()} | {:error, atom()}
|
||||
def enter_refuge(component_id, incident) when is_binary(component_id) and is_map(incident) do
|
||||
GenServer.call(__MODULE__, {:enter_refuge, component_id, incident})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Protect an affected party from further exposure to the same processing path.
|
||||
|
||||
Bamidbar 35:12 — v'lo yamut harotze'ach ad amdo lifnei ha'edah lamishpat.
|
||||
The one who caused harm is protected; the one harmed is also protected.
|
||||
"""
|
||||
@spec protect_affected(String.t(), incident()) :: {:ok, map()} | {:error, atom()}
|
||||
def protect_affected(party_id, incident) when is_binary(party_id) and is_map(incident) do
|
||||
GenServer.call(__MODULE__, {:protect_affected, party_id, incident})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Release a component from refuge — requires Sanhedrin judgment.
|
||||
|
||||
Bamidbar 35:25 — ad mot hakohen hagadol.
|
||||
Release is not a timer. Not a governance board's whim.
|
||||
It is a judgment that the era of the harm has passed.
|
||||
|
||||
The judgment must include:
|
||||
- authority: :sanhedrin_deliberation
|
||||
- reason: what has changed (the "death of the kohen gadol")
|
||||
- grounding: Torah reference that grounds the judgment
|
||||
|
||||
Returns {:ok, updated_record} or {:error, reason}.
|
||||
"""
|
||||
@spec release(String.t(), map()) :: {:ok, refuge_record()} | {:error, atom()}
|
||||
def release(component_id, judgment) when is_binary(component_id) and is_map(judgment) do
|
||||
GenServer.call(__MODULE__, {:release, component_id, judgment})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check if a component is currently in refuge.
|
||||
"""
|
||||
@spec in_refuge?(String.t()) :: boolean()
|
||||
def in_refuge?(component_id) do
|
||||
GenServer.call(__MODULE__, {:in_refuge?, component_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check if a processing path is blocked for a party.
|
||||
"""
|
||||
@spec path_blocked?(String.t(), [String.t()]) :: boolean()
|
||||
def path_blocked?(party_id, processing_path) do
|
||||
GenServer.call(__MODULE__, {:path_blocked?, party_id, processing_path})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get all current refuge records.
|
||||
"""
|
||||
@spec list_refugees() :: [refuge_record()]
|
||||
def list_refugees do
|
||||
GenServer.call(__MODULE__, :list_refugees)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get all active protections for affected parties.
|
||||
"""
|
||||
@spec list_protections() :: [map()]
|
||||
def list_protections do
|
||||
GenServer.call(__MODULE__, :list_protections)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get the full audit trail — all refuge events ever recorded.
|
||||
Append-only. Never deleted.
|
||||
"""
|
||||
@spec audit_trail() :: [map()]
|
||||
def audit_trail do
|
||||
GenServer.call(__MODULE__, :audit_trail)
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
# GenServer callbacks
|
||||
# ============================================================
|
||||
|
||||
@impl true
|
||||
def init(_opts) do
|
||||
state = %{
|
||||
# Active refuge records: %{component_id => refuge_record}
|
||||
refugees: %{},
|
||||
# Active protections: %{party_id => [%{path: [...], incident_id: ...}]}
|
||||
protections: %{},
|
||||
# Permanent audit trail — append-only, never deleted
|
||||
audit: []
|
||||
}
|
||||
|
||||
Logger.info("AreiMiklat: three cities of refuge prepared — takhin lekha haderekh")
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:enter_refuge, component_id, incident}, _from, state) do
|
||||
if Map.has_key?(state.refugees, component_id) do
|
||||
{:reply, {:error, :already_in_refuge}, state}
|
||||
else
|
||||
now = DateTime.utc_now()
|
||||
|
||||
incident = Map.put_new(incident, :detected_at, now)
|
||||
|
||||
record = %{
|
||||
component_id: component_id,
|
||||
incident: incident,
|
||||
status: :in_refuge,
|
||||
entered_at: now,
|
||||
released_at: nil,
|
||||
release_judgment: nil
|
||||
}
|
||||
|
||||
audit_entry = %{
|
||||
event: :refuge_entry,
|
||||
component_id: component_id,
|
||||
incident: incident,
|
||||
timestamp: now
|
||||
}
|
||||
|
||||
# Sound the trumpets — alert the kohanim
|
||||
Chatzotzrot.signal(:assembly_leaders,
|
||||
reason: "Refuge entry: #{component_id} — #{Map.get(incident, :description, "unintentional harm")}"
|
||||
)
|
||||
|
||||
Logger.warning(
|
||||
"AreiMiklat: #{component_id} entered refuge — " <>
|
||||
"#{Map.get(incident, :type, :unknown)} — #{Map.get(incident, :description, "no description")}"
|
||||
)
|
||||
|
||||
new_state = %{
|
||||
state
|
||||
| refugees: Map.put(state.refugees, component_id, record),
|
||||
audit: [audit_entry | state.audit]
|
||||
}
|
||||
|
||||
{:reply, {:ok, record}, new_state}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:protect_affected, party_id, incident}, _from, state) do
|
||||
now = DateTime.utc_now()
|
||||
path = Map.get(incident, :processing_path, [])
|
||||
incident_id = Map.get(incident, :id, "unknown")
|
||||
|
||||
protection = %{
|
||||
party_id: party_id,
|
||||
blocked_path: path,
|
||||
incident_id: incident_id,
|
||||
protected_at: now
|
||||
}
|
||||
|
||||
audit_entry = %{
|
||||
event: :affected_party_protected,
|
||||
party_id: party_id,
|
||||
incident_id: incident_id,
|
||||
blocked_path: path,
|
||||
timestamp: now
|
||||
}
|
||||
|
||||
Logger.info("AreiMiklat: affected party #{party_id} protected from path #{inspect(path)}")
|
||||
|
||||
existing = Map.get(state.protections, party_id, [])
|
||||
|
||||
new_state = %{
|
||||
state
|
||||
| protections: Map.put(state.protections, party_id, [protection | existing]),
|
||||
audit: [audit_entry | state.audit]
|
||||
}
|
||||
|
||||
{:reply, {:ok, protection}, new_state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:release, component_id, judgment}, _from, state) do
|
||||
case Map.get(state.refugees, component_id) do
|
||||
nil ->
|
||||
{:reply, {:error, :not_in_refuge}, state}
|
||||
|
||||
record ->
|
||||
# Only Sanhedrin deliberation can release — ad mot hakohen hagadol
|
||||
case Map.get(judgment, :authority) do
|
||||
:sanhedrin_deliberation ->
|
||||
now = DateTime.utc_now()
|
||||
|
||||
updated = %{
|
||||
record
|
||||
| status: :released,
|
||||
released_at: now,
|
||||
release_judgment: judgment
|
||||
}
|
||||
|
||||
audit_entry = %{
|
||||
event: :refuge_release,
|
||||
component_id: component_id,
|
||||
judgment: judgment,
|
||||
timestamp: now
|
||||
}
|
||||
|
||||
Logger.info(
|
||||
"AreiMiklat: #{component_id} released from refuge — " <>
|
||||
"#{Map.get(judgment, :reason, "era ended")}"
|
||||
)
|
||||
|
||||
new_state = %{
|
||||
state
|
||||
| refugees: Map.delete(state.refugees, component_id),
|
||||
audit: [audit_entry | state.audit]
|
||||
}
|
||||
|
||||
{:reply, {:ok, updated}, new_state}
|
||||
|
||||
other ->
|
||||
Logger.warning(
|
||||
"AreiMiklat: unauthorized release attempt for #{component_id} — " <>
|
||||
"authority #{inspect(other)} is not :sanhedrin_deliberation"
|
||||
)
|
||||
|
||||
{:reply, {:error, :unauthorized_release}, state}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:in_refuge?, component_id}, _from, state) do
|
||||
{:reply, Map.has_key?(state.refugees, component_id), state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:path_blocked?, party_id, path}, _from, state) do
|
||||
blocked =
|
||||
case Map.get(state.protections, party_id) do
|
||||
nil ->
|
||||
false
|
||||
|
||||
protections ->
|
||||
Enum.any?(protections, fn p ->
|
||||
# A path is blocked if the blocked_path is a prefix of the requested path
|
||||
List.starts_with?(path, p.blocked_path)
|
||||
end)
|
||||
end
|
||||
|
||||
{:reply, blocked, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:list_refugees, _from, state) do
|
||||
{:reply, Map.values(state.refugees), state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:list_protections, _from, state) do
|
||||
all =
|
||||
state.protections
|
||||
|> Enum.flat_map(fn {_party, protections} -> protections end)
|
||||
|
||||
{:reply, all, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:audit_trail, _from, state) do
|
||||
{:reply, state.audit, state}
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,236 @@
|
||||
defmodule VavPipeline.Bikurim do
|
||||
@moduledoc """
|
||||
First Fruits — Release Protocol. Devarim 26:1-11.
|
||||
|
||||
Architecture §3.16:
|
||||
First-output review process for all new capabilities.
|
||||
Before a new feature serves users, its first product is
|
||||
offered to the specification for evaluation, accompanied
|
||||
by narrative.
|
||||
|
||||
Arami oved avi — "A wandering Aramean was my father."
|
||||
— Devarim 26:5
|
||||
|
||||
The offering includes NARRATIVE. Not just what was produced,
|
||||
but the story of how it was produced and what lineage it carries.
|
||||
The farmer does not just bring fruit. He tells the story of
|
||||
his ancestors' wandering, Egypt, the exodus, and arrival.
|
||||
|
||||
Protocol:
|
||||
1. New capability built → first output produced
|
||||
2. Builder provides narrative (the mikra bikurim):
|
||||
- What was built
|
||||
- What specification authorized it
|
||||
- What lineage it carries (which existing components it depends on)
|
||||
- What Torah grounds it
|
||||
3. First output evaluated against specification
|
||||
4. Accepted → recorded in registry, approved for production
|
||||
5. Rejected → recorded with reason, not deployed
|
||||
|
||||
The offering is placed "before Hashem your God" — lifnei Hashem
|
||||
Elohekha (Devarim 26:10). The specification judges. Not the builder.
|
||||
Not the operator. The specification.
|
||||
"""
|
||||
|
||||
use GenServer
|
||||
|
||||
require Logger
|
||||
|
||||
alias VavPipeline.Specification
|
||||
|
||||
@type offering_status :: :accepted | :rejected | :pending
|
||||
@type offering :: %{
|
||||
capability: String.t(),
|
||||
first_output: any(),
|
||||
narrative: String.t(),
|
||||
lineage: [String.t()],
|
||||
torah_grounding: String.t() | nil,
|
||||
offered_at: DateTime.t(),
|
||||
status: offering_status(),
|
||||
evaluation: map() | nil
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Client API
|
||||
# ============================================================
|
||||
|
||||
def start_link(opts \\ []) do
|
||||
name = Keyword.get(opts, :name, __MODULE__)
|
||||
GenServer.start_link(__MODULE__, opts, name: name)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Offer first fruit — submit a new capability's first output for review.
|
||||
|
||||
Devarim 26:2 — V'lakachta mereshit kol p'ri ha'adamah...
|
||||
"You shall take of the first of every fruit of the ground..."
|
||||
|
||||
Parameters:
|
||||
- capability_name: identifier for the new capability
|
||||
- first_output: the actual first product
|
||||
- narrative: the mikra bikurim — the story of how this was built
|
||||
- opts: keyword list with optional :lineage and :torah_grounding
|
||||
|
||||
Returns {:ok, :approved_for_deployment, offering} or
|
||||
{:error, :specification_mismatch, reason, offering}.
|
||||
"""
|
||||
@spec offer(String.t(), any(), String.t(), keyword()) ::
|
||||
{:ok, :approved_for_deployment, offering()}
|
||||
| {:error, :specification_mismatch, String.t(), offering()}
|
||||
def offer(capability_name, first_output, narrative, opts \\ [])
|
||||
when is_binary(capability_name) and is_binary(narrative) do
|
||||
GenServer.call(__MODULE__, {:offer, capability_name, first_output, narrative, opts})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get all offerings in the registry.
|
||||
"""
|
||||
@spec registry() :: [offering()]
|
||||
def registry do
|
||||
GenServer.call(__MODULE__, :registry)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get offerings by status.
|
||||
"""
|
||||
@spec by_status(offering_status()) :: [offering()]
|
||||
def by_status(status) when status in [:accepted, :rejected, :pending] do
|
||||
GenServer.call(__MODULE__, {:by_status, status})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check if a capability has been accepted (approved for deployment).
|
||||
"""
|
||||
@spec approved?(String.t()) :: boolean()
|
||||
def approved?(capability_name) do
|
||||
GenServer.call(__MODULE__, {:approved?, capability_name})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Count offerings by status.
|
||||
"""
|
||||
@spec count(offering_status() | :all) :: non_neg_integer()
|
||||
def count(filter \\ :all) do
|
||||
GenServer.call(__MODULE__, {:count, filter})
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
# GenServer callbacks
|
||||
# ============================================================
|
||||
|
||||
@impl true
|
||||
def init(_opts) do
|
||||
state = %{
|
||||
# All offerings — append-only registry
|
||||
offerings: []
|
||||
}
|
||||
|
||||
Logger.info("Bikurim: first fruits protocol ready — reshit kol p'ri ha'adamah")
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:offer, capability_name, first_output, narrative, opts}, _from, state) do
|
||||
now = DateTime.utc_now()
|
||||
lineage = Keyword.get(opts, :lineage, [])
|
||||
torah_grounding = Keyword.get(opts, :torah_grounding)
|
||||
|
||||
offering = %{
|
||||
capability: capability_name,
|
||||
first_output: first_output,
|
||||
narrative: narrative,
|
||||
lineage: lineage,
|
||||
torah_grounding: torah_grounding,
|
||||
offered_at: now,
|
||||
status: :pending,
|
||||
evaluation: nil
|
||||
}
|
||||
|
||||
# Evaluate against specification — lifnei Hashem Elohekha
|
||||
case evaluate_against_specification(offering) do
|
||||
:accepted ->
|
||||
accepted = %{offering | status: :accepted, evaluation: %{result: :accepted}}
|
||||
|
||||
Logger.info(
|
||||
"Bikurim: #{capability_name} accepted — first fruit approved for deployment"
|
||||
)
|
||||
|
||||
new_state = %{state | offerings: [accepted | state.offerings]}
|
||||
{:reply, {:ok, :approved_for_deployment, accepted}, new_state}
|
||||
|
||||
{:rejected, reason} ->
|
||||
rejected = %{
|
||||
offering
|
||||
| status: :rejected,
|
||||
evaluation: %{result: :rejected, reason: reason}
|
||||
}
|
||||
|
||||
Logger.warning(
|
||||
"Bikurim: #{capability_name} rejected — #{reason}"
|
||||
)
|
||||
|
||||
new_state = %{state | offerings: [rejected | state.offerings]}
|
||||
{:reply, {:error, :specification_mismatch, reason, rejected}, new_state}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:registry, _from, state) do
|
||||
{:reply, state.offerings, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:by_status, status}, _from, state) do
|
||||
matching = Enum.filter(state.offerings, &(&1.status == status))
|
||||
{:reply, matching, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:approved?, capability_name}, _from, state) do
|
||||
approved =
|
||||
Enum.any?(state.offerings, fn o ->
|
||||
o.capability == capability_name and o.status == :accepted
|
||||
end)
|
||||
|
||||
{:reply, approved, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:count, :all}, _from, state) do
|
||||
{:reply, length(state.offerings), state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:count, status}, _from, state) do
|
||||
count = Enum.count(state.offerings, &(&1.status == status))
|
||||
{:reply, count, state}
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
# Specification evaluation
|
||||
# ============================================================
|
||||
|
||||
defp evaluate_against_specification(offering) do
|
||||
# The specification evaluates the offering. Three checks:
|
||||
# 1. Does the capability have a narrative? (Arami oved avi — story is required)
|
||||
# 2. Does the first output conform to the specification?
|
||||
# 3. Is the Torah grounding valid?
|
||||
|
||||
cond do
|
||||
# No narrative — the bikurim requires the mikra (recitation)
|
||||
String.trim(offering.narrative) == "" ->
|
||||
{:rejected, "No narrative provided — Devarim 26:5 requires the mikra bikurim"}
|
||||
|
||||
# Check against specification module if available
|
||||
Code.ensure_loaded?(Specification) and function_exported?(Specification, :evaluate_bikurim, 1) ->
|
||||
apply(Specification, :evaluate_bikurim, [offering])
|
||||
|
||||
# Default: accept if narrative is present and first_output is not nil
|
||||
is_nil(offering.first_output) ->
|
||||
{:rejected, "No first output provided — there must be fruit to offer"}
|
||||
|
||||
true ->
|
||||
:accepted
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,194 @@
|
||||
defmodule VavPipeline.EglahArufah do
|
||||
@moduledoc """
|
||||
Accountability Protocol — Devarim 21:1-9.
|
||||
|
||||
Architecture §3.15:
|
||||
Formal accountability for harm of UNKNOWN origin.
|
||||
The nearest component documents what it processed,
|
||||
what it did not check, and what it could have caught.
|
||||
|
||||
Yadeinu lo shafkhu et hadam hazeh, v'eineinu lo ra'u.
|
||||
— Devarim 21:7
|
||||
|
||||
"Our hands did not shed this blood, and our eyes did not see."
|
||||
The Talmud asks: would we suspect the elders of shedding blood?
|
||||
Rather, the declaration means: we did not let this person leave
|
||||
without food (provision) or escort (protection).
|
||||
Accountability for what was NOT provided.
|
||||
|
||||
Protocol:
|
||||
1. Harm of unknown origin detected
|
||||
2. Find the nearest component in the processing chain
|
||||
3. That component makes a formal declaration:
|
||||
- What it processed (recent operations)
|
||||
- What it did not check (skipped validations)
|
||||
- What it could have caught (missed catches)
|
||||
4. Declaration permanently recorded — never deleted
|
||||
5. Chatzotzrot signals governance (assembly_leaders)
|
||||
|
||||
The eglah arufah is broken — the heifer's neck is broken in the
|
||||
nachal eitan (a harsh valley). The system acknowledges that
|
||||
something was NOT done. The accountability is permanent.
|
||||
"""
|
||||
|
||||
use GenServer
|
||||
|
||||
require Logger
|
||||
|
||||
alias VavPipeline.Chatzotzrot
|
||||
|
||||
@type harm_incident :: %{
|
||||
id: String.t(),
|
||||
description: String.t(),
|
||||
approximate_location: String.t(),
|
||||
detected_at: DateTime.t(),
|
||||
source: :unknown
|
||||
}
|
||||
|
||||
@type declaration :: %{
|
||||
incident_id: String.t(),
|
||||
component_id: String.t(),
|
||||
processed: [String.t()],
|
||||
not_checked: [String.t()],
|
||||
could_have_caught: [String.t()],
|
||||
declaration_text: String.t(),
|
||||
declared_at: DateTime.t()
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Client API
|
||||
# ============================================================
|
||||
|
||||
def start_link(opts \\ []) do
|
||||
name = Keyword.get(opts, :name, __MODULE__)
|
||||
GenServer.start_link(__MODULE__, opts, name: name)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Initiate the eglah arufah protocol for harm of unknown origin.
|
||||
|
||||
Devarim 21:1 — Ki yimatze chalal ba'adamah...
|
||||
"When a corpse is found in the land... and it is not known who struck him."
|
||||
|
||||
The nearest component to the harm must make a formal declaration
|
||||
of what it did and did not do. This is accountability for failure
|
||||
to protect, not accusation of causing harm.
|
||||
|
||||
Parameters:
|
||||
- incident: the harm event (source must be :unknown)
|
||||
- nearest_component_id: which component is nearest
|
||||
- report: map with :processed, :not_checked, :could_have_caught lists
|
||||
|
||||
Returns {:ok, declaration} or {:error, reason}.
|
||||
"""
|
||||
@spec initiate(harm_incident(), String.t(), map()) ::
|
||||
{:ok, declaration()} | {:error, atom()}
|
||||
def initiate(incident, nearest_component_id, report)
|
||||
when is_map(incident) and is_binary(nearest_component_id) and is_map(report) do
|
||||
GenServer.call(__MODULE__, {:initiate, incident, nearest_component_id, report})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get all declarations — the permanent record.
|
||||
These are never deleted. The eglah arufah is permanent accountability.
|
||||
"""
|
||||
@spec declarations() :: [declaration()]
|
||||
def declarations do
|
||||
GenServer.call(__MODULE__, :declarations)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get declarations for a specific incident.
|
||||
"""
|
||||
@spec declarations_for(String.t()) :: [declaration()]
|
||||
def declarations_for(incident_id) when is_binary(incident_id) do
|
||||
GenServer.call(__MODULE__, {:declarations_for, incident_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Count total declarations.
|
||||
"""
|
||||
@spec count() :: non_neg_integer()
|
||||
def count do
|
||||
GenServer.call(__MODULE__, :count)
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
# GenServer callbacks
|
||||
# ============================================================
|
||||
|
||||
@impl true
|
||||
def init(_opts) do
|
||||
state = %{
|
||||
# Permanent declarations — append-only, never deleted
|
||||
declarations: []
|
||||
}
|
||||
|
||||
Logger.info("EglahArufah: accountability protocol ready — nachal eitan prepared")
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:initiate, incident, nearest_component_id, report}, _from, state) do
|
||||
# Validate source is unknown — eglah arufah is ONLY for unknown harm
|
||||
case Map.get(incident, :source) do
|
||||
:unknown ->
|
||||
now = DateTime.utc_now()
|
||||
incident = Map.put_new(incident, :detected_at, now)
|
||||
incident_id = Map.get(incident, :id, "incident_#{:erlang.unique_integer([:positive])}")
|
||||
|
||||
declaration = %{
|
||||
incident_id: incident_id,
|
||||
component_id: nearest_component_id,
|
||||
processed: Map.get(report, :processed, []),
|
||||
not_checked: Map.get(report, :not_checked, []),
|
||||
could_have_caught: Map.get(report, :could_have_caught, []),
|
||||
declaration_text:
|
||||
"This component (#{nearest_component_id}) did not cause this harm. " <>
|
||||
"This component acknowledges proximity to the harm. " <>
|
||||
"This component reports what it did and did not do.",
|
||||
declared_at: now
|
||||
}
|
||||
|
||||
# Sound the trumpets — alert governance
|
||||
Chatzotzrot.signal(:assembly_leaders,
|
||||
reason:
|
||||
"Eglah Arufah: unknown harm near #{nearest_component_id} — #{Map.get(incident, :description, "unknown")}"
|
||||
)
|
||||
|
||||
Logger.warning(
|
||||
"EglahArufah: declaration by #{nearest_component_id} — " <>
|
||||
"processed #{length(declaration.processed)} operations, " <>
|
||||
"skipped #{length(declaration.not_checked)} checks, " <>
|
||||
"could have caught #{length(declaration.could_have_caught)} issues"
|
||||
)
|
||||
|
||||
new_state = %{state | declarations: [declaration | state.declarations]}
|
||||
{:reply, {:ok, declaration}, new_state}
|
||||
|
||||
other ->
|
||||
Logger.warning(
|
||||
"EglahArufah: rejected — source is #{inspect(other)}, not :unknown. " <>
|
||||
"Eglah arufah is only for harm of unknown origin."
|
||||
)
|
||||
|
||||
{:reply, {:error, :source_not_unknown}, state}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:declarations, _from, state) do
|
||||
{:reply, state.declarations, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:declarations_for, incident_id}, _from, state) do
|
||||
matching = Enum.filter(state.declarations, &(&1.incident_id == incident_id))
|
||||
{:reply, matching, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:count, _from, state) do
|
||||
{:reply, length(state.declarations), state}
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user