feat(rotate-key): add report + key fingerprint in hexdigit fmt

This commit is contained in:
Edouard Vanbelle
2026-08-02 01:05:02 +02:00
parent ddede168f9
commit 0a41f561d0
11 changed files with 210 additions and 23 deletions
@@ -321,7 +321,7 @@ impl RecoverableJobHandler for BackendConsistencyCheck {
backend = backend.backend_type(),
"backend refused enumeration (typical during migration or on backends without list support)"
);
return RunOutcome::Completed;
return RunOutcome::completed();
}
return RunOutcome::Failed {
message: format!("backend list failed mid-scan: {e}"),
@@ -373,7 +373,7 @@ impl RecoverableJobHandler for BackendConsistencyCheck {
"backend_consistency completed with {} finding(s)",
finding_count
);
return RunOutcome::Completed;
return RunOutcome::completed();
}
// Batch DB probe: which of these hashes have a
@@ -451,7 +451,7 @@ impl RecoverableJobHandler for BackendConsistencyCheck {
"backend_consistency completed with {} finding(s)",
finding_count
);
return RunOutcome::Completed;
return RunOutcome::completed();
}
}
}
@@ -381,7 +381,7 @@ impl RecoverableJobHandler for BlobsConsistencyCheck {
"blobs_consistency completed with {} finding(s)",
finding_count
);
return RunOutcome::Completed;
return RunOutcome::completed();
}
let grace_cutoff = Utc::now() - CREATE_GRACE;
@@ -528,7 +528,7 @@ impl RecoverableJobHandler for BlobsConsistencyCheck {
"blobs_consistency completed with {} finding(s)",
finding_count
);
return RunOutcome::Completed;
return RunOutcome::completed();
}
}
}
@@ -199,7 +199,7 @@ impl RecoverableJobHandler for DrivesConsistencyCheck {
"drives_consistency completed with {} drift finding(s)",
drift_count
);
return RunOutcome::Completed;
return RunOutcome::completed();
}
// Per-row check: cached vs actual. This is the ONE check
@@ -255,7 +255,7 @@ impl RecoverableJobHandler for DrivesConsistencyCheck {
"drives_consistency completed with {} drift finding(s)",
drift_count
);
return RunOutcome::Completed;
return RunOutcome::completed();
}
}
}
@@ -313,6 +313,35 @@ impl BlobFormat {
}
}
impl std::fmt::Display for BlobFormat {
/// Human-friendly format for audit logs + finding details.
/// Renders `key_fp` as SSH-style colon-hex (e.g.
/// `83:96:ff:90:94:d7:ef:de`) instead of the raw byte-array Debug
/// shape (`[131, 150, 255, ...]`). Same spelling `xxd` produces
/// when you inspect a blob's on-disk header, so operators can
/// cross-check without a mental conversion.
///
/// Handlers that render this in tracing macros should use `%`
/// (Display) — `?` (Debug) still gives the raw byte-array shape
/// for programmer-consumers who need the exact bytes.
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BlobFormat::Legacy => write!(f, "legacy"),
BlobFormat::PlaintextV1 => write!(f, "plaintext-v1"),
BlobFormat::EncryptedV1 { key_fp } => {
write!(f, "encrypted-v1 key_fp=")?;
for (i, byte) in key_fp.iter().enumerate() {
if i > 0 {
write!(f, ":")?;
}
write!(f, "{byte:02x}")?;
}
Ok(())
}
}
}
}
/// Assemble an encrypted-v1 blob:
/// `OXCPT | v1 | key_fp | nonce | ciphertext | tag`.
///
@@ -312,7 +312,7 @@ impl RecoverableJobHandler for FilesConsistencyCheck {
"files_consistency completed with {} finding(s)",
finding_count
);
return RunOutcome::Completed;
return RunOutcome::completed();
}
for row in &rows {
@@ -490,7 +490,7 @@ impl RecoverableJobHandler for FilesConsistencyCheck {
"files_consistency completed with {} finding(s)",
finding_count
);
return RunOutcome::Completed;
return RunOutcome::completed();
}
}
}
@@ -255,7 +255,7 @@ impl RecoverableJobHandler for FoldersConsistencyCheck {
"folders_consistency completed with {} finding(s)",
finding_count
);
return RunOutcome::Completed;
return RunOutcome::completed();
}
// Per-row branches. Add new ones here — same pattern as
@@ -352,7 +352,7 @@ impl RecoverableJobHandler for FoldersConsistencyCheck {
"folders_consistency completed with {} finding(s)",
finding_count
);
return RunOutcome::Completed;
return RunOutcome::completed();
}
}
}
@@ -799,7 +799,15 @@ impl StorageMigrationService {
"✅ storage_migration completed — hot-swapped runtime backend to `{target_name}`, \
writes resumed. No restart required."
);
RunOutcome::Completed
// Per-run summary counters merged into `stats` for the admin
// UI drawer. Same shape as `storage_rotate`'s extras + one
// extra `source_missing` counter unique to migration.
RunOutcome::completed_with(serde_json::json!({
"copied": copied,
"skipped": skipped,
"failed": failed,
"source_missing": source_missing,
}))
}
}
@@ -232,9 +232,13 @@ impl RecoverableJobHandler for StorageRotateService {
event = "storage_rotate.run_started",
run_id = %store.run_id(),
target_name = %target_name,
head_format = ?head_format,
// `%` (Display) → SSH-style `encrypted-v1 key_fp=83:96:...`
// instead of the raw `[131, 150, 255, ...]` byte-array
// shape Debug produces. Matches how `xxd` renders the
// header bytes on disk.
head_format = %head_format,
resuming = !is_fresh,
"storage_rotate started on `{target_name}` (head_format = {head_format:?})"
"storage_rotate started on `{target_name}` (head_format = {head_format})"
);
// Seed the progress snapshot. Total = count_total's estimate;
@@ -411,8 +415,8 @@ impl RecoverableJobHandler for StorageRotateService {
serde_json::json!({
"hash": hash,
"phase": "write",
"from": format!("{current_format:?}"),
"to": format!("{head_format:?}"),
"from": format!("{current_format}"),
"to": format!("{head_format}"),
"error": e.to_string(),
}),
)
@@ -484,7 +488,16 @@ impl StorageRotateService {
failed = failed,
"storage_rotate completed on `{target_name}` — {rewritten} rewritten, {skipped} skipped, {failed} failed"
);
RunOutcome::Completed
// Surface the per-run summary counters as extras merged into
// the run row's `stats` JSONB. Frontend renders whatever keys
// are present, so no wire-format bumping is needed — the
// admin UI's run drawer just picks these up alongside the
// engine-owned `finding_count` + `scanned_count`.
RunOutcome::completed_with(serde_json::json!({
"rewritten": rewritten,
"skipped": skipped,
"failed": failed,
}))
}
fn clear_progress(&self) {