From 1da96bd74ff576a0ae6aae4d44b997e5c44bfff0 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 12 Jul 2026 12:57:59 +0100 Subject: [PATCH] Add Idunn managed service desired state --- .../src/bin/epiphany-daemon-supervisor.rs | 134 ++++++++++++++++++ epiphany-core/src/cultmesh_integration.rs | 100 +++++++++++++ epiphany-core/src/lib.rs | 5 + notes/epiphany-current-algorithmic-map.md | 8 ++ notes/fresh-workspace-handoff.md | 9 ++ notes/perfect-machine-audit-roadmap.md | 10 ++ state/ledgers.msgpack | Bin 424955 -> 425312 bytes state/map.yaml | 2 +- 8 files changed, 267 insertions(+), 1 deletion(-) diff --git a/epiphany-core/src/bin/epiphany-daemon-supervisor.rs b/epiphany-core/src/bin/epiphany-daemon-supervisor.rs index 8ae75b2a..cc32e968 100644 --- a/epiphany-core/src/bin/epiphany-daemon-supervisor.rs +++ b/epiphany-core/src/bin/epiphany-daemon-supervisor.rs @@ -6,9 +6,11 @@ use chrono::Utc; use epiphany_core::EPIPHANY_CULTMESH_DAEMON_RESTART_POLICY_SCHEMA_VERSION; use epiphany_core::EPIPHANY_CULTMESH_DAEMON_SCHEDULER_RECEIPT_SCHEMA_VERSION; use epiphany_core::EPIPHANY_CULTMESH_DAEMON_SERVICE_LIFECYCLE_RECEIPT_SCHEMA_VERSION; +use epiphany_core::EPIPHANY_CULTMESH_MANAGED_SERVICE_POLICY_SCHEMA_VERSION; use epiphany_core::EpiphanyCultMeshDaemonRestartPolicyEntry; use epiphany_core::EpiphanyCultMeshDaemonSchedulerReceiptEntry; use epiphany_core::EpiphanyCultMeshDaemonServiceLifecycleReceiptEntry; +use epiphany_core::EpiphanyCultMeshManagedServicePolicyEntry; use epiphany_core::EpiphanyCultMeshDaemonStatusEntry; use epiphany_core::EpiphanyLocalVerseContext; use epiphany_core::EpiphanyServiceExecutionAuditReport; @@ -18,6 +20,7 @@ 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_managed_service_policy; use epiphany_core::query_epiphany_local_verse_context; use epiphany_core::seed_epiphany_local_verse_context; use epiphany_core::write_epiphany_cultmesh_daemon_poke_intent; @@ -25,6 +28,7 @@ use epiphany_core::write_epiphany_cultmesh_daemon_poke_receipt; use epiphany_core::write_epiphany_cultmesh_daemon_restart_policy; use epiphany_core::write_epiphany_cultmesh_daemon_scheduler_receipt; use epiphany_core::write_epiphany_cultmesh_daemon_service_lifecycle_receipt; +use epiphany_core::write_epiphany_cultmesh_managed_service_policy; use epiphany_core::write_epiphany_cultmesh_daemon_status; use serde_json::Value; use serde_json::json; @@ -47,6 +51,8 @@ fn main() -> Result<()> { "serve" | "loop" | "daemon" => serve(args), "service-plan" | "install-service" => service_plan(args), "service-launch" | "launch-service" | "start-service" => service_launch(args), + "managed-service-policy" | "service-desired-state" => managed_service_policy(args), + "managed-service-read" | "service-desired-state-read" => managed_service_read(args), "service-runbook" | "runbook-service" => service_runbook(args), "repo-work-service-audit" | "repo-work-service-readiness" @@ -330,6 +336,121 @@ fn service_launch(args: Args) -> Result<()> { Ok(()) } +fn managed_service_policy(args: Args) -> Result<()> { + seed_epiphany_local_verse_context( + &args.store, + args.runtime_id.clone(), + Utc::now().to_rfc3339(), + )?; + let command = service_command_path(&args)?; + let stdout_artifact = args.stdout_artifact.clone().unwrap_or_else(|| { + PathBuf::from(format!(".epiphany-run/services/{}.stdout.log", args.service_id)) + }); + let stderr_artifact = args.stderr_artifact.clone().unwrap_or_else(|| { + PathBuf::from(format!(".epiphany-run/services/{}.stderr.log", args.service_id)) + }); + let latest = load_epiphany_cultmesh_daemon_service_lifecycle_receipts( + &args.store, + args.runtime_id.clone(), + )? + .into_iter() + .filter(|receipt| receipt.service_id == args.service_id) + .max_by(|left, right| left.started_at_utc.cmp(&right.started_at_utc)); + let policy = EpiphanyCultMeshManagedServicePolicyEntry { + schema_version: EPIPHANY_CULTMESH_MANAGED_SERVICE_POLICY_SCHEMA_VERSION.to_string(), + policy_id: args + .policy_id + .clone() + .unwrap_or_else(|| format!("managed-service-policy-{}", sanitize_id(&args.service_id))), + service_id: args.service_id.clone(), + owner_daemon_id: "epiphany-daemon-supervisor".to_string(), + command: command.display().to_string(), + args: service_serve_args(&args), + cwd: args.cwd.as_ref().map(|path| path.display().to_string()), + enabled: !args.disabled, + restart_mode: args.restart_mode.clone(), + cooldown_seconds: args.cooldown_seconds, + backoff_multiplier: args.backoff_multiplier, + stdout_artifact: stdout_artifact.display().to_string(), + stderr_artifact: stderr_artifact.display().to_string(), + last_lifecycle_receipt_id: latest + .as_ref() + .map(|receipt| receipt.receipt_id.clone()) + .unwrap_or_default(), + updated_at_utc: Utc::now().to_rfc3339(), + private_state_exposed: false, + notes: vec![ + "Idunn owns desired child-service state; the service binary owns its domain state." + .to_string(), + "Every start or restart delegates to the existing typed service lifecycle primitive." + .to_string(), + ], + }; + let written = write_epiphany_cultmesh_managed_service_policy( + &args.store, + args.runtime_id.clone(), + policy, + )?; + println!("{}", serde_json::to_string_pretty(&json!({ + "schemaVersion": written.schema_version, + "status": "written", + "policyId": written.policy_id, + "serviceId": written.service_id, + "ownerDaemonId": written.owner_daemon_id, + "enabled": written.enabled, + "restartMode": written.restart_mode, + "command": written.command, + "args": written.args, + "stdoutArtifact": written.stdout_artifact, + "stderrArtifact": written.stderr_artifact, + "lastLifecycleReceiptId": written.last_lifecycle_receipt_id, + "privateStateExposed": written.private_state_exposed, + }))?); + Ok(()) +} + +fn managed_service_read(args: Args) -> Result<()> { + let policy = load_epiphany_cultmesh_managed_service_policy( + &args.store, + args.runtime_id.clone(), + &args.service_id, + )? + .with_context(|| format!("managed service policy missing for {}", args.service_id))?; + let latest = load_epiphany_cultmesh_daemon_service_lifecycle_receipts( + &args.store, + args.runtime_id.clone(), + )? + .into_iter() + .filter(|receipt| receipt.service_id == args.service_id) + .max_by(|left, right| left.started_at_utc.cmp(&right.started_at_utc)); + println!("{}", serde_json::to_string_pretty(&json!({ + "schemaVersion": "epiphany.cultmesh.managed_service_readback.v0", + "status": "desired-state-ready", + "serviceId": policy.service_id, + "ownerDaemonId": policy.owner_daemon_id, + "desired": { + "enabled": policy.enabled, + "restartMode": policy.restart_mode, + "command": policy.command, + "args": policy.args, + "cwd": policy.cwd, + "stdoutArtifact": policy.stdout_artifact, + "stderrArtifact": policy.stderr_artifact, + }, + "latestLifecycle": latest.as_ref().map(|receipt| json!({ + "receiptId": receipt.receipt_id, + "status": receipt.status, + "action": receipt.action, + "processId": receipt.process_id, + "startedAtUtc": receipt.started_at_utc, + "completedAtUtc": receipt.completed_at_utc, + })), + "processObservation": "unknown-until-managed-service-reconcile", + "privateStateExposed": false, + }))?); + Ok(()) +} + fn service_runbook(args: Args) -> Result<()> { seed_epiphany_local_verse_context( &args.store, @@ -3202,6 +3323,7 @@ struct Args { service_display_name: Option, service_description: Option, service_start_type: String, + restart_mode: String, service_command: Option, service_args: Vec, runbook_path: Option, @@ -3249,6 +3371,7 @@ impl Args { let mut service_display_name = None; let mut service_description = None; let mut service_start_type = "demand".to_string(); + let mut restart_mode = "on-failure".to_string(); let mut service_command = None; let mut service_args = Vec::new(); let mut runbook_path = None; @@ -3353,6 +3476,9 @@ impl Args { .next() .context("missing --service-start-type value")?; } + "--restart-mode" => { + restart_mode = values.next().context("missing --restart-mode value")?; + } "--service-command" => { service_command = Some(PathBuf::from( values.next().context("missing --service-command value")?, @@ -3462,6 +3588,10 @@ impl Args { | "service-launch" | "launch-service" | "start-service" + | "managed-service-policy" + | "service-desired-state" + | "managed-service-read" + | "service-desired-state-read" | "service-runbook" | "runbook-service" | "repo-work-service-audit" @@ -3521,6 +3651,9 @@ impl Args { if !matches!(service_start_type.as_str(), "auto" | "demand" | "disabled") { anyhow::bail!("--service-start-type must be auto, demand, or disabled"); } + if !matches!(restart_mode.as_str(), "always" | "on-failure" | "never") { + anyhow::bail!("--restart-mode must be always, on-failure, or never"); + } Ok(Self { command, @@ -3544,6 +3677,7 @@ impl Args { service_display_name, service_description, service_start_type, + restart_mode, service_command, service_args, runbook_path, diff --git a/epiphany-core/src/cultmesh_integration.rs b/epiphany-core/src/cultmesh_integration.rs index f2804950..573e7d2d 100644 --- a/epiphany-core/src/cultmesh_integration.rs +++ b/epiphany-core/src/cultmesh_integration.rs @@ -160,6 +160,10 @@ pub const EPIPHANY_CULTMESH_DAEMON_SERVICE_LIFECYCLE_RECEIPT_SCHEMA_VERSION: &st "epiphany.cultmesh.daemon_service_lifecycle_receipt.v0"; pub const EPIPHANY_CULTMESH_DAEMON_SERVICE_LIFECYCLE_RECEIPT_LATEST_KEY: &str = "epiphany-local/daemon-service-lifecycle-receipt/latest"; +pub const EPIPHANY_CULTMESH_MANAGED_SERVICE_POLICY_TYPE: &str = + "epiphany.cultmesh.managed_service_policy"; +pub const EPIPHANY_CULTMESH_MANAGED_SERVICE_POLICY_SCHEMA_VERSION: &str = + "epiphany.cultmesh.managed_service_policy.v0"; pub const EPIPHANY_CULTMESH_IDUNN_DEPLOYMENT_RECEIPT_SCHEMA_VERSION: &str = "gamecult.idunn.deployment_receipt.v0"; pub const EPIPHANY_CULTMESH_IDUNN_DEPLOYMENT_RECEIPT_LATEST_KEY: &str = @@ -1475,6 +1479,48 @@ pub struct EpiphanyCultMeshDaemonServiceLifecycleReceiptEntry { pub schema_catalog_sha256: String, } +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "epiphany.cultmesh.managed_service_policy", + schema = "EpiphanyCultMeshManagedServicePolicyEntry" +)] +pub struct EpiphanyCultMeshManagedServicePolicyEntry { + #[cultcache(key = 0)] + pub schema_version: String, + #[cultcache(key = 1)] + pub policy_id: String, + #[cultcache(key = 2)] + pub service_id: String, + #[cultcache(key = 3)] + pub owner_daemon_id: String, + #[cultcache(key = 4)] + pub command: String, + #[cultcache(key = 5)] + pub args: Vec, + #[cultcache(key = 6)] + pub cwd: Option, + #[cultcache(key = 7)] + pub enabled: bool, + #[cultcache(key = 8)] + pub restart_mode: String, + #[cultcache(key = 9)] + pub cooldown_seconds: i64, + #[cultcache(key = 10)] + pub backoff_multiplier: u32, + #[cultcache(key = 11)] + pub stdout_artifact: String, + #[cultcache(key = 12)] + pub stderr_artifact: String, + #[cultcache(key = 13)] + pub last_lifecycle_receipt_id: String, + #[cultcache(key = 14)] + pub updated_at_utc: String, + #[cultcache(key = 15)] + pub private_state_exposed: bool, + #[cultcache(key = 16)] + pub notes: Vec, +} + #[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] #[cultcache( type = "gamecult.idunn.deployment_receipt", @@ -2584,6 +2630,7 @@ cultmesh_documents!(EpiphanyCultMeshDocuments { EpiphanyCultMeshDaemonRestartPolicyEntry => EPIPHANY_CULTMESH_DAEMON_RESTART_POLICY_SCHEMA_VERSION, EpiphanyCultMeshDaemonSchedulerReceiptEntry => EPIPHANY_CULTMESH_DAEMON_SCHEDULER_RECEIPT_SCHEMA_VERSION, EpiphanyCultMeshDaemonServiceLifecycleReceiptEntry => EPIPHANY_CULTMESH_DAEMON_SERVICE_LIFECYCLE_RECEIPT_SCHEMA_VERSION, + EpiphanyCultMeshManagedServicePolicyEntry => EPIPHANY_CULTMESH_MANAGED_SERVICE_POLICY_SCHEMA_VERSION, EpiphanyCultMeshIdunnDeploymentReceiptEntry => EPIPHANY_CULTMESH_IDUNN_DEPLOYMENT_RECEIPT_SCHEMA_VERSION, EpiphanyCultMeshIdunnAftercareAuditReceiptEntry => EPIPHANY_CULTMESH_IDUNN_AFTERCARE_AUDIT_RECEIPT_SCHEMA_VERSION, EpiphanyCultMeshSwarmBrakeEntry => EPIPHANY_CULTMESH_SWARM_BRAKE_SCHEMA_VERSION, @@ -3598,6 +3645,28 @@ pub fn load_epiphany_cultmesh_daemon_service_lifecycle_receipts( .collect()) } +pub fn write_epiphany_cultmesh_managed_service_policy( + store_path: impl AsRef, + runtime_id: impl Into, + policy: EpiphanyCultMeshManagedServicePolicyEntry, +) -> Result { + validate_managed_service_policy(&policy)?; + let mut node = open_epiphany_cultmesh_node(store_path, runtime_id)?; + let key = epiphany_cultmesh_managed_service_policy_key(&policy.service_id); + let written = node.put(key.as_str(), &policy)?; + node.flush()?; + Ok(written) +} + +pub fn load_epiphany_cultmesh_managed_service_policy( + store_path: impl AsRef, + runtime_id: impl Into, + service_id: &str, +) -> Result> { + let node = open_epiphany_cultmesh_node(store_path, runtime_id)?; + node.get(&epiphany_cultmesh_managed_service_policy_key(service_id)) +} + pub fn write_epiphany_cultmesh_idunn_deployment_receipt( store_path: impl AsRef, runtime_id: impl Into, @@ -3951,6 +4020,33 @@ fn validate_daemon_restart_policy(policy: &EpiphanyCultMeshDaemonRestartPolicyEn Ok(()) } +fn validate_managed_service_policy(policy: &EpiphanyCultMeshManagedServicePolicyEntry) -> Result<()> { + if policy.private_state_exposed { + return Err(anyhow!("managed service policies must not expose private state")); + } + for (label, value) in [ + ("policy id", policy.policy_id.as_str()), + ("service id", policy.service_id.as_str()), + ("owner daemon id", policy.owner_daemon_id.as_str()), + ("command", policy.command.as_str()), + ("restart mode", policy.restart_mode.as_str()), + ("stdout artifact", policy.stdout_artifact.as_str()), + ("stderr artifact", policy.stderr_artifact.as_str()), + ("updated timestamp", policy.updated_at_utc.as_str()), + ] { + if value.trim().is_empty() { + return Err(anyhow!("managed service policy missing {label}")); + } + } + if !matches!(policy.restart_mode.as_str(), "always" | "on-failure" | "never") { + return Err(anyhow!("managed service policy restart_mode must be always, on-failure, or never")); + } + if policy.cooldown_seconds < 0 || policy.backoff_multiplier == 0 { + return Err(anyhow!("managed service policy requires non-negative cooldown and positive backoff")); + } + Ok(()) +} + fn validate_daemon_scheduler_receipt( receipt: &EpiphanyCultMeshDaemonSchedulerReceiptEntry, ) -> Result<()> { @@ -6219,6 +6315,10 @@ fn epiphany_cultmesh_daemon_service_lifecycle_receipt_key(receipt_id: &str) -> S format!("epiphany-local/daemon-service-lifecycle-receipt/{receipt_id}") } +fn epiphany_cultmesh_managed_service_policy_key(service_id: &str) -> String { + format!("epiphany-local/managed-service-policy/{service_id}") +} + fn epiphany_cultmesh_idunn_deployment_receipt_key(receipt_id: &str) -> String { format!("gamecult-local/idunn/deployment-receipt/{receipt_id}") } diff --git a/epiphany-core/src/lib.rs b/epiphany-core/src/lib.rs index 62a43314..fc0b78aa 100644 --- a/epiphany-core/src/lib.rs +++ b/epiphany-core/src/lib.rs @@ -220,6 +220,8 @@ pub use cultmesh_integration::EPIPHANY_CULTMESH_DAEMON_SCHEDULER_RECEIPT_TYPE; pub use cultmesh_integration::EPIPHANY_CULTMESH_DAEMON_SERVICE_LIFECYCLE_RECEIPT_LATEST_KEY; pub use cultmesh_integration::EPIPHANY_CULTMESH_DAEMON_SERVICE_LIFECYCLE_RECEIPT_SCHEMA_VERSION; pub use cultmesh_integration::EPIPHANY_CULTMESH_DAEMON_SERVICE_LIFECYCLE_RECEIPT_TYPE; +pub use cultmesh_integration::EPIPHANY_CULTMESH_MANAGED_SERVICE_POLICY_SCHEMA_VERSION; +pub use cultmesh_integration::EPIPHANY_CULTMESH_MANAGED_SERVICE_POLICY_TYPE; pub use cultmesh_integration::EPIPHANY_CULTMESH_DAEMON_STATUS_SCHEMA_VERSION; pub use cultmesh_integration::EPIPHANY_CULTMESH_DAEMON_STATUS_TYPE; pub use cultmesh_integration::EPIPHANY_CULTMESH_DAEMON_TOOL_CAPABILITY_SCHEMA_VERSION; @@ -326,6 +328,7 @@ pub use cultmesh_integration::EpiphanyCultMeshDaemonPokeReceiptEntry; pub use cultmesh_integration::EpiphanyCultMeshDaemonRestartPolicyEntry; pub use cultmesh_integration::EpiphanyCultMeshDaemonSchedulerReceiptEntry; pub use cultmesh_integration::EpiphanyCultMeshDaemonServiceLifecycleReceiptEntry; +pub use cultmesh_integration::EpiphanyCultMeshManagedServicePolicyEntry; pub use cultmesh_integration::EpiphanyCultMeshDaemonStatusEntry; pub use cultmesh_integration::EpiphanyCultMeshDaemonToolCapabilityEntry; pub use cultmesh_integration::EpiphanyCultMeshDaemonToolInvocationIntentEntry; @@ -412,6 +415,7 @@ pub use cultmesh_integration::load_epiphany_cultmesh_daemon_liveness; pub use cultmesh_integration::load_epiphany_cultmesh_daemon_restart_policy; pub use cultmesh_integration::load_epiphany_cultmesh_daemon_restart_policy_directory; pub use cultmesh_integration::load_epiphany_cultmesh_daemon_service_lifecycle_receipts; +pub use cultmesh_integration::load_epiphany_cultmesh_managed_service_policy; pub use cultmesh_integration::load_epiphany_cultmesh_daemon_tool_directory; pub use cultmesh_integration::load_epiphany_cultmesh_eve_surface_directory; pub use cultmesh_integration::load_epiphany_cultmesh_idunn_aftercare_audit_receipt; @@ -477,6 +481,7 @@ pub use cultmesh_integration::write_epiphany_cultmesh_daemon_poke_receipt; pub use cultmesh_integration::write_epiphany_cultmesh_daemon_restart_policy; pub use cultmesh_integration::write_epiphany_cultmesh_daemon_scheduler_receipt; pub use cultmesh_integration::write_epiphany_cultmesh_daemon_service_lifecycle_receipt; +pub use cultmesh_integration::write_epiphany_cultmesh_managed_service_policy; pub use cultmesh_integration::write_epiphany_cultmesh_daemon_status; pub use cultmesh_integration::write_epiphany_cultmesh_daemon_statuses; pub use cultmesh_integration::write_epiphany_cultmesh_daemon_tool_capabilities; diff --git a/notes/epiphany-current-algorithmic-map.md b/notes/epiphany-current-algorithmic-map.md index 5abb4764..cc5e8a9a 100644 --- a/notes/epiphany-current-algorithmic-map.md +++ b/notes/epiphany-current-algorithmic-map.md @@ -368,6 +368,14 @@ Idunn-managed child service. The durable owner must therefore be a typed managed-service desired-state document keyed by service id. It delegates all starts to the existing service lifecycle primitive and must not invent an `epiphany-daemon-heartbeat` topology identity. +The typed desired-state surface is now implemented as +`epiphany.cultmesh.managed_service_policy.v0`, persisted at a service-id key. +`epiphany-daemon-supervisor managed-service-policy` writes command, args, cwd, +enabled/restart/backoff intent, sealed log refs, and the latest lifecycle +witness; `managed-service-read` projects desired state plus +`latestLifecycle`. It deliberately reports process observation as unknown +until a real reconcile probe exists, so an old launch receipt cannot claim the +service is alive. This map must change when ownership changes. Historical scars belong in git, evidence, or an explicitly archived note—not in the machine's proprioception. diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 3164182b..e14df944 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -2208,6 +2208,15 @@ that family requires a standing topology daemon plus daemon liveness status. Heartbeat is an Idunn-managed child service. Add a typed managed-service desired-state policy keyed by service id, and have reconciliation delegate to the existing `service-launch` lifecycle owner. +The persistence/readback half is landed. New typed document +`epiphany.cultmesh.managed_service_policy.v0` is registered in CultMesh and +keyed by service id. Supervisor commands `managed-service-policy` and +`managed-service-read` round-tripped live service `idunn-epiphany-heartbeat` in +the existing heartbeat fixture, preserving the prior lifecycle receipt and +showing desired command/args/cwd/restart mode/log refs without routine payloads. +Readback calls the historical evidence `latestLifecycle`, not `actual`, and +reports `processObservation=unknown-until-managed-service-reconcile`. Next add +that reconcile probe and delegate restarts to the existing lifecycle primitive. ## Immediate Re-entry Instruction diff --git a/notes/perfect-machine-audit-roadmap.md b/notes/perfect-machine-audit-roadmap.md index 013bcbc9..30e710c2 100644 --- a/notes/perfect-machine-audit-roadmap.md +++ b/notes/perfect-machine-audit-roadmap.md @@ -108,6 +108,12 @@ their liveness status. The heartbeat loop is an Idunn-managed child service. The missing owner is a typed managed-service desired-state policy keyed by service id, plus compact operator readback; do not forge an eighth standing daemon to reuse the daemon policy table. +That desired-state document is now landed as +`epiphany.cultmesh.managed_service_policy.v0`. Idunn can write it from the same +service command/args/cwd and sealed log refs used by lifecycle launch, and can +read it beside the latest lifecycle receipt. Readback explicitly reports +process observation as unknown until reconciliation probes reality; a past +`launched` receipt is not allowed to impersonate a living process. ### Owner @@ -140,6 +146,10 @@ mode, cooldown/backoff, expected heartbeat store, stdout/stderr artifact refs, and latest lifecycle receipt. It emits desired-state readback and delegates every actual start/restart to the existing service lifecycle launch primitive. It does not own heartbeat scheduling state, daemon topology, or routine output. +The persistence/publication half is complete. Next add reconciliation that +probes the policy's last PID or an equivalent platform process witness, applies +enabled/restart-mode/cooldown intent, and delegates any restart to the existing +service lifecycle launch primitive. ### Forbidden writers diff --git a/state/ledgers.msgpack b/state/ledgers.msgpack index f33ba9cb8852667b5232def491c7b145c40ebedc..7c60ff22bee17e7d8bb3cac202a9e3d00fab0898 100644 GIT binary patch delta 374 zcmX|+u}T9$5Qe>|DFhpBOd$-|Cb=Y%hyfo!NF`Pl!rty&vShP+&hF(n6jE6D02+J^ z!9px;e2zSWji8NlASwP`W@q@m+28NR>ZkG2h;|0Urr$QMPK*J+QYII zajJWhv&GqdTh{7nx%c$m>+}Y#&Pl7=yXTth55>}ytP-c9dtK8b>>)ZOl z0(=kwVTvWcCnb2Kxt0i8HKEX90&`)s;t>VX&1OuA1&w_y3m!l#5`!uxWD12i5pwF% z6toR!Lz7{G+hE)n2vgq#!yq5P=oDos4Z?yQ7CpC^Tp2PU1Nc0v*6@y?ARUkeV(s^B zxZtvKu^H1Zl~527GzGuyFo@yvUe9;Qhiwj?QHuGAZ3Rr delta 56 zcmaFxOY--3$qkZ>T!vN#22sZt*s5-9R%R?@Wc=7Xm8pFy6C)6VXl5X00bP( FDgjsj6GH$1 diff --git a/state/map.yaml b/state/map.yaml index 7a196da8..3ce4b299 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -659,4 +659,4 @@ current_status: - Service lifecycle receipt history is now event-only: `load_epiphany_cultmesh_daemon_service_lifecycle_receipts` filters out the `epiphany-local/daemon-service-lifecycle-receipt/latest` mirror, while `load_latest_epiphany_cultmesh_daemon_service_lifecycle_receipt` remains the latest-state reader. Focused unit test `service_lifecycle_receipt_history_excludes_latest_mirror` proves two per-receipt writes return exactly two history records and latest points at the second. Fresh smoke `verse-query-lifecycle-history-filter-smoke` and live overview/triage `local-20260618-145632-497-efac6f0d` / `local-20260618-145632-321-867947af` prove compact swarm sights still expose service lifecycle attention plus the present elevated runbook artifact without daemon pokes or service mutation. Do not use a broad unscoped cargo test filter for this check on Windows; it fans out across bin harnesses and hit a broken pipe during this pass. - `epiphany-verse-query seed-compact` is the compact seed receipt for wrappers and read-only rites. The older `seed` command still prints the full local Verse context for explicit debugging, but compact wrapper modes now avoid full `seed`/final `query` readback after the combined overview proof exposed Windows debug stack overflow from full-context serialization pressure. last_completed_repo_swarm_cut: Huginn live-fire has crossed the first real repo Body from Persona pressure to branch-local Hands commit and Soul/Mind closure, and now has repeatable closure and redacted-public-proof mouths. On `E:\Projects\Huginn` branch `epiphany/huginn/first-awakening`, `tools/epiphany_local_run.ps1 -Mode repo-persona-intake` accepted item `huginn-inspection-readback-livefire`, Weksa/Persona intake remained sealed, and `epiphany-work derive-plan` lowered it to model-authored safe family `repo.status_section` for `README.md` only. `tools/epiphany_local_run.ps1 -Mode repo-swarm-run` advanced four bounded pulses: run-from-plan, adopt-from-plan, execute-from-plan, and close-from-execute. Huginn commit `e9b07c38fceabbdc89ea35c2156326c80c0393a2` updated only `README.md`; closure receipt `.epiphany\work\work-close-huginn-inspection-readback-livefire.json` records status `closed`, Soul verdict `passed`, Mind state commit `repo-work-close-huginn-inspection-readback-livefire-mind-commit`, safe family `repo.status_section`, path scope `README.md`, Bifrost publication gate, and `privateStateExposed=false`. Verification also ran Huginn's TypeScript compiler through concrete Node path: `C:\Program Files\nodejs\node.exe E:\Projects\Huginn\node_modules\typescript\bin\tsc -p tsconfig.json` passed. Native verifier `epiphany-repo-livefire-closure-smoke --workspace E:\Projects\Huginn --item huginn-inspection-readback-livefire --expected-path README.md --expected-family repo.status_section --expected-gate awaiting-publication --expected-blocker bifrost-publication-missing` checks closure receipt, Mind map entry, overview gate/blocker, authority seals, and `git show` changed paths, returning `status=ok`, `soulVerdict=passed`, `publicationGate=Bifrost`, and `privateStateExposed=false`. Wrapper `tools/epiphany_local_run.ps1 -Mode repo-livefire-closure -Workspace E:\Projects\Huginn -RepoWorkItem huginn-inspection-readback-livefire -SkipBuild` exposes the same read-only proof through the local operator mouth; artifact `local-20260621-190156-803-2f85ba15` printed commit, path, gate, blocker, Soul verdict, Mind commit receipt, and `privateStateExposed=False` without push, merge, publication, or target mutation. New wrapper `tools/epiphany_local_run.ps1 -Mode repo-work-public-proof -Workspace E:\Projects\Huginn -RepoWorkItem huginn-inspection-readback-livefire -SkipBuild` exports the redacted proof bundle and repo-local Verse index without publishing; artifact `local-20260621-190645-345-4555b1dc` wrote `.epiphany\public\proof-bundles\repo-work-public-proof-huginn-inspection-readback-livefire.json`, proof id `repo-work-public-proof-huginn-inspection-readback-livefire`, SHA-256 `857545b199f3e37eb06c2e757e5cfc201958e46667bb1f8403fd333a22620f63`, nine artifact rows, zero publication rows, and `privateStateExposed=False`. Gjallar readback `local-20260621-190701-140-b9519ec6` sees the `REPO-WORK-PUBLIC-PROOF` row and action row from Huginn's repo-local Verse with the same SHA-256 while Bifrost publication remains missing. Launcher compact modes are now named compact operator-context modes instead of read-only modes, and `bifrost-public-proof` summaries print item, target public Verse, room, proof SHA-256, review receipts, and credit receipts so publication receipts are auditable without claiming upstream sync. - next_action: Add the missing typed Idunn managed-service desired-state policy keyed by service id; do not forge a topology daemon or reuse standing-daemon restart policy. Persist heartbeat serve command/args/cwd, enabled/restart/backoff intent, heartbeat store and sealed log refs, reconcile by delegating to the existing service-launch lifecycle primitive, and expose compact desired/actual/last-pulse readback before proving unattended restart. + next_action: Add Idunn managed-service reconciliation over the landed service-id desired-state policy. Probe the latest lifecycle PID or equivalent platform witness, keep historical lifecycle separate from current observation, honor enabled/restart-mode/cooldown intent, delegate every restart to the existing service-launch primitive, and prove heartbeat unattended restart from persisted state without duplicate pending turns.