fix: preserve ambiguous legacy session attribution

This commit is contained in:
2026-10-05 12:50:47 +09:00
parent d2a0b75045
commit cc97987482
6 changed files with 322 additions and 70 deletions
+5
View File
@@ -4782,6 +4782,11 @@ pub struct SubjektivStageCandidateResponse {
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct SubjektivRecordSessionRequest { pub struct SubjektivRecordSessionRequest {
pub session_id: String, pub session_id: String,
/// Creation is allowed only while finalizing a newly generated Session or
/// recovering that exact lifecycle's durable incomplete state. Restoring a
/// legacy Session uses verification-only mode so current ownership cannot
/// invent historical attribution.
pub create_if_missing: bool,
} }
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
@@ -119,10 +119,19 @@ pub struct WorkerPeer {
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "state", rename_all = "snake_case", deny_unknown_fields)] #[serde(tag = "state", rename_all = "snake_case", deny_unknown_fields)]
pub enum SubjektivSessionAttributionState { pub enum SubjektivSessionAttributionState {
/// A lifecycle-created Session sent a create-if-missing request but the
/// Host could not determine whether the Backend committed it.
OutcomeUnknown { OutcomeUnknown {
session_id: SessionId, session_id: SessionId,
diagnostic: String, diagnostic: String,
}, },
/// A legacy restored Session has no local lifecycle evidence. Recovery may
/// verify an immutable Backend record but must never create one from current
/// ownership.
LegacyUnknown {
session_id: SessionId,
diagnostic: String,
},
Failed { Failed {
session_id: SessionId, session_id: SessionId,
diagnostic: String, diagnostic: String,
@@ -137,6 +146,7 @@ impl SubjektivSessionAttributionState {
pub fn session_id(&self) -> SessionId { pub fn session_id(&self) -> SessionId {
match self { match self {
Self::OutcomeUnknown { session_id, .. } Self::OutcomeUnknown { session_id, .. }
| Self::LegacyUnknown { session_id, .. }
| Self::Failed { session_id, .. } | Self::Failed { session_id, .. }
| Self::Confirmed { session_id, .. } => *session_id, | Self::Confirmed { session_id, .. } => *session_id,
} }
+13 -6
View File
@@ -129,10 +129,11 @@ use worker::feature::builtin::{
#[cfg(feature = "ws-server")] #[cfg(feature = "ws-server")]
use worker::ipc::protocol_session::{live_log_entry_event, subscribe_worker_protocol_session}; use worker::ipc::protocol_session::{live_log_entry_event, subscribe_worker_protocol_session};
use worker::{ use worker::{
PreparedWorker, PromptCatalogSource, SegmentLogSink, Worker, WorkerBootstrap, PreparedWorker, PromptCatalogSource, SegmentLogSink, SubjektivSessionAttributionLifecycle,
WorkerBootstrapError, WorkerBootstrapLayout, WorkerControllerTransport, WorkerError, Worker, WorkerBootstrap, WorkerBootstrapError, WorkerBootstrapLayout,
WorkerFilesystemAuthority, WorkerHandle, WorkerSharedState, WorkerWorkspaceContext, WorkerControllerTransport, WorkerError, WorkerFilesystemAuthority, WorkerHandle,
WorkspaceClient, WorkspaceId, bash_output_dir_for_worker_id, WorkerSharedState, WorkerWorkspaceContext, WorkspaceClient, WorkspaceId,
bash_output_dir_for_worker_id,
}; };
const DEFAULT_BACKEND_ID: &str = "worker-crate"; const DEFAULT_BACKEND_ID: &str = "worker-crate";
@@ -1280,7 +1281,10 @@ impl RuntimeWorkerFactory for ProfileRuntimeWorkerFactory {
let worker = prepared.worker_mut(); let worker = prepared.worker_mut();
validate_worker_memory_settings(worker.manifest(), &request.request)?; validate_worker_memory_settings(worker.manifest(), &request.request)?;
worker worker
.finalize_subjektiv_session_attribution(request.request.subjektiv_attached) .finalize_subjektiv_session_attribution(
request.request.subjektiv_attached,
SubjektivSessionAttributionLifecycle::NewSession,
)
.map_err(|error| format!("finalize Worker Session attribution: {error}"))?; .map_err(|error| format!("finalize Worker Session attribution: {error}"))?;
let workdir_capabilities = request let workdir_capabilities = request
.workdir_attachments .workdir_attachments
@@ -1521,7 +1525,10 @@ impl RuntimeWorkerFactory for ProfileRuntimeWorkerFactory {
}; };
validate_worker_memory_settings(worker.manifest(), &request.request)?; validate_worker_memory_settings(worker.manifest(), &request.request)?;
worker worker
.finalize_subjektiv_session_attribution(request.request.subjektiv_attached) .finalize_subjektiv_session_attribution(
request.request.subjektiv_attached,
SubjektivSessionAttributionLifecycle::RestoredSession,
)
.map_err(|error| format!("finalize restored Worker Session attribution: {error}"))?; .map_err(|error| format!("finalize restored Worker Session attribution: {error}"))?;
let flow_transition_enabled = worker.manifest().feature.flow.enabled; let flow_transition_enabled = worker.manifest().feature.flow.enabled;
let workdir_sessions = restored_workdir_router( let workdir_sessions = restored_workdir_router(
+6 -5
View File
@@ -58,9 +58,10 @@ pub use session_history::{
}; };
pub use shared_state::WorkerSharedState; pub use shared_state::WorkerSharedState;
pub use worker::{ pub use worker::{
LocalWorkingDirectory, Worker, WorkerError, WorkerFilesystemAuthority, WorkerRunResult, LocalWorkingDirectory, SubjektivSessionAttributionLifecycle, Worker, WorkerError,
WorkerWorkspaceContext, WorkspaceClient, WorkspaceClientError, WorkspaceId, WorkspaceIdError, WorkerFilesystemAuthority, WorkerRunResult, WorkerWorkspaceContext, WorkspaceClient,
WorkspacePromptCatalogResolution, WorkspaceRequest, WorkspaceRequestMethod, WorkspaceResponse, WorkspaceClientError, WorkspaceId, WorkspaceIdError, WorkspacePromptCatalogResolution,
WorkspaceServerOperation, WorkspaceWorkerDiscoveryRequest, apply_worker_manifest, WorkspaceRequest, WorkspaceRequestMethod, WorkspaceResponse, WorkspaceServerOperation,
marker_workspace_client, unavailable_workspace_client, WorkspaceWorkerDiscoveryRequest, apply_worker_manifest, marker_workspace_client,
unavailable_workspace_client,
}; };
+237 -54
View File
@@ -6513,6 +6513,38 @@ impl<C: LlmClient + 'static, St: Store> Worker<C, St> {
} }
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SubjektivSessionAttributionLifecycle {
NewSession,
RestoredSession,
}
fn incomplete_subjektiv_attribution_state(
session_id: SessionId,
create_if_missing: bool,
diagnostic: String,
) -> SubjektivSessionAttributionState {
if create_if_missing {
SubjektivSessionAttributionState::OutcomeUnknown {
session_id,
diagnostic,
}
} else {
SubjektivSessionAttributionState::LegacyUnknown {
session_id,
diagnostic,
}
}
}
fn incomplete_subjektiv_attribution_label(create_if_missing: bool) -> &'static str {
if create_if_missing {
"outcome_unknown"
} else {
"legacy_unknown"
}
}
fn bounded_attribution_diagnostic(mut diagnostic: String) -> String { fn bounded_attribution_diagnostic(mut diagnostic: String) -> String {
const MAX_CHARS: usize = 1024; const MAX_CHARS: usize = 1024;
if diagnostic.chars().count() > MAX_CHARS { if diagnostic.chars().count() > MAX_CHARS {
@@ -6714,15 +6746,18 @@ where
/// Complete the Host-owned Session-to-Subject attribution boundary before a /// Complete the Host-owned Session-to-Subject attribution boundary before a
/// runtime-managed Worker is exposed or accepts input. The authoritative /// runtime-managed Worker is exposed or accepts input. The authoritative
/// attachment bit comes from the Runtime create/restore request; names, /// attachment bit and lifecycle kind come from the Runtime create/restore
/// Profiles, and current singleton ownership are never used as identity. /// request; names, Profiles, and current singleton ownership are never used
/// as historical identity.
/// ///
/// `OutcomeUnknown` is persisted before network I/O. A lost response or /// New Sessions persist `OutcomeUnknown` before create-if-missing network I/O.
/// process interruption is therefore recovered only by a later create/restore /// A restore retries creation only for that durable state (or `Failed`). An
/// lifecycle, where the Backend's same-attribution idempotency is safe to use. /// unmarked legacy restore is verification-only and persists `LegacyUnknown`,
/// so current ownership can never invent historical attribution.
pub fn finalize_subjektiv_session_attribution( pub fn finalize_subjektiv_session_attribution(
&self, &self,
subjektiv_attached: bool, subjektiv_attached: bool,
lifecycle: SubjektivSessionAttributionLifecycle,
) -> Result<(), WorkerError> { ) -> Result<(), WorkerError> {
let worker_name = self.manifest.worker.name.as_str(); let worker_name = self.manifest.worker.name.as_str();
let metadata = self.store.read_by_name(worker_name)?.ok_or_else(|| { let metadata = self.store.read_by_name(worker_name)?.ok_or_else(|| {
@@ -6762,32 +6797,41 @@ where
return Ok(()); return Ok(());
} }
if let Some(state) = metadata.subjektiv_session_attribution.as_ref() { let create_if_missing = match metadata.subjektiv_session_attribution.as_ref() {
if state.session_id() != session_id { Some(state) => {
return Err(WorkerError::SubjektivSessionAttribution { if state.session_id() != session_id {
state: "session_mismatch", return Err(WorkerError::SubjektivSessionAttribution {
message: format!( state: "session_mismatch",
"persisted Subjektiv attribution targets Session {}, not {session_id}", message: format!(
state.session_id() "persisted Subjektiv attribution targets Session {}, not {session_id}",
), state.session_id()
}); ),
});
}
match state {
SubjektivSessionAttributionState::Confirmed { .. } => return Ok(()),
SubjektivSessionAttributionState::OutcomeUnknown { .. }
| SubjektivSessionAttributionState::Failed { .. } => true,
SubjektivSessionAttributionState::LegacyUnknown { .. } => false,
}
} }
if matches!(state, SubjektivSessionAttributionState::Confirmed { .. }) { None => matches!(lifecycle, SubjektivSessionAttributionLifecycle::NewSession),
return Ok(()); };
} let initial_diagnostic = if create_if_missing {
} "attribution creation started; completion is not yet confirmed"
} else {
self.persist_subjektiv_session_attribution( "legacy Session has no local attribution evidence; immutable Backend verification started"
SubjektivSessionAttributionState::OutcomeUnknown { };
session_id, self.persist_subjektiv_session_attribution(incomplete_subjektiv_attribution_state(
diagnostic: "attribution request started; completion is not yet confirmed" session_id,
.to_string(), create_if_missing,
}, initial_diagnostic.to_string(),
)?; ))?;
let response = match self.workspace_client().execute_server_operation( let response = match self.workspace_client().execute_server_operation(
WorkspaceServerOperation::SubjektivRecordSession( WorkspaceServerOperation::SubjektivRecordSession(
server_api::SubjektivRecordSessionRequest { server_api::SubjektivRecordSessionRequest {
session_id: session_id.to_string(), session_id: session_id.to_string(),
create_if_missing,
}, },
), ),
) { ) {
@@ -6797,13 +6841,14 @@ where
"Session attribution transport outcome is unknown: {error}" "Session attribution transport outcome is unknown: {error}"
)); ));
self.persist_subjektiv_session_attribution( self.persist_subjektiv_session_attribution(
SubjektivSessionAttributionState::OutcomeUnknown { incomplete_subjektiv_attribution_state(
session_id, session_id,
diagnostic: message.clone(), create_if_missing,
}, message.clone(),
),
)?; )?;
return Err(WorkerError::SubjektivSessionAttribution { return Err(WorkerError::SubjektivSessionAttribution {
state: "outcome_unknown", state: incomplete_subjektiv_attribution_label(create_if_missing),
message, message,
}); });
} }
@@ -6813,12 +6858,24 @@ where
"Session attribution was rejected with HTTP {}: {}", "Session attribution was rejected with HTTP {}: {}",
response.status, response.body response.status, response.body
)); ));
self.persist_subjektiv_session_attribution(SubjektivSessionAttributionState::Failed { let state = if create_if_missing {
session_id, SubjektivSessionAttributionState::Failed {
diagnostic: message.clone(), session_id,
})?; diagnostic: message.clone(),
}
} else {
SubjektivSessionAttributionState::LegacyUnknown {
session_id,
diagnostic: message.clone(),
}
};
self.persist_subjektiv_session_attribution(state)?;
return Err(WorkerError::SubjektivSessionAttribution { return Err(WorkerError::SubjektivSessionAttribution {
state: "failed", state: if create_if_missing {
"failed"
} else {
"legacy_unknown"
},
message, message,
}); });
} }
@@ -6830,13 +6887,14 @@ where
"Session attribution response outcome is unknown: {error}" "Session attribution response outcome is unknown: {error}"
)); ));
self.persist_subjektiv_session_attribution( self.persist_subjektiv_session_attribution(
SubjektivSessionAttributionState::OutcomeUnknown { incomplete_subjektiv_attribution_state(
session_id, session_id,
diagnostic: message.clone(), create_if_missing,
}, message.clone(),
),
)?; )?;
return Err(WorkerError::SubjektivSessionAttribution { return Err(WorkerError::SubjektivSessionAttribution {
state: "outcome_unknown", state: incomplete_subjektiv_attribution_label(create_if_missing),
message, message,
}); });
} }
@@ -6848,14 +6906,13 @@ where
let message = let message =
"Session attribution response did not confirm the requested immutable identity" "Session attribution response did not confirm the requested immutable identity"
.to_string(); .to_string();
self.persist_subjektiv_session_attribution( self.persist_subjektiv_session_attribution(incomplete_subjektiv_attribution_state(
SubjektivSessionAttributionState::OutcomeUnknown { session_id,
session_id, create_if_missing,
diagnostic: message.clone(), message.clone(),
}, ))?;
)?;
return Err(WorkerError::SubjektivSessionAttribution { return Err(WorkerError::SubjektivSessionAttribution {
state: "outcome_unknown", state: incomplete_subjektiv_attribution_label(create_if_missing),
message, message,
}); });
} }
@@ -9794,10 +9851,25 @@ mod build_summary_prompt_tests {
let (worker, store, _temp) = attribution_lifecycle_worker(client.clone()).await; let (worker, store, _temp) = attribution_lifecycle_worker(client.clone()).await;
let session_id = worker.session_id(); let session_id = worker.session_id();
worker.finalize_subjektiv_session_attribution(true).unwrap(); worker
worker.finalize_subjektiv_session_attribution(true).unwrap(); .finalize_subjektiv_session_attribution(
true,
SubjektivSessionAttributionLifecycle::NewSession,
)
.unwrap();
worker
.finalize_subjektiv_session_attribution(
true,
SubjektivSessionAttributionLifecycle::RestoredSession,
)
.unwrap();
assert_eq!(client.requests.lock().unwrap().len(), 1); let requests = client.requests.lock().unwrap();
assert_eq!(requests.len(), 1);
let request: server_api::SubjektivRecordSessionRequest =
serde_json::from_str(requests[0].body.as_deref().unwrap()).unwrap();
assert!(request.create_if_missing);
drop(requests);
let metadata = store let metadata = store
.read_by_name(&worker.manifest().worker.name) .read_by_name(&worker.manifest().worker.name)
.unwrap() .unwrap()
@@ -9823,7 +9895,10 @@ mod build_summary_prompt_tests {
])); ]));
let (worker, store, _temp) = attribution_lifecycle_worker(client.clone()).await; let (worker, store, _temp) = attribution_lifecycle_worker(client.clone()).await;
let error = worker let error = worker
.finalize_subjektiv_session_attribution(true) .finalize_subjektiv_session_attribution(
true,
SubjektivSessionAttributionLifecycle::NewSession,
)
.unwrap_err(); .unwrap_err();
assert!(error.to_string().contains(expected_state)); assert!(error.to_string().contains(expected_state));
let incomplete = store let incomplete = store
@@ -9840,7 +9915,12 @@ mod build_summary_prompt_tests {
) | ("failed", SubjektivSessionAttributionState::Failed { .. }) ) | ("failed", SubjektivSessionAttributionState::Failed { .. })
)); ));
worker.finalize_subjektiv_session_attribution(true).unwrap(); worker
.finalize_subjektiv_session_attribution(
true,
SubjektivSessionAttributionLifecycle::RestoredSession,
)
.unwrap();
assert_eq!(client.requests.lock().unwrap().len(), 2); assert_eq!(client.requests.lock().unwrap().len(), 2);
assert!(matches!( assert!(matches!(
store store
@@ -9861,7 +9941,10 @@ mod build_summary_prompt_tests {
let (worker, store, _temp) = attribution_lifecycle_worker(client.clone()).await; let (worker, store, _temp) = attribution_lifecycle_worker(client.clone()).await;
worker worker
.finalize_subjektiv_session_attribution(false) .finalize_subjektiv_session_attribution(
false,
SubjektivSessionAttributionLifecycle::NewSession,
)
.unwrap(); .unwrap();
assert!(client.requests.lock().unwrap().is_empty()); assert!(client.requests.lock().unwrap().is_empty());
@@ -9923,8 +10006,18 @@ mod build_summary_prompt_tests {
.unwrap(); .unwrap();
assert_eq!(worker.session_id(), session_id); assert_eq!(worker.session_id(), session_id);
worker.finalize_subjektiv_session_attribution(true).unwrap(); worker
assert_eq!(client.requests.lock().unwrap().len(), 1); .finalize_subjektiv_session_attribution(
true,
SubjektivSessionAttributionLifecycle::RestoredSession,
)
.unwrap();
let requests = client.requests.lock().unwrap();
assert_eq!(requests.len(), 1);
let request: server_api::SubjektivRecordSessionRequest =
serde_json::from_str(requests[0].body.as_deref().unwrap()).unwrap();
assert!(request.create_if_missing);
drop(requests);
assert!(matches!( assert!(matches!(
store store
.read_by_name(&worker.manifest().worker.name) .read_by_name(&worker.manifest().worker.name)
@@ -9938,6 +10031,96 @@ mod build_summary_prompt_tests {
)); ));
} }
#[tokio::test]
async fn materialized_legacy_session_without_backend_attribution_stays_ambiguous() {
let client = Arc::new(AttributionLifecycleClient::new([
AttributionOutcome::Rejected,
]));
let (worker, store, _temp) = attribution_lifecycle_worker(client.clone()).await;
let session_id = worker.session_id();
store
.set_active(
&worker.manifest().worker.name,
Some(WorkerActiveSegmentRef::active_segment(
session_id,
worker.segment_state.location().segment_id,
)),
None,
)
.unwrap();
let error = worker
.finalize_subjektiv_session_attribution(
true,
SubjektivSessionAttributionLifecycle::RestoredSession,
)
.unwrap_err();
assert!(error.to_string().contains("legacy_unknown"));
let requests = client.requests.lock().unwrap();
assert_eq!(requests.len(), 1);
let request: server_api::SubjektivRecordSessionRequest =
serde_json::from_str(requests[0].body.as_deref().unwrap()).unwrap();
assert_eq!(request.session_id, session_id.to_string());
assert!(!request.create_if_missing);
drop(requests);
assert!(matches!(
store
.read_by_name(&worker.manifest().worker.name)
.unwrap()
.unwrap()
.subjektiv_session_attribution,
Some(SubjektivSessionAttributionState::LegacyUnknown {
session_id: stored_session_id,
..
}) if stored_session_id == session_id
));
}
#[tokio::test]
async fn materialized_legacy_session_adopts_only_matching_backend_attribution() {
let client = Arc::new(AttributionLifecycleClient::new([
AttributionOutcome::Success,
]));
let (worker, store, _temp) = attribution_lifecycle_worker(client.clone()).await;
let session_id = worker.session_id();
store
.set_active(
&worker.manifest().worker.name,
Some(WorkerActiveSegmentRef::active_segment(
session_id,
worker.segment_state.location().segment_id,
)),
None,
)
.unwrap();
worker
.finalize_subjektiv_session_attribution(
true,
SubjektivSessionAttributionLifecycle::RestoredSession,
)
.unwrap();
let requests = client.requests.lock().unwrap();
assert_eq!(requests.len(), 1);
let request: server_api::SubjektivRecordSessionRequest =
serde_json::from_str(requests[0].body.as_deref().unwrap()).unwrap();
assert!(!request.create_if_missing);
drop(requests);
assert!(matches!(
store
.read_by_name(&worker.manifest().worker.name)
.unwrap()
.unwrap()
.subjektiv_session_attribution,
Some(SubjektivSessionAttributionState::Confirmed {
session_id: stored_session_id,
ref subject_id,
}) if stored_session_id == session_id && subject_id == "subject-test"
));
}
fn text_segment(text: &str) -> Segment { fn text_segment(text: &str) -> Segment {
Segment::Text { Segment::Text {
content: text.into(), content: text.into(),
+51 -5
View File
@@ -17638,11 +17638,20 @@ async fn scoped_record_subjektiv_session(
Json(request): Json<server_api::SubjektivRecordSessionRequest>, Json(request): Json<server_api::SubjektivRecordSessionRequest>,
) -> ApiResult<Json<server_api::SubjektivRecordSessionResponse>> { ) -> ApiResult<Json<server_api::SubjektivRecordSessionResponse>> {
validate_workspace_scope(&api, &path.workspace_id)?; validate_workspace_scope(&api, &path.workspace_id)?;
let attribution = let attribution = if request.create_if_missing {
subjektiv_session_attribution(&api, &path.workspace_id, &context, &request.session_id)?; let attribution =
let attribution = open_subjektiv_store(&api)? subjektiv_session_attribution(&api, &path.workspace_id, &context, &request.session_id)?;
.record_session_attribution(attribution) open_subjektiv_store(&api)?
.map_err(|error| Error::InvalidInput(error.to_string()))?; .record_session_attribution(attribution)
.map_err(|error| Error::InvalidInput(error.to_string()))?
} else {
require_existing_subjektiv_session_attribution(
&api,
&path.workspace_id,
&context,
&request.session_id,
)?
};
Ok(Json(server_api::SubjektivRecordSessionResponse { Ok(Json(server_api::SubjektivRecordSessionResponse {
subject_id: attribution.subject_id, subject_id: attribution.subject_id,
session_id: attribution.session_id, session_id: attribution.session_id,
@@ -39627,6 +39636,28 @@ mod tests {
} }
) )
)); ));
let missing_legacy = scoped_record_subjektiv_session(
State(api.clone()),
AxumPath(ScopedWorkspacePath {
workspace_id: TEST_WORKSPACE_ID.to_string(),
}),
context.clone(),
Json(server_api::SubjektivRecordSessionRequest {
session_id: "session-1".to_string(),
create_if_missing: false,
}),
)
.await
.unwrap_err()
.into_response();
assert_eq!(missing_legacy.status(), StatusCode::CONFLICT);
assert!(
open_subjektiv_store(&api)
.unwrap()
.session_attribution("session-1")
.unwrap()
.is_none()
);
let Json(recorded_session) = scoped_record_subjektiv_session( let Json(recorded_session) = scoped_record_subjektiv_session(
State(api.clone()), State(api.clone()),
AxumPath(ScopedWorkspacePath { AxumPath(ScopedWorkspacePath {
@@ -39635,11 +39666,26 @@ mod tests {
context.clone(), context.clone(),
Json(server_api::SubjektivRecordSessionRequest { Json(server_api::SubjektivRecordSessionRequest {
session_id: "session-1".to_string(), session_id: "session-1".to_string(),
create_if_missing: true,
}), }),
) )
.await .await
.unwrap(); .unwrap();
assert_eq!(recorded_session.subject_id, subject.id); assert_eq!(recorded_session.subject_id, subject.id);
let Json(verified_legacy) = scoped_record_subjektiv_session(
State(api.clone()),
AxumPath(ScopedWorkspacePath {
workspace_id: TEST_WORKSPACE_ID.to_string(),
}),
context.clone(),
Json(server_api::SubjektivRecordSessionRequest {
session_id: "session-1".to_string(),
create_if_missing: false,
}),
)
.await
.unwrap();
assert_eq!(verified_legacy, recorded_session);
let operation = memory::backend::MemoryStageCandidateOperation { let operation = memory::backend::MemoryStageCandidateOperation {
source: memory::schema::SourceRef { source: memory::schema::SourceRef {