把审计证据归档到 S3-Compatible WORM 存储
使用应用自有预签名、稳定 Object Key、条件写、Checksum、Object Lock 与不确定后的 HEAD 协调。
归档边界
WorkflowTaskTombstoneArchive 接收精确有序 Tombstone Batch,以及从 Tenant 与 Cursor
Range 派生的稳定 Idempotency Key。它必须持久化精确 Batch 或 Replay 原始 Receipt。只有
Receipt 返回后,数据库 Export Watermark 才推进。
Archive 是证据交付,不是普通日志;失败会阻止之后的 Purge Authority。
Object 契约
S3-Compatible Adapter 把 Canonical JSON 写入稳定 Object Key,使用条件
If-None-Match: *、SHA-256 Checksum Request 与持久 Checksum Metadata、必须的 Server-side
Encryption,以及可选 GOVERNANCE 或 COMPLIANCE Object Lock。
Receipt 只包含 Bucket、Object Key 与 Checksum。需要在 Object Store 验证 Retention 配置; Request Header 不能证明 Bucket Policy 接受了 WORM 语义。
凭证模型
Runifold 使用应用自有 Pre-signing,而不是保存长期 Cloud Credential。Pre-signer 通过短期 URL,只授予一个 Object 和有界 PUT 或 HEAD Operation 的权限。
绝不能持久化或记录 Signing Credential 与 Pre-signed URL。Pre-signer 自身的网络和凭证 I/O 要与 Archive Request Timeout 分开限制。
不确定 PUT 恢复
Response 丢失不能证明 Object 没有提交。Runifold 不会在同一次 Archive Call 中盲目发送第二 次 PUT,而是获得独立 HEAD Authority;只有 Stored Checksum Metadata 与稳定 Payload 精确 匹配时才接受成功。
Timeout、Authorization、Unavailable、Integrity、Ambiguous、Configuration 与 Other Failure 保持区分和低基数。Message 排除 URL、Credential、Payload 与 Tenant Identity。
合规检查
- 最终确定 Bucket Policy 前启用 Versioning 与需要的 Object Lock;
- 选择 Encryption 与 KMS Ownership;
- 限制 URL Lifetime 与 Allowed Header;
- 测试条件 Replay 与 Response-loss Reconciliation;
- 在真实 Object Store 验证 Retention 与 Legal Hold;
- 监控 Export Watermark Lag 与 Archive Failure Kind;
- 记录 Restore、Audit Access、删除例外与 Key Rotation Policy。
创建 S3 归档
启用 archive-s3 Feature,并在 Workflow Worker 外部构造策略和凭据。下面
使用适合 MinIO 的 path-style 地址;生产环境应按对象存储提供商调整。
use runifold::archive_s3::{
S3ArchiveEncryption, S3ObjectLock, S3ObjectLockMode, S3SigV4Credentials,
S3SigV4Presigner, S3SigV4PresignerConfig, S3TombstoneArchive,
S3TombstoneArchiveConfig,
};
use std::{num::NonZeroU32, sync::Arc, time::Duration};
let policy = S3TombstoneArchiveConfig::new("audit-bucket", "runifold", S3ArchiveEncryption::Aes256)?
.with_request_timeout(Duration::from_secs(30))?
.with_object_lock(S3ObjectLock {
mode: S3ObjectLockMode::Compliance,
retention_days: NonZeroU32::new(365).expect("retention is non-zero"),
});
let signer = S3SigV4Presigner::new(
S3SigV4PresignerConfig::new(endpoint, "us-east-1", 300, true)?,
S3SigV4Credentials::new(access_key, secret_key, session_token)?,
);
let archive = S3TombstoneArchive::new(policy, Arc::new(signer));上线前必须执行真实 MinIO 或 AWS 的重放测试:PUT 已成功但响应丢失时,系统 应通过 HEAD 与校验和一致性完成对账;任何不一致都必须阻止导出水位推进, 从而阻止清除。