diff --git a/epiphany-core/src/bin/epiphany-daemon-supervisor.rs b/epiphany-core/src/bin/epiphany-daemon-supervisor.rs index e7fec5ed..318e3b5b 100644 --- a/epiphany-core/src/bin/epiphany-daemon-supervisor.rs +++ b/epiphany-core/src/bin/epiphany-daemon-supervisor.rs @@ -59,14 +59,16 @@ use epiphany_core::{ WORKSPACE_COVERAGE_PROCESS_LAUNCH_SCHEMA_VERSION, WorkspaceCoverageManagedProcessLaunchEntry, WorkspaceCoverageProcessBootstrap, WorkspaceCoverageProcessLifecycleObservation, authenticate_current_workspace_coverage_claim_sight, + authenticate_historical_workspace_coverage_managed_process_launch, authenticate_workspace_coverage_managed_process_launch, authenticate_workspace_coverage_provider_heartbeat, authenticate_workspace_coverage_termination_with_envelope_digest, capture_process_instance, load_latest_workspace_coverage_managed_process_launch, load_latest_workspace_coverage_provider_heartbeat, load_workspace_coverage_process_termination_observation, native_boot_identity, - observe_process_instance, observe_workspace_coverage_managed_process, - open_default_host_identity, sign_workspace_coverage_launch, + observe_historical_workspace_coverage_managed_process, observe_process_instance, + observe_workspace_coverage_managed_process, open_default_host_identity, + process_identity_from_workspace_coverage_launch, sign_workspace_coverage_launch, workspace_coverage_host_identity_record_digest, write_workspace_coverage_managed_process_launch, write_workspace_coverage_process_bootstrap, write_workspace_coverage_process_termination_observation, @@ -2352,6 +2354,113 @@ fn reconcile_workspace_coverage_projector( let _ = service_launch_internal(args, None, true)?; return Ok(()); }; + let current_policy_matches_latest = latest.policy_id == policy.policy_id + && latest.command == policy.command + && latest.args == policy.args + && latest.cwd == policy.cwd + && load_epiphany_cultmesh_managed_service_policy_with_digest( + &args.store, + args.runtime_id.clone(), + &policy.service_id, + )? + .is_some_and(|(_, digest)| digest == latest.policy_envelope_digest); + if !current_policy_matches_latest { + let observation = match observe_historical_workspace_coverage_managed_process( + &args.store, + args.runtime_id.clone(), + &latest.launch_id, + host.entry(), + ) { + Ok(observation) => observation, + Err(error) => { + println!( + "{}", + serde_json::to_string_pretty(&json!({ + "schemaVersion": "epiphany.workspace_coverage_reconcile.v0", + "status": "observation-refused", + "serviceId": policy.service_id, + "launchId": latest.launch_id, + "reason": error.to_string(), + "restarted": false, + "privateStateExposed": false, + }))? + ); + return Ok(()); + } + }; + if observation == WorkspaceCoverageProcessLifecycleObservation::ExactAlive { + terminate_native_process_instance(&process_identity_from_workspace_coverage_launch( + &latest, + ))?; + println!( + "{}", + serde_json::to_string_pretty(&json!({ + "schemaVersion": "epiphany.workspace_coverage_reconcile.v0", + "status": "stale-policy-termination-requested", + "serviceId": policy.service_id, + "launchId": latest.launch_id, + "restarted": false, + "privateStateExposed": false, + }))? + ); + return Ok(()); + } + if matches!( + observation, + WorkspaceCoverageProcessLifecycleObservation::Inaccessible + | WorkspaceCoverageProcessLifecycleObservation::Indeterminate { .. } + ) { + println!( + "{}", + serde_json::to_string_pretty(&json!({ + "schemaVersion": "epiphany.workspace_coverage_reconcile.v0", + "status": "observation-degraded", + "serviceId": policy.service_id, + "launchId": latest.launch_id, + "observation": format!("{observation:?}"), + "restarted": false, + "privateStateExposed": false, + }))? + ); + return Ok(()); + } + if load_workspace_coverage_process_termination_observation( + &args.store, + args.runtime_id.clone(), + &latest.launch_id, + )? + .is_none() + { + write_workspace_coverage_process_termination_observation( + &args.store, + args.runtime_id.clone(), + &latest.launch_id, + &host, + )?; + } + let (termination, termination_digest) = + authenticate_workspace_coverage_termination_with_envelope_digest( + &args.store, + args.runtime_id.clone(), + &latest.launch_id, + host.entry(), + )?; + args.service_command = Some(PathBuf::from(&policy.command)); + args.service_args = policy.args.clone(); + args.cwd = policy.cwd.clone().map(PathBuf::from); + args.stdout_artifact = Some(PathBuf::from(policy.stdout_artifact.clone())); + args.stderr_artifact = Some(PathBuf::from(policy.stderr_artifact.clone())); + let _ = service_launch_internal( + args, + Some(CoverageReplacementEvidence { + old_launch_id: latest.launch_id, + termination_id: termination.termination_id, + termination_envelope_digest: termination_digest, + }), + false, + )?; + return Ok(()); + } if target .as_ref() .is_some_and(|claim| claim.launch_id != latest.launch_id) @@ -2367,7 +2476,7 @@ fn reconcile_workspace_coverage_projector( &old_target.launch_id, host.entry(), )?; - let old_launch = authenticate_workspace_coverage_managed_process_launch( + authenticate_historical_workspace_coverage_managed_process_launch( &args.store, args.runtime_id.clone(), &old_target.launch_id, @@ -2384,11 +2493,6 @@ fn reconcile_workspace_coverage_projector( != Some(termination.termination_id.as_str()) || replacement.replaces_termination_envelope_digest.as_deref() != Some(termination_digest.as_str()) - || replacement.policy_id != old_launch.policy_id - || replacement.policy_envelope_digest != old_launch.policy_envelope_digest - || replacement.command != old_launch.command - || replacement.args != old_launch.args - || replacement.cwd != old_launch.cwd { anyhow::bail!( "latest launch is not the exact authenticated in-flight replacement lineage" @@ -4309,14 +4413,15 @@ mod semantic_projector_authority_tests { let end = tail.find("\nfn service_runbook").unwrap(); let body = &tail[..end]; let observation = body - .find("observe_workspace_coverage_managed_process") + .rfind("let observation = match observe_workspace_coverage_managed_process") .unwrap(); - let alive = body.find("status\": \"observed-alive").unwrap(); - let degraded = body.find("status\": \"observation-degraded").unwrap(); + let normal = &body[observation..]; + let alive = normal.find("status\": \"observed-alive").unwrap(); + let degraded = normal.find("status\": \"observation-degraded").unwrap(); let termination_write = body - .find("write_workspace_coverage_process_termination_observation") + .rfind("write_workspace_coverage_process_termination_observation") .unwrap(); - let termination = body + let termination = normal .rfind("authenticate_workspace_coverage_termination_with_envelope_digest") .unwrap(); let launch = body.rfind("service_launch_internal").unwrap(); @@ -4326,10 +4431,20 @@ mod semantic_projector_authority_tests { let recovery = body .rfind("write_workspace_coverage_recovery_directive") .unwrap(); - assert!(observation < alive && alive < termination_write); - assert!(observation < degraded && degraded < termination_write); - assert!(termination_write < termination); - assert!(termination < launch && launch < readiness && readiness < recovery); + assert!( + alive + < normal + .find("write_workspace_coverage_process_termination_observation") + .unwrap() + ); + assert!( + degraded + < normal + .find("write_workspace_coverage_process_termination_observation") + .unwrap() + ); + assert!(termination_write < launch); + assert!(observation + termination < launch && launch < readiness && readiness < recovery); assert!(body.contains("write_workspace_coverage_process_termination_observation")); assert!(body.contains("status == \"ready\"")); assert!( @@ -4396,12 +4511,16 @@ mod semantic_projector_authority_tests { .unwrap(); let body = &tail[..end]; let mismatch = body.find("claim.launch_id != latest.launch_id").unwrap(); - let branch = &body[mismatch..body.find("let observation = match").unwrap()]; + let normal_observation = mismatch + + body[mismatch..] + .find("\n let observation = match observe_workspace_coverage_managed_process") + .unwrap(); + let branch = &body[mismatch..normal_observation]; let termination = branch .find("authenticate_workspace_coverage_termination_with_envelope_digest") .unwrap(); let old_launch = branch - .find("let old_launch = authenticate_workspace_coverage_managed_process_launch") + .find("authenticate_historical_workspace_coverage_managed_process_launch") .unwrap(); let replacement = branch .find("let replacement = authenticate_workspace_coverage_managed_process_launch") @@ -4414,11 +4533,7 @@ mod semantic_projector_authority_tests { .unwrap(); assert!(termination < old_launch && old_launch < replacement); assert!(replacement < lineage && lineage < resume); - assert!( - branch.contains( - "replacement.policy_envelope_digest != old_launch.policy_envelope_digest" - ) - ); + assert!(!branch.contains("replacement.policy_envelope_digest != old_launch")); assert!(!branch.contains("service_launch_internal")); let finish = &source[source @@ -4440,7 +4555,11 @@ mod semantic_projector_authority_tests { .unwrap(); let body = &tail[..end]; let mismatch = body.find("claim.launch_id != latest.launch_id").unwrap(); - let branch = &body[mismatch..body.find("let observation = match").unwrap()]; + let normal_observation = mismatch + + body[mismatch..] + .find("\n let observation = match observe_workspace_coverage_managed_process") + .unwrap(); + let branch = &body[mismatch..normal_observation]; let replacement_sight = branch .find("let replacement_observation = match observe_workspace_coverage_managed_process") .unwrap(); diff --git a/epiphany-core/src/lib.rs b/epiphany-core/src/lib.rs index 09506c14..ec633530 100644 --- a/epiphany-core/src/lib.rs +++ b/epiphany-core/src/lib.rs @@ -1241,6 +1241,7 @@ pub use workspace_coverage_process_documents::{ authenticate_current_workspace_coverage_advancement_sight, authenticate_current_workspace_coverage_claim_sight, authenticate_current_workspace_coverage_terminal_sight, + authenticate_historical_workspace_coverage_managed_process_launch, authenticate_workspace_coverage_managed_process_launch, authenticate_workspace_coverage_process_termination_observation, authenticate_workspace_coverage_provider_heartbeat, @@ -1250,12 +1251,13 @@ pub use workspace_coverage_process_documents::{ load_workspace_coverage_managed_process_launch, load_workspace_coverage_managed_process_launch_with_digest, load_workspace_coverage_process_termination_observation, - load_workspace_coverage_provider_heartbeat, observe_workspace_coverage_managed_process, - process_identity_from_workspace_coverage_launch, sign_workspace_coverage_heartbeat, - sign_workspace_coverage_launch, sign_workspace_coverage_termination, - workspace_coverage_heartbeat_statement, workspace_coverage_host_identity_record_digest, - workspace_coverage_launch_statement, workspace_coverage_termination_statement, - write_workspace_coverage_managed_process_launch, + load_workspace_coverage_provider_heartbeat, + observe_historical_workspace_coverage_managed_process, + observe_workspace_coverage_managed_process, process_identity_from_workspace_coverage_launch, + sign_workspace_coverage_heartbeat, sign_workspace_coverage_launch, + sign_workspace_coverage_termination, workspace_coverage_heartbeat_statement, + workspace_coverage_host_identity_record_digest, workspace_coverage_launch_statement, + workspace_coverage_termination_statement, write_workspace_coverage_managed_process_launch, write_workspace_coverage_process_termination_observation, write_workspace_coverage_provider_heartbeat, write_workspace_coverage_recovery_directive, }; diff --git a/epiphany-core/src/workspace_coverage_process_documents.rs b/epiphany-core/src/workspace_coverage_process_documents.rs index da19c05e..e4324fe0 100644 --- a/epiphany-core/src/workspace_coverage_process_documents.rs +++ b/epiphany-core/src/workspace_coverage_process_documents.rs @@ -1143,6 +1143,23 @@ pub fn authenticate_workspace_coverage_managed_process_launch( Ok(entry) } +pub fn authenticate_historical_workspace_coverage_managed_process_launch( + store_path: impl AsRef, + runtime_id: impl Into, + launch_id: &str, + host_identity: &HostIncarnationIdentityEntry, +) -> Result { + let runtime_id = runtime_id.into(); + let entry = + load_workspace_coverage_managed_process_launch(store_path, runtime_id.clone(), launch_id)? + .ok_or_else(|| anyhow!("workspace coverage managed process launch is absent"))?; + if entry.runtime_id != runtime_id { + bail!("workspace coverage launch runtime argument disagrees with signed runtime id"); + } + validate_launch(&entry, host_identity)?; + Ok(entry) +} + pub fn authenticate_workspace_coverage_provider_heartbeat( store_path: impl AsRef, runtime_id: impl Into, @@ -1231,6 +1248,24 @@ pub fn observe_workspace_coverage_managed_process( )) } +pub fn observe_historical_workspace_coverage_managed_process( + store_path: impl AsRef, + runtime_id: impl Into, + launch_id: &str, + host_identity: &HostIncarnationIdentityEntry, +) -> Result { + let launch = authenticate_historical_workspace_coverage_managed_process_launch( + store_path, + runtime_id, + launch_id, + host_identity, + )?; + Ok(observe_workspace_coverage_process_with_source( + &launch, + &NativeWorkspaceCoverageProcessObservationSource, + )) +} + pub(crate) fn authenticate_workspace_coverage_provider_heartbeat_with_envelope_digest( store_path: impl AsRef, runtime_id: impl Into, @@ -1276,14 +1311,6 @@ fn write_workspace_coverage_process_termination_observation_with_source( let runtime_id = runtime_id.into(); let node = open_epiphany_cultmesh_node(store_path, runtime_id.clone())?; - let policy_envelope = node - .cache() - .get_envelope::(&managed_policy_key())? - .ok_or_else(|| anyhow!("workspace coverage managed policy is absent"))?; - let policy: EpiphanyCultMeshManagedServicePolicyEntry = - rmp_serde::from_slice(&policy_envelope.payload)?; - validate_workspace_coverage_projector_managed_service_policy(&policy)?; - let launch_envelope = node .cache() .get_envelope::(&launch_key(launch_id))? @@ -1291,14 +1318,8 @@ fn write_workspace_coverage_process_termination_observation_with_source( let launch: WorkspaceCoverageManagedProcessLaunchEntry = rmp_serde::from_slice(&launch_envelope.payload)?; validate_launch(&launch, host.entry())?; - if launch.runtime_id != runtime_id - || launch.policy_id != policy.policy_id - || launch.policy_envelope_digest != envelope_digest(&policy_envelope) - || launch.command != policy.command - || launch.args != policy.args - || launch.cwd != policy.cwd - { - bail!("workspace coverage termination launch disagrees with current managed policy"); + if launch.runtime_id != runtime_id { + bail!("workspace coverage termination launch runtime disagrees with signed identity"); } let evidence_head_key = process_evidence_head_key(launch_id); @@ -1376,8 +1397,8 @@ fn write_workspace_coverage_process_termination_observation_with_source( heartbeat_envelope_digest: heartbeat_evidence .as_ref() .map(|(envelope, _)| envelope_digest(envelope)), - policy_id: policy.policy_id.clone(), - policy_envelope_digest: envelope_digest(&policy_envelope), + policy_id: launch.policy_id.clone(), + policy_envelope_digest: launch.policy_envelope_digest.clone(), runtime_id, host_identity_id: host.entry().identity_id.clone(), host_identity_record_digest: workspace_coverage_host_identity_record_digest(host.entry())?, @@ -1417,13 +1438,8 @@ fn write_workspace_coverage_process_termination_observation_with_source( termination_id: Some(entry.termination_id.clone()), }; let replacement = node.cache().prepare_entry(&key, &entry)?.0; - let mut expected = vec![ - policy_envelope.clone(), - launch_envelope.clone(), - evidence_head_envelope, - ]; + let mut expected = vec![launch_envelope.clone(), evidence_head_envelope]; let mut replacements = vec![ - policy_envelope, launch_envelope, node.cache() .prepare_entry(&evidence_head_key, &terminal_head)? @@ -1437,7 +1453,7 @@ fn write_workspace_coverage_process_termination_observation_with_source( if !SingleFileMessagePackBackingStore::new(store_path) .compare_and_swap_batch(&expected, replacements)? { - bail!("workspace coverage termination lost exact policy/launch/heartbeat CAS or collided"); + bail!("workspace coverage termination lost exact launch/heartbeat CAS or collided"); } Ok(entry) } @@ -1470,13 +1486,6 @@ pub fn authenticate_workspace_coverage_process_termination_observation( bail!("workspace coverage termination request disagrees with signed identity"); } let node = open_epiphany_cultmesh_node(store_path, runtime_id)?; - let policy_envelope = node - .cache() - .get_envelope::(&managed_policy_key())? - .ok_or_else(|| anyhow!("workspace coverage termination policy evidence is absent"))?; - let policy: EpiphanyCultMeshManagedServicePolicyEntry = - rmp_serde::from_slice(&policy_envelope.payload)?; - validate_workspace_coverage_projector_managed_service_policy(&policy)?; let launch_envelope = node .cache() .get_envelope::(&launch_key(launch_id))? @@ -1524,9 +1533,7 @@ pub fn authenticate_workspace_coverage_process_termination_observation( (None, None) => None, _ => bail!("workspace coverage termination has partial heartbeat evidence"), }; - if entry.policy_id != policy.policy_id - || entry.policy_envelope_digest != envelope_digest(&policy_envelope) - || launch.policy_id != policy.policy_id + if entry.policy_id != launch.policy_id || launch.policy_envelope_digest != entry.policy_envelope_digest || entry.launch_envelope_digest != envelope_digest(&launch_envelope) || entry.host_identity_id != launch.host_identity_id @@ -3927,16 +3934,15 @@ mod tests { "local", advanced_policy, )?; - assert!( + assert_eq!( authenticate_workspace_coverage_process_termination_observation( &store, "local", &launch.launch_id, host.entry(), - ) - .expect_err("moved policy source invalidates exact termination chain") - .to_string() - .contains("disagrees") + )?, + proof, + "policy rotation must not erase historical process evidence" ); } @@ -4085,6 +4091,42 @@ mod tests { Ok(()) } + #[test] + fn policy_rotation_preserves_historical_termination_authority() -> Result<()> { + let temp = tempfile::tempdir()?; + let (store, host, launch, _provider) = persisted_chain(temp.path())?; + let mut moved_policy = policy()?; + moved_policy.updated_at_utc = "2026-07-17T00:00:01Z".into(); + write_epiphany_cultmesh_workspace_coverage_projector_service_policy( + &store, + "local", + moved_policy, + )?; + + let termination = write_workspace_coverage_process_termination_observation_with_source( + &store, + "local", + &launch.launch_id, + &host, + &FakeObservation { + boot: Some(launch.boot_identity.clone()), + process: ProcessInstanceObservation::Missing, + }, + )?; + assert_eq!(termination.policy_id, launch.policy_id); + assert_eq!( + termination.policy_envelope_digest, + launch.policy_envelope_digest + ); + authenticate_workspace_coverage_process_termination_observation( + &store, + "local", + &launch.launch_id, + host.entry(), + )?; + Ok(()) + } + #[test] fn termination_before_first_heartbeat_seals_the_process_evidence_head() -> Result<()> { let temp = tempfile::tempdir()?; diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index a2420892..116661d2 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -2821,3 +2821,31 @@ commit, and let Idunn deploy under the engaged brake. Require the immutable package/witness, all three healthy service owners, signed aggregate health, and `deployment.env`; then prove the ordinary organization loop and its negative authority boundaries. No host reboot is authorized. + +## Managed-policy rotation preserves historical process evidence (2026-07-19) + +Exact candidate `2d4c92d24c2007858914d352b5892f599e6a20e9` sealed release +`sha256-1dd1271405e82958df2c03133b0d89d2555a8a3c7013486e1c41465ec84f6b3c` +and witness +`sha256-e01803545e557488cf2a0a42c31bb65e17ff7ead21790d7c982216901d94e12b`. +The generation handoff and resident mixed-store repairs held: all three services +started, Heartbeat remained braked, and resident Self remained stably braked. +Workspace coverage did not launch because its prior signed launch was checked +against the newly written current managed policy and became impossible to +observe, terminalize, or replace. The exact transaction was rolled back, +Bifrost authority revoked, services stopped, active pointer removed, and no +deployment witness exists. + +The source authority is now split correctly. Historical host-signed launch +evidence remains authentic across policy rotation and may prove only exact +process identity and immutable termination. New process admission still +requires the exact current policy digest. Reconciliation terminalizes the stale +identity without deleting history, then launches a causally bound replacement +under the current policy. Public current-process observation continues to reject +stale launches. Focused process tests pass 11/11, supervisor authority tests +pass 24/24, full core passes 573/573 with one ignored, and all binaries compile. + +Next action: commit/push the successor, cross its exact SHA through Bifrost and +Idunn under brake, and require historical terminal evidence, current-policy +replacement launch, three stable services, signed aggregate health, and the +final deployment witness before rehearsing Starfire-independent operation. diff --git a/state/map.yaml b/state/map.yaml index 16b123f6..313c1f4b 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -790,4 +790,6 @@ current_status: - Exact successor `aa3d2330062a34145cb70b527e02fa00215eac1b` passed local proof but was revoked before deployment because live packaging repeated `Permission denied` when creating a random staging directory directly under the shared root-owned release directory. This disproved the predecessor-cleanup race hypothesis. The builder's long-lived ownership lease over `/srv/epiphany/app/releases` was the incoherent boundary; no package, service, or witness was published from this attempt. - Release construction is now generation-scoped. The packager creates its random staging sibling beneath the exact source-commit directory, then atomically renames to the final release id within that same directory, preserving canonical absolute witness paths. Idunn keeps the shared release root root-owned and grants `epiphany-builder` only the exact commit directory during construction; cleanup removes only nested staging and seals that generation root-owned. Candidate `03e1afd24546a7ca37e900c47ca26ae257b14161` proved this boundary by sealing immutable release `sha256-fe7f25ff60d84ae9316d7b4aa2833bd5d1b65189c9d08cc64e903a83d8f35268`; its first app publication could not traverse the still builder-owned 0750 generation parent. Cleanup sealed the parent and an automatic exact-package retry started all three services. The actuator must seal the exact generation parent root-owned 0755 before app-owned publication, not only during cleanup. - Candidate `03e1afd24546a7ca37e900c47ca26ae257b14161` was not deployed. Projection and Heartbeat were healthy and braked, but resident Self opened the shared resident store as if every row belonged to its schema catalog and rejected the legitimate `epiphany.resident_cognition.provider_readiness` row. The exact process group was terminated, all three services were stopped, `active-release.env` was removed, and no deployment witness exists. Resident Self now constructs an owner-filtered typed view from raw envelopes, admits only its six owned document families, rejects duplicate owned identities, and preserves foreign readiness rows during writes. Full core proof passes 572/572 with one ignored. - next_action: "Commit and push the resident-Self mixed-store repair and generation-parent sealing actuator, install the exact root-owned actuator on Yggdrasil, revoke obsolete Bifrost authority, authorize the new exact Epiphany commit, and let Idunn deploy it under the engaged sleep brake. Require immutable package inspection, app-owned release publication, three healthy local service owners, signed aggregate health, and the final deployment.env witness. Then run the Starfire-offline rehearsal before 2026-07-22: persistent Modeling map -> autonomous bounded Imagination proposal, with classified organization feedback through Persona as an additional pressure path; explicit Mind adoption -> Hands consequence -> Bifrost release -> Idunn deployment remain separate gates. Prove replay/tamper refusal and that conversation alone grants no Mind, Hands, Persona-speech, release, or deployment authority. No host reboot is authorized." + - Candidate `2d4c92d24c2007858914d352b5892f599e6a20e9` sealed immutable release `sha256-1dd1271405e82958df2c03133b0d89d2555a8a3c7013486e1c41465ec84f6b3c` with witness `sha256-e01803545e557488cf2a0a42c31bb65e17ff7ead21790d7c982216901d94e12b`. Generation-parent sealing and resident mixed-store ownership both held: all three services started, Heartbeat remained `braked-after-ack-reconciliation`, and resident Self remained stably `braked`. Deployment still failed honestly because the prior workspace-coverage launch was authenticated only against the newly written current policy. Release rotation therefore made signed historical launch evidence impossible to observe, terminalize, or replace. The transaction was rolled back, its Bifrost authority revoked, services stopped, active pointer removed, and no deployment witness exists. + - Workspace-coverage process authority now separates history from admission. A stored host-signed launch remains authentic historical identity after managed-policy rotation and may authorize only exact process observation and immutable termination evidence. Its termination binds the launch's signed policy id/digest and CAS-fences the exact launch/evidence head; it no longer pretends the current policy authored history. Reconciliation detects a stale-policy latest launch, terminates only its exact live process identity or records terminal sight, then launches a replacement bound to the exact current policy and historical termination. Current public observation still rejects stale launches, and only a current-policy launch can run. Full core proof passes 573/573 with one ignored; all binaries compile. + next_action: "Commit and push the historical-process/current-policy authority split, authorize the exact successor through Bifrost, and let Idunn redeploy under the engaged sleep brake. Require stale launch terminalization without evidence deletion, a replacement workspace projector bound to the new policy, immutable package inspection, app-owned release publication, three healthy local service owners, signed aggregate health, and the final deployment.env witness. Then run the Starfire-offline rehearsal before 2026-07-22: persistent Modeling map -> autonomous bounded Imagination proposal, with classified organization feedback through Persona as an additional pressure path; explicit Mind adoption -> Hands consequence -> Bifrost release -> Idunn deployment remain separate gates. Prove replay/tamper refusal and that conversation alone grants no Mind, Hands, Persona-speech, release, or deployment authority. No host reboot is authorized."