6c53eda71c
新增九类编辑器生成 durable receipt 与稳定提交指纹 统一资源、素材、binding、画布和队列终态的 SpacetimeDB 原子事务 补齐未知结果有界重放、CAS 刷新、lease fencing 与原子失败退款 收紧 owner、project、object、跨记录语义和 replay 一致性校验 保持 Agent 与 External compact 结果契约及 512 KiB 上限 延迟 inline 计费完成边界并区分 procedure dispatch 前后取消 同步 schema、迁移、生成绑定、架构文档和故障回归测试
203 lines
5.1 KiB
Rust
203 lines
5.1 KiB
Rust
use sha2::{Digest, Sha256};
|
|
|
|
pub const EDITOR_GENERATION_RESOURCE_ID_PREFIX: &str = "editor-resource-";
|
|
pub const EDITOR_GENERATION_ASSET_ID_PREFIX: &str = "editor-asset-";
|
|
pub const EDITOR_GENERATION_ASSET_OBJECT_ID_PREFIX: &str = "assetobj_";
|
|
pub const EDITOR_GENERATION_ASSET_BINDING_ID_PREFIX: &str = "assetbind_";
|
|
|
|
pub fn editor_generation_request_fingerprint(
|
|
operation_kind: &str,
|
|
request_payload_json: &str,
|
|
) -> String {
|
|
sha256_hex(
|
|
format!(
|
|
"editor-generation-request-v1\0{}\0{}",
|
|
operation_kind.trim(),
|
|
request_payload_json
|
|
)
|
|
.as_bytes(),
|
|
)
|
|
}
|
|
|
|
pub fn editor_generation_stable_asset_object_id(
|
|
owner_user_id: &str,
|
|
operation_kind: &str,
|
|
operation_id: &str,
|
|
slot: &str,
|
|
) -> String {
|
|
editor_generation_stable_record_id(
|
|
EDITOR_GENERATION_ASSET_OBJECT_ID_PREFIX,
|
|
owner_user_id,
|
|
operation_kind,
|
|
operation_id,
|
|
slot,
|
|
"asset-object",
|
|
)
|
|
}
|
|
|
|
pub fn editor_generation_stable_asset_binding_id(
|
|
owner_user_id: &str,
|
|
operation_kind: &str,
|
|
operation_id: &str,
|
|
slot: &str,
|
|
) -> String {
|
|
editor_generation_stable_record_id(
|
|
EDITOR_GENERATION_ASSET_BINDING_ID_PREFIX,
|
|
owner_user_id,
|
|
operation_kind,
|
|
operation_id,
|
|
slot,
|
|
"asset-binding",
|
|
)
|
|
}
|
|
|
|
pub fn editor_generation_stable_resource_id(
|
|
owner_user_id: &str,
|
|
operation_kind: &str,
|
|
operation_id: &str,
|
|
slot: &str,
|
|
) -> String {
|
|
editor_generation_stable_record_id(
|
|
EDITOR_GENERATION_RESOURCE_ID_PREFIX,
|
|
owner_user_id,
|
|
operation_kind,
|
|
operation_id,
|
|
slot,
|
|
"project-resource",
|
|
)
|
|
}
|
|
|
|
pub fn editor_generation_stable_asset_id(
|
|
owner_user_id: &str,
|
|
operation_kind: &str,
|
|
operation_id: &str,
|
|
slot: &str,
|
|
) -> String {
|
|
editor_generation_stable_record_id(
|
|
EDITOR_GENERATION_ASSET_ID_PREFIX,
|
|
owner_user_id,
|
|
operation_kind,
|
|
operation_id,
|
|
slot,
|
|
"account-asset",
|
|
)
|
|
}
|
|
|
|
fn editor_generation_stable_record_id(
|
|
prefix: &str,
|
|
owner_user_id: &str,
|
|
operation_kind: &str,
|
|
operation_id: &str,
|
|
slot: &str,
|
|
record_kind: &str,
|
|
) -> String {
|
|
let digest = sha256_hex(
|
|
format!(
|
|
"editor-generation-result-v1\0{}\0{}\0{}\0{}\0{}",
|
|
owner_user_id.trim(),
|
|
operation_kind.trim(),
|
|
operation_id.trim(),
|
|
slot.trim(),
|
|
record_kind,
|
|
)
|
|
.as_bytes(),
|
|
);
|
|
format!("{prefix}{}", &digest[..32])
|
|
}
|
|
|
|
fn sha256_hex(bytes: &[u8]) -> String {
|
|
format!("{:x}", Sha256::digest(bytes))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn stable_editor_generation_ids_are_replay_safe_and_domain_separated() {
|
|
let first = editor_generation_stable_resource_id(
|
|
"owner-1",
|
|
"editor_image_generation",
|
|
"task-1",
|
|
"primary",
|
|
);
|
|
assert_eq!(
|
|
first,
|
|
editor_generation_stable_resource_id(
|
|
"owner-1",
|
|
"editor_image_generation",
|
|
"task-1",
|
|
"primary",
|
|
)
|
|
);
|
|
assert_ne!(
|
|
first,
|
|
editor_generation_stable_resource_id(
|
|
"owner-1",
|
|
"editor_image_generation",
|
|
"task-1",
|
|
"provider-source",
|
|
)
|
|
);
|
|
assert_ne!(
|
|
first,
|
|
editor_generation_stable_asset_id(
|
|
"owner-1",
|
|
"editor_image_generation",
|
|
"task-1",
|
|
"primary",
|
|
)
|
|
);
|
|
assert_ne!(
|
|
first,
|
|
editor_generation_stable_asset_object_id(
|
|
"owner-1",
|
|
"editor_image_generation",
|
|
"task-1",
|
|
"primary",
|
|
)
|
|
);
|
|
assert_eq!(
|
|
editor_generation_stable_asset_object_id(
|
|
"owner-1",
|
|
"editor_image_generation",
|
|
"task-1",
|
|
"primary",
|
|
)
|
|
.len(),
|
|
EDITOR_GENERATION_ASSET_OBJECT_ID_PREFIX.len() + 32
|
|
);
|
|
assert_ne!(
|
|
editor_generation_stable_asset_object_id(
|
|
"owner-1",
|
|
"editor_image_generation",
|
|
"task-1",
|
|
"primary",
|
|
),
|
|
editor_generation_stable_asset_binding_id(
|
|
"owner-1",
|
|
"editor_image_generation",
|
|
"task-1",
|
|
"primary",
|
|
)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn request_fingerprint_binds_kind_and_exact_payload() {
|
|
let first = editor_generation_request_fingerprint("image", r#"{"prompt":"a"}"#);
|
|
assert_eq!(
|
|
first,
|
|
editor_generation_request_fingerprint("image", r#"{"prompt":"a"}"#)
|
|
);
|
|
assert_ne!(
|
|
first,
|
|
editor_generation_request_fingerprint("image", r#"{"prompt":"b"}"#)
|
|
);
|
|
assert_ne!(
|
|
first,
|
|
editor_generation_request_fingerprint("video", r#"{"prompt":"a"}"#)
|
|
);
|
|
}
|
|
}
|