feat(job): fix key rotation on local storage (replace blob)

This commit is contained in:
Edouard Vanbelle
2026-08-02 00:45:52 +02:00
parent e12d8bcb90
commit bc481bdd99
4 changed files with 160 additions and 6 deletions
@@ -128,6 +128,42 @@ pub trait BlobStorageBackend: Send + Sync + 'static {
self.put_blob_from_bytes(hash, data)
}
/// Store a blob from in-memory bytes, **replacing** any existing
/// object at that hash. Distinct from [`Self::put_blob_from_bytes`]:
/// the standard variant is idempotent-skip (correct for uploads —
/// same plaintext always produces bytes that decrypt back to the
/// same plaintext), whereas this variant is required by callers
/// that need the on-disk BYTES to change even when the CONTENT
/// hash doesn't:
///
/// * `storage_rotate` — rewrites every blob under the head pair's
/// format (legacy → v1 header, old key → new key, plaintext ↔
/// encrypted). If the target's `put_blob_from_bytes` silently
/// skipped, rotation would report success while leaving the old
/// format on disk.
/// * `storage_migration` — same story when a target already has a
/// blob at that hash from an earlier state (Ed hit this on
/// 2026-08-01 in the S3 → local migration test).
///
/// Must be **atomic** — a concurrent reader must see either the
/// old bytes or the new bytes, never a truncated partial write.
/// On POSIX that's a `write-to-tempfile + rename(2)` pattern; on
/// object storage (S3, Azure) it's a straight `PUT` (already
/// overwrites atomically).
///
/// Default: delegates to `put_blob_from_bytes`. That default is
/// CORRECT for backends whose `put_blob_from_bytes` already
/// overwrites (S3, Azure — object stores overwrite on PUT by
/// default). Backends whose `put_blob_from_bytes` is
/// idempotent-skip (like `LocalBlobBackend`) MUST override.
fn put_blob_from_bytes_replace(
&self,
hash: &str,
data: Bytes,
) -> BoxFut<'_, Result<u64, DomainError>> {
self.put_blob_from_bytes(hash, data)
}
/// Make previously written blobs durable in one batched operation.
///
/// Durability barrier for blobs written via `put_blob_from_bytes_unsynced`: