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`
|
||||
- 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`
|
||||
- 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
|
||||
- pending:
|
||||
- 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));
|
||||
|
||||
Some(format!(
|
||||
"crank:response:workspace:{}:agent:{}:operation:{}:request:{}",
|
||||
"crank:response:workspace:{}:agent:{}:operation:{}:version:{}:request:{}",
|
||||
scope.workspace_key,
|
||||
scope.agent_key,
|
||||
operation.operation_id.as_str(),
|
||||
operation.operation_version,
|
||||
fingerprint_hash
|
||||
))
|
||||
}
|
||||
@@ -1062,6 +1063,32 @@ mod tests {
|
||||
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]
|
||||
async fn emits_runtime_tracing_with_request_context() {
|
||||
let base_url = spawn_runtime_server().await;
|
||||
|
||||
@@ -9,6 +9,7 @@ use serde_json::Value;
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeOperation {
|
||||
pub operation_id: OperationId,
|
||||
pub operation_version: u32,
|
||||
pub tool_name: String,
|
||||
pub protocol: Protocol,
|
||||
pub target: Target,
|
||||
@@ -24,6 +25,7 @@ impl From<Operation<Schema, MappingSet>> for RuntimeOperation {
|
||||
fn from(value: Operation<Schema, MappingSet>) -> Self {
|
||||
Self {
|
||||
operation_id: value.id,
|
||||
operation_version: value.version,
|
||||
tool_name: value.name,
|
||||
protocol: value.protocol,
|
||||
target: value.target,
|
||||
|
||||
@@ -195,7 +195,7 @@
|
||||
- Enterprise может подключать свой `Valkey/Redis` на уровне single-node или cluster deployment.
|
||||
- cache namespaces документированы так, чтобы:
|
||||
- platform / coordination state не смешивался с response cache;
|
||||
- response cache по умолчанию изолировался по `workspace + agent + operation + request fingerprint`;
|
||||
- response cache по умолчанию изолировался по `workspace + agent + operation + operation version + request fingerprint`;
|
||||
- внешний cache backend можно было безопасно шарить между несколькими рабочими областями и агентами.
|
||||
- shared coordination cache уже применяется не только для rate limiting, но и для multi-instance snapshots published MCP catalogs.
|
||||
|
||||
|
||||
@@ -241,7 +241,7 @@ Crank должен корректно работать, если MCP client де
|
||||
- `REST`
|
||||
- `GET`
|
||||
- без `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;
|
||||
- reload published tools без пересборки сервиса;
|
||||
- никакой draft-логики или admin CRUD в MCP слое.
|
||||
|
||||
@@ -195,7 +195,7 @@ Demo/deployment:
|
||||
|
||||
Стартовая модель namespace для response cache:
|
||||
|
||||
- `workspace + agent + operation + request fingerprint`
|
||||
- `workspace + agent + operation + operation version + request fingerprint`
|
||||
|
||||
Стартовая модель namespace для platform / coordination cache:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user