Archive audit evidence to S3-compatible WORM storage
Use application-owned pre-signing, stable object keys, conditional writes, checksums, Object Lock, and HEAD reconciliation after ambiguity.
Archive boundary
WorkflowTaskTombstoneArchive receives an exact ordered tombstone batch and a
stable idempotency key derived from tenant and cursor range. It must persist
that exact batch or replay the original receipt. The database export watermark
advances only after the receipt returns.
Archive is evidence delivery, not ordinary logging. Failure blocks later purge authority.
Object contract
The S3-compatible adapter writes canonical JSON under a stable object key with
conditional If-None-Match: *, SHA-256 checksum request and durable checksum
metadata, required server-side encryption, and optional GOVERNANCE or
COMPLIANCE Object Lock.
The receipt contains only bucket, object key, and checksum. Validate retention configuration in the object store; a request header is not proof that bucket policy accepted WORM semantics.
Credential model
Runifold uses application-owned pre-signing rather than storing long-lived cloud credentials. The pre-signer grants authority for one object and bounded PUT or HEAD operations through short-lived URLs.
Never persist or log signing credentials or pre-signed URLs. Bound the pre-signer's own network and credential I/O separately from archive request timeouts.
Ambiguous PUT recovery
A lost response does not prove the object was not committed. Runifold never blindly sends a second PUT inside the same archive call. It obtains independent HEAD authority and accepts success only when stored checksum metadata exactly matches the stable payload.
Timeout, authorization, unavailable, integrity, ambiguous, configuration, and other failures stay distinct and low-cardinality. Messages exclude URLs, credentials, payloads, and tenant identities.
Compliance checklist
- enable versioning and required Object Lock before bucket creation policy is finalized;
- select encryption and KMS ownership;
- bound URL lifetime and allowed headers;
- test conditional replay and response-loss reconciliation;
- verify retention and legal-hold behavior against the real object store;
- monitor export watermark lag and archive failure kind;
- document restore, audit access, deletion exception, and key-rotation policy.
Construct the S3 archive
Enable the archive-s3 Feature and build policy and credentials outside the
workflow worker. This example uses path-style addressing for MinIO; use the
endpoint style required by your provider.
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));Run the live MinIO/AWS reconciliation test before production. A successful PUT followed by a lost response must reconcile through HEAD and checksum equality; any mismatch blocks watermark advancement and therefore blocks purge.