Recover exact receipt-free coordinator deaths

This commit is contained in:
Meta Vens 2026-08-10 09:51:10 +01:00
commit e97ee1fa1b
9 changed files with 1250 additions and 92 deletions

View file

@ -301,7 +301,21 @@ fn run_coordinator(args: &Args) -> Result<Value> {
"ephemeral": args.ephemeral,
"workspace": cwd,
}));
let runtime_session_id = format!("coordinator-{thread_id}");
let resident_launch_digest = args
.resident_binding
.get("launch-digest")
.map(String::as_str);
let runtime_session_id = epiphany_core::coordinator_run_session_id(
&thread_id,
resident_launch_digest,
)?;
let runtime_session_objective = args
.resident_binding
.get("objective-digest")
.map(|digest| format!("Resident coordinator launch {digest}."))
.unwrap_or_else(|| {
"Coordinate the Epiphany MVP lanes with native runtime job receipts.".to_string()
});
let runtime_identity = initialize_runtime_spine(
&runtime_store,
coordinator_runtime_identity_options(
@ -313,7 +327,8 @@ fn run_coordinator(args: &Args) -> Result<Value> {
&runtime_store,
&runtime_session_id,
&thread_id,
"Coordinate the Epiphany MVP lanes with native runtime job receipts.",
resident_launch_digest,
&runtime_session_objective,
&now(),
)?;
startup_events.push(json!({

View file

@ -7,16 +7,21 @@ use epiphany_core::{
ResidentSelfState, acknowledge_resident_self_launch, acquire_resident_process_singleton,
authenticate_resident_self_policy, bind_runtime_repository_domain,
bridge_admitted_persona_feedback_to_heartbeat, cancel_resident_self_turn,
capture_process_instance, coordinator_run_receipts, derive_resident_cognition_readiness,
capture_process_instance, complete_resident_self_turn_after_death,
coordinator_run_receipts, derive_resident_cognition_readiness,
enqueue_resident_self_pressure, import_bifrost_persona_feedback_deliveries,
ingest_resident_self_domain_pressure, load_epiphany_cultmesh_swarm_brake,
load_resident_self_state, observe_process_instance, pending_resident_self_acks,
prepare_resident_self_launch, publish_resident_provider_readiness,
recover_receipt_free_dead_coordinator_session,
resident_prepared_launch_thread_id, resident_self_child_claim,
resident_self_local_provider_status, retain_completed_runtime_sessions,
resident_self_grant_has_typed_request, resident_self_local_provider_status,
resident_self_typed_attempt_exists,
retain_completed_runtime_sessions,
retain_coordinator_run_receipts, retain_resident_self_lifecycles,
settle_resident_self_exited_coordinator, terminate_process_instance,
validate_persona_feedback_store_separation, validate_resident_self_store_separation,
validate_persona_feedback_store_separation, validate_resident_self_coordinator_receipt,
validate_resident_self_store_separation, verify_resident_self_grant_fulfillment,
};
use serde_json::json;
use std::collections::{BTreeMap, BTreeSet};
@ -243,6 +248,100 @@ fn outcome_after_cancel(shutdown_requested: bool) -> ResidentSelfOutcome {
}
}
fn exact_resident_coordinator_receipt(
runtime_store: &std::path::Path,
lease: &epiphany_core::ResidentSelfTurnLease,
) -> Result<Option<epiphany_core::EpiphanyCoordinatorRunReceipt>> {
let session_id = epiphany_core::coordinator_run_session_id(
&lease.turn_id,
Some(&lease.launch_digest),
)?;
let mut receipts = coordinator_run_receipts(runtime_store)?
.into_iter()
.filter(|receipt| receipt.session_id == session_id)
.collect::<Vec<_>>();
if receipts.len() > 1 {
return Err(anyhow!(
"resident coordinator session has multiple terminal receipts"
));
}
let Some(receipt) = receipts.pop() else {
return Ok(None);
};
validate_resident_self_coordinator_receipt(lease, &receipt)?;
Ok(Some(receipt))
}
fn settle_receipt_free_dead_coordinator(
args: &Args,
lease: &epiphany_core::ResidentSelfTurnLease,
observation: ChildObservation,
shutdown_requested: bool,
brake_engaged: bool,
timed_out: bool,
now: u64,
) -> Result<ResidentSelfOutcome> {
let typed = resident_self_grant_has_typed_request(&args.state_store, &lease.grant_id)?;
if typed {
match verify_resident_self_grant_fulfillment(
&args.state_store,
&args.policy.runtime_store,
&lease.grant_id,
)? {
epiphany_core::ResidentSelfGrantFulfillment::Fulfilled => {
let recovery = recover_receipt_free_dead_coordinator_session(
&args.state_store,
&args.policy.runtime_store,
lease,
observation,
now,
)?
.ok_or_else(|| {
anyhow!(
"typed fulfillment cannot predate its coordinator runtime incarnation"
)
})?;
complete_resident_self_turn_after_death(
&args.state_store,
lease,
&recovery,
now,
args.policy.cooldown_seconds,
)?;
return Ok(ResidentSelfOutcome::Completed);
}
epiphany_core::ResidentSelfGrantFulfillment::Pending => {
if resident_self_typed_attempt_exists(
&args.state_store,
&args.policy.runtime_store,
&lease.grant_id,
)? {
return Ok(ResidentSelfOutcome::AwaitingFulfillment);
}
}
}
}
let recovery = recover_receipt_free_dead_coordinator_session(
&args.state_store,
&args.policy.runtime_store,
lease,
observation,
now,
)?;
cancel_resident_self_turn(
&args.state_store,
lease,
cancelled_turn_status(shutdown_requested, brake_engaged, timed_out),
if recovery.is_some() {
"exact coordinator process died after atomic opening and before a terminal receipt"
} else {
"exact coordinator process died before atomic runtime opening"
},
now,
)?;
Ok(outcome_after_cancel(shutdown_requested))
}
fn cycle(
args: &Args,
state: &mut ResidentSelfState,
@ -344,55 +443,52 @@ fn cycle(
}
return match ports.observe_child(&lease)? {
ChildObservation::Running => Ok(ResidentSelfOutcome::Running),
ChildObservation::Exited(0) => {
let receipt = coordinator_run_receipts(&args.policy.runtime_store)?
.into_iter()
.find(|receipt| {
receipt.thread_id == lease.turn_id
&& receipt.resident_launch_digest.as_deref()
== Some(&lease.launch_digest)
})
.ok_or_else(|| {
anyhow!(
"coordinator exited zero without its exact resident terminal receipt"
)
})?;
let outcome = settle_resident_self_exited_coordinator(
&args.state_store,
&args.policy.runtime_store,
&lease,
&receipt,
shutdown_requested,
brake_engaged,
timed_out,
now,
args.policy.cooldown_seconds,
)?;
observation => {
let receipt =
exact_resident_coordinator_receipt(&args.policy.runtime_store, &lease)?;
if let Some(receipt) = receipt.as_ref().filter(|receipt| {
matches!(
receipt.status.as_str(),
"planned" | "needsReview" | "completed"
)
}) {
let outcome = settle_resident_self_exited_coordinator(
&args.state_store,
&args.policy.runtime_store,
&lease,
receipt,
shutdown_requested,
brake_engaged,
timed_out,
now,
args.policy.cooldown_seconds,
)?;
*state = load_resident_self_state(&args.state_store)?;
return Ok(outcome);
}
let outcome = if receipt.is_some() {
cancel_resident_self_turn(
&args.state_store,
&lease,
cancelled_turn_status(shutdown_requested, brake_engaged, timed_out),
"exact coordinator terminal receipt reports failure",
now,
)?;
outcome_after_cancel(shutdown_requested)
} else {
settle_receipt_free_dead_coordinator(
args,
&lease,
observation,
shutdown_requested,
brake_engaged,
timed_out,
now,
)?
};
*state = load_resident_self_state(&args.state_store)?;
Ok(outcome)
}
ChildObservation::Exited(code) => {
cancel_resident_self_turn(
&args.state_store,
&lease,
cancelled_turn_status(shutdown_requested, brake_engaged, timed_out),
&format!("coordinator terminal observation exit={code}"),
now,
)?;
*state = load_resident_self_state(&args.state_store)?;
Ok(outcome_after_cancel(shutdown_requested))
}
ChildObservation::Missing => {
cancel_resident_self_turn(
&args.state_store,
&lease,
cancelled_turn_status(shutdown_requested, brake_engaged, timed_out),
"exact coordinator process is missing",
now,
)?;
*state = load_resident_self_state(&args.state_store)?;
Ok(outcome_after_cancel(shutdown_requested))
}
};
}
if shutdown_requested || ports.brake_engaged()? {

View file

@ -981,8 +981,10 @@ pub use runtime_spine::ARCHIVED_RUNTIME_SESSION_SCHEMA_VERSION;
pub use runtime_spine::ARCHIVED_RUNTIME_SESSION_TYPE;
pub use runtime_spine::COORDINATOR_RUN_RECEIPT_SCHEMA_VERSION;
pub use runtime_spine::COORDINATOR_RUN_RECEIPT_TYPE;
pub use runtime_spine::COORDINATOR_DEATH_RECOVERY_SCHEMA_VERSION;
pub use runtime_spine::EpiphanyArchivedRuntimeSession;
pub use runtime_spine::EpiphanyCoordinatorRunReceipt;
pub use runtime_spine::EpiphanyCoordinatorDeathRecovery;
pub use runtime_spine::EpiphanyCoordinatorRunReceiptRetentionHead;
pub use runtime_spine::EpiphanyJobInterruptRequest;
pub use runtime_spine::EpiphanyJobInterruptResult;
@ -1056,6 +1058,7 @@ pub use runtime_spine::commit_repo_model_claim_challenge;
pub use runtime_spine::commit_repo_model_claim_repair_request;
pub use runtime_spine::complete_runtime_job;
pub use runtime_spine::coordinator_run_receipts;
pub use runtime_spine::coordinator_run_session_id;
pub use runtime_spine::create_runtime_job;
pub use runtime_spine::create_runtime_session;
pub use runtime_spine::ensure_runtime_repo_model;
@ -1142,6 +1145,7 @@ pub use runtime_spine::runtime_substrate_gate_repo_access_grant_receipt;
pub use runtime_spine::runtime_swarm_binding;
pub use runtime_spine::runtime_tool_invocation_statuses;
pub use runtime_spine::runtime_typed_request_fulfillment;
pub use runtime_spine::runtime_typed_request_attempt_exists;
pub use runtime_spine::runtime_worker_launch_body_basis;
pub use runtime_spine::runtime_worker_launch_request;
pub use runtime_spine::select_and_commit_repo_frontier_planning_request;

View file

@ -553,9 +553,12 @@ pub fn coordinator_argv(
policy: &ResidentSelfPolicy,
runtime_id: &str,
turn_id: &str,
artifact_incarnation: Option<&str>,
wake: &ResidentSelfWake,
) -> Vec<String> {
let artifact_dir = policy.artifact_root.join(turn_id);
let artifact_dir = policy
.artifact_root
.join(artifact_incarnation.unwrap_or(turn_id));
let mut argv = vec![
"--model-runtime-bin".into(),
policy.model_runtime_bin.display().to_string(),
@ -870,7 +873,13 @@ fn reconcile_resident_self(
let launch = CoordinatorLaunch {
turn_id: turn_id.clone(),
wake: wake.clone(),
argv: coordinator_argv(policy, &policy.release_runtime_id, &turn_id, &wake),
argv: coordinator_argv(
policy,
&policy.release_runtime_id,
&turn_id,
None,
&wake,
),
};
let process = ports.launch_coordinator(&launch)?;
let process_id = process.process_id;
@ -1207,7 +1216,23 @@ pub fn verify_resident_self_grant_fulfillment(
let grant = state_cache(resident_store)?
.get::<ResidentSelfHeartbeatGrant>(grant_id)?
.ok_or_else(|| anyhow!("resident Self fulfillment check lost its grant"))?;
let request = match grant.pressure_kind.as_str() {
let request = resident_self_typed_request_ref(&grant)?;
let Some(request) = request else {
return Ok(ResidentSelfGrantFulfillment::Fulfilled);
};
Ok(
if crate::runtime_typed_request_fulfillment(runtime_store, request)?.is_some() {
ResidentSelfGrantFulfillment::Fulfilled
} else {
ResidentSelfGrantFulfillment::Pending
},
)
}
fn resident_self_typed_request_ref<'a>(
grant: &'a ResidentSelfHeartbeatGrant,
) -> Result<Option<crate::RuntimeTypedRequestRef<'a>>> {
Ok(match grant.pressure_kind.as_str() {
"repo-frontier-proposal-modeling" => {
let request_id = grant
.provenance_ref
@ -1239,17 +1264,31 @@ pub fn verify_resident_self_grant_fulfillment(
))
}
_ => None,
})
}
pub fn resident_self_typed_attempt_exists(
resident_store: &Path,
runtime_store: &Path,
grant_id: &str,
) -> Result<bool> {
let grant = state_cache(resident_store)?
.get::<ResidentSelfHeartbeatGrant>(grant_id)?
.ok_or_else(|| anyhow!("resident Self attempt check lost its grant"))?;
let Some(request) = resident_self_typed_request_ref(&grant)? else {
return Ok(false);
};
let Some(request) = request else {
return Ok(ResidentSelfGrantFulfillment::Fulfilled);
};
Ok(
if crate::runtime_typed_request_fulfillment(runtime_store, request)?.is_some() {
ResidentSelfGrantFulfillment::Fulfilled
} else {
ResidentSelfGrantFulfillment::Pending
},
)
crate::runtime_typed_request_attempt_exists(runtime_store, request)
}
pub fn resident_self_grant_has_typed_request(
resident_store: &Path,
grant_id: &str,
) -> Result<bool> {
let grant = state_cache(resident_store)?
.get::<ResidentSelfHeartbeatGrant>(grant_id)?
.ok_or_else(|| anyhow!("resident Self typed-request check lost its grant"))?;
Ok(resident_self_typed_request_ref(&grant)?.is_some())
}
pub fn settle_resident_self_exited_coordinator(
@ -1315,6 +1354,118 @@ pub fn settle_resident_self_exited_coordinator(
}
}
pub fn recover_receipt_free_dead_coordinator_session(
resident_store: &Path,
runtime_store: &Path,
lease: &ResidentSelfTurnLease,
observation: ChildObservation,
now_millis: u64,
) -> Result<Option<crate::EpiphanyCoordinatorDeathRecovery>> {
let (observation, exit_code) = match observation {
ChildObservation::Exited(code) => ("exited", Some(code)),
ChildObservation::Missing => ("missing", None),
ChildObservation::Running => {
return Err(anyhow!(
"Continuity refuses coordinator death recovery while the exact child is running"
));
}
};
let cache = state_cache(resident_store)?;
let state = cache
.get::<ResidentSelfState>(RESIDENT_SELF_STATE_KEY)?
.ok_or_else(|| anyhow!("coordinator death recovery lost resident state"))?;
if state.active_turn.as_ref() != Some(lease) {
return Err(anyhow!(
"coordinator death recovery lease is not the active resident authority"
));
}
let grant = cache
.get::<ResidentSelfHeartbeatGrant>(&lease.grant_id)?
.ok_or_else(|| anyhow!("coordinator death recovery lost its grant"))?;
if grant.consumed_at_millis.is_none()
|| grant.terminal_at_millis.is_some()
|| grant.terminal_status.is_some()
{
return Err(anyhow!(
"coordinator death recovery grant is not active and unterminated"
));
}
let preparation_id = format!("resident-self-prepared-{}", lease.grant_id);
let claim = resident_self_child_claim(resident_store, &preparation_id)?
.ok_or_else(|| anyhow!("coordinator death recovery lost its immutable child claim"))?;
if claim.grant_id != lease.grant_id
|| claim.launch_digest != lease.launch_digest
|| claim.process_id != lease.process_id
|| claim.process_creation_token != lease.process_creation_token
|| claim.executable_path != lease.process_executable_path
|| claim.executable_digest != lease.coordinator_executable_digest
{
return Err(anyhow!(
"coordinator death recovery child claim disagrees with the exact lease"
));
}
let expected_process = crate::ProcessInstanceIdentity {
process_id: lease.process_id,
creation_token: lease.process_creation_token,
created_at_rfc3339: None,
executable_path: lease.process_executable_path.clone(),
};
match crate::observe_process_instance(&expected_process) {
crate::ProcessInstanceObservation::ExactAlive => {
return Err(anyhow!(
"Continuity refuses coordinator death recovery while the exact process incarnation is alive"
));
}
crate::ProcessInstanceObservation::Inaccessible
| crate::ProcessInstanceObservation::Indeterminate { .. } => {
return Err(anyhow!(
"Continuity cannot prove the exact coordinator process incarnation dead"
));
}
crate::ProcessInstanceObservation::ExactExited { .. }
| crate::ProcessInstanceObservation::Missing
| crate::ProcessInstanceObservation::Replaced { .. } => {}
}
let recovered_at = chrono::DateTime::<chrono::Utc>::from_timestamp_millis(now_millis as i64)
.ok_or_else(|| anyhow!("coordinator death recovery timestamp is out of range"))?
.to_rfc3339_opts(chrono::SecondsFormat::Millis, true);
let session_id = crate::coordinator_run_session_id(
&lease.turn_id,
Some(&lease.launch_digest),
)?;
if crate::runtime_spine::coordinator_run_incarnation_is_absent(
runtime_store,
&lease.turn_id,
&lease.launch_digest,
)? {
return Ok(None);
}
let recovery = crate::EpiphanyCoordinatorDeathRecovery {
schema_version: crate::runtime_spine::COORDINATOR_DEATH_RECOVERY_SCHEMA_VERSION.into(),
recovery_id: format!("coordinator-death-recovery-{session_id}"),
session_id,
thread_id: lease.turn_id.clone(),
resident_grant_id: lease.grant_id.clone(),
resident_launch_digest: lease.launch_digest.clone(),
process_id: lease.process_id,
process_creation_token: lease.process_creation_token,
process_executable_path: lease.process_executable_path.display().to_string(),
resident_started_at_millis: lease.started_at_millis,
observation: observation.into(),
recovered_at,
private_state_exposed: false,
exit_code,
};
let runtime_session_objective =
format!("Resident coordinator launch {}.", lease.objective_digest);
crate::runtime_spine::recover_coordinator_run_after_exact_process_death(
runtime_store,
&recovery,
&runtime_session_objective,
)?;
Ok(Some(recovery))
}
pub fn pending_resident_self_grant(path: &Path) -> Result<Option<ResidentSelfHeartbeatGrant>> {
let cache = state_cache(path)?;
let legacy_terminal_grant_ids = cache
@ -1390,7 +1541,13 @@ pub fn prepare_resident_self_launch(
let wake = ResidentSelfWake::Explicit {
objective: grant.objective.clone(),
};
let mut argv = coordinator_argv(policy, &binding.runtime_id, &binding.thread_id, &wake);
let mut argv = coordinator_argv(
policy,
&binding.runtime_id,
&binding.thread_id,
Some(&grant.grant_id),
&wake,
);
if let Some((request_flag, request_id)) = binding.typed_request {
let objective = argv.pop();
let flag = argv.pop();
@ -1623,7 +1780,24 @@ fn complete_resident_self_turn(
now_millis: u64,
cooldown_seconds: u64,
) -> Result<ResidentSelfTerminalAck> {
validate_resident_self_coordinator_receipt(lease, coordinator)?;
complete_resident_self_turn_with_terminal(
path,
lease,
&coordinator.receipt_id,
&coordinator.status,
now_millis,
cooldown_seconds,
)
}
pub fn validate_resident_self_coordinator_receipt(
lease: &ResidentSelfTurnLease,
coordinator: &crate::EpiphanyCoordinatorRunReceipt,
) -> Result<()> {
if coordinator.thread_id != lease.turn_id
|| coordinator.session_id
!= crate::coordinator_run_session_id(&lease.turn_id, Some(&lease.launch_digest))?
|| !matches!(
coordinator.status.as_str(),
"planned" | "needsReview" | "completed"
@ -1643,6 +1817,48 @@ fn complete_resident_self_turn(
"coordinator terminal receipt does not exactly bind the resident launch contract"
));
}
Ok(())
}
pub fn complete_resident_self_turn_after_death(
path: &Path,
lease: &ResidentSelfTurnLease,
recovery: &crate::EpiphanyCoordinatorDeathRecovery,
now_millis: u64,
cooldown_seconds: u64,
) -> Result<ResidentSelfTerminalAck> {
if recovery.session_id
!= crate::coordinator_run_session_id(&lease.turn_id, Some(&lease.launch_digest))?
|| recovery.thread_id != lease.turn_id
|| recovery.resident_grant_id != lease.grant_id
|| recovery.resident_launch_digest != lease.launch_digest
|| recovery.process_id != lease.process_id
|| recovery.process_creation_token != lease.process_creation_token
|| recovery.process_executable_path != lease.process_executable_path.display().to_string()
|| recovery.resident_started_at_millis != lease.started_at_millis
{
return Err(anyhow!(
"coordinator death recovery does not exactly bind the resident lease"
));
}
complete_resident_self_turn_with_terminal(
path,
lease,
&recovery.recovery_id,
"recovered-fulfilled",
now_millis,
cooldown_seconds,
)
}
fn complete_resident_self_turn_with_terminal(
path: &Path,
lease: &ResidentSelfTurnLease,
terminal_id: &str,
terminal_status: &str,
now_millis: u64,
cooldown_seconds: u64,
) -> Result<ResidentSelfTerminalAck> {
let cache = state_cache(path)?;
let mut state = cache
.get::<ResidentSelfState>(RESIDENT_SELF_STATE_KEY)?
@ -1662,8 +1878,8 @@ fn complete_resident_self_turn(
heartbeat_schedule_id: grant.heartbeat_schedule_id.clone(),
heartbeat_action_id: grant.heartbeat_action_id.clone(),
launch_digest: lease.launch_digest.clone(),
coordinator_receipt_id: coordinator.receipt_id.clone(),
terminal_status: coordinator.status.clone(),
coordinator_receipt_id: terminal_id.to_string(),
terminal_status: terminal_status.to_string(),
completed_at_millis: now_millis,
consumed_by_heartbeat_at_millis: None,
private_state_exposed: false,
@ -1686,13 +1902,13 @@ fn complete_resident_self_turn(
.cloned()
.ok_or_else(|| anyhow!("resident Self grant lost envelope at terminal ack"))?;
state.active_turn = None;
state.last_coordinator_receipt_id = Some(coordinator.receipt_id.clone());
state.last_coordinator_receipt_id = Some(terminal_id.to_string());
state.next_eligible_at_millis =
now_millis.saturating_add(cooldown_seconds.saturating_mul(1000));
state.revision += 1;
grant.schema_version = RESIDENT_SELF_GRANT_SCHEMA_VERSION.into();
grant.terminal_at_millis = Some(now_millis);
grant.terminal_status = Some(coordinator.status.clone());
grant.terminal_status = Some(terminal_status.to_string());
let (state_entry, _) = cache.prepare_entry(RESIDENT_SELF_STATE_KEY, &state)?;
let (grant_entry, _) = cache.prepare_entry(&grant.grant_id, &grant)?;
let (ack_entry, _) = cache.prepare_entry(&ack.ack_id, &ack)?;
@ -2610,7 +2826,11 @@ mod tests {
crate::EpiphanyCoordinatorRunReceipt {
schema_version: crate::COORDINATOR_RUN_RECEIPT_SCHEMA_VERSION.into(),
receipt_id: format!("coordinator-terminal-{}", lease.turn_id),
session_id: format!("session-{}", lease.turn_id),
session_id: crate::coordinator_run_session_id(
&lease.turn_id,
Some(&lease.launch_digest),
)
.expect("resident lease has a valid session incarnation"),
thread_id: lease.turn_id.clone(),
mode: "plan".into(),
status: "planned".into(),
@ -2766,6 +2986,7 @@ mod tests {
let mut policy = policy();
policy.coordinator_bin = coordinator.clone();
policy.runtime_store = temp.path().join("runtime.cc");
policy.artifact_root = temp.path().join("artifacts");
seed_model_direction_request(
&policy.runtime_store,
"request-model-1",
@ -2869,7 +3090,10 @@ mod tests {
let receipt = crate::EpiphanyCoordinatorRunReceipt {
schema_version: crate::COORDINATOR_RUN_RECEIPT_SCHEMA_VERSION.into(),
receipt_id: "coordinator-terminal-1".into(),
session_id: "session-1".into(),
session_id: crate::coordinator_run_session_id(
&lease.turn_id,
Some(&lease.launch_digest),
)?,
thread_id: lease.turn_id.clone(),
mode: "plan".into(),
status: "planned".into(),
@ -2897,6 +3121,9 @@ mod tests {
complete_resident_self_turn(&store, &lease, &wrong, 8, policy.cooldown_seconds)
.is_err()
);
let mut wrong_session = receipt.clone();
wrong_session.session_id = format!("{}-substituted", receipt.session_id);
assert!(validate_resident_self_coordinator_receipt(&lease, &wrong_session).is_err());
assert_eq!(
settle_resident_self_exited_coordinator(
&store,
@ -3439,6 +3666,253 @@ mod tests {
Ok(())
}
#[test]
fn receipt_free_dead_coordinator_recovery_preserves_exact_lease_then_requeues() -> Result<()> {
let temp = tempfile::tempdir()?;
let resident_store = temp.path().join("resident-self.cc");
let coordinator = temp.path().join("epiphany-mvp-coordinator");
std::fs::write(&coordinator, b"witnessed executable")?;
let mut policy = policy();
policy.coordinator_bin = coordinator.clone();
policy.runtime_store = temp.path().join("runtime.cc");
policy.artifact_root = temp.path().join("artifacts");
seed_runtime_identity(&policy.runtime_store, "test-runtime")?;
let objective = "Recover a coordinator killed after atomic opening.";
enqueue_resident_self_pressure(
&resident_store,
&ResidentSelfPressure {
schema_version: RESIDENT_SELF_PRESSURE_SCHEMA_VERSION.into(),
pressure_id: "pressure-dead-coordinator".into(),
kind: "operator-objective".into(),
provenance_ref: "operator://dead-coordinator-test".into(),
objective: objective.into(),
created_at_millis: 1,
status: "pending".into(),
consumed_by_grant_id: None,
private_state_exposed: false,
},
)?;
let grant = heartbeat_issue_resident_self_grant(
&resident_store,
"heartbeat-dead",
"action-dead",
2,
)?
.expect("dead coordinator grant");
let prepared = prepare_resident_self_launch(&resident_store, &policy, 3)?
.expect("dead coordinator preparation");
let first_artifact_index = prepared
.argv
.iter()
.position(|value| value == "--artifact-dir")
.expect("first artifact flag");
let first_artifact_dir = PathBuf::from(&prepared.argv[first_artifact_index + 1]);
std::fs::create_dir_all(&first_artifact_dir)?;
let first_artifact = first_artifact_dir.join("sealed-first-attempt.txt");
std::fs::write(&first_artifact, b"first attempt evidence")?;
let process = LaunchedCoordinator {
process_id: u32::MAX - 1,
process_creation_token: 155,
process_executable_path: coordinator,
};
claim_resident_self_preparation_as_child(
&resident_store,
&prepared.preparation_id,
&process,
4,
)?;
let lease = acknowledge_resident_self_launch(
&resident_store,
&prepared.preparation_id,
&process,
5,
)?;
let runtime_session_id =
crate::coordinator_run_session_id(&lease.turn_id, Some(&lease.launch_digest))?;
let runtime_session_objective =
format!("Resident coordinator launch {}.", lease.objective_digest);
crate::open_coordinator_run(
&policy.runtime_store,
&runtime_session_id,
&lease.turn_id,
Some(&lease.launch_digest),
&runtime_session_objective,
"1970-01-01T00:00:00.005Z",
)?;
assert!(
recover_receipt_free_dead_coordinator_session(
&resident_store,
&policy.runtime_store,
&lease,
ChildObservation::Running,
6,
)
.is_err()
);
let recovery = recover_receipt_free_dead_coordinator_session(
&resident_store,
&policy.runtime_store,
&lease,
ChildObservation::Missing,
6,
)?
.expect("opened coordinator death recovery");
assert_eq!(
load_resident_self_state(&resident_store)?.active_turn,
Some(lease.clone())
);
assert_eq!(
recover_receipt_free_dead_coordinator_session(
&resident_store,
&policy.runtime_store,
&lease,
ChildObservation::Missing,
6,
)?,
Some(recovery.clone())
);
let fulfilled_copy = temp.path().join("resident-self-fulfilled-copy.cc");
std::fs::copy(&resident_store, &fulfilled_copy)?;
let recovered_ack = complete_resident_self_turn_after_death(
&fulfilled_copy,
&lease,
&recovery,
7,
policy.cooldown_seconds,
)?;
assert_eq!(recovered_ack.terminal_status, "recovered-fulfilled");
assert!(load_resident_self_state(&fulfilled_copy)?.active_turn.is_none());
assert!(crate::coordinator_run_receipts(&policy.runtime_store)?.is_empty());
cancel_resident_self_turn(
&resident_store,
&lease,
"process-failed",
"exact coordinator process died without a run receipt",
7,
)?;
assert!(load_resident_self_state(&resident_store)?.active_turn.is_none());
assert!(pending_resident_self_pressure(&resident_store)?);
let retry_grant = heartbeat_issue_resident_self_grant(
&resident_store,
"heartbeat-retry",
"action-retry",
8,
)?
.expect("retry grant");
let retry_prepared = prepare_resident_self_launch(&resident_store, &policy, 9)?
.expect("retry preparation");
let retry_artifact_index = retry_prepared
.argv
.iter()
.position(|value| value == "--artifact-dir")
.expect("retry artifact flag");
let retry_artifact_dir = PathBuf::from(&retry_prepared.argv[retry_artifact_index + 1]);
assert_ne!(retry_artifact_dir, first_artifact_dir);
assert_eq!(std::fs::read(&first_artifact)?, b"first attempt evidence");
let retry_thread = resident_prepared_launch_thread_id(&retry_prepared)?;
assert_eq!(retry_thread, lease.turn_id);
assert_ne!(retry_prepared.launch_digest, lease.launch_digest);
let retry_session_id = crate::coordinator_run_session_id(
&retry_thread,
Some(&retry_prepared.launch_digest),
)?;
assert_ne!(retry_session_id, runtime_session_id);
crate::open_coordinator_run(
&policy.runtime_store,
&retry_session_id,
&retry_thread,
Some(&retry_prepared.launch_digest),
&format!(
"Resident coordinator launch {}.",
retry_prepared.objective_digest
),
"1970-01-01T00:00:00.009Z",
)?;
assert_eq!(retry_grant.terminal_at_millis, None);
assert_eq!(
state_cache(&resident_store)?
.get::<ResidentSelfHeartbeatGrant>(&grant.grant_id)?
.expect("terminal original grant")
.terminal_status
.as_deref(),
Some("process-failed")
);
Ok(())
}
#[test]
fn receipt_free_death_before_runtime_opening_is_proven_absent_and_requeued() -> Result<()> {
let temp = tempfile::tempdir()?;
let resident_store = temp.path().join("resident-self.cc");
let coordinator = temp.path().join("epiphany-mvp-coordinator");
std::fs::write(&coordinator, b"witnessed executable")?;
let mut policy = policy();
policy.coordinator_bin = coordinator.clone();
policy.runtime_store = temp.path().join("runtime.cc");
policy.artifact_root = temp.path().join("artifacts");
seed_runtime_identity(&policy.runtime_store, "test-runtime")?;
enqueue_resident_self_pressure(
&resident_store,
&ResidentSelfPressure {
schema_version: RESIDENT_SELF_PRESSURE_SCHEMA_VERSION.into(),
pressure_id: "pressure-pre-open-death".into(),
kind: "operator-objective".into(),
provenance_ref: "operator://pre-open-death".into(),
objective: "Recover death before runtime opening.".into(),
created_at_millis: 1,
status: "pending".into(),
consumed_by_grant_id: None,
private_state_exposed: false,
},
)?;
heartbeat_issue_resident_self_grant(
&resident_store,
"heartbeat-pre-open",
"action-pre-open",
2,
)?
.expect("pre-open grant");
let prepared = prepare_resident_self_launch(&resident_store, &policy, 3)?
.expect("pre-open preparation");
let process = LaunchedCoordinator {
process_id: u32::MAX - 2,
process_creation_token: 255,
process_executable_path: coordinator,
};
claim_resident_self_preparation_as_child(
&resident_store,
&prepared.preparation_id,
&process,
4,
)?;
let lease = acknowledge_resident_self_launch(
&resident_store,
&prepared.preparation_id,
&process,
5,
)?;
assert_eq!(
recover_receipt_free_dead_coordinator_session(
&resident_store,
&policy.runtime_store,
&lease,
ChildObservation::Missing,
6,
)?,
None
);
cancel_resident_self_turn(
&resident_store,
&lease,
"process-failed",
"exact child died before runtime opening",
7,
)?;
assert!(pending_resident_self_pressure(&resident_store)?);
assert!(load_resident_self_state(&resident_store)?.active_turn.is_none());
Ok(())
}
#[test]
fn shutdown_cancellation_requeues_pressure_without_failure_backoff() -> Result<()> {
let temp = tempfile::tempdir()?;

View file

@ -1083,6 +1083,7 @@ pub fn runtime_spine_cache(store_path: impl AsRef<Path>) -> Result<CultCache> {
cache.register_entry_type::<EpiphanyRuntimeJobResult>()?;
cache.register_entry_type::<EpiphanyRuntimeEvent>()?;
cache.register_entry_type::<EpiphanyCoordinatorRunReceipt>()?;
cache.register_entry_type::<EpiphanyCoordinatorDeathRecovery>()?;
cache.register_entry_type::<EpiphanyCoordinatorRunReceiptRetentionHead>()?;
cache.register_entry_type::<MindGatewayReview>()?;
cache.register_entry_type::<MindStateCommitReceipt>()?;
@ -1928,6 +1929,7 @@ pub fn retain_completed_runtime_sessions(
let jobs = cache.get_all::<EpiphanyRuntimeJob>()?;
let bindings = cache.get_all::<EpiphanyRuntimeModelExecutionBinding>()?;
let receipts = cache.get_all::<EpiphanyCoordinatorRunReceipt>()?;
let death_recoveries = cache.get_all::<EpiphanyCoordinatorDeathRecovery>()?;
let events = cache.get_all::<EpiphanyRuntimeEvent>()?;
let mut candidates = cache
.get_all::<EpiphanyRuntimeSession>()?
@ -1945,11 +1947,15 @@ pub fn retain_completed_runtime_sessions(
});
let coordinator_family = session_jobs.is_empty()
&& session.session_id.starts_with("coordinator-")
&& receipts
&& (receipts
.iter()
.filter(|receipt| receipt.session_id == session.session_id)
.count()
== 1
+ death_recoveries
.iter()
.filter(|recovery| recovery.session_id == session.session_id)
.count()
== 1)
&& !receipts.iter().any(|receipt| {
receipt.session_id == session.session_id
&& preserve_coordinator_receipt_ids.contains(&receipt.receipt_id)
@ -2574,6 +2580,10 @@ where
.get_all::<EpiphanyCoordinatorRunReceipt>()?
.iter()
.any(|receipt| receipt.session_id == session_id)
|| cache
.get_all::<EpiphanyCoordinatorDeathRecovery>()?
.iter()
.any(|recovery| recovery.session_id == session_id)
|| cache
.get_all::<EpiphanyRuntimeEvent>()?
.iter()
@ -2604,16 +2614,28 @@ where
.into_iter()
.filter(|receipt| receipt.session_id == session_id)
.collect::<Vec<_>>();
if receipts.len() != 1 {
let recoveries = cache
.get_all::<EpiphanyCoordinatorDeathRecovery>()?
.into_iter()
.filter(|recovery| recovery.session_id == session_id)
.collect::<Vec<_>>();
if receipts.len() + recoveries.len() != 1 {
return Err(anyhow!(
"coordinator session archive requires one exact run receipt"
"coordinator session archive requires one exact terminal authority"
));
}
let receipt = &receipts[0];
if session_id
.strip_prefix("coordinator-")
.is_none_or(|thread_id| thread_id != receipt.thread_id)
{
let expected_session_id = if let Some(receipt) = receipts.first() {
coordinator_run_session_id(
&receipt.thread_id,
receipt.resident_launch_digest.as_deref(),
)?
} else {
coordinator_run_session_id(
&recoveries[0].thread_id,
Some(&recoveries[0].resident_launch_digest),
)?
};
if session_id != expected_session_id {
return Err(anyhow!(
"coordinator session archive found a substituted thread binding"
));
@ -2624,7 +2646,11 @@ where
.filter(|event| event.session_id.as_deref() == Some(session_id))
.collect::<Vec<_>>();
events.sort_by(|left, right| left.event_id.cmp(&right.event_id));
let expected_completion = coordinator_completion_event(receipt);
let expected_completion = if let Some(receipt) = receipts.first() {
coordinator_completion_event(receipt)
} else {
coordinator_death_recovery_event(&recoveries[0])
};
if events
.iter()
.filter(|event| **event == expected_completion)
@ -2641,7 +2667,11 @@ where
|| event.source != "epiphany-mvp-coordinator"
|| event.job_id.is_some())
})
|| session.updated_at != receipt.created_at
|| session.updated_at
!= match receipts.first() {
Some(receipt) => receipt.created_at.as_str(),
None => recoveries[0].recovered_at.as_str(),
}
|| session.coordinator_note != expected_completion.summary
{
return Err(anyhow!(
@ -2650,12 +2680,17 @@ where
}
let snapshot = cache.snapshot_envelopes();
let mut deletions = Vec::new();
let terminal_envelope = if let Some(receipt) = receipts.first() {
(EpiphanyCoordinatorRunReceipt::TYPE, receipt.receipt_id.clone())
} else {
(
EpiphanyCoordinatorDeathRecovery::TYPE,
recoveries[0].recovery_id.clone(),
)
};
for (document_type, key) in
std::iter::once((EpiphanyRuntimeSession::TYPE, session_id.to_string()))
.chain(std::iter::once((
EpiphanyCoordinatorRunReceipt::TYPE,
receipt.receipt_id.clone(),
)))
.chain(std::iter::once(terminal_envelope))
.chain(
events
.iter()
@ -10108,6 +10143,37 @@ fn validate_coordinator_run_receipt(receipt: &EpiphanyCoordinatorRunReceipt) ->
Ok(())
}
pub fn runtime_typed_request_attempt_exists(
store_path: impl AsRef<Path>,
request: RuntimeTypedRequestRef<'_>,
) -> Result<bool> {
let request_id = match request {
RuntimeTypedRequestRef::ProposalModeling(id)
| RuntimeTypedRequestRef::ImaginationConsideration(id)
| RuntimeTypedRequestRef::AdmittedModelDirection(id) => id,
};
validate_non_empty(request_id, "typed attempt request id")?;
let mut cache = runtime_spine_cache(store_path)?;
cache.pull_all_backing_stores()?;
Ok(cache
.get_all::<EpiphanyRuntimeWorkerLaunchRequest>()?
.iter()
.any(|launch| match request {
RuntimeTypedRequestRef::ProposalModeling(_) => {
launch.proposal_modeling_request_id.as_deref() == Some(request_id)
}
RuntimeTypedRequestRef::ImaginationConsideration(_) => {
launch.imagination_consideration_request_id.as_deref() == Some(request_id)
}
RuntimeTypedRequestRef::AdmittedModelDirection(_) => {
launch
.admitted_model_direction_consideration_request_id
.as_deref()
== Some(request_id)
}
}))
}
fn coordinator_completion_event(receipt: &EpiphanyCoordinatorRunReceipt) -> EpiphanyRuntimeEvent {
EpiphanyRuntimeEvent {
schema_version: RUNTIME_SPINE_SCHEMA_VERSION.to_string(),
@ -10125,10 +10191,71 @@ fn coordinator_completion_event(receipt: &EpiphanyCoordinatorRunReceipt) -> Epip
}
}
pub const COORDINATOR_DEATH_RECOVERY_SCHEMA_VERSION: &str =
"epiphany.coordinator_death_recovery.v0";
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
#[cultcache(
type = "epiphany.coordinator_death_recovery.v0",
schema = "EpiphanyCoordinatorDeathRecovery"
)]
pub struct EpiphanyCoordinatorDeathRecovery {
#[cultcache(key = 0)]
pub schema_version: String,
#[cultcache(key = 1)]
pub recovery_id: String,
#[cultcache(key = 2)]
pub session_id: String,
#[cultcache(key = 3)]
pub thread_id: String,
#[cultcache(key = 4)]
pub resident_grant_id: String,
#[cultcache(key = 5)]
pub resident_launch_digest: String,
#[cultcache(key = 6)]
pub process_id: u32,
#[cultcache(key = 7)]
pub process_creation_token: u64,
#[cultcache(key = 8)]
pub process_executable_path: String,
#[cultcache(key = 9)]
pub resident_started_at_millis: u64,
#[cultcache(key = 10)]
pub observation: String,
#[cultcache(key = 11)]
pub recovered_at: String,
#[cultcache(key = 12, default)]
pub private_state_exposed: bool,
#[cultcache(key = 13, default)]
pub exit_code: Option<i32>,
}
pub fn coordinator_run_session_id(
thread_id: &str,
resident_launch_digest: Option<&str>,
) -> Result<String> {
validate_non_empty(thread_id, "coordinator run thread id")?;
match resident_launch_digest {
None => Ok(format!("coordinator-{thread_id}")),
Some(digest) => {
let hex = digest.strip_prefix("sha256:").ok_or_else(|| {
anyhow!("resident coordinator session requires a SHA-256 launch digest")
})?;
if hex.len() != 64 || !hex.bytes().all(|byte| byte.is_ascii_hexdigit()) {
return Err(anyhow!(
"resident coordinator session launch digest is invalid"
));
}
Ok(format!("coordinator-{thread_id}-resident-{}", hex.to_ascii_lowercase()))
}
}
}
pub fn open_coordinator_run(
store_path: impl AsRef<Path>,
session_id: &str,
thread_id: &str,
resident_launch_digest: Option<&str>,
objective: &str,
started_at: &str,
) -> Result<(EpiphanyRuntimeSession, EpiphanyRuntimeEvent)> {
@ -10136,6 +10263,7 @@ pub fn open_coordinator_run(
store_path,
session_id,
thread_id,
resident_launch_digest,
objective,
started_at,
|| Ok(()),
@ -10146,6 +10274,7 @@ fn open_coordinator_run_with_before_commit<F>(
store_path: impl AsRef<Path>,
session_id: &str,
thread_id: &str,
resident_launch_digest: Option<&str>,
objective: &str,
started_at: &str,
before_commit: F,
@ -10158,7 +10287,7 @@ where
validate_non_empty(objective, "coordinator run objective")?;
chrono::DateTime::parse_from_rfc3339(started_at)
.map_err(|error| anyhow!("coordinator run start timestamp is invalid: {error}"))?;
if session_id != format!("coordinator-{thread_id}") {
if session_id != coordinator_run_session_id(thread_id, resident_launch_digest)? {
return Err(anyhow!(
"coordinator run session id is not bound to its thread"
));
@ -10169,11 +10298,21 @@ where
require_identity(&cache)?;
let event_id = format!("event-coordinator-started-{session_id}");
if cache.get::<EpiphanyRuntimeSession>(session_id)?.is_some()
|| cache
.get::<EpiphanyArchivedRuntimeSession>(session_id)?
.is_some()
|| cache.get::<EpiphanyRuntimeEvent>(&event_id)?.is_some()
|| cache
.get_all::<EpiphanyCoordinatorRunReceipt>()?
.iter()
.any(|receipt| receipt.session_id == session_id || receipt.thread_id == thread_id)
.any(|receipt| {
receipt.session_id == session_id
|| (resident_launch_digest.is_none() && receipt.thread_id == thread_id)
})
|| cache
.get_all::<EpiphanyCoordinatorDeathRecovery>()?
.iter()
.any(|recovery| recovery.session_id == session_id)
{
return Err(anyhow!(
"coordinator run session or thread authority already exists"
@ -10224,6 +10363,189 @@ pub fn finalize_coordinator_run(
finalize_coordinator_run_with_before_commit(store_path, receipt, || Ok(()))
}
fn coordinator_death_recovery_event(
recovery: &EpiphanyCoordinatorDeathRecovery,
) -> EpiphanyRuntimeEvent {
EpiphanyRuntimeEvent {
schema_version: RUNTIME_SPINE_SCHEMA_VERSION.to_string(),
event_id: format!("event-coordinator-death-recovered-{}", recovery.session_id),
occurred_at: recovery.recovered_at.clone(),
event_type: "coordinator.death-recovered".to_string(),
source: "epiphany-continuity".to_string(),
session_id: Some(recovery.session_id.clone()),
job_id: None,
summary: format!(
"Continuity terminalized coordinator session after exact process observation {:?}{}.",
recovery.observation,
recovery
.exit_code
.map(|code| format!(" with exit code {code}"))
.unwrap_or_default()
),
metadata: BTreeMap::new(),
}
}
pub(crate) fn recover_coordinator_run_after_exact_process_death(
store_path: impl AsRef<Path>,
recovery: &EpiphanyCoordinatorDeathRecovery,
expected_objective: &str,
) -> Result<EpiphanyRuntimeSession> {
recover_coordinator_run_after_exact_process_death_with_before_commit(
store_path,
recovery,
expected_objective,
|| Ok(()),
)
}
pub(crate) fn coordinator_run_incarnation_is_absent(
store_path: impl AsRef<Path>,
thread_id: &str,
resident_launch_digest: &str,
) -> Result<bool> {
let session_id = coordinator_run_session_id(thread_id, Some(resident_launch_digest))?;
let mut cache = runtime_spine_cache(store_path)?;
cache.pull_all_backing_stores()?;
require_identity(&cache)?;
Ok(cache.get::<EpiphanyRuntimeSession>(&session_id)?.is_none()
&& cache
.get::<EpiphanyArchivedRuntimeSession>(&session_id)?
.is_none()
&& !cache
.get_all::<EpiphanyRuntimeEvent>()?
.iter()
.any(|event| event.session_id.as_deref() == Some(session_id.as_str()))
&& !cache
.get_all::<EpiphanyRuntimeJob>()?
.iter()
.any(|job| job.session_id == session_id)
&& !cache
.get_all::<EpiphanyCoordinatorRunReceipt>()?
.iter()
.any(|receipt| receipt.session_id == session_id)
&& !cache
.get_all::<EpiphanyCoordinatorDeathRecovery>()?
.iter()
.any(|recovery| recovery.session_id == session_id))
}
fn recover_coordinator_run_after_exact_process_death_with_before_commit<F>(
store_path: impl AsRef<Path>,
recovery: &EpiphanyCoordinatorDeathRecovery,
expected_objective: &str,
before_commit: F,
) -> Result<EpiphanyRuntimeSession>
where
F: FnOnce() -> Result<()>,
{
validate_non_empty(expected_objective, "recovered coordinator objective")?;
if recovery.schema_version != COORDINATOR_DEATH_RECOVERY_SCHEMA_VERSION
|| recovery.recovery_id != format!("coordinator-death-recovery-{}", recovery.session_id)
|| recovery.session_id
!= coordinator_run_session_id(
&recovery.thread_id,
Some(&recovery.resident_launch_digest),
)?
|| recovery.resident_grant_id.trim().is_empty()
|| !recovery.resident_launch_digest.starts_with("sha256:")
|| recovery.process_id == 0
|| recovery.process_creation_token == 0
|| recovery.process_executable_path.trim().is_empty()
|| recovery.resident_started_at_millis == 0
|| !matches!(recovery.observation.as_str(), "exited" | "missing")
|| (recovery.observation == "missing" && recovery.exit_code.is_some())
|| recovery.private_state_exposed
{
return Err(anyhow!("coordinator death recovery contract is invalid"));
}
chrono::DateTime::parse_from_rfc3339(&recovery.recovered_at)
.map_err(|error| anyhow!("coordinator death recovery timestamp is invalid: {error}"))?;
let store_path = store_path.as_ref();
let mut cache = runtime_spine_cache(store_path)?;
cache.pull_all_backing_stores()?;
require_identity(&cache)?;
let mut session = cache
.get::<EpiphanyRuntimeSession>(&recovery.session_id)?
.ok_or_else(|| anyhow!("coordinator death recovery session is absent"))?;
let expected_event = coordinator_death_recovery_event(recovery);
if session.status == EpiphanyRuntimeSessionStatus::Completed {
let existing_recovery = cache
.get::<EpiphanyCoordinatorDeathRecovery>(&recovery.recovery_id)?;
let existing_event = cache.get::<EpiphanyRuntimeEvent>(&expected_event.event_id)?;
if existing_recovery.as_ref() == Some(recovery)
&& existing_event.as_ref() == Some(&expected_event)
&& session.objective == expected_objective
&& session.updated_at == recovery.recovered_at
&& session.coordinator_note == expected_event.summary
{
return Ok(session);
}
return Err(anyhow!(
"completed coordinator session does not match exact death recovery"
));
}
if session.status != EpiphanyRuntimeSessionStatus::Active
|| session.objective != expected_objective
{
return Err(anyhow!(
"coordinator death recovery requires the exact active session objective"
));
}
let start_event_id = format!("event-coordinator-started-{}", recovery.session_id);
let start_event = cache
.get::<EpiphanyRuntimeEvent>(&start_event_id)?
.ok_or_else(|| anyhow!("coordinator death recovery lost its start event"))?;
let session_started_at = chrono::DateTime::parse_from_rfc3339(&session.created_at)
.map_err(|error| anyhow!("coordinator session start timestamp is invalid: {error}"))?;
let recovered_at = chrono::DateTime::parse_from_rfc3339(&recovery.recovered_at)
.map_err(|error| anyhow!("coordinator death recovery timestamp is invalid: {error}"))?;
if (session_started_at.timestamp_millis().max(0) as u64)
< recovery.resident_started_at_millis
|| recovered_at < session_started_at
|| start_event.occurred_at != session.created_at
|| start_event.event_type != "coordinator.started"
|| start_event.source != "epiphany-mvp-coordinator"
|| start_event.session_id.as_deref() != Some(recovery.session_id.as_str())
|| start_event.job_id.is_some()
|| cache
.get_all::<EpiphanyRuntimeJob>()?
.iter()
.any(|job| job.session_id == recovery.session_id)
|| cache
.get_all::<EpiphanyCoordinatorRunReceipt>()?
.iter()
.any(|receipt| receipt.session_id == recovery.session_id)
|| cache
.get_all::<EpiphanyCoordinatorDeathRecovery>()?
.iter()
.any(|existing| existing.session_id == recovery.session_id)
|| cache.get::<EpiphanyRuntimeEvent>(&expected_event.event_id)?.is_some()
{
return Err(anyhow!(
"coordinator death recovery found substituted or competing authority"
));
}
let snapshot = cache.snapshot_envelopes();
session.status = EpiphanyRuntimeSessionStatus::Completed;
session.updated_at = recovery.recovered_at.clone();
session.coordinator_note = expected_event.summary.clone();
let replacements = vec![
cache.prepare_entry(&session.session_id, &session)?.0,
cache.prepare_entry(&recovery.recovery_id, recovery)?.0,
cache.prepare_entry(&expected_event.event_id, &expected_event)?.0,
];
before_commit()?;
if !runtime_spine_backing_store(store_path)?
.replace_and_append_if_snapshot_unchanged(&snapshot, replacements)?
{
return Err(anyhow!(
"coordinator death recovery lost its full snapshot fence"
));
}
Ok(session)
}
fn finalize_coordinator_run_with_before_commit<F>(
store_path: impl AsRef<Path>,
receipt: &EpiphanyCoordinatorRunReceipt,
@ -10233,6 +10555,16 @@ where
F: FnOnce() -> Result<()>,
{
validate_coordinator_run_receipt(receipt)?;
if receipt.session_id
!= coordinator_run_session_id(
&receipt.thread_id,
receipt.resident_launch_digest.as_deref(),
)?
{
return Err(anyhow!(
"coordinator run receipt session is not bound to its run incarnation"
));
}
let store_path = store_path.as_ref();
let mut cache = runtime_spine_cache(store_path)?;
cache.pull_all_backing_stores()?;
@ -12677,6 +13009,14 @@ pub(crate) mod tests {
.proposal_modeling_request_id
.clone()
.expect("proposal request echo");
assert!(runtime_typed_request_attempt_exists(
&store,
RuntimeTypedRequestRef::ProposalModeling(&request_id),
)?);
assert!(!runtime_typed_request_attempt_exists(
&store,
RuntimeTypedRequestRef::ProposalModeling("absent-proposal-request"),
)?);
assert_eq!(
runtime_typed_request_fulfillment(
&store,
@ -15935,6 +16275,7 @@ pub(crate) mod tests {
&store,
"coordinator-thread-open",
"thread-open",
None,
"Open one coordinator authority family.",
"2026-08-10T10:00:01Z",
)?;
@ -15947,6 +16288,7 @@ pub(crate) mod tests {
&store,
"coordinator-thread-open",
"thread-open",
None,
"Open one coordinator authority family.",
"2026-08-10T10:00:01Z",
)
@ -15958,6 +16300,7 @@ pub(crate) mod tests {
&store,
"coordinator-swapped",
"thread-bound",
None,
"Refuse split identity.",
"2026-08-10T10:00:02Z",
)
@ -15968,6 +16311,7 @@ pub(crate) mod tests {
&store,
"coordinator-thread-raced-open",
"thread-raced-open",
None,
"Lose the opening snapshot.",
"2026-08-10T10:00:03Z",
|| {
@ -16009,6 +16353,129 @@ pub(crate) mod tests {
Ok(())
}
#[test]
fn coordinator_death_recovery_is_exact_single_use_and_archivable() -> Result<()> {
let temp = tempdir()?;
let store = temp.path().join("runtime.msgpack");
initialize_runtime_spine(
&store,
RuntimeSpineInitOptions {
runtime_id: "epiphany-coordinator-death-test".to_string(),
display_name: "Epiphany Coordinator Death Test".to_string(),
created_at: "2026-08-10T11:00:00Z".to_string(),
},
)?;
let objective = "Recover one exact dead coordinator.";
let launch_digest = format!("sha256:{}", "a".repeat(64));
let session_id = coordinator_run_session_id("thread-dead", Some(&launch_digest))?;
open_coordinator_run(
&store,
&session_id,
"thread-dead",
Some(&launch_digest),
objective,
"2026-08-10T11:00:01Z",
)?;
let recovery = EpiphanyCoordinatorDeathRecovery {
schema_version: COORDINATOR_DEATH_RECOVERY_SCHEMA_VERSION.to_string(),
recovery_id: format!("coordinator-death-recovery-{session_id}"),
session_id,
thread_id: "thread-dead".to_string(),
resident_grant_id: "grant-dead".to_string(),
resident_launch_digest: launch_digest,
process_id: 41,
process_creation_token: 73,
process_executable_path: "/release/epiphany-mvp-coordinator".to_string(),
resident_started_at_millis: 1,
observation: "missing".to_string(),
recovered_at: "2026-08-10T11:00:02Z".to_string(),
private_state_exposed: false,
exit_code: None,
};
let completed = recover_coordinator_run_after_exact_process_death(
&store,
&recovery,
objective,
)?;
assert_eq!(completed.status, EpiphanyRuntimeSessionStatus::Completed);
assert_eq!(
recover_coordinator_run_after_exact_process_death(&store, &recovery, objective)?,
completed
);
let mut substituted = recovery.clone();
substituted.process_creation_token += 1;
assert!(
recover_coordinator_run_after_exact_process_death(&store, &substituted, objective)
.is_err()
);
let archived = archive_completed_coordinator_session(
&store,
&recovery.session_id,
"2026-08-10T11:00:03Z",
)?;
assert_eq!(archived.retired_envelope_count, 4);
let mut cache = runtime_spine_cache(&store)?;
cache.pull_all_backing_stores()?;
assert!(
cache
.get::<EpiphanyCoordinatorDeathRecovery>(&recovery.recovery_id)?
.is_none()
);
let raced_launch_digest = format!("sha256:{}", "b".repeat(64));
let raced_session_id =
coordinator_run_session_id("thread-raced-death", Some(&raced_launch_digest))?;
open_coordinator_run(
&store,
&raced_session_id,
"thread-raced-death",
Some(&raced_launch_digest),
objective,
"2026-08-10T11:01:01Z",
)?;
let mut raced = recovery.clone();
raced.recovery_id = format!("coordinator-death-recovery-{raced_session_id}");
raced.session_id = raced_session_id;
raced.thread_id = "thread-raced-death".to_string();
raced.resident_launch_digest = raced_launch_digest;
let result = recover_coordinator_run_after_exact_process_death_with_before_commit(
&store,
&raced,
objective,
|| {
append_runtime_event(
&store,
RuntimeSpineEventOptions {
event_id: "event-racing-death-recovery".to_string(),
occurred_at: "2026-08-10T11:01:02Z".to_string(),
event_type: "test.concurrent".to_string(),
source: "test".to_string(),
session_id: None,
job_id: None,
summary: "Invalidate death recovery snapshot.".to_string(),
},
)?;
Ok(())
},
);
assert!(result.is_err());
let mut cache = runtime_spine_cache(&store)?;
cache.pull_all_backing_stores()?;
assert_eq!(
cache
.get::<EpiphanyRuntimeSession>(&raced.session_id)?
.expect("raced active session")
.status,
EpiphanyRuntimeSessionStatus::Active
);
assert!(
cache
.get::<EpiphanyCoordinatorDeathRecovery>(&raced.recovery_id)?
.is_none()
);
Ok(())
}
#[test]
fn coordinator_session_retention_repairs_legacy_and_owns_the_exact_family() -> Result<()> {
let temp = tempdir()?;

View file

@ -2860,6 +2860,63 @@ bind the opened session to exact process or resident lease identity, prove that
identity dead, and terminalize or quarantine the session through a dedicated
receipt-grounded recovery path before retention can see it as closed.
#### Receipt-free coordinator death authority map
- Owner: resident Self owns the exact child lease and its process-incarnation
observation; runtime Continuity owns terminal recovery of the corresponding
orphaned coordinator session.
- Inputs: the persisted active resident lease, its immutable child claim and
terminal grant, an exact `Missing` or `Exited` observation for that process
incarnation, and the runtime's matching Active coordinator session plus
deterministic start event.
- Outputs: one typed coordinator-death recovery receipt and one deterministic
recovery event, atomically replacing the runtime session as Completed. Self
then uses its existing exact-grant cancellation transaction to requeue the
pressure.
- Identity split: the cognitive thread remains request-owned context. A
resident coordinator session is a distinct run incarnation derived from the
exact resident launch digest; ordinary non-resident runs retain the simple
`coordinator-{thread}` identity. Requeue therefore creates a new session
without rewriting or reusing the cognitive thread.
- Derived state: timestamps, session age, receipt absence, PID presence, and
operator projections remain observations only. None can establish death.
- Forbidden writers: retention, legacy coordinator repair, the coordinator
itself after death, generic runtime session closure, and timeout policy may
not synthesize this terminal family. A normal coordinator receipt and a
death-recovery receipt are mutually exclusive.
- Shared paths: zero exit without a receipt, nonzero exit without a receipt,
and restart observation of a missing exact child share one settlement
classifier. Death before runtime opening proves that exact incarnation family
wholly absent and requeues without inventing runtime state. Death after
opening uses the recovery transaction. Normal exits with an exact fully bound
run receipt retain the normal finalizer path.
- Typed-work boundary: authenticated terminal typed fulfillment lets the
death-recovery authority close the original grant exactly once without a
duplicate launch. No typed attempt permits canonical requeue. A typed launch
attempt without terminal fulfillment remains active and
`awaiting-fulfillment`; Continuity does not yet own orphan-worker terminality
or supersession, so timeout or brake cannot manufacture a retry.
- Cut line: replace direct receipt-free resident cancellation with runtime
recovery followed by canonical resident cancellation, and stop using the
cognitive thread as the resident run identity; do not add an age-based
scavenger or let session retention repair Active state.
- Verification: exact lease/claim/session/thread/objective/start-event binding,
hostile substitution refusal, live or indeterminate process refusal,
single-use replay, full-snapshot CAS refusal, mutual exclusion with normal
receipts, restart recovery, pressure requeue without grant resurrection, and
eventual archive of the recovered terminal family.
Retry artifacts are grant-incarnation-owned rather than thread-owned. Prepared
argv carries `artifact_root/{grant_id}`, so a new launch on the same cognitive
thread cannot erase the prior attempt's sealed directory.
Source verification for this cut is complete: the serial core suite passed 653
tests with one intentional ignore, followed by all 17 coordinator-binary tests
and all 8 swarm tests. The detached receipt is
`.epiphany-run/continuity-death-recovery-source`; stderr is empty and exit is
zero. Exact packaging and copied-live process-death falsification remain the
acceptance boundary.
### Semantic logical lifecycle implementation
`retain_memory_semantic_projection_lifecycles` now realizes the safe unattempted

View file

@ -2075,3 +2075,48 @@ Normal coordinator lifecycle ownership is accepted. The next distinct fault is
a hard process death after atomic opening but before any receipt. Continuity
must ground that repair in exact process/lease authority; age, retention, and
absence of a receipt cannot infer death.
## Receipt-free coordinator death recovery source cut — 2026-08-10
The Continuity owner is now implemented but not yet packaged. Resident Self
requires its exact persisted active lease, immutable child claim, active
unterminated grant, and an independent native observation proving the exact
process incarnation exited, disappeared, or was replaced. Exact-alive,
inaccessible, and indeterminate observations refuse recovery. Runtime
Continuity then atomically replaces only the matching Active jobless
coordinator session as Completed while appending one typed death-recovery
receipt and one deterministic recovery event. A normal coordinator receipt and
a death-recovery receipt cannot coexist. Replays are exact and snapshot races
leave the Active family unchanged.
The cut exposed and removed a deeper identity conflation. The cognitive thread
is request-owned context and must survive retries; it is not a run identity.
Ordinary coordinators retain `coordinator-{thread}` sessions, while resident
coordinator sessions are now derived from the exact launch digest. A cancelled
grant therefore terminalizes and requeues through the existing resident CAS,
then the new grant produces a distinct session incarnation on the same
cognitive thread. Archived session tombstones and terminal families still
refuse exact incarnation reuse.
The receipt-free classifier distinguishes three crash boundaries. If the exact
process died before atomic runtime opening, the entire incarnation family must
be absent and resident cancellation may requeue without synthetic runtime
state. If a typed result is already authenticated, runtime death recovery can
terminalize the process and the original grant closes as
`recovered-fulfilled`. If a typed worker launch exists without terminal
fulfillment, the active lease remains `awaiting-fulfillment`; duplicate launch
is forbidden until orphan-worker terminality and explicit supersession have a
real owner. This last Continuity seam remains open. Retry artifact directories
are keyed by grant incarnation, not cognitive thread, preserving prior attempt
evidence byte-for-byte.
Focused death-recovery, resident requeue/new-incarnation, and legacy retention
tests pass. Final serial verification under
`.epiphany-run/continuity-death-recovery-source` passed 653 library tests with
one intentional ignore, all 17 coordinator-binary tests, and all 8 swarm tests;
stderr is empty and the detached suite recorded exit zero. The first parallel
full-library run had exposed one real eager-fallback archive bug plus two Git
fixture flakes; the archive bug is fixed, the serial suite removes the fixture
contention, and the complete corrected source pass is green. Commit, exact
packaging, and copied-live hard-death proof remain. Active c005 remains
untouched.

Binary file not shown.

View file

@ -36,9 +36,9 @@ invariants:
current_status:
summary: Epiphany is a supervised engineering alpha on Starfire, not deployment-ready. Exact b8138a6b active-child SIGTERM and restart, exact 9f369528 deterministic warm packaging, exact c930f505 cross-container ownership, exact ed15f83a resident lifecycle retention, exact 16c4b19f coordinator receipt and Heartbeat artifact retention, exact cce2b8ac narrow model-generation retention, exact eea15e82 model-turn JSON ingress, exact b1e8c89a semantic unattempted-generation retention, exact 6bc3ec3f semantic physical-epoch retirement, and exact 44230a05 Persona conversation retention are proven. Gamecult-ops 059821f owns Yggdrasil stream retention and exact 6ee08a4 owns stable Starfire Persona-permit firewall ingress. Exact 16c4b19f genuine idle and exact a987d2f7 active cognition completed 900-pulse bounded plateaus. Runtime-owned typed fulfillment is accepted live under exact 6dd9b132. Yggdrasil is the small public crossing body, never the release forge.
latest_phase: Exact 497df458 coordinator lifecycle ownership is accepted for all normal exits and legacy receipt-grounded repair. Release sha256-3563c909 with witness sha256-d02d2fe5 passed 24-binary inspection with no warnings; identical warm replay completed in 13.82s wall and Cargo 0.83s. Copied-live execution atomically terminalized a deliberate coordinator error, repaired and archived one older exact legacy family, retained the newer failed family, exposed zero active sessions/jobs/actionable frontiers, replayed with a byte-identical runtime store, and held that digest across 26 additional braked serve cycles at roughly 28 MiB before SIGTERM exit zero. Hard process death after atomic opening but before a receipt remains the next Continuity boundary.
latest_phase: Receipt-free coordinator death recovery is source-verified and ready to commit. Resident Self preserves the exact child claim/lease and independently refuses live or indeterminate process identity; the only exposed combined primitive lets runtime Continuity atomically close the matching Active session with a dedicated recovery receipt/event. Pre-open death instead proves the exact runtime family absent before canonical requeue. Cognitive thread identity is no longer misused as run identity: each launch digest derives a unique resident session and each grant owns a distinct artifact directory. Exact typed fulfillment can close once through death recovery; a typed launch without terminal fulfillment remains fail-closed awaiting orphan-worker Continuity rather than duplicating work. Normal receipts are fully lease-authenticated and mutually exclusive with recovery. Serial verification passed 653 library tests with one intentional ignore, all 17 coordinator-binary tests, and all 8 swarm tests; package and copied-live proof remain open.
operational_state:
local_worktree: coordinator_lifecycle_497df458_live_accepted_pending_receipt_commit
local_worktree: coordinator_death_recovery_source_verified_pending_commit
operator_wireguard: restored
starfire_persona_permit_firewall: persistent_exact_gamecult_ops_6ee08a4
yggdrasil: canonical_small_public_crossing_body
@ -69,8 +69,8 @@ current_status:
starfire_linux_modeling_peak: 109887488_bytes_resident_body_plus_modeling_circuit
yggdrasil_epiphany_service_contract: repaired_in_gamecult_ops_812b427_not_installed
resident_physiology: authenticated_9b8df0b4_c005_bound_to_eyes_tools_nervous_system_with_217dd_and_bd96305e_helpers_running
unresolved_authority: Coordinator receipt, Heartbeat pulse-artifact, deployed supervisor/projector log retention, genuine-idle and active resource plateaus, exact-grant terminality, restart-persistent cooldown refusal, schedule/action issuance cardinality, runtime-owned typed fulfillment, narrow model-generation retention, published model-turn JSON ingress, semantic logical/physical retention, Persona conversation retention, stable Starfire firewall ownership, completed model-adapter session archival, and normal coordinator lifecycle opening/error closure/legacy repair/archival are accepted live. A hard process death between atomic opening and terminal receipt remains Continuity-owned recovery work rather than inferred retention. Historical migrated semantic attempts without recorded physical namespace remain durable and non-retirable. Repository Body observation still performs two authenticated full-tree Git index/manifest passes. Execute-mode workers require a persistent process body. Keyed storage has not earned live migration. One completed Discord consequence remains blocked by the absent Yggdrasil credential.
next_action: Map and implement receipt-free dead coordinator-session recovery from exact process/lease authority; do not infer death from age or let retention synthesize terminality. Then continue supervisor/projector log ownership and remaining organ/endurance audit. Preserve active c005 and exact prior receipts. Yggdrasil never builds.
unresolved_authority: Coordinator receipt, Heartbeat pulse-artifact, deployed supervisor/projector log retention, genuine-idle and active resource plateaus, exact-grant terminality, restart-persistent cooldown refusal, schedule/action issuance cardinality, runtime-owned typed fulfillment, narrow model-generation retention, published model-turn JSON ingress, semantic logical/physical retention, Persona conversation retention, stable Starfire firewall ownership, completed model-adapter session archival, and normal coordinator lifecycle opening/error closure/legacy repair/archival are accepted live. Receipt-free hard-death recovery is source-implemented but remains uncommitted, unpackaged, and unproven on copied exact pre-open/opened process kills. A typed worker launch without valid terminal fulfillment deliberately remains active awaiting an owner for exact orphan-worker death and supersession; it is not retryable yet. Historical migrated semantic attempts without recorded physical namespace remain durable and non-retirable. Repository Body observation still performs two authenticated full-tree Git index/manifest passes. Execute-mode workers require a persistent process body. Keyed storage has not earned live migration. One completed Discord consequence remains blocked by the absent Yggdrasil credential.
next_action: Commit and push the source-verified Continuity cut, package it once with the persistent Starfire cache, then prove copied exact pre-open and post-open/pre-attempt deaths, canonical requeue under a new session/artifact incarnation, post-fulfillment recovery, archival/non-resurrection, and bounded replay. Then map orphan-worker death/supersession for typed launched attempts. Preserve active c005. Yggdrasil never builds.
active_subgoals:
- Prove Persona, Self, Eyes, Modeling, Imagination, Hands, Soul, Mind, Continuity, and nervous-system behavior individually.