diff --git a/epiphany-core/src/resident_self.rs b/epiphany-core/src/resident_self.rs index 05ecd659..ac365aed 100644 --- a/epiphany-core/src/resident_self.rs +++ b/epiphany-core/src/resident_self.rs @@ -2691,23 +2691,44 @@ pub fn validate_resident_self_prepared_objective( let state = cache .get::(RESIDENT_SELF_STATE_KEY)? .ok_or_else(|| anyhow!("resident Self state is absent at objective bootstrap"))?; - let prepared = state - .prepared_launch - .as_ref() - .filter(|prepared| prepared.preparation_id == preparation_id) - .ok_or_else(|| anyhow!("resident Self preparation is absent at objective bootstrap"))?; let objective_digest = digest_parts([objective.as_bytes()]); - if prepared.grant.pressure_kind != "operator-objective" - || prepared.grant.grant_id != claim.grant_id - || prepared.launch_digest != claim.launch_digest - || prepared.grant.objective != objective - || prepared.objective_digest != objective_digest - { + if claim.preparation_id != preparation_id { return Err(anyhow!( "resident coordinator objective disagrees with its exact prepared grant" )); } - Ok(()) + if let Some(prepared) = state + .prepared_launch + .as_ref() + .filter(|prepared| prepared.preparation_id == preparation_id) + { + if prepared.grant.pressure_kind == "operator-objective" + && prepared.grant.grant_id == claim.grant_id + && prepared.launch_digest == claim.launch_digest + && prepared.grant.objective == objective + && prepared.objective_digest == objective_digest + { + return Ok(()); + } + } + if let Some(active) = state.active_turn.as_ref() { + let ResidentSelfWake::Explicit { + objective: active_objective, + } = &active.wake; + if active.grant_id == claim.grant_id + && active.launch_digest == claim.launch_digest + && active.process_id == claim.process_id + && active.process_creation_token == claim.process_creation_token + && active.process_executable_path == claim.executable_path + && active_objective == objective + && active.objective_digest == objective_digest + { + return Ok(()); + } + } + Err(anyhow!( + "resident coordinator objective disagrees with its exact prepared grant or active lease" + )) } #[cfg(test)] @@ -4509,6 +4530,13 @@ mod tests { &claim, "Run one bounded planning turn.", )?; + acknowledge_resident_self_launch(&store, &prepared.preparation_id, &process, 5)?; + validate_resident_self_prepared_objective( + &store, + &prepared.preparation_id, + &claim, + "Run one bounded planning turn.", + )?; assert!( validate_resident_self_prepared_objective( &store, diff --git a/notes/epiphany-current-algorithmic-map.md b/notes/epiphany-current-algorithmic-map.md index 8b4532a8..4235a44b 100644 --- a/notes/epiphany-current-algorithmic-map.md +++ b/notes/epiphany-current-algorithmic-map.md @@ -4240,8 +4240,9 @@ Soul binding, and split-admission replay. - Owner: canonical coordinator state owns the long-lived thread objective; resident Self's exact prepared launch owns one resident turn directive. - Inputs: standalone startup may supply a typed user-objective intake. Resident - startup supplies an exact preparation, grant, child claim, launch digest, - objective digest, and argv objective. + startup supplies an exact preparation or its canonically promoted active + lease, grant, child claim, process identity, launch digest, objective digest, + and argv objective. - Outputs: standalone intake atomically seeds state plus `UserObjectiveIntake`. Resident bootstrap emits authenticated per-turn command context and leaves the canonical objective byte-identical. @@ -4250,7 +4251,9 @@ Soul binding, and split-admission replay. - Forbidden writers: a resident directive cannot call the seed-only objective intake, replace canonical state, or rely on an absent legacy intake envelope. A foreign objective cannot pass the prepared-launch digest checks. -- Shared paths: plain operator pressures use resident authentication; typed +- Shared paths: objective authentication reads either side of the single + prepared-to-active CAS, so parent acknowledgement cannot race away the + authority. Plain operator pressures use resident authentication; typed proposal/consideration continuations remain objective-free; standalone coordinator startup retains initial intake. - Cut line: branch objective handling on authenticated resident bootstrap diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index c73965bd..53eadd5a 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -3880,10 +3880,12 @@ cannot own later resident directives. The source cut separates those authorities. Standalone coordinator startup still atomically seeds the canonical Mind objective. A resident launch instead -authenticates its argv objective against the exact persisted prepared grant, -child claim, launch digest, and objective digest, then treats it as per-turn +authenticates its argv objective against the exact persisted prepared grant or +its canonically promoted active lease, plus child claim, process identity, +launch digest, and objective digest. It then treats the directive as per-turn command context without changing the canonical objective. Wrong objective or -non-operator pressure refuses. The focused resident test, standalone seed-only -test, and coordinator-runtime binary check pass. Package/publish this cut, +foreign launch refuses. The focused resident test proves both sides of the +prepare-to-active transition; the standalone seed-only test and +coordinator-runtime binary check pass. Package/publish this cut, restart c008, release only for committed attempt 2, and require exact settlement before re-engaging the brake.