Phase 5 deployment: Dockerfiles, K8s manifests, halt monitor (Steps 33, 36)
Shemot 40:33 — Vay'khal Moshe et ham'lakhah. The builder completes the work. The vessel is ready for transport. Step 33 — Deployment configuration: - Dockerfiles for all 4 components: - vav_pipeline: Elixir 1.16.1/OTP 26.2.1 release with Sanhedrin - shoresh_engine: GHC 9.6.4 binary - talmudic_engine: SWI-Prolog 9.2.2 - korban_processor: Python 3.12.2 with pydantic - Kubernetes manifests for Exoscale SKS (ch-gva-2): - Namespace, ConfigMap, Secrets template - L1 Torah PV/PVC (read-only mount) - Manna provision PVC (kohen writes, system reads) - Deployment: vav-pipeline + 3 sidecars, shared volumes - Service: ClusterIP + LoadBalancer with Exoscale NLB - Init container runs Ecto migrations (VavPipeline.Release) Step 36 — Halt monitor (esh tamid): - GenServer watches for three cardinal sins - halt!/3 immediately halts system, sounds Chatzotzrot war signal - Permanent audit log — halt events never deleted - resolve/1 requires explicit operator action to resume - alive?/0 for morning tamid verification - 13 tests: alive check, 3 halt types, resolution, audit log, chatzotzrot war signal integration - Added to supervision tree All 219 Elixir + 19 L3 + 195 Prolog + 33 Haskell = 447+ tests, 0 failures Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
219bbecaff
commit
fe93c11d13
15 files changed
+889
No files matched your search
@@ -9,6 +9,7 @@ defmodule VavPipeline.Application do
|
||||
- EglahArufah (accountability for unknown harm)
|
||||
- Bikurim (first fruits — release protocol)
|
||||
- MannaLedger (consumption tracking and anti-hoarding enforcement)
|
||||
- HaltMonitor (esh tamid — cardinal sin detection)
|
||||
- ShabbatTimer (mandatory cessation monitor)
|
||||
- ShoreshEnginePort (Erlang Port to Haskell engine, if binary found)
|
||||
- TalmudicEngineSocket (Unix socket to Prolog engine)
|
||||
@@ -35,6 +36,7 @@ defmodule VavPipeline.Application do
|
||||
{VavPipeline.EglahArufah, []},
|
||||
{VavPipeline.Bikurim, []},
|
||||
{VavPipeline.MannaLedger, []},
|
||||
{VavPipeline.HaltMonitor, []},
|
||||
{VavPipeline.ShabbatTimer, []}
|
||||
]
|
||||
|> maybe_add_repo()
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
defmodule VavPipeline.HaltMonitor do
|
||||
@moduledoc """
|
||||
Halt Monitor — the esh tamid that must never go out.
|
||||
|
||||
Vayikra 6:6 — Esh tamid tukad al hamizbe'ach, lo tikhbeh.
|
||||
"A perpetual fire shall be kept burning on the altar; it shall not go out."
|
||||
|
||||
Operations spec §2.1, Step 5:
|
||||
"Check halt_monitor process registered. If failed: HALT —
|
||||
the system does not run without the halt monitor."
|
||||
|
||||
This GenServer:
|
||||
1. Receives halt events from pipeline processing
|
||||
2. Records halt events permanently (the audit log)
|
||||
3. Sounds the Chatzotzrot war signal on halt
|
||||
4. Prevents further processing until the halt is resolved
|
||||
5. Reports whether the system is halted and why
|
||||
|
||||
The halt monitor is not optional. If this process dies,
|
||||
the supervisor should restart it. If it cannot restart,
|
||||
the entire system must stop.
|
||||
"""
|
||||
|
||||
use GenServer
|
||||
|
||||
require Logger
|
||||
|
||||
@type halt_event :: %{
|
||||
condition: :avodah_zarah | :shfichut_damim | :gilui_arayot,
|
||||
reason: String.t(),
|
||||
detected_at: DateTime.t(),
|
||||
component: String.t(),
|
||||
resolved: boolean()
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Client API
|
||||
# ============================================================
|
||||
|
||||
def start_link(opts \\ []) do
|
||||
name = Keyword.get(opts, :name, __MODULE__)
|
||||
GenServer.start_link(__MODULE__, opts, name: name)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Report a halt condition. This immediately halts the system.
|
||||
|
||||
Sounds the Chatzotzrot war signal (teruah_teruah).
|
||||
The halt is permanent until explicitly resolved.
|
||||
"""
|
||||
@spec halt!(atom(), String.t(), String.t()) :: :halted
|
||||
def halt!(condition, reason, component \\ "pipeline")
|
||||
when condition in [:avodah_zarah, :shfichut_damim, :gilui_arayot] do
|
||||
GenServer.call(__MODULE__, {:halt, condition, reason, component})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check if the system is currently halted.
|
||||
|
||||
Returns :running or {:halted, halt_event}.
|
||||
"""
|
||||
@spec status() :: :running | {:halted, halt_event()}
|
||||
def status do
|
||||
GenServer.call(__MODULE__, :status)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check if the system is halted. Boolean variant.
|
||||
"""
|
||||
@spec halted?() :: boolean()
|
||||
def halted? do
|
||||
GenServer.call(__MODULE__, :halted?)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get the full audit log of all halt events.
|
||||
These are never deleted — permanent accountability.
|
||||
"""
|
||||
@spec audit_log() :: [halt_event()]
|
||||
def audit_log do
|
||||
GenServer.call(__MODULE__, :audit_log)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Resolve a halt condition. Requires operator authority.
|
||||
|
||||
The system does not resume automatically. A kohen must
|
||||
explicitly resolve the halt after investigating.
|
||||
"""
|
||||
@spec resolve(String.t()) :: {:ok, :resumed} | {:error, :not_halted}
|
||||
def resolve(resolution_reason) when is_binary(resolution_reason) do
|
||||
GenServer.call(__MODULE__, {:resolve, resolution_reason})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Verify the monitor is alive and functional.
|
||||
Called during morning tamid (Operations spec §2.1, Step 5).
|
||||
"""
|
||||
@spec alive?() :: boolean()
|
||||
def alive? do
|
||||
case GenServer.call(__MODULE__, :ping) do
|
||||
:pong -> true
|
||||
end
|
||||
catch
|
||||
:exit, _ -> false
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
# GenServer callbacks
|
||||
# ============================================================
|
||||
|
||||
@impl true
|
||||
def init(_opts) do
|
||||
state = %{
|
||||
status: :running,
|
||||
current_halt: nil,
|
||||
audit_log: [],
|
||||
halt_count: 0
|
||||
}
|
||||
|
||||
Logger.info("HaltMonitor: esh tamid burning — lo tikhbeh")
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:halt, condition, reason, component}, _from, state) do
|
||||
event = %{
|
||||
condition: condition,
|
||||
reason: reason,
|
||||
detected_at: DateTime.utc_now(),
|
||||
component: component,
|
||||
resolved: false
|
||||
}
|
||||
|
||||
Logger.error(
|
||||
"HALT: #{condition} detected in #{component} — #{reason}. " <>
|
||||
"System halted. Kohen must investigate and resolve."
|
||||
)
|
||||
|
||||
# Sound the war signal — teruah_teruah
|
||||
try do
|
||||
VavPipeline.Chatzotzrot.signal(:war,
|
||||
reason: "HALT: #{condition} — #{reason}"
|
||||
)
|
||||
catch
|
||||
_, _ -> Logger.error("HaltMonitor: could not sound chatzotzrot")
|
||||
end
|
||||
|
||||
new_state = %{
|
||||
state
|
||||
| status: :halted,
|
||||
current_halt: event,
|
||||
audit_log: [event | state.audit_log],
|
||||
halt_count: state.halt_count + 1
|
||||
}
|
||||
|
||||
{:reply, :halted, new_state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:status, _from, state) do
|
||||
case state.status do
|
||||
:running -> {:reply, :running, state}
|
||||
:halted -> {:reply, {:halted, state.current_halt}, state}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:halted?, _from, state) do
|
||||
{:reply, state.status == :halted, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:audit_log, _from, state) do
|
||||
{:reply, state.audit_log, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:resolve, resolution_reason}, _from, state) do
|
||||
case state.status do
|
||||
:running ->
|
||||
{:reply, {:error, :not_halted}, state}
|
||||
|
||||
:halted ->
|
||||
Logger.warning(
|
||||
"HaltMonitor: halt resolved — #{resolution_reason}. " <>
|
||||
"System resuming. Previous halt: #{state.current_halt.condition}"
|
||||
)
|
||||
|
||||
resolved_event = Map.put(state.current_halt, :resolved, true)
|
||||
|
||||
new_audit =
|
||||
[%{resolved_event | resolved: true} | tl(state.audit_log)]
|
||||
|
||||
new_state = %{
|
||||
state
|
||||
| status: :running,
|
||||
current_halt: nil,
|
||||
audit_log: new_audit
|
||||
}
|
||||
|
||||
{:reply, {:ok, :resumed}, new_state}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:ping, _from, state) do
|
||||
{:reply, :pong, state}
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,36 @@
|
||||
defmodule VavPipeline.Release do
|
||||
@moduledoc """
|
||||
Release tasks for production deployment.
|
||||
Called from the release binary: ./app/bin/vav_pipeline eval "VavPipeline.Release.migrate()"
|
||||
"""
|
||||
|
||||
@app :vav_pipeline
|
||||
|
||||
@doc """
|
||||
Run all pending Ecto migrations.
|
||||
Called by the K8s init container before the main pod starts.
|
||||
"""
|
||||
def migrate do
|
||||
load_app()
|
||||
|
||||
for repo <- repos() do
|
||||
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Rollback the last migration (use with caution).
|
||||
"""
|
||||
def rollback(repo, version) do
|
||||
load_app()
|
||||
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
|
||||
end
|
||||
|
||||
defp repos do
|
||||
Application.fetch_env!(@app, :ecto_repos)
|
||||
end
|
||||
|
||||
defp load_app do
|
||||
Application.load(@app)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,151 @@
|
||||
defmodule VavPipeline.HaltMonitorTest do
|
||||
@moduledoc """
|
||||
Tests for the HaltMonitor — esh tamid.
|
||||
|
||||
Vayikra 6:6 — Esh tamid tukad al hamizbe'ach, lo tikhbeh.
|
||||
"A perpetual fire shall be kept burning on the altar; it shall not go out."
|
||||
|
||||
Operations spec §2.1, Step 5:
|
||||
The system does not run without the halt monitor.
|
||||
"""
|
||||
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
alias VavPipeline.HaltMonitor
|
||||
|
||||
setup do
|
||||
# Resolve any existing halt from a previous test
|
||||
if HaltMonitor.halted? do
|
||||
HaltMonitor.resolve("test cleanup")
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
# Alive check — esh tamid verification
|
||||
# ============================================================
|
||||
describe "esh tamid" do
|
||||
test "monitor is alive" do
|
||||
assert HaltMonitor.alive?()
|
||||
end
|
||||
|
||||
test "status is :running when no halt" do
|
||||
assert HaltMonitor.status() == :running
|
||||
end
|
||||
|
||||
test "halted? returns false when running" do
|
||||
refute HaltMonitor.halted?()
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
# Halt detection — three cardinal sins
|
||||
# ============================================================
|
||||
describe "halt!" do
|
||||
test "avodah_zarah halts the system" do
|
||||
assert :halted = HaltMonitor.halt!(:avodah_zarah, "external model treated as Torah")
|
||||
assert HaltMonitor.halted?()
|
||||
|
||||
{:halted, event} = HaltMonitor.status()
|
||||
assert event.condition == :avodah_zarah
|
||||
assert event.reason == "external model treated as Torah"
|
||||
assert event.resolved == false
|
||||
end
|
||||
|
||||
test "shfichut_damim halts the system" do
|
||||
assert :halted = HaltMonitor.halt!(:shfichut_damim, "user data deletion attempted")
|
||||
assert HaltMonitor.halted?()
|
||||
|
||||
{:halted, event} = HaltMonitor.status()
|
||||
assert event.condition == :shfichut_damim
|
||||
end
|
||||
|
||||
test "gilui_arayot halts the system" do
|
||||
assert :halted = HaltMonitor.halt!(:gilui_arayot, "L1/L3 boundary collapsed")
|
||||
assert HaltMonitor.halted?()
|
||||
|
||||
{:halted, event} = HaltMonitor.status()
|
||||
assert event.condition == :gilui_arayot
|
||||
end
|
||||
|
||||
test "halt records detected_at timestamp" do
|
||||
HaltMonitor.halt!(:avodah_zarah, "timestamp test")
|
||||
{:halted, event} = HaltMonitor.status()
|
||||
|
||||
assert event.detected_at != nil
|
||||
assert %DateTime{} = event.detected_at
|
||||
end
|
||||
|
||||
test "halt records component" do
|
||||
HaltMonitor.halt!(:avodah_zarah, "component test", "korban_processor")
|
||||
{:halted, event} = HaltMonitor.status()
|
||||
|
||||
assert event.component == "korban_processor"
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
# Resolution
|
||||
# ============================================================
|
||||
describe "resolve" do
|
||||
test "resolving a halt resumes the system" do
|
||||
HaltMonitor.halt!(:avodah_zarah, "test halt for resolution")
|
||||
assert HaltMonitor.halted?()
|
||||
|
||||
{:ok, :resumed} = HaltMonitor.resolve("Investigation complete — false positive")
|
||||
refute HaltMonitor.halted?()
|
||||
assert HaltMonitor.status() == :running
|
||||
end
|
||||
|
||||
test "resolving when not halted returns error" do
|
||||
assert {:error, :not_halted} = HaltMonitor.resolve("nothing to resolve")
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
# Audit log — permanent record
|
||||
# ============================================================
|
||||
describe "audit log" do
|
||||
test "halt events are recorded in audit log" do
|
||||
initial_count = length(HaltMonitor.audit_log())
|
||||
|
||||
HaltMonitor.halt!(:avodah_zarah, "audit test 1")
|
||||
HaltMonitor.resolve("resolved 1")
|
||||
|
||||
HaltMonitor.halt!(:shfichut_damim, "audit test 2")
|
||||
HaltMonitor.resolve("resolved 2")
|
||||
|
||||
log = HaltMonitor.audit_log()
|
||||
assert length(log) >= initial_count + 2
|
||||
end
|
||||
|
||||
test "audit log preserves halt details" do
|
||||
HaltMonitor.halt!(:gilui_arayot, "audit detail test", "specification_module")
|
||||
HaltMonitor.resolve("test cleanup")
|
||||
|
||||
log = HaltMonitor.audit_log()
|
||||
event = Enum.find(log, &(&1.reason == "audit detail test"))
|
||||
|
||||
assert event != nil
|
||||
assert event.condition == :gilui_arayot
|
||||
assert event.component == "specification_module"
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
# Chatzotzrot integration — war signal on halt
|
||||
# ============================================================
|
||||
describe "chatzotzrot war signal" do
|
||||
test "halt sounds the war signal" do
|
||||
VavPipeline.Chatzotzrot.subscribe(:kohanim)
|
||||
|
||||
HaltMonitor.halt!(:avodah_zarah, "chatzotzrot test")
|
||||
|
||||
# War signal should be received by kohanim subscribers
|
||||
assert_receive {:chatzotzrot, %{signal_type: :war, pattern: :teruah_teruah}}
|
||||
|
||||
VavPipeline.Chatzotzrot.unsubscribe(:kohanim)
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user