fix: preserve ambiguous legacy session attribution
This commit is contained in:
@@ -4782,6 +4782,11 @@ pub struct SubjektivStageCandidateResponse {
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct SubjektivRecordSessionRequest {
|
||||
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)]
|
||||
|
||||
@@ -119,10 +119,19 @@ pub struct WorkerPeer {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "state", rename_all = "snake_case", deny_unknown_fields)]
|
||||
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 {
|
||||
session_id: SessionId,
|
||||
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 {
|
||||
session_id: SessionId,
|
||||
diagnostic: String,
|
||||
@@ -137,6 +146,7 @@ impl SubjektivSessionAttributionState {
|
||||
pub fn session_id(&self) -> SessionId {
|
||||
match self {
|
||||
Self::OutcomeUnknown { session_id, .. }
|
||||
| Self::LegacyUnknown { session_id, .. }
|
||||
| Self::Failed { session_id, .. }
|
||||
| Self::Confirmed { session_id, .. } => *session_id,
|
||||
}
|
||||
|
||||
@@ -129,10 +129,11 @@ use worker::feature::builtin::{
|
||||
#[cfg(feature = "ws-server")]
|
||||
use worker::ipc::protocol_session::{live_log_entry_event, subscribe_worker_protocol_session};
|
||||
use worker::{
|
||||
PreparedWorker, PromptCatalogSource, SegmentLogSink, Worker, WorkerBootstrap,
|
||||
WorkerBootstrapError, WorkerBootstrapLayout, WorkerControllerTransport, WorkerError,
|
||||
WorkerFilesystemAuthority, WorkerHandle, WorkerSharedState, WorkerWorkspaceContext,
|
||||
WorkspaceClient, WorkspaceId, bash_output_dir_for_worker_id,
|
||||
PreparedWorker, PromptCatalogSource, SegmentLogSink, SubjektivSessionAttributionLifecycle,
|
||||
Worker, WorkerBootstrap, WorkerBootstrapError, WorkerBootstrapLayout,
|
||||
WorkerControllerTransport, WorkerError, WorkerFilesystemAuthority, WorkerHandle,
|
||||
WorkerSharedState, WorkerWorkspaceContext, WorkspaceClient, WorkspaceId,
|
||||
bash_output_dir_for_worker_id,
|
||||
};
|
||||
|
||||
const DEFAULT_BACKEND_ID: &str = "worker-crate";
|
||||
@@ -1280,7 +1281,10 @@ impl RuntimeWorkerFactory for ProfileRuntimeWorkerFactory {
|
||||
let worker = prepared.worker_mut();
|
||||
validate_worker_memory_settings(worker.manifest(), &request.request)?;
|
||||
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}"))?;
|
||||
let workdir_capabilities = request
|
||||
.workdir_attachments
|
||||
@@ -1521,7 +1525,10 @@ impl RuntimeWorkerFactory for ProfileRuntimeWorkerFactory {
|
||||
};
|
||||
validate_worker_memory_settings(worker.manifest(), &request.request)?;
|
||||
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}"))?;
|
||||
let flow_transition_enabled = worker.manifest().feature.flow.enabled;
|
||||
let workdir_sessions = restored_workdir_router(
|
||||
|
||||
@@ -58,9 +58,10 @@ pub use session_history::{
|
||||
};
|
||||
pub use shared_state::WorkerSharedState;
|
||||
pub use worker::{
|
||||
LocalWorkingDirectory, Worker, WorkerError, WorkerFilesystemAuthority, WorkerRunResult,
|
||||
WorkerWorkspaceContext, WorkspaceClient, WorkspaceClientError, WorkspaceId, WorkspaceIdError,
|
||||
WorkspacePromptCatalogResolution, WorkspaceRequest, WorkspaceRequestMethod, WorkspaceResponse,
|
||||
WorkspaceServerOperation, WorkspaceWorkerDiscoveryRequest, apply_worker_manifest,
|
||||
marker_workspace_client, unavailable_workspace_client,
|
||||
LocalWorkingDirectory, SubjektivSessionAttributionLifecycle, Worker, WorkerError,
|
||||
WorkerFilesystemAuthority, WorkerRunResult, WorkerWorkspaceContext, WorkspaceClient,
|
||||
WorkspaceClientError, WorkspaceId, WorkspaceIdError, WorkspacePromptCatalogResolution,
|
||||
WorkspaceRequest, WorkspaceRequestMethod, WorkspaceResponse, WorkspaceServerOperation,
|
||||
WorkspaceWorkerDiscoveryRequest, apply_worker_manifest, marker_workspace_client,
|
||||
unavailable_workspace_client,
|
||||
};
|
||||
|
||||
+223
-40
@@ -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 {
|
||||
const MAX_CHARS: usize = 1024;
|
||||
if diagnostic.chars().count() > MAX_CHARS {
|
||||
@@ -6714,15 +6746,18 @@ where
|
||||
|
||||
/// Complete the Host-owned Session-to-Subject attribution boundary before a
|
||||
/// runtime-managed Worker is exposed or accepts input. The authoritative
|
||||
/// attachment bit comes from the Runtime create/restore request; names,
|
||||
/// Profiles, and current singleton ownership are never used as identity.
|
||||
/// attachment bit and lifecycle kind come from the Runtime create/restore
|
||||
/// request; names, Profiles, and current singleton ownership are never used
|
||||
/// as historical identity.
|
||||
///
|
||||
/// `OutcomeUnknown` is persisted before network I/O. A lost response or
|
||||
/// process interruption is therefore recovered only by a later create/restore
|
||||
/// lifecycle, where the Backend's same-attribution idempotency is safe to use.
|
||||
/// New Sessions persist `OutcomeUnknown` before create-if-missing network I/O.
|
||||
/// A restore retries creation only for that durable state (or `Failed`). An
|
||||
/// unmarked legacy restore is verification-only and persists `LegacyUnknown`,
|
||||
/// so current ownership can never invent historical attribution.
|
||||
pub fn finalize_subjektiv_session_attribution(
|
||||
&self,
|
||||
subjektiv_attached: bool,
|
||||
lifecycle: SubjektivSessionAttributionLifecycle,
|
||||
) -> Result<(), WorkerError> {
|
||||
let worker_name = self.manifest.worker.name.as_str();
|
||||
let metadata = self.store.read_by_name(worker_name)?.ok_or_else(|| {
|
||||
@@ -6762,7 +6797,8 @@ where
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(state) = metadata.subjektiv_session_attribution.as_ref() {
|
||||
let create_if_missing = match metadata.subjektiv_session_attribution.as_ref() {
|
||||
Some(state) => {
|
||||
if state.session_id() != session_id {
|
||||
return Err(WorkerError::SubjektivSessionAttribution {
|
||||
state: "session_mismatch",
|
||||
@@ -6772,22 +6808,30 @@ where
|
||||
),
|
||||
});
|
||||
}
|
||||
if matches!(state, SubjektivSessionAttributionState::Confirmed { .. }) {
|
||||
return Ok(());
|
||||
match state {
|
||||
SubjektivSessionAttributionState::Confirmed { .. } => return Ok(()),
|
||||
SubjektivSessionAttributionState::OutcomeUnknown { .. }
|
||||
| SubjektivSessionAttributionState::Failed { .. } => true,
|
||||
SubjektivSessionAttributionState::LegacyUnknown { .. } => false,
|
||||
}
|
||||
}
|
||||
|
||||
self.persist_subjektiv_session_attribution(
|
||||
SubjektivSessionAttributionState::OutcomeUnknown {
|
||||
None => matches!(lifecycle, SubjektivSessionAttributionLifecycle::NewSession),
|
||||
};
|
||||
let initial_diagnostic = if create_if_missing {
|
||||
"attribution creation started; completion is not yet confirmed"
|
||||
} else {
|
||||
"legacy Session has no local attribution evidence; immutable Backend verification started"
|
||||
};
|
||||
self.persist_subjektiv_session_attribution(incomplete_subjektiv_attribution_state(
|
||||
session_id,
|
||||
diagnostic: "attribution request started; completion is not yet confirmed"
|
||||
.to_string(),
|
||||
},
|
||||
)?;
|
||||
create_if_missing,
|
||||
initial_diagnostic.to_string(),
|
||||
))?;
|
||||
let response = match self.workspace_client().execute_server_operation(
|
||||
WorkspaceServerOperation::SubjektivRecordSession(
|
||||
server_api::SubjektivRecordSessionRequest {
|
||||
session_id: session_id.to_string(),
|
||||
create_if_missing,
|
||||
},
|
||||
),
|
||||
) {
|
||||
@@ -6797,13 +6841,14 @@ where
|
||||
"Session attribution transport outcome is unknown: {error}"
|
||||
));
|
||||
self.persist_subjektiv_session_attribution(
|
||||
SubjektivSessionAttributionState::OutcomeUnknown {
|
||||
incomplete_subjektiv_attribution_state(
|
||||
session_id,
|
||||
diagnostic: message.clone(),
|
||||
},
|
||||
create_if_missing,
|
||||
message.clone(),
|
||||
),
|
||||
)?;
|
||||
return Err(WorkerError::SubjektivSessionAttribution {
|
||||
state: "outcome_unknown",
|
||||
state: incomplete_subjektiv_attribution_label(create_if_missing),
|
||||
message,
|
||||
});
|
||||
}
|
||||
@@ -6813,12 +6858,24 @@ where
|
||||
"Session attribution was rejected with HTTP {}: {}",
|
||||
response.status, response.body
|
||||
));
|
||||
self.persist_subjektiv_session_attribution(SubjektivSessionAttributionState::Failed {
|
||||
let state = if create_if_missing {
|
||||
SubjektivSessionAttributionState::Failed {
|
||||
session_id,
|
||||
diagnostic: message.clone(),
|
||||
})?;
|
||||
}
|
||||
} else {
|
||||
SubjektivSessionAttributionState::LegacyUnknown {
|
||||
session_id,
|
||||
diagnostic: message.clone(),
|
||||
}
|
||||
};
|
||||
self.persist_subjektiv_session_attribution(state)?;
|
||||
return Err(WorkerError::SubjektivSessionAttribution {
|
||||
state: "failed",
|
||||
state: if create_if_missing {
|
||||
"failed"
|
||||
} else {
|
||||
"legacy_unknown"
|
||||
},
|
||||
message,
|
||||
});
|
||||
}
|
||||
@@ -6830,13 +6887,14 @@ where
|
||||
"Session attribution response outcome is unknown: {error}"
|
||||
));
|
||||
self.persist_subjektiv_session_attribution(
|
||||
SubjektivSessionAttributionState::OutcomeUnknown {
|
||||
incomplete_subjektiv_attribution_state(
|
||||
session_id,
|
||||
diagnostic: message.clone(),
|
||||
},
|
||||
create_if_missing,
|
||||
message.clone(),
|
||||
),
|
||||
)?;
|
||||
return Err(WorkerError::SubjektivSessionAttribution {
|
||||
state: "outcome_unknown",
|
||||
state: incomplete_subjektiv_attribution_label(create_if_missing),
|
||||
message,
|
||||
});
|
||||
}
|
||||
@@ -6848,14 +6906,13 @@ where
|
||||
let message =
|
||||
"Session attribution response did not confirm the requested immutable identity"
|
||||
.to_string();
|
||||
self.persist_subjektiv_session_attribution(
|
||||
SubjektivSessionAttributionState::OutcomeUnknown {
|
||||
self.persist_subjektiv_session_attribution(incomplete_subjektiv_attribution_state(
|
||||
session_id,
|
||||
diagnostic: message.clone(),
|
||||
},
|
||||
)?;
|
||||
create_if_missing,
|
||||
message.clone(),
|
||||
))?;
|
||||
return Err(WorkerError::SubjektivSessionAttribution {
|
||||
state: "outcome_unknown",
|
||||
state: incomplete_subjektiv_attribution_label(create_if_missing),
|
||||
message,
|
||||
});
|
||||
}
|
||||
@@ -9794,10 +9851,25 @@ mod build_summary_prompt_tests {
|
||||
let (worker, store, _temp) = attribution_lifecycle_worker(client.clone()).await;
|
||||
let session_id = worker.session_id();
|
||||
|
||||
worker.finalize_subjektiv_session_attribution(true).unwrap();
|
||||
worker.finalize_subjektiv_session_attribution(true).unwrap();
|
||||
worker
|
||||
.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
|
||||
.read_by_name(&worker.manifest().worker.name)
|
||||
.unwrap()
|
||||
@@ -9823,7 +9895,10 @@ mod build_summary_prompt_tests {
|
||||
]));
|
||||
let (worker, store, _temp) = attribution_lifecycle_worker(client.clone()).await;
|
||||
let error = worker
|
||||
.finalize_subjektiv_session_attribution(true)
|
||||
.finalize_subjektiv_session_attribution(
|
||||
true,
|
||||
SubjektivSessionAttributionLifecycle::NewSession,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(error.to_string().contains(expected_state));
|
||||
let incomplete = store
|
||||
@@ -9840,7 +9915,12 @@ mod build_summary_prompt_tests {
|
||||
) | ("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!(matches!(
|
||||
store
|
||||
@@ -9861,7 +9941,10 @@ mod build_summary_prompt_tests {
|
||||
let (worker, store, _temp) = attribution_lifecycle_worker(client.clone()).await;
|
||||
|
||||
worker
|
||||
.finalize_subjektiv_session_attribution(false)
|
||||
.finalize_subjektiv_session_attribution(
|
||||
false,
|
||||
SubjektivSessionAttributionLifecycle::NewSession,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(client.requests.lock().unwrap().is_empty());
|
||||
@@ -9923,8 +10006,18 @@ mod build_summary_prompt_tests {
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(worker.session_id(), session_id);
|
||||
worker.finalize_subjektiv_session_attribution(true).unwrap();
|
||||
assert_eq!(client.requests.lock().unwrap().len(), 1);
|
||||
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)
|
||||
@@ -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 {
|
||||
Segment::Text {
|
||||
content: text.into(),
|
||||
|
||||
@@ -17638,11 +17638,20 @@ async fn scoped_record_subjektiv_session(
|
||||
Json(request): Json<server_api::SubjektivRecordSessionRequest>,
|
||||
) -> ApiResult<Json<server_api::SubjektivRecordSessionResponse>> {
|
||||
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||
let attribution = if request.create_if_missing {
|
||||
let attribution =
|
||||
subjektiv_session_attribution(&api, &path.workspace_id, &context, &request.session_id)?;
|
||||
let attribution = open_subjektiv_store(&api)?
|
||||
open_subjektiv_store(&api)?
|
||||
.record_session_attribution(attribution)
|
||||
.map_err(|error| Error::InvalidInput(error.to_string()))?;
|
||||
.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 {
|
||||
subject_id: attribution.subject_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(
|
||||
State(api.clone()),
|
||||
AxumPath(ScopedWorkspacePath {
|
||||
@@ -39635,11 +39666,26 @@ mod tests {
|
||||
context.clone(),
|
||||
Json(server_api::SubjektivRecordSessionRequest {
|
||||
session_id: "session-1".to_string(),
|
||||
create_if_missing: true,
|
||||
}),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
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 {
|
||||
source: memory::schema::SourceRef {
|
||||
|
||||
Reference in New Issue
Block a user