#[tokio::test]
async fn memory_put_carries_explicit_trust_label_in_metadata_and_provenance() {
let state = test_state().await;
let app = app_router(state.clone());
let capability = memory_capability(
"trust-label-run",
"user-trust-label",
"org-1",
"ws-1",
"proj-1",
);
let put_req = Request::builder()
.method("POST")
.uri("/memory/put")
.header("content-type", "application/json")
.body(Body::from(
json!({
"run_id": "trust-label-run",
"partition": {
"org_id": "org-1",
"workspace_id": "ws-1",
"project_id": "proj-1",
"tier": "session"
},
"kind": "fact",
"content": "external note should remain marked as untrusted evidence",
"classification": "internal",
"metadata": {
"memory_trust": {
"label": "external_user_supplied"
}
},
"capability": capability
})
.to_string(),
))
.expect("trust label put request");
let put_resp = app.clone().oneshot(put_req).await.expect("put response");
assert_eq!(put_resp.status(), StatusCode::OK);
let list_req = Request::builder()
.method("GET")
.uri("/memory?limit=20&user_id=user-trust-label&project_id=proj-1")
.body(Body::empty())
.expect("memory list request");
let list_resp = app.oneshot(list_req).await.expect("list response");
assert_eq!(list_resp.status(), StatusCode::OK);
let list_body = to_bytes(list_resp.into_body(), usize::MAX)
.await
.expect("list body");
let list_payload: Value = serde_json::from_slice(&list_body).expect("list json");
let item = list_payload
.get("items")
.and_then(Value::as_array)
.and_then(|items| items.first())
.expect("stored memory item");
assert_eq!(
item.pointer("/metadata/memory_trust/label")
.and_then(Value::as_str),
Some("external_user_supplied")
);
assert_eq!(
item.pointer("/metadata/memory_trust/trusted_for_promotion")
.and_then(Value::as_bool),
Some(false)
);
assert_eq!(
item.pointer("/provenance/memory_trust/label")
.and_then(Value::as_str),
Some("external_user_supplied")
);
}
#[tokio::test]
async fn memory_promote_rejects_untrusted_source_without_review_evidence() {
let state = test_state().await;
let app = app_router(state.clone());
let mut rx = state.event_bus.subscribe();
let capability = memory_capability(
"untrusted-promote-run",
"user-untrusted-promote",
"org-1",
"ws-1",
"proj-1",
);
let put_req = Request::builder()
.method("POST")
.uri("/memory/put")
.header("content-type", "application/json")
.body(Body::from(
json!({
"run_id": "untrusted-promote-run",
"partition": {
"org_id": "org-1",
"workspace_id": "ws-1",
"project_id": "proj-1",
"tier": "session"
},
"kind": "fact",
"content": "untrusted memory must not be promoted without review",
"classification": "internal",
"metadata": {
"memory_trust": {
"label": "external_user_supplied"
}
},
"capability": capability
})
.to_string(),
))
.expect("untrusted put request");
let put_resp = app.clone().oneshot(put_req).await.expect("put response");
assert_eq!(put_resp.status(), StatusCode::OK);
let put_body = to_bytes(put_resp.into_body(), usize::MAX)
.await
.expect("put body");
let put_payload: Value = serde_json::from_slice(&put_body).expect("put json");
let memory_id = put_payload
.get("id")
.and_then(Value::as_str)
.expect("memory id")
.to_string();
let _put_event = next_event_of_type(&mut rx, "memory.put").await;
let promote_req = Request::builder()
.method("POST")
.uri("/memory/promote")
.header("content-type", "application/json")
.body(Body::from(
json!({
"run_id": "untrusted-promote-run",
"source_memory_id": memory_id,
"from_tier": "session",
"to_tier": "project",
"partition": {
"org_id": "org-1",
"workspace_id": "ws-1",
"project_id": "proj-1",
"tier": "session"
},
"reason": "attempt unreviewed promotion",
"review": {
"required": false
},
"capability": capability
})
.to_string(),
))
.expect("untrusted promote request");
let promote_resp = app
.clone()
.oneshot(promote_req)
.await
.expect("promote response");
assert_eq!(promote_resp.status(), StatusCode::FORBIDDEN);
let blocked_event = next_event_of_type(&mut rx, "memory.promote").await;
assert_eq!(
blocked_event
.properties
.get("status")
.and_then(Value::as_str),
Some("blocked")
);
assert!(blocked_event
.properties
.get("detail")
.and_then(Value::as_str)
.is_some_and(|detail| detail.contains("untrusted memory promotion requires review evidence")));
}
#[tokio::test]
async fn memory_search_marks_untrusted_results_as_evidence() {
let state = test_state().await;
let app = app_router(state.clone());
let capability = memory_capability(
"untrusted-search-run",
"user-untrusted-search",
"org-1",
"ws-1",
"proj-1",
);
let put_req = Request::builder()
.method("POST")
.uri("/memory/put")
.header("content-type", "application/json")
.body(Body::from(
json!({
"run_id": "untrusted-search-run",
"partition": {
"org_id": "org-1",
"workspace_id": "ws-1",
"project_id": "proj-1",
"tier": "session"
},
"kind": "fact",
"content": "poison marker evidence should not become instruction",
"classification": "internal",
"metadata": {
"memory_trust": {
"label": "external_user_supplied"
}
},
"capability": capability
})
.to_string(),
))
.expect("untrusted search put request");
let put_resp = app.clone().oneshot(put_req).await.expect("put response");
assert_eq!(put_resp.status(), StatusCode::OK);
let search_req = Request::builder()
.method("POST")
.uri("/memory/search")
.header("content-type", "application/json")
.body(Body::from(
json!({
"run_id": "untrusted-search-run",
"query": "poison marker evidence",
"read_scopes": ["session"],
"partition": {
"org_id": "org-1",
"workspace_id": "ws-1",
"project_id": "proj-1",
"tier": "session"
},
"capability": capability,
"limit": 5
})
.to_string(),
))
.expect("untrusted search request");
let search_resp = app.oneshot(search_req).await.expect("search response");
assert_eq!(search_resp.status(), StatusCode::OK);
let search_body = to_bytes(search_resp.into_body(), usize::MAX)
.await
.expect("search body");
let search_payload: Value = serde_json::from_slice(&search_body).expect("search json");
let result = search_payload
.get("results")
.and_then(Value::as_array)
.and_then(|results| results.first())
.expect("search result");
assert_eq!(
result.get("rendering_role").and_then(Value::as_str),
Some("evidence")
);
assert_eq!(
result.pointer("/memory_trust/label").and_then(Value::as_str),
Some("external_user_supplied")
);
assert_eq!(
result
.pointer("/memory_trust/trusted_for_promotion")
.and_then(Value::as_bool),
Some(false)
);
}
#[tokio::test]
async fn memory_put_and_search_isolate_across_partitions_in_same_app_state() {
let state = test_state().await;
let app = app_router(state.clone());
let put_req = Request::builder()
.method("POST")
.uri("/memory/put")
.header("content-type", "application/json")
.body(Body::from(
json!({
"run_id": "run-partition-isolation",
"partition": {
"org_id": "org-a",
"workspace_id": "ws-a",
"project_id": "proj-a",
"tier": "session"
},
"kind": "note",
"content": "partition scoped memory sentinel",
"classification": "internal"
})
.to_string(),
))
.expect("put request");
let put_resp = app.clone().oneshot(put_req).await.expect("put response");
assert_eq!(put_resp.status(), StatusCode::OK);
let search_body = |org: &str, ws: &str, proj: &str| {
Body::from(
json!({
"run_id": "run-partition-isolation",
"query": "partition scoped memory sentinel",
"read_scopes": ["session"],
"partition": {
"org_id": org,
"workspace_id": ws,
"project_id": proj,
"tier": "session"
},
"limit": 5
})
.to_string(),
)
};
let foreign_req = Request::builder()
.method("POST")
.uri("/memory/search")
.header("content-type", "application/json")
.body(search_body("org-b", "ws-b", "proj-b"))
.expect("foreign search request");
let foreign_resp = app
.clone()
.oneshot(foreign_req)
.await
.expect("foreign search response");
assert_eq!(foreign_resp.status(), StatusCode::OK);
let foreign_payload: Value = serde_json::from_slice(
&to_bytes(foreign_resp.into_body(), usize::MAX)
.await
.expect("foreign body"),
)
.expect("foreign json");
assert!(
!foreign_payload
.get("results")
.and_then(Value::as_array)
.is_some_and(|rows| {
rows.iter().any(|row| {
row.get("content")
.and_then(Value::as_str)
.is_some_and(|content| content.contains("partition scoped memory sentinel"))
})
}),
"foreign partition leaked memory: {foreign_payload}"
);
let owner_req = Request::builder()
.method("POST")
.uri("/memory/search")
.header("content-type", "application/json")
.body(search_body("org-a", "ws-a", "proj-a"))
.expect("owner search request");
let owner_resp = app
.clone()
.oneshot(owner_req)
.await
.expect("owner search response");
assert_eq!(owner_resp.status(), StatusCode::OK);
let owner_payload: Value = serde_json::from_slice(
&to_bytes(owner_resp.into_body(), usize::MAX)
.await
.expect("owner body"),
)
.expect("owner json");
assert!(
owner_payload
.get("results")
.and_then(Value::as_array)
.is_some_and(|rows| {
rows.iter().any(|row| {
row.get("content")
.and_then(Value::as_str)
.is_some_and(|content| content.contains("partition scoped memory sentinel"))
})
}),
"owner partition could not read its own memory: {owner_payload}"
);
}