diff --git a/Cargo.lock b/Cargo.lock index edb7fced8..be1c8ccf5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7580,6 +7580,7 @@ dependencies = [ "content-history", "content-history-adapters", "content-history-store", + "error-contract", "feature-storage", "flate2", "flow", diff --git a/crates/worker-runtime/src/worker_backend/restore_recovery.rs b/crates/worker-runtime/src/worker_backend/restore_recovery.rs index 4c751e872..db5c8c85c 100644 --- a/crates/worker-runtime/src/worker_backend/restore_recovery.rs +++ b/crates/worker-runtime/src/worker_backend/restore_recovery.rs @@ -116,7 +116,7 @@ impl ProfileRuntimeWorkerFactory { .await; let mut worker = worker_result.map_err(|error| { self.retain_construction_failure(request, &error); - cause(restore_worker_cause(&error)) + RuntimeRestoreFailure::from_worker(RuntimeRestoreStage::Recovery, error) })?; let tasks = worker.retain_startup_feature_cleanup(); let sessions = worker.workdir_sessions(); @@ -141,11 +141,13 @@ impl ProfileRuntimeWorkerFactory { .map_err(|_| cause("recovery Backend Job binding failed"))?; } worker - .finalize_subjektiv_session_attribution( + .finalize_subjektiv_session_attribution_retaining_startup_failure( request.request.subjektiv_attached, SubjektivSessionAttributionLifecycle::RestoredSession, ) - .map_err(|error| cause(restore_worker_cause(&error)))?; + .map_err(|error| { + RuntimeRestoreFailure::from_worker(RuntimeRestoreStage::Recovery, error) + })?; if pending { worker::WorkerController::recover_pending_head(&mut worker, &root) .await @@ -161,21 +163,38 @@ impl ProfileRuntimeWorkerFactory { tasks .shutdown() .await - .map_err(|_| cause("recovery Feature settlement failed"))?; + .map_err(|_| RuntimeRestoreCleanupFailure::Feature)?; sessions .close_all() .await - .map_err(|_| cause("recovery Workdir close failed"))?; + .map_err(|_| RuntimeRestoreCleanupFailure::Workdir)?; if let Some(allocation) = allocation { allocation .release() - .map_err(|_| cause("recovery allocation release failed"))?; + .map_err(|_| RuntimeRestoreCleanupFailure::Allocation)?; } - Ok::<_, RuntimeRestoreFailure>(()) + Ok::<_, RuntimeRestoreCleanupFailure>(()) } .await; - result?; - cleanup?; + match (result, cleanup) { + (Err(mut primary), cleanup) => { + primary.cleanup = match cleanup { + Ok(()) => RuntimeRestoreCleanup::Confirmed, + Err(failure) => { + primary.cleanup_failure = Some(failure); + RuntimeRestoreCleanup::Failed(failure) + } + }; + return Err(primary); + } + (Ok(()), Err(cleanup)) => { + let mut failure = cause("restore recovery cleanup failed"); + failure.cleanup_failure = Some(cleanup); + failure.cleanup = RuntimeRestoreCleanup::Failed(cleanup); + return Err(failure); + } + (Ok(()), Ok(())) => {} + } self.failed_restore_resources .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) diff --git a/crates/workspace-server/Cargo.toml b/crates/workspace-server/Cargo.toml index 9a828dd41..35e3d3770 100644 --- a/crates/workspace-server/Cargo.toml +++ b/crates/workspace-server/Cargo.toml @@ -66,6 +66,7 @@ webauthn-rs = { workspace = true, features = ["conditional-ui"] } webauthn-rs-proto = "0.5.5" [dev-dependencies] +error-contract.workspace = true content-history.workspace = true content-history-store.workspace = true content-history-adapters.workspace = true diff --git a/crates/workspace-server/src/server/tests/subjektiv_journal_connected_tests.rs b/crates/workspace-server/src/server/tests/subjektiv_journal_connected_tests.rs index d008b6667..7b0f561b4 100644 --- a/crates/workspace-server/src/server/tests/subjektiv_journal_connected_tests.rs +++ b/crates/workspace-server/src/server/tests/subjektiv_journal_connected_tests.rs @@ -2365,9 +2365,21 @@ async fn connected_explicit_repair(scenario: ConnectedRepair) { assert_eq!(probe.activations.load(SeqCst), 0); assert_eq!(probe.publications.load(SeqCst), 0); assert_eq!(fixture.model.calls.load(SeqCst), model_before); - assert!( - matches!(metadata.read_by_name(&name).unwrap().unwrap().subjektiv_session_attribution, - Some(session_store::SubjektivSessionAttributionState::Failed { session_id: id, .. }) if id == session_id) + let saved_failure = metadata.read_by_name(&name).unwrap().unwrap(); + // Trust rejection occurs before the Session domain handler. An unsigned + // legacy 401 attests neither a Session refusal nor absence of earlier effects. + assert!(matches!(saved_failure.subjektiv_session_attribution, + Some(session_store::SubjektivSessionAttributionState::OutcomeUnknown { session_id: id, .. }) if id == session_id)); + let session_store::worker_metadata::StartupFailureRead::Present( + error_contract::DecodeFailure::Known(cause), + ) = saved_failure + .decode_startup_failure::() + else { + panic!("recovery must retain its typed Session unknown cause") + }; + assert_eq!( + *cause.facts().cause(), + protocol::session_failure::SessionRegistrationCause::outcome_unknown() ); let denied = record(); assert_eq!(