feat(orchestrator): CeremonyOrchestrator + Chronicle.Consumer stubs
CeremonyOrchestrator: GenServer providing a PubSub broadcast interface for ceremony status changes. LiveView subscribes to these broadcasts for real-time updates. A K8s CRD watcher will feed events into this in a future sprint; for now the init log makes the stub state explicit. Chronicle.Consumer: stub for the Ops DB projector that will consume Chronicle events and hydrate the Ecto tables. Projector design (idempotent, checkpointed, catch-up on restart) per DESIGN-OPS-DB-CHAIN-OF-CUSTODY-0001 §2.5. Both modules document the orchestrator/engine distinction: guildhall orchestrates, substrate decides. Both are now supervised by their respective application trees (Guildhall.Orchestrator.Supervisor, Guildhall.Chronicle.Supervisor). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Tyler J King <tking@guildhouse.dev>
This commit is contained in:
parent
69297f1ac0
commit
48a7495ef5
4 changed files with 77 additions and 14 deletions
|
|
@ -1,19 +1,13 @@
|
||||||
defmodule Guildhall.Chronicle.Application do
|
defmodule Guildhall.Chronicle.Application do
|
||||||
# See https://hexdocs.pm/elixir/Application.html
|
|
||||||
# for more information on OTP Applications
|
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
|
|
||||||
use Application
|
use Application
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def start(_type, _args) do
|
def start(_type, _args) do
|
||||||
children = [
|
children = [
|
||||||
# Starts a worker by calling: Guildhall.Chronicle.Worker.start_link(arg)
|
Guildhall.Chronicle.Consumer
|
||||||
# {Guildhall.Chronicle.Worker, arg}
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# See https://hexdocs.pm/elixir/Supervisor.html
|
|
||||||
# for other strategies and supported options
|
|
||||||
opts = [strategy: :one_for_one, name: Guildhall.Chronicle.Supervisor]
|
opts = [strategy: :one_for_one, name: Guildhall.Chronicle.Supervisor]
|
||||||
Supervisor.start_link(children, opts)
|
Supervisor.start_link(children, opts)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
26
apps/guildhall_chronicle/lib/guildhall/chronicle/consumer.ex
Normal file
26
apps/guildhall_chronicle/lib/guildhall/chronicle/consumer.ex
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
defmodule Guildhall.Chronicle.Consumer do
|
||||||
|
@moduledoc """
|
||||||
|
Consumes Chronicle events and projects them into the Ops DB.
|
||||||
|
|
||||||
|
See DESIGN-OPS-DB-CHAIN-OF-CUSTODY-0001 §2.5 for the projector
|
||||||
|
design — idempotent insert, checkpoint recovery, catch-up on
|
||||||
|
restart, backpressure handling.
|
||||||
|
|
||||||
|
Stub — the transport (gRPC vs NATS vs Kafka) and the Chronicle
|
||||||
|
event schema are not yet finalized. This module will subscribe
|
||||||
|
to the chosen transport and call into the Ops DB repo for each
|
||||||
|
event, updating `projector_checkpoint` after a successful batch.
|
||||||
|
"""
|
||||||
|
use GenServer
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
def start_link(opts \\ []) do
|
||||||
|
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def init(_opts) do
|
||||||
|
Logger.info("Chronicle.Consumer started (stub — no transport configured)")
|
||||||
|
{:ok, %{last_entry_id: nil}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,19 +1,13 @@
|
||||||
defmodule Guildhall.Orchestrator.Application do
|
defmodule Guildhall.Orchestrator.Application do
|
||||||
# See https://hexdocs.pm/elixir/Application.html
|
|
||||||
# for more information on OTP Applications
|
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
|
|
||||||
use Application
|
use Application
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def start(_type, _args) do
|
def start(_type, _args) do
|
||||||
children = [
|
children = [
|
||||||
# Starts a worker by calling: Guildhall.Orchestrator.Worker.start_link(arg)
|
Guildhall.Orchestrator.CeremonyOrchestrator
|
||||||
# {Guildhall.Orchestrator.Worker, arg}
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# See https://hexdocs.pm/elixir/Supervisor.html
|
|
||||||
# for other strategies and supported options
|
|
||||||
opts = [strategy: :one_for_one, name: Guildhall.Orchestrator.Supervisor]
|
opts = [strategy: :one_for_one, name: Guildhall.Orchestrator.Supervisor]
|
||||||
Supervisor.start_link(children, opts)
|
Supervisor.start_link(children, opts)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
defmodule Guildhall.Orchestrator.CeremonyOrchestrator do
|
||||||
|
@moduledoc """
|
||||||
|
Coordinates ceremony workflows by (eventually) watching substrate
|
||||||
|
CeremonyRequest CRDs and notifying witnesses via PubSub.
|
||||||
|
|
||||||
|
Currently a stub — the K8s CRD watcher will be wired in once the
|
||||||
|
`:k8s` package is added and a kubeconfig is available.
|
||||||
|
|
||||||
|
### Orchestrator vs engine
|
||||||
|
|
||||||
|
This module ORCHESTRATES (notifies humans, collects signatures,
|
||||||
|
broadcasts status). The substrate `CeremonyEngine` DECIDES
|
||||||
|
(evaluates Accord conditions, advances the state machine).
|
||||||
|
|
||||||
|
See DESIGN-ORG-OPS-FRAMEWORK-0001 §3.2 for the separation.
|
||||||
|
"""
|
||||||
|
use GenServer
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
def start_link(opts \\ []) do
|
||||||
|
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Broadcast a ceremony status change to all LiveView subscribers.
|
||||||
|
|
||||||
|
Called by the K8s watcher (future) or manually in tests. Fans
|
||||||
|
out on both the per-ceremony topic and the wildcard topic.
|
||||||
|
"""
|
||||||
|
def broadcast_ceremony_status(ceremony_id, status) do
|
||||||
|
Phoenix.PubSub.broadcast(
|
||||||
|
Guildhall.PubSub,
|
||||||
|
"ceremony:#{ceremony_id}",
|
||||||
|
{:ceremony_status, ceremony_id, status}
|
||||||
|
)
|
||||||
|
|
||||||
|
Phoenix.PubSub.broadcast(
|
||||||
|
Guildhall.PubSub,
|
||||||
|
"ceremony:*",
|
||||||
|
{:ceremony_status, ceremony_id, status}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def init(_opts) do
|
||||||
|
Logger.info("CeremonyOrchestrator started (stub — no K8s watcher yet)")
|
||||||
|
{:ok, %{watcher: nil}}
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in a new issue