cache: scope response entries by operation version
This commit is contained in:
@@ -62,6 +62,7 @@ Progress:
|
|||||||
- `ExecutionConfig.response_cache` is now part of the public operation model, and `admin-api` validates the safe Community baseline: only explicit `REST GET` operations without `auth_profile_ref`
|
- `ExecutionConfig.response_cache` is now part of the public operation model, and `admin-api` validates the safe Community baseline: only explicit `REST GET` operations without `auth_profile_ref`
|
||||||
- cache boundary and namespace rules are now documented for `platform / coordination cache` versus `response cache`
|
- cache boundary and namespace rules are now documented for `platform / coordination cache` versus `response cache`
|
||||||
- runtime executor now performs actual response cache reads/writes for eligible `REST GET` operations with keys isolated by `workspace + agent + operation + request fingerprint`
|
- runtime executor now performs actual response cache reads/writes for eligible `REST GET` operations with keys isolated by `workspace + agent + operation + request fingerprint`
|
||||||
|
- response cache keys now also include `operation version`, so a new published version does not reuse stale cached payloads from the previous one
|
||||||
- mcp-server published tool catalog now uses shared coordination snapshots across instances, while preserving the same `workspace + agent` isolation model
|
- mcp-server published tool catalog now uses shared coordination snapshots across instances, while preserving the same `workspace + agent` isolation model
|
||||||
- pending:
|
- pending:
|
||||||
- preserve the same isolation model while extending response cache beyond the first `REST GET` hot path
|
- preserve the same isolation model while extending response cache beyond the first `REST GET` hot path
|
||||||
|
|||||||
@@ -789,10 +789,11 @@ fn response_cache_key(
|
|||||||
let fingerprint_hash = URL_SAFE_NO_PAD.encode(Sha256::digest(fingerprint_bytes));
|
let fingerprint_hash = URL_SAFE_NO_PAD.encode(Sha256::digest(fingerprint_bytes));
|
||||||
|
|
||||||
Some(format!(
|
Some(format!(
|
||||||
"crank:response:workspace:{}:agent:{}:operation:{}:request:{}",
|
"crank:response:workspace:{}:agent:{}:operation:{}:version:{}:request:{}",
|
||||||
scope.workspace_key,
|
scope.workspace_key,
|
||||||
scope.agent_key,
|
scope.agent_key,
|
||||||
operation.operation_id.as_str(),
|
operation.operation_id.as_str(),
|
||||||
|
operation.operation_version,
|
||||||
fingerprint_hash
|
fingerprint_hash
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -1062,6 +1063,32 @@ mod tests {
|
|||||||
assert_eq!(request_count.load(Ordering::SeqCst), 2);
|
assert_eq!(request_count.load(Ordering::SeqCst), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn invalidates_rest_get_cache_on_operation_version_change() {
|
||||||
|
let request_count = Arc::new(AtomicUsize::new(0));
|
||||||
|
let base_url = spawn_cached_rest_server(Arc::clone(&request_count)).await;
|
||||||
|
let executor = RuntimeExecutor::new()
|
||||||
|
.with_response_cache_store(Arc::new(InMemoryResponseCacheStore::default()));
|
||||||
|
let operation_v1 = test_cached_rest_get_operation(&base_url);
|
||||||
|
let mut operation_v2 = operation_v1.clone();
|
||||||
|
let context = RuntimeRequestContext::from_request_id("req_cache_version")
|
||||||
|
.with_response_cache_scope("ws_cache", "agent_sales");
|
||||||
|
|
||||||
|
let first_output = executor
|
||||||
|
.execute_with_context(&operation_v1, &json!({ "city": "msk" }), Some(&context))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
operation_v2.operation_version = 2;
|
||||||
|
let second_output = executor
|
||||||
|
.execute_with_context(&operation_v2, &json!({ "city": "msk" }), Some(&context))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(first_output, json!({ "city": "msk", "request_count": 1 }));
|
||||||
|
assert_eq!(second_output, json!({ "city": "msk", "request_count": 2 }));
|
||||||
|
assert_eq!(request_count.load(Ordering::SeqCst), 2);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn emits_runtime_tracing_with_request_context() {
|
async fn emits_runtime_tracing_with_request_context() {
|
||||||
let base_url = spawn_runtime_server().await;
|
let base_url = spawn_runtime_server().await;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use serde_json::Value;
|
|||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct RuntimeOperation {
|
pub struct RuntimeOperation {
|
||||||
pub operation_id: OperationId,
|
pub operation_id: OperationId,
|
||||||
|
pub operation_version: u32,
|
||||||
pub tool_name: String,
|
pub tool_name: String,
|
||||||
pub protocol: Protocol,
|
pub protocol: Protocol,
|
||||||
pub target: Target,
|
pub target: Target,
|
||||||
@@ -24,6 +25,7 @@ impl From<Operation<Schema, MappingSet>> for RuntimeOperation {
|
|||||||
fn from(value: Operation<Schema, MappingSet>) -> Self {
|
fn from(value: Operation<Schema, MappingSet>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
operation_id: value.id,
|
operation_id: value.id,
|
||||||
|
operation_version: value.version,
|
||||||
tool_name: value.name,
|
tool_name: value.name,
|
||||||
protocol: value.protocol,
|
protocol: value.protocol,
|
||||||
target: value.target,
|
target: value.target,
|
||||||
|
|||||||
@@ -195,7 +195,7 @@
|
|||||||
- Enterprise может подключать свой `Valkey/Redis` на уровне single-node или cluster deployment.
|
- Enterprise может подключать свой `Valkey/Redis` на уровне single-node или cluster deployment.
|
||||||
- cache namespaces документированы так, чтобы:
|
- cache namespaces документированы так, чтобы:
|
||||||
- platform / coordination state не смешивался с response cache;
|
- platform / coordination state не смешивался с response cache;
|
||||||
- response cache по умолчанию изолировался по `workspace + agent + operation + request fingerprint`;
|
- response cache по умолчанию изолировался по `workspace + agent + operation + operation version + request fingerprint`;
|
||||||
- внешний cache backend можно было безопасно шарить между несколькими рабочими областями и агентами.
|
- внешний cache backend можно было безопасно шарить между несколькими рабочими областями и агентами.
|
||||||
- shared coordination cache уже применяется не только для rate limiting, но и для multi-instance snapshots published MCP catalogs.
|
- shared coordination cache уже применяется не только для rate limiting, но и для multi-instance snapshots published MCP catalogs.
|
||||||
|
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ Crank должен корректно работать, если MCP client де
|
|||||||
- `REST`
|
- `REST`
|
||||||
- `GET`
|
- `GET`
|
||||||
- без `auth_profile_ref`
|
- без `auth_profile_ref`
|
||||||
- cache keys изолируются минимум по `workspace + agent + operation + request fingerprint`
|
- cache keys изолируются минимум по `workspace + agent + operation + operation version + request fingerprint`
|
||||||
- streaming operations публикуются как bounded tools или tool families;
|
- streaming operations публикуются как bounded tools или tool families;
|
||||||
- reload published tools без пересборки сервиса;
|
- reload published tools без пересборки сервиса;
|
||||||
- никакой draft-логики или admin CRUD в MCP слое.
|
- никакой draft-логики или admin CRUD в MCP слое.
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ Demo/deployment:
|
|||||||
|
|
||||||
Стартовая модель namespace для response cache:
|
Стартовая модель namespace для response cache:
|
||||||
|
|
||||||
- `workspace + agent + operation + request fingerprint`
|
- `workspace + agent + operation + operation version + request fingerprint`
|
||||||
|
|
||||||
Стартовая модель namespace для platform / coordination cache:
|
Стартовая модель namespace для platform / coordination cache:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user