Files
Genarrative/server-rs/crates/shared-contracts/src/editor_generation.rs
T
kdletters 6c53eda71c
Project CI / Repository checks (push) Successful in 1m23s
Project CI / Native shell tests (push) Failing after 56s
Project CI / Frontend tests (push) Successful in 3m44s
Project CI / Backend tests (push) Successful in 4m33s
修复编辑器生成结果原子提交
新增九类编辑器生成 durable receipt 与稳定提交指纹
统一资源、素材、binding、画布和队列终态的 SpacetimeDB 原子事务
补齐未知结果有界重放、CAS 刷新、lease fencing 与原子失败退款
收紧 owner、project、object、跨记录语义和 replay 一致性校验
保持 Agent 与 External compact 结果契约及 512 KiB 上限
延迟 inline 计费完成边界并区分 procedure dispatch 前后取消
同步 schema、迁移、生成绑定、架构文档和故障回归测试
2026-08-07 18:03:30 +08:00

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"}"#)
);
}
}