Separate daemon supervision from bootstrap
This commit is contained in:
parent
cc00f361f7
commit
5caa80f136
6 changed files with 124 additions and 108 deletions
|
|
@ -9,8 +9,10 @@ use epiphany_core::epiphany_cultmesh_daemon_poke_receipt_for_intent;
|
|||
use epiphany_core::epiphany_service_execution_audit_report;
|
||||
use epiphany_core::load_epiphany_cultmesh_daemon_restart_policy;
|
||||
use epiphany_core::load_epiphany_cultmesh_daemon_service_lifecycle_receipts;
|
||||
use epiphany_core::load_epiphany_cultmesh_cluster_topology;
|
||||
use epiphany_core::load_epiphany_cultmesh_managed_service_policies;
|
||||
use epiphany_core::load_epiphany_cultmesh_managed_service_policy;
|
||||
use epiphany_core::load_epiphany_cultmesh_status;
|
||||
use epiphany_core::load_epiphany_cultmesh_swarm_brake;
|
||||
use epiphany_core::observe_native_process as observe_process;
|
||||
use epiphany_core::query_epiphany_local_verse_context;
|
||||
|
|
@ -134,12 +136,46 @@ fn main() -> Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
fn reconcile(args: Args) -> Result<()> {
|
||||
fn require_supervisor_bootstrap(args: &Args) -> Result<()> {
|
||||
load_epiphany_cultmesh_status(&args.store, args.runtime_id.clone())?
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"local Verse is not bootstrapped at {}; run explicit bootstrap before daemon-supervisor commands",
|
||||
args.store.display()
|
||||
)
|
||||
})?;
|
||||
let topology =
|
||||
load_epiphany_cultmesh_cluster_topology(&args.store, args.runtime_id.clone())?;
|
||||
if topology.is_empty() {
|
||||
anyhow::bail!(
|
||||
"local Verse has no persisted cluster topology at {}; run explicit bootstrap before daemon-supervisor commands",
|
||||
args.store.display()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn seed_supervisor_smoke_fixture(args: &Args) -> Result<()> {
|
||||
if !args
|
||||
.store
|
||||
.components()
|
||||
.any(|component| component.as_os_str() == ".epiphany-smoke")
|
||||
{
|
||||
anyhow::bail!(
|
||||
"daemon-supervisor smoke fixtures may write only beneath .epiphany-smoke"
|
||||
);
|
||||
}
|
||||
seed_epiphany_local_verse_context(
|
||||
&args.store,
|
||||
args.runtime_id.clone(),
|
||||
Utc::now().to_rfc3339(),
|
||||
)?;
|
||||
)
|
||||
}
|
||||
|
||||
fn reconcile(args: Args) -> Result<()> {
|
||||
|
||||
require_supervisor_bootstrap(&args)?;
|
||||
|
||||
let context = query_epiphany_local_verse_context(&args.store, args.runtime_id.clone())?;
|
||||
let daemon_status = context
|
||||
.daemon_statuses
|
||||
|
|
@ -381,11 +417,9 @@ fn service_launch(args: Args) -> Result<()> {
|
|||
}
|
||||
|
||||
fn managed_service_policy(args: Args) -> Result<()> {
|
||||
seed_epiphany_local_verse_context(
|
||||
&args.store,
|
||||
args.runtime_id.clone(),
|
||||
Utc::now().to_rfc3339(),
|
||||
)?;
|
||||
|
||||
require_supervisor_bootstrap(&args)?;
|
||||
|
||||
let command = service_command_path(&args)?;
|
||||
let stdout_artifact = args.stdout_artifact.clone().unwrap_or_else(|| {
|
||||
PathBuf::from(format!(
|
||||
|
|
@ -614,11 +648,9 @@ fn managed_service_reconcile(mut args: Args) -> Result<()> {
|
|||
}
|
||||
|
||||
fn service_runbook(args: Args) -> Result<()> {
|
||||
seed_epiphany_local_verse_context(
|
||||
&args.store,
|
||||
args.runtime_id.clone(),
|
||||
Utc::now().to_rfc3339(),
|
||||
)?;
|
||||
|
||||
require_supervisor_bootstrap(&args)?;
|
||||
|
||||
let context = query_epiphany_local_verse_context(&args.store, args.runtime_id.clone())?;
|
||||
assert_swarm_brake_allows_service_lifecycle(&context)?;
|
||||
let started_at = Utc::now();
|
||||
|
|
@ -670,11 +702,9 @@ fn service_runbook(args: Args) -> Result<()> {
|
|||
}
|
||||
|
||||
fn repo_work_service_audit(args: Args) -> Result<()> {
|
||||
seed_epiphany_local_verse_context(
|
||||
&args.store,
|
||||
args.runtime_id.clone(),
|
||||
Utc::now().to_rfc3339(),
|
||||
)?;
|
||||
|
||||
require_supervisor_bootstrap(&args)?;
|
||||
|
||||
let context = query_epiphany_local_verse_context(&args.store, args.runtime_id.clone())?;
|
||||
assert_swarm_brake_allows_service_lifecycle(&context)?;
|
||||
let started_at = Utc::now();
|
||||
|
|
@ -867,11 +897,9 @@ fn service_artifact_status(artifact_ref: &str) -> &'static str {
|
|||
}
|
||||
|
||||
fn cluster_daemon_runbook(args: Args) -> Result<()> {
|
||||
seed_epiphany_local_verse_context(
|
||||
&args.store,
|
||||
args.runtime_id.clone(),
|
||||
Utc::now().to_rfc3339(),
|
||||
)?;
|
||||
|
||||
require_supervisor_bootstrap(&args)?;
|
||||
|
||||
let context = query_epiphany_local_verse_context(&args.store, args.runtime_id.clone())?;
|
||||
assert_swarm_brake_allows_service_lifecycle(&context)?;
|
||||
let started_at = Utc::now();
|
||||
|
|
@ -940,11 +968,9 @@ fn cluster_daemon_runbook(args: Args) -> Result<()> {
|
|||
}
|
||||
|
||||
fn cluster_windows_service_install(args: Args) -> Result<()> {
|
||||
seed_epiphany_local_verse_context(
|
||||
&args.store,
|
||||
args.runtime_id.clone(),
|
||||
Utc::now().to_rfc3339(),
|
||||
)?;
|
||||
|
||||
require_supervisor_bootstrap(&args)?;
|
||||
|
||||
let context = query_epiphany_local_verse_context(&args.store, args.runtime_id.clone())?;
|
||||
assert_swarm_brake_allows_service_lifecycle(&context)?;
|
||||
let started_at = Utc::now();
|
||||
|
|
@ -1072,11 +1098,9 @@ fn cluster_windows_service_install(args: Args) -> Result<()> {
|
|||
}
|
||||
|
||||
fn cluster_windows_service_audit(args: Args) -> Result<()> {
|
||||
seed_epiphany_local_verse_context(
|
||||
&args.store,
|
||||
args.runtime_id.clone(),
|
||||
Utc::now().to_rfc3339(),
|
||||
)?;
|
||||
|
||||
require_supervisor_bootstrap(&args)?;
|
||||
|
||||
let context = query_epiphany_local_verse_context(&args.store, args.runtime_id.clone())?;
|
||||
assert_swarm_brake_allows_service_lifecycle(&context)?;
|
||||
let started_at = Utc::now();
|
||||
|
|
@ -1182,11 +1206,9 @@ fn cluster_windows_service_audit(args: Args) -> Result<()> {
|
|||
}
|
||||
|
||||
fn cluster_windows_service_control(args: Args, control: &str) -> Result<()> {
|
||||
seed_epiphany_local_verse_context(
|
||||
&args.store,
|
||||
args.runtime_id.clone(),
|
||||
Utc::now().to_rfc3339(),
|
||||
)?;
|
||||
|
||||
require_supervisor_bootstrap(&args)?;
|
||||
|
||||
let context = query_epiphany_local_verse_context(&args.store, args.runtime_id.clone())?;
|
||||
assert_swarm_brake_allows_service_lifecycle(&context)?;
|
||||
let started_at = Utc::now();
|
||||
|
|
@ -1323,11 +1345,9 @@ fn cluster_windows_service_control(args: Args, control: &str) -> Result<()> {
|
|||
}
|
||||
|
||||
fn windows_service_install(args: Args) -> Result<()> {
|
||||
seed_epiphany_local_verse_context(
|
||||
&args.store,
|
||||
args.runtime_id.clone(),
|
||||
Utc::now().to_rfc3339(),
|
||||
)?;
|
||||
|
||||
require_supervisor_bootstrap(&args)?;
|
||||
|
||||
let context = query_epiphany_local_verse_context(&args.store, args.runtime_id.clone())?;
|
||||
assert_swarm_brake_allows_service_lifecycle(&context)?;
|
||||
let started_at = Utc::now();
|
||||
|
|
@ -1421,11 +1441,9 @@ fn windows_service_install(args: Args) -> Result<()> {
|
|||
}
|
||||
|
||||
fn windows_service_status(args: Args) -> Result<()> {
|
||||
seed_epiphany_local_verse_context(
|
||||
&args.store,
|
||||
args.runtime_id.clone(),
|
||||
Utc::now().to_rfc3339(),
|
||||
)?;
|
||||
|
||||
require_supervisor_bootstrap(&args)?;
|
||||
|
||||
let context = query_epiphany_local_verse_context(&args.store, args.runtime_id.clone())?;
|
||||
assert_swarm_brake_allows_service_lifecycle(&context)?;
|
||||
let started_at = Utc::now();
|
||||
|
|
@ -1480,11 +1498,9 @@ fn windows_service_status(args: Args) -> Result<()> {
|
|||
}
|
||||
|
||||
fn windows_service_execution_readiness(args: Args) -> Result<()> {
|
||||
seed_epiphany_local_verse_context(
|
||||
&args.store,
|
||||
args.runtime_id.clone(),
|
||||
Utc::now().to_rfc3339(),
|
||||
)?;
|
||||
|
||||
require_supervisor_bootstrap(&args)?;
|
||||
|
||||
let context = query_epiphany_local_verse_context(&args.store, args.runtime_id.clone())?;
|
||||
assert_swarm_brake_allows_service_lifecycle(&context)?;
|
||||
let started_at = Utc::now();
|
||||
|
|
@ -1535,11 +1551,9 @@ fn windows_service_execution_readiness(args: Args) -> Result<()> {
|
|||
}
|
||||
|
||||
fn cluster_windows_service_execution_readiness(args: Args) -> Result<()> {
|
||||
seed_epiphany_local_verse_context(
|
||||
&args.store,
|
||||
args.runtime_id.clone(),
|
||||
Utc::now().to_rfc3339(),
|
||||
)?;
|
||||
|
||||
require_supervisor_bootstrap(&args)?;
|
||||
|
||||
let context = query_epiphany_local_verse_context(&args.store, args.runtime_id.clone())?;
|
||||
assert_swarm_brake_allows_service_lifecycle(&context)?;
|
||||
let started_at = Utc::now();
|
||||
|
|
@ -1604,11 +1618,9 @@ fn cluster_windows_service_execution_readiness(args: Args) -> Result<()> {
|
|||
}
|
||||
|
||||
fn windows_service_execution_audit(args: Args) -> Result<()> {
|
||||
seed_epiphany_local_verse_context(
|
||||
&args.store,
|
||||
args.runtime_id.clone(),
|
||||
Utc::now().to_rfc3339(),
|
||||
)?;
|
||||
|
||||
require_supervisor_bootstrap(&args)?;
|
||||
|
||||
let context = query_epiphany_local_verse_context(&args.store, args.runtime_id.clone())?;
|
||||
assert_swarm_brake_allows_service_lifecycle(&context)?;
|
||||
let started_at = Utc::now();
|
||||
|
|
@ -1668,11 +1680,9 @@ fn windows_service_execution_audit(args: Args) -> Result<()> {
|
|||
}
|
||||
|
||||
fn windows_service_execution_audit_smoke(args: Args) -> Result<()> {
|
||||
seed_epiphany_local_verse_context(
|
||||
&args.store,
|
||||
args.runtime_id.clone(),
|
||||
Utc::now().to_rfc3339(),
|
||||
)?;
|
||||
|
||||
seed_supervisor_smoke_fixture(&args)?;
|
||||
|
||||
let started_at = Utc::now();
|
||||
for (action, status) in [
|
||||
("windows-service-execution-runbook", "written"),
|
||||
|
|
@ -1761,11 +1771,9 @@ fn windows_service_execution_audit_smoke(args: Args) -> Result<()> {
|
|||
}
|
||||
|
||||
fn cluster_windows_service_execution_runbook(args: Args) -> Result<()> {
|
||||
seed_epiphany_local_verse_context(
|
||||
&args.store,
|
||||
args.runtime_id.clone(),
|
||||
Utc::now().to_rfc3339(),
|
||||
)?;
|
||||
|
||||
require_supervisor_bootstrap(&args)?;
|
||||
|
||||
let context = query_epiphany_local_verse_context(&args.store, args.runtime_id.clone())?;
|
||||
assert_swarm_brake_allows_service_lifecycle(&context)?;
|
||||
let started_at = Utc::now();
|
||||
|
|
@ -1854,11 +1862,9 @@ fn cluster_windows_service_execution_runbook(args: Args) -> Result<()> {
|
|||
}
|
||||
|
||||
fn cluster_windows_service_execution_audit(args: Args) -> Result<()> {
|
||||
seed_epiphany_local_verse_context(
|
||||
&args.store,
|
||||
args.runtime_id.clone(),
|
||||
Utc::now().to_rfc3339(),
|
||||
)?;
|
||||
|
||||
require_supervisor_bootstrap(&args)?;
|
||||
|
||||
let context = query_epiphany_local_verse_context(&args.store, args.runtime_id.clone())?;
|
||||
assert_swarm_brake_allows_service_lifecycle(&context)?;
|
||||
let started_at = Utc::now();
|
||||
|
|
@ -1917,11 +1923,9 @@ fn cluster_windows_service_execution_audit(args: Args) -> Result<()> {
|
|||
}
|
||||
|
||||
fn cluster_windows_service_execution_audit_smoke(args: Args) -> Result<()> {
|
||||
seed_epiphany_local_verse_context(
|
||||
&args.store,
|
||||
args.runtime_id.clone(),
|
||||
Utc::now().to_rfc3339(),
|
||||
)?;
|
||||
|
||||
seed_supervisor_smoke_fixture(&args)?;
|
||||
|
||||
let started_at = Utc::now();
|
||||
for (action, status) in [
|
||||
("cluster-windows-service-execution-runbook", "written"),
|
||||
|
|
@ -2067,11 +2071,9 @@ fn elevated_powershell_runbook_command(path: &str) -> String {
|
|||
}
|
||||
|
||||
fn windows_service_execution_runbook(args: Args) -> Result<()> {
|
||||
seed_epiphany_local_verse_context(
|
||||
&args.store,
|
||||
args.runtime_id.clone(),
|
||||
Utc::now().to_rfc3339(),
|
||||
)?;
|
||||
|
||||
require_supervisor_bootstrap(&args)?;
|
||||
|
||||
let context = query_epiphany_local_verse_context(&args.store, args.runtime_id.clone())?;
|
||||
assert_swarm_brake_allows_service_lifecycle(&context)?;
|
||||
let started_at = Utc::now();
|
||||
|
|
@ -2163,11 +2165,9 @@ fn windows_service_execution_runbook(args: Args) -> Result<()> {
|
|||
}
|
||||
|
||||
fn windows_service_reconcile(args: Args) -> Result<()> {
|
||||
seed_epiphany_local_verse_context(
|
||||
&args.store,
|
||||
args.runtime_id.clone(),
|
||||
Utc::now().to_rfc3339(),
|
||||
)?;
|
||||
|
||||
require_supervisor_bootstrap(&args)?;
|
||||
|
||||
let context = query_epiphany_local_verse_context(&args.store, args.runtime_id.clone())?;
|
||||
assert_swarm_brake_allows_service_lifecycle(&context)?;
|
||||
let started_at = Utc::now();
|
||||
|
|
@ -2264,11 +2264,9 @@ fn windows_service_reconcile(args: Args) -> Result<()> {
|
|||
}
|
||||
|
||||
fn windows_service_control(args: Args, control: &str) -> Result<()> {
|
||||
seed_epiphany_local_verse_context(
|
||||
&args.store,
|
||||
args.runtime_id.clone(),
|
||||
Utc::now().to_rfc3339(),
|
||||
)?;
|
||||
|
||||
require_supervisor_bootstrap(&args)?;
|
||||
|
||||
let context = query_epiphany_local_verse_context(&args.store, args.runtime_id.clone())?;
|
||||
assert_swarm_brake_allows_service_lifecycle(&context)?;
|
||||
let started_at = Utc::now();
|
||||
|
|
@ -2357,11 +2355,9 @@ fn windows_service_control(args: Args, control: &str) -> Result<()> {
|
|||
|
||||
fn run_tick(args: &Args, iteration: u64, next_wake_utc: Option<String>) -> Result<Value> {
|
||||
let tick_started = Utc::now();
|
||||
seed_epiphany_local_verse_context(
|
||||
&args.store,
|
||||
args.runtime_id.clone(),
|
||||
Utc::now().to_rfc3339(),
|
||||
)?;
|
||||
|
||||
require_supervisor_bootstrap(&args)?;
|
||||
|
||||
let mut context = query_epiphany_local_verse_context(&args.store, args.runtime_id.clone())?;
|
||||
assert_swarm_brake_allows_scheduler_tick(&context)?;
|
||||
|
||||
|
|
@ -2513,11 +2509,9 @@ fn run_tick(args: &Args, iteration: u64, next_wake_utc: Option<String>) -> Resul
|
|||
}
|
||||
|
||||
fn write_policy(args: Args) -> Result<()> {
|
||||
seed_epiphany_local_verse_context(
|
||||
&args.store,
|
||||
args.runtime_id.clone(),
|
||||
Utc::now().to_rfc3339(),
|
||||
)?;
|
||||
|
||||
require_supervisor_bootstrap(&args)?;
|
||||
|
||||
let context = query_epiphany_local_verse_context(&args.store, args.runtime_id.clone())?;
|
||||
let daemon_status = context
|
||||
.daemon_statuses
|
||||
|
|
|
|||
|
|
@ -138,3 +138,18 @@ owning daemon's provider state, and write only its heartbeat. An unbootstrapped
|
|||
daemon fails before creating the CultCache store and tells the operator to run
|
||||
explicit bootstrap. The wrapper already performs that bootstrap at the
|
||||
operator boundary.
|
||||
|
||||
## Supervisor/bootstrap split
|
||||
|
||||
`epiphany-daemon-supervisor` repeated the same authority collapse across twenty
|
||||
production lifecycle, policy, scheduler, runbook, audit, status, and control
|
||||
paths. Every command seeded shared policy, topology, contracts, brake, and
|
||||
operator status before performing Idunn work.
|
||||
|
||||
Production supervisor paths now require persisted Epiphany status and cluster
|
||||
topology through a read-only prerequisite. Missing bootstrap fails before a
|
||||
CultCache store is created. The two synthetic execution-audit smoke commands
|
||||
retain explicit fixture initialization, but it is isolated behind
|
||||
`seed_supervisor_smoke_fixture`, which refuses every store outside an
|
||||
`.epiphany-smoke` path before writing. Idunn consumes shared initialization; it
|
||||
does not own or repair it.
|
||||
|
|
|
|||
|
|
@ -98,6 +98,10 @@
|
|||
context. They require persisted topology, read narrow liveness, then write
|
||||
only their bounded provider state and heartbeat. Explicit operator bootstrap
|
||||
owns policy/topology/contract initialization.
|
||||
- `epiphany-daemon-supervisor` also requires explicit persisted bootstrap for
|
||||
every production lifecycle, policy, scheduler, runbook, audit, status, and
|
||||
control path. Only its two synthetic audit-smoke commands may initialize
|
||||
fixtures, and only beneath `.epiphany-smoke`.
|
||||
- Store/runtime overrides fail before fixture seeding or receipt construction.
|
||||
- Negative proof: targeting `state/local-verse.ccmp` was rejected and its SHA-256 did not change.
|
||||
- Positive proof: the built-in quarantined smoke completes successfully and reports its quarantine coordinates.
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@ The confirmed conceptual substitutions have been cut:
|
|||
- cluster daemons do not bootstrap or query the full Verse. Missing persisted
|
||||
topology is a pre-write refusal; explicit operator bootstrap owns shared
|
||||
policy, topology, contracts, brake initialization, and operator status.
|
||||
- daemon-supervisor production commands likewise require persisted bootstrap
|
||||
and cannot create shared state. Its two audit-smoke fixture initializers are
|
||||
hard-confined beneath `.epiphany-smoke`.
|
||||
|
||||
The presentation boundary is now plain: `swarm overview` is a generic compact read-only projection. Gjallar is a downstream TUI application on Nightwing and is not an Epiphany organ, provider, owner, runtime, or architectural dependency. Eve/CultUI graphs may be lowered or composited downstream without Epiphany caring which presentation client does it.
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -70,7 +70,7 @@ data_flow:
|
|||
- move historical proof to git, commits, smoke artifacts, or distilled evidence rather than plan prose
|
||||
|
||||
active_subgoals:
|
||||
- Use `notes/conceptual-substitution-audit-2026-07-12.md` plus `notes/receipt-writer-provenance-audit-2026-07-12.md` as the current authority-corrosion maps. Snapshot promotion, synthetic smoke escape, unbound operator completion, hidden service execution flags, generic-bootstrap publication of provider advertisements/Eve surfaces/hosted tools, the central topology-derived Odin publisher, all-provider state writers, and daemon-side generic bootstrap are cut. Provider absence survives bootstrap; the query CLI cannot proclaim seven providers active over RUDP; each cluster daemon requires persisted topology and writes only its own advertisement, surface, tools, and heartbeat through narrow reads. Next remove or test-gate dead public receipt constructors where provider binaries are absent and audit remaining projections, caches, schedulers, adapters, and non-receipt smokes for borrowed authority or path escape.
|
||||
- Use `notes/conceptual-substitution-audit-2026-07-12.md` plus `notes/receipt-writer-provenance-audit-2026-07-12.md` as the current authority-corrosion maps. Snapshot promotion, synthetic smoke escape, unbound operator completion, hidden service execution flags, generic-bootstrap publication of provider advertisements/Eve surfaces/hosted tools, the central topology-derived Odin publisher, all-provider state writers, cluster-daemon bootstrap, and daemon-supervisor production bootstrap are cut. Provider absence survives bootstrap; the query CLI cannot proclaim seven providers active over RUDP; each cluster daemon writes only its own presence and pulse; Idunn production paths require shared initialization rather than repainting it; supervisor audit-smoke seeding is confined beneath `.epiphany-smoke`. Next remove or test-gate dead public receipt constructors where provider binaries are absent and audit remaining projections, caches, schedulers, adapters, and non-receipt smokes for borrowed authority or path escape.
|
||||
- `swarm-overview` is an Epiphany-local, renderer-neutral diagnostic projection over provider-owned typed state. It owns no lifecycle, discovery, composition, or mutation authority. Epiphany publishes clean provider-owned Eve surfaces; Odin may discover them; downstream lowerers and compositors remain outside Epiphany's concern.
|
||||
- Idunn owns daemon survival and service lifecycle physiology. Codex/operator sessions may inspect, generate runbooks, and route explicit operator authority, but keeping daemons alive belongs to Idunn-owned service lifecycle receipts, scheduler policy, audits, and aftercare. Do not let Self, diagnostic projections, or ad hoc wrapper summaries become a second daemon-keeper. Global tool readbacks for Self `service-health`, `service-policy-directory`, and `swarm-online-runbook` state `lifecycleOwner=Idunn` and `hostedBody=Epiphany` without service mutation or private-state exposure.
|
||||
- Use `notes/epiphany-cultmesh-dreaming-roadmap.md` as the design authority for distributed Epiphany dreaming. Public dreams are separately authored typed documents distributed through CultMesh/CultNet; private state remains local and cannot be shared by flipping a visibility flag. Foreign dreams are external thought weather until a reviewed local adoption receipt digests them into memory, planning, doctrine, or governance.
|
||||
|
|
|
|||
Reference in a new issue