diff --git a/epiphany-core/src/bin/epiphany-daemon-supervisor.rs b/epiphany-core/src/bin/epiphany-daemon-supervisor.rs index 267e86ef..f4ab81ec 100644 --- a/epiphany-core/src/bin/epiphany-daemon-supervisor.rs +++ b/epiphany-core/src/bin/epiphany-daemon-supervisor.rs @@ -93,33 +93,6 @@ enum ManagedServiceLineage { Stale(String), } -enum WorkspaceCoverageRecoveryActuation { - Written(T), - Degraded(WorkspaceCoverageProcessLifecycleObservation), - Terminal(WorkspaceCoverageProcessLifecycleObservation), -} - -fn actuate_workspace_coverage_recovery_if_alive( - observation: WorkspaceCoverageProcessLifecycleObservation, - write: impl FnOnce() -> Result, -) -> Result> { - match observation { - WorkspaceCoverageProcessLifecycleObservation::ExactAlive => { - Ok(WorkspaceCoverageRecoveryActuation::Written(write()?)) - } - WorkspaceCoverageProcessLifecycleObservation::Inaccessible - | WorkspaceCoverageProcessLifecycleObservation::Indeterminate { .. } => { - Ok(WorkspaceCoverageRecoveryActuation::Degraded(observation)) - } - WorkspaceCoverageProcessLifecycleObservation::BootSuperseded { .. } - | WorkspaceCoverageProcessLifecycleObservation::ExactExited { .. } - | WorkspaceCoverageProcessLifecycleObservation::Missing - | WorkspaceCoverageProcessLifecycleObservation::Replaced { .. } => { - Ok(WorkspaceCoverageRecoveryActuation::Terminal(observation)) - } - } -} - fn report_workspace_coverage_replacement_not_alive( policy: &EpiphanyCultMeshManagedServicePolicyEntry, replacement: &WorkspaceCoverageManagedProcessLaunchEntry, @@ -238,16 +211,6 @@ fn lowercase_hex(bytes: &[u8]) -> String { bytes.iter().map(|byte| format!("{byte:02x}")).collect() } -#[cfg(test)] -mod provider_health_identity_output_tests { - use super::lowercase_hex; - - #[test] - fn public_identity_key_is_exact_lowercase_hex() { - assert_eq!(lowercase_hex(&[0x00, 0x7f, 0x80, 0xff]), "007f80ff"); - } -} - fn managed_service_task_name(args: &Args) -> String { args.service_name .clone() @@ -1489,24 +1452,14 @@ fn write_managed_service_policy(args: Args) -> Result<()> { schema_version: EPIPHANY_CULTMESH_MANAGED_SERVICE_POLICY_SCHEMA_VERSION.to_string(), policy_id: 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: args.service_args.clone(), cwd: args.cwd.as_ref().map(|path| path.display().to_string()), enabled: !args.disabled, - restart_mode: "always".to_string(), cooldown_seconds: args.cooldown_seconds, - backoff_multiplier: 1, stdout_artifact: stdout_artifact.display().to_string(), stderr_artifact: stderr_artifact.display().to_string(), - 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 = if policy.service_id == SEMANTIC_PROJECTOR_SERVICE_ID { write_epiphany_cultmesh_semantic_projector_service_policy( @@ -1532,9 +1485,7 @@ fn write_managed_service_policy(args: Args) -> Result<()> { "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, @@ -1715,9 +1666,10 @@ fn managed_service_reconcile(mut args: Args) -> Result<()> { .transpose()? .unwrap_or(ProcessObservation::Missing); if observation == ProcessObservation::Alive { - let lineage = managed_service_lineage(&args, &pinned_release, &policy).with_context(|| { - format!("failed to authenticate {} child lineage", policy.service_id) - })?; + let lineage = + managed_service_lineage(&args, &pinned_release, &policy).with_context(|| { + format!("failed to authenticate {} child lineage", policy.service_id) + })?; if let Some(identity) = replacement_process_identity( &lineage, latest @@ -2508,20 +2460,20 @@ fn finish_workspace_coverage_recovery( return Ok(()); } }; - let actuation = actuate_workspace_coverage_recovery_if_alive(actuation_observation, || { - write_workspace_coverage_recovery_directive( - &args.store, - runtime_store, - &args.runtime_id, - &target, - &replacement.launch_id, - &ready.heartbeat_id, - host, - ) - })?; - let directive = match actuation { - WorkspaceCoverageRecoveryActuation::Written(directive) => directive, - WorkspaceCoverageRecoveryActuation::Degraded(observation) => { + let directive = match actuation_observation { + WorkspaceCoverageProcessLifecycleObservation::ExactAlive => { + write_workspace_coverage_recovery_directive( + &args.store, + runtime_store, + &args.runtime_id, + &target, + &replacement.launch_id, + &ready.heartbeat_id, + host, + )? + } + observation @ (WorkspaceCoverageProcessLifecycleObservation::Inaccessible + | WorkspaceCoverageProcessLifecycleObservation::Indeterminate { .. }) => { return report_workspace_coverage_replacement_not_alive( policy, &replacement, @@ -2529,7 +2481,10 @@ fn finish_workspace_coverage_recovery( "observation-degraded", ); } - WorkspaceCoverageRecoveryActuation::Terminal(observation) => { + observation @ (WorkspaceCoverageProcessLifecycleObservation::BootSuperseded { .. } + | WorkspaceCoverageProcessLifecycleObservation::ExactExited { .. } + | WorkspaceCoverageProcessLifecycleObservation::Missing + | WorkspaceCoverageProcessLifecycleObservation::Replaced { .. }) => { return report_workspace_coverage_replacement_not_alive( policy, &replacement, @@ -2716,41 +2671,9 @@ mod service_lifecycle_brake_authority_tests { } #[cfg(test)] -mod workspace_coverage_recovery_tests { +mod supervisor_invariant_tests { use super::*; - #[test] - fn workspace_coverage_recovery_writes_only_for_exact_alive_process() -> Result<()> { - let mut directive_writes = 0_u8; - let outcome = actuate_workspace_coverage_recovery_if_alive( - WorkspaceCoverageProcessLifecycleObservation::Missing, - || { - directive_writes += 1; - Ok(()) - }, - )?; - assert!(matches!( - outcome, - WorkspaceCoverageRecoveryActuation::Terminal( - WorkspaceCoverageProcessLifecycleObservation::Missing - ) - )); - assert_eq!(directive_writes, 0); - let outcome = actuate_workspace_coverage_recovery_if_alive( - WorkspaceCoverageProcessLifecycleObservation::ExactAlive, - || { - directive_writes += 1; - Ok("directive") - }, - )?; - assert!(matches!( - outcome, - WorkspaceCoverageRecoveryActuation::Written("directive") - )); - assert_eq!(directive_writes, 1); - Ok(()) - } - #[test] fn windows_argv_quoting_preserves_paths_and_escapes_parser_boundaries() { assert_eq!( diff --git a/epiphany-core/src/cultmesh_integration.rs b/epiphany-core/src/cultmesh_integration.rs index d146d9fa..28ceac15 100644 --- a/epiphany-core/src/cultmesh_integration.rs +++ b/epiphany-core/src/cultmesh_integration.rs @@ -57,7 +57,7 @@ pub const EPIPHANY_CULTMESH_DAEMON_SERVICE_LIFECYCLE_RECEIPT_LATEST_KEY: &str = 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"; + "epiphany.cultmesh.managed_service_policy.v1"; const EPIPHANY_SEMANTIC_PROJECTOR_SERVICE_ID: &str = "epiphany-memory-semantic-projector-service"; pub const EPIPHANY_WORKSPACE_COVERAGE_PROJECTOR_SERVICE_ID: &str = "epiphany-workspace-coverage-projector-service"; @@ -313,8 +313,6 @@ pub struct EpiphanyCultMeshManagedServicePolicyEntry { 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)] @@ -323,22 +321,14 @@ pub struct EpiphanyCultMeshManagedServicePolicyEntry { 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 = 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)] @@ -899,6 +889,7 @@ pub fn load_epiphany_cultmesh_managed_service_policy_with_digest( let Some(policy) = node.get::(&key)? else { return Ok(None); }; + validate_managed_service_policy(&policy)?; let digest = cultmesh_envelope_digest::(&node, &key)?; Ok(Some((policy, digest))) @@ -981,7 +972,6 @@ pub fn write_epiphany_cultmesh_semantic_projector_service_policy( "semantic projector policy writer requires its reserved service id" )); } - validate_semantic_projector_managed_service_policy(&policy)?; write_validated_managed_service_policy(store_path, runtime_id, policy) } @@ -996,7 +986,6 @@ pub fn write_epiphany_cultmesh_workspace_coverage_projector_service_policy( "workspace coverage projector policy writer requires its reserved service id" )); } - validate_workspace_coverage_projector_managed_service_policy(&policy)?; write_validated_managed_service_policy(store_path, runtime_id, policy) } @@ -1018,7 +1007,13 @@ pub fn load_epiphany_cultmesh_managed_service_policy( service_id: &str, ) -> Result> { let node = open_epiphany_cultmesh_node(store_path, runtime_id)?; - node.get(&epiphany_cultmesh_managed_service_policy_key(service_id)) + let policy = node.get(&epiphany_cultmesh_managed_service_policy_key(service_id))?; + policy + .map(|policy| { + validate_managed_service_policy(&policy)?; + Ok(policy) + }) + .transpose() } pub fn load_epiphany_cultmesh_managed_service_policies( @@ -1026,11 +1021,14 @@ pub fn load_epiphany_cultmesh_managed_service_policies( runtime_id: impl Into, ) -> Result> { let node = open_epiphany_cultmesh_node(store_path, runtime_id)?; - Ok(node + node .get_all_with_keys::()? .into_iter() - .map(|(_, policy)| policy) - .collect()) + .map(|(_, policy)| { + validate_managed_service_policy(&policy)?; + Ok(policy) + }) + .collect() } pub fn default_epiphany_cultmesh_swarm_brake( @@ -1217,6 +1215,9 @@ fn validate_swarm_brake(brake: &EpiphanyCultMeshSwarmBrakeEntry) -> Result<()> { fn validate_managed_service_policy( policy: &EpiphanyCultMeshManagedServicePolicyEntry, ) -> Result<()> { + if policy.schema_version != EPIPHANY_CULTMESH_MANAGED_SERVICE_POLICY_SCHEMA_VERSION { + return Err(anyhow!("historical managed service policy schema is not writable")); + } if policy.private_state_exposed { return Err(anyhow!( "managed service policies must not expose private state" @@ -1225,31 +1226,28 @@ fn validate_managed_service_policy( 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" - ) { + if policy.cooldown_seconds < 0 { return Err(anyhow!( - "managed service policy restart_mode must be always, on-failure, or never" + "managed service policy requires a non-negative cooldown" )); } - if policy.cooldown_seconds < 0 || policy.backoff_multiplier == 0 { - return Err(anyhow!( - "managed service policy requires non-negative cooldown and positive backoff" - )); + match policy.service_id.as_str() { + EPIPHANY_SEMANTIC_PROJECTOR_SERVICE_ID => { + validate_semantic_projector_managed_service_policy(policy) + } + EPIPHANY_WORKSPACE_COVERAGE_PROJECTOR_SERVICE_ID => { + validate_workspace_coverage_projector_managed_service_policy(policy) + } + _ => Err(anyhow!("unregistered managed service policy is not writable")), } - Ok(()) } fn validate_semantic_projector_managed_service_policy( @@ -1270,8 +1268,6 @@ fn validate_semantic_projector_managed_service_policy( )); } if policy.policy_id != "managed-service-policy-epiphany-memory-semantic-projector-service" - || policy.owner_daemon_id != "epiphany-daemon-supervisor" - || policy.restart_mode != "always" || policy.args.len() != 15 || policy.args[0] != "serve" || policy.args[1] != "--runtime-store" @@ -1319,9 +1315,7 @@ pub(crate) fn validate_workspace_coverage_projector_managed_service_policy( } if policy.policy_id != "managed-service-policy-epiphany-workspace-coverage-projector-service" || policy.service_id != EPIPHANY_WORKSPACE_COVERAGE_PROJECTOR_SERVICE_ID - || policy.owner_daemon_id != "epiphany-daemon-supervisor" || !policy.enabled - || policy.restart_mode != "always" || policy.args.len() != 17 || policy.args[0] != "serve" || policy.args[1] != "--runtime-store" @@ -2402,7 +2396,6 @@ mod tests { schema_version: EPIPHANY_CULTMESH_MANAGED_SERVICE_POLICY_SCHEMA_VERSION.to_string(), policy_id: "managed-service-policy-epiphany-memory-semantic-projector-service".into(), service_id: EPIPHANY_SEMANTIC_PROJECTOR_SERVICE_ID.into(), - owner_daemon_id: "epiphany-daemon-supervisor".into(), command: binary.into(), args: vec![ "serve", @@ -2426,15 +2419,30 @@ mod tests { .collect(), cwd: None, enabled: true, - restart_mode: "always".into(), cooldown_seconds: 0, - backoff_multiplier: 1, stdout_artifact: "projector.stdout.log".into(), stderr_artifact: "projector.stderr.log".into(), - updated_at_utc: "2026-07-15T12:00:00Z".into(), private_state_exposed: false, - notes: vec![], }; + let historical_store = temp.path().join("historical-verse.ccmp"); + let mut historical = exact.clone(); + historical.schema_version = "epiphany.cultmesh.managed_service_policy.v0".into(); + let mut node = open_epiphany_cultmesh_node(&historical_store, "local")?; + node.put( + epiphany_cultmesh_managed_service_policy_key(&historical.service_id), + &historical, + )?; + node.flush()?; + assert!( + load_epiphany_cultmesh_managed_service_policy( + &historical_store, + "local", + EPIPHANY_SEMANTIC_PROJECTOR_SERVICE_ID, + ) + .expect_err("historical writable policy must refuse at the load boundary") + .to_string() + .contains("historical managed service policy schema") + ); let mut forged = exact.clone(); forged.command = "arbitrary.exe".into(); assert!( @@ -2534,7 +2542,6 @@ mod tests { policy_id: "managed-service-policy-epiphany-workspace-coverage-projector-service" .into(), service_id: EPIPHANY_WORKSPACE_COVERAGE_PROJECTOR_SERVICE_ID.into(), - owner_daemon_id: "epiphany-daemon-supervisor".into(), command: binary.display().to_string(), args: vec![ "serve", @@ -2560,14 +2567,10 @@ mod tests { .collect(), cwd: None, enabled: true, - restart_mode: "always".into(), cooldown_seconds: 0, - backoff_multiplier: 1, stdout_artifact: "workspace-projector.stdout.log".into(), stderr_artifact: "workspace-projector.stderr.log".into(), - updated_at_utc: "2026-07-15T12:00:00Z".into(), private_state_exposed: false, - notes: vec![], }; let mut arbitrary_binary = exact.clone(); arbitrary_binary.command = "arbitrary-projector.exe".into(); @@ -2661,7 +2664,7 @@ mod tests { ); let mut advanced = exact; - advanced.updated_at_utc = "2026-07-15T12:00:02Z".into(); + advanced.args[8] = "61".into(); write_epiphany_cultmesh_workspace_coverage_projector_service_policy( &store, "local", advanced, )?; @@ -3745,7 +3748,6 @@ mod tests { schema_version: EPIPHANY_CULTMESH_MANAGED_SERVICE_POLICY_SCHEMA_VERSION.to_string(), policy_id: "managed-service-policy-epiphany-memory-semantic-projector-service".into(), service_id: EPIPHANY_SEMANTIC_PROJECTOR_SERVICE_ID.into(), - owner_daemon_id: "epiphany-daemon-supervisor".into(), command: binary.into(), args: vec![ "serve", @@ -3769,14 +3771,10 @@ mod tests { .collect(), cwd: None, enabled: true, - restart_mode: "always".into(), cooldown_seconds: 0, - backoff_multiplier: 1, stdout_artifact: "projector.stdout.log".into(), stderr_artifact: "projector.stderr.log".into(), - updated_at_utc: "2026-07-15T12:01:00Z".into(), private_state_exposed: false, - notes: vec![], }; write_epiphany_cultmesh_semantic_projector_service_policy( &verse, @@ -3857,7 +3855,7 @@ mod tests { ); let mut advanced_policy = policy.clone(); - advanced_policy.updated_at_utc = "2026-07-15T12:02:30Z".into(); + advanced_policy.args[8] = "61".into(); write_epiphany_cultmesh_semantic_projector_service_policy( &verse, "runtime-test", diff --git a/epiphany-core/src/workspace_coverage_process_documents.rs b/epiphany-core/src/workspace_coverage_process_documents.rs index ea6271ff..d2608cad 100644 --- a/epiphany-core/src/workspace_coverage_process_documents.rs +++ b/epiphany-core/src/workspace_coverage_process_documents.rs @@ -3494,7 +3494,6 @@ mod tests { policy_id: "managed-service-policy-epiphany-workspace-coverage-projector-service" .to_string(), service_id: EPIPHANY_WORKSPACE_COVERAGE_PROJECTOR_SERVICE_ID.to_string(), - owner_daemon_id: "epiphany-daemon-supervisor".to_string(), command, args: vec![ "serve", @@ -3520,14 +3519,10 @@ mod tests { .collect(), cwd: None, enabled: true, - restart_mode: "always".to_string(), cooldown_seconds: 5, - backoff_multiplier: 2, stdout_artifact: "stdout.log".to_string(), stderr_artifact: "stderr.log".to_string(), - updated_at_utc: chrono::Utc::now().to_rfc3339(), private_state_exposed: false, - notes: Vec::new(), }) } @@ -3925,7 +3920,7 @@ mod tests { chrono::Utc::now(), )?; let mut rotated_policy = policy.clone(); - rotated_policy.updated_at_utc = "2026-07-17T00:00:01Z".into(); + rotated_policy.args[8] = "31".into(); write_epiphany_cultmesh_workspace_coverage_projector_service_policy( &verse, "local", @@ -4115,7 +4110,7 @@ mod tests { .contains("already has terminal evidence") ); let mut advanced_policy = policy()?; - advanced_policy.updated_at_utc = "2026-07-16T23:59:59Z".to_string(); + advanced_policy.args[8] = "31".into(); write_epiphany_cultmesh_workspace_coverage_projector_service_policy( &store, "local", @@ -4250,7 +4245,7 @@ mod tests { let temp = tempfile::tempdir()?; let (store, host, launch, _provider) = persisted_chain(temp.path())?; let mut moved_policy = policy()?; - moved_policy.updated_at_utc = "2026-07-17T00:00:01Z".into(); + moved_policy.args[8] = "31".into(); write_epiphany_cultmesh_workspace_coverage_projector_service_policy( &store, "local", @@ -4283,7 +4278,7 @@ mod tests { let temp = tempfile::tempdir()?; let (store, host, launch, _provider) = persisted_chain(temp.path())?; let mut moved_policy = policy()?; - moved_policy.updated_at_utc = "2026-07-17T00:00:01Z".into(); + moved_policy.args[8] = "31".into(); write_epiphany_cultmesh_workspace_coverage_projector_service_policy( &store, "local", @@ -4446,7 +4441,7 @@ mod tests { ); let mut rotated_policy = policy()?; - rotated_policy.updated_at_utc = "2026-07-17T00:00:01Z".into(); + rotated_policy.args[8] = "31".into(); write_epiphany_cultmesh_workspace_coverage_projector_service_policy( &verse, "local",