Fail closed on unreadable manifests instead of losing blobs (closes #157)
Prune learned which blobs are in use by reading every snapshot's manifest, but merely logged and skipped one it could not download or decode. Blobs referenced only by that snapshot then looked unreferenced and were deleted, with a zero exit -- and snapshot create --prune runs this unattended. collectReferencedBlobs now errors, naming the remote key, so prune deletes nothing and exits non-zero. Manifest generation likewise skipped a blob whose lookup failed or was missing, yielding a manifest short of what the snapshot needs; it now fails. Deep verify only warned when the manifest omitted a database blob; it now fails on any divergence. Docs corrected. Model: opus-4-8
This commit was merged in pull request #180.
This commit is contained in:
+15
-12
@@ -28,8 +28,10 @@ var (
|
||||
errChunkHashMismatch = errors.New("chunk hash mismatch")
|
||||
errTrailingBlobData = errors.New(
|
||||
"blob has unexpected trailing bytes not covered by chunk list")
|
||||
errManifestExtraBlob = errors.New("manifest contains blob not in database")
|
||||
errBlobSizeMismatch = errors.New("blob size mismatch")
|
||||
errManifestExtraBlob = errors.New("manifest contains blob not in database")
|
||||
errManifestMissingBlob = errors.New(
|
||||
"manifest omits blob present in database")
|
||||
errBlobSizeMismatch = errors.New("blob size mismatch")
|
||||
)
|
||||
|
||||
// verifyStatusFailed is the JSON status value for a failed verification.
|
||||
@@ -575,16 +577,11 @@ func (v *Vaultik) verifyManifestAgainstDatabase(
|
||||
manifestBlobMap[blob.Hash] = blob.CompressedSize
|
||||
}
|
||||
|
||||
// Check counts match
|
||||
if len(dbBlobMap) != len(manifestBlobMap) {
|
||||
log.Warn("Manifest blob count mismatch",
|
||||
"database_blobs", len(dbBlobMap),
|
||||
"manifest_blobs", len(manifestBlobMap),
|
||||
)
|
||||
// This is a warning, not an error - database is authoritative
|
||||
}
|
||||
|
||||
// Check each manifest blob exists in database with correct size
|
||||
// The manifest is the only blob list prune consults, so it must match
|
||||
// the database exactly. A blob in the manifest but not the database
|
||||
// points at a corrupt manifest; a blob in the database but omitted
|
||||
// from the manifest would be pruned away while this snapshot still
|
||||
// needs it. Either divergence fails verification.
|
||||
for hash, manifestSize := range manifestBlobMap {
|
||||
dbSize, exists := dbBlobMap[hash]
|
||||
if !exists {
|
||||
@@ -598,6 +595,12 @@ func (v *Vaultik) verifyManifestAgainstDatabase(
|
||||
}
|
||||
}
|
||||
|
||||
for hash := range dbBlobMap {
|
||||
if _, exists := manifestBlobMap[hash]; !exists {
|
||||
return fmt.Errorf("%w: %s", errManifestMissingBlob, hash)
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("✓ Manifest verified against database",
|
||||
"manifest_blobs", len(manifestBlobMap),
|
||||
"database_blobs", len(dbBlobMap),
|
||||
|
||||
Reference in New Issue
Block a user