Add Idunn managed service desired state
This commit is contained in:
parent
bf7dd9fc81
commit
1da96bd74f
8 changed files with 267 additions and 1 deletions
|
|
@ -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<String>,
|
||||
service_description: Option<String>,
|
||||
service_start_type: String,
|
||||
restart_mode: String,
|
||||
service_command: Option<PathBuf>,
|
||||
service_args: Vec<String>,
|
||||
runbook_path: Option<PathBuf>,
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
#[cultcache(key = 6)]
|
||||
pub cwd: Option<String>,
|
||||
#[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<String>,
|
||||
}
|
||||
|
||||
#[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<Path>,
|
||||
runtime_id: impl Into<String>,
|
||||
policy: EpiphanyCultMeshManagedServicePolicyEntry,
|
||||
) -> Result<EpiphanyCultMeshManagedServicePolicyEntry> {
|
||||
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<Path>,
|
||||
runtime_id: impl Into<String>,
|
||||
service_id: &str,
|
||||
) -> Result<Option<EpiphanyCultMeshManagedServicePolicyEntry>> {
|
||||
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<Path>,
|
||||
runtime_id: impl Into<String>,
|
||||
|
|
@ -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}")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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.
|
||||
|
|
|
|||
Reference in a new issue