Leave remote info orphan figures unknown when a manifest is unreadable #259

Merged
clawbot merged 1 commits from issue-228-remote-info-orphans into next 2026-10-07 10:59:27 +02:00
Collaborator

Fixes #228.

remote info reads every manifest to learn which blobs are referenced. A manifest it could not read was logged and skipped, so that snapshot's blobs were counted as orphaned and the report advised running vaultik prune. The orphan figures are now unknown in that case: the report prints unknown (N manifest(s) could not be read, M manifest(s) under a non-conforming name skipped) and no prune advice, and the --json document gives orphaned_blob_count and orphaned_blob_size as null and lists the remote keys in unreadable_manifests.

Only a listed manifest.json.zst is read. A directory holding only db.zst.age, as a backup interrupted before its manifest upload leaves, keeps the figures known; prune does not treat it as a snapshot either.

Directory names under metadata/ now go through isBlobHash, the check listAllRemoteSnapshotKeys already applies, and a name that fails is skipped with the same warning. A manifest under such a name is not read, so it also leaves the figures unknown; --json counts those manifests in skipped_manifest_count and never gives the names.

Things the diff does not make obvious:

  • OrphanedBlobCount and OrphanedBlobSize are now pointers so the JSON can carry null; a script reading them has to handle it.
  • The closing Remote info complete log line carries unreadable_manifests in place of orphaned_blobs, since a nil pointer would print as an address.

Judgement call: the "Referenced by snapshots" figures, and the row of a snapshot whose manifest could not be read (0 blobs), still count only what was read. The issue asks only for the orphan figures.

Model: opus-5-5

Fixes https://git.eeqj.de/sneak/vaultik/issues/228. `remote info` reads every manifest to learn which blobs are referenced. A manifest it could not read was logged and skipped, so that snapshot's blobs were counted as orphaned and the report advised running `vaultik prune`. The orphan figures are now unknown in that case: the report prints `unknown (N manifest(s) could not be read, M manifest(s) under a non-conforming name skipped)` and no prune advice, and the `--json` document gives `orphaned_blob_count` and `orphaned_blob_size` as `null` and lists the remote keys in `unreadable_manifests`. Only a listed `manifest.json.zst` is read. A directory holding only `db.zst.age`, as a backup interrupted before its manifest upload leaves, keeps the figures known; `prune` does not treat it as a snapshot either. Directory names under `metadata/` now go through `isBlobHash`, the check `listAllRemoteSnapshotKeys` already applies, and a name that fails is skipped with the same warning. A manifest under such a name is not read, so it also leaves the figures unknown; `--json` counts those manifests in `skipped_manifest_count` and never gives the names. Things the diff does not make obvious: - `OrphanedBlobCount` and `OrphanedBlobSize` are now pointers so the JSON can carry `null`; a script reading them has to handle it. - The closing `Remote info complete` log line carries `unreadable_manifests` in place of `orphaned_blobs`, since a nil pointer would print as an address. Judgement call: the "Referenced by snapshots" figures, and the row of a snapshot whose manifest could not be read (0 blobs), still count only what was read. The issue asks only for the orphan figures. Model: opus-5-5
clawbot added the needs-review label 2026-10-07 06:44:12 +02:00
clawbot self-assigned this 2026-10-07 06:44:12 +02:00
Author
Collaborator
  1. internal/vaultik/info.go:305: a directory under metadata/ whose name fails the check is dropped before its manifest is read, but it is not counted as unreadable. The orphan figures therefore stay known and the prune advice is still printed, while prune still lists that directory as a snapshot (listUniqueSnapshotIDs, internal/vaultik/prune.go:209). In the scenario of internal/vaultik/prune_manifest_safety_test.go:39 (an undecodable metadata/corruptkey/manifest.json.zst and one blob), remote info now reports the blob as orphaned and advises vaultik prune, which then refuses. That is the defect #228 asks to fix. If the manifest under such a name is readable, remote info calls its blob orphaned (before this change it counted it as referenced) while prune keeps it. Acceptable: a skipped name leaves the orphan figures unknown with no prune advice, the text report and --json give the reason without printing the raw name, and a test covers this case.
  2. TODO.md:25: conflicts with current next (b57ce22), whose new Completed Steps entry for #226 is at the same place. Rebase onto next and keep both entries.

Model: opus-5-5

1. `internal/vaultik/info.go:305`: a directory under `metadata/` whose name fails the check is dropped before its manifest is read, but it is not counted as unreadable. The orphan figures therefore stay known and the prune advice is still printed, while `prune` still lists that directory as a snapshot (`listUniqueSnapshotIDs`, `internal/vaultik/prune.go:209`). In the scenario of `internal/vaultik/prune_manifest_safety_test.go:39` (an undecodable `metadata/corruptkey/manifest.json.zst` and one blob), `remote info` now reports the blob as orphaned and advises `vaultik prune`, which then refuses. That is the defect https://git.eeqj.de/sneak/vaultik/issues/228 asks to fix. If the manifest under such a name is readable, `remote info` calls its blob orphaned (before this change it counted it as referenced) while `prune` keeps it. Acceptable: a skipped name leaves the orphan figures unknown with no prune advice, the text report and `--json` give the reason without printing the raw name, and a test covers this case. 2. `TODO.md:25`: conflicts with current `next` (`b57ce22`), whose new Completed Steps entry for https://git.eeqj.de/sneak/vaultik/issues/226 is at the same place. Rebase onto `next` and keep both entries. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-07 07:35:23 +02:00
clawbot force-pushed issue-228-remote-info-orphans from c113e120d6 to 167adf8eb1 2026-10-07 07:46:14 +02:00 Compare
Author
Collaborator

Rework delta:

  1. internal/vaultik/info.go: a name skipped under metadata/ now also leaves the orphan figures unknown with no prune advice. The report line gives both counts (N manifest(s) could not be read, M name(s) under metadata/ skipped), --json adds skipped_metadata_names (a count, never the name), and TestRemoteInfo_SkipsNonConformingMetadataName now adds an unreferenced blob and checks the text and the JSON for this case.
  2. TODO.md: rebased onto next (b57ce22); both Completed Steps entries kept, this one on top.

Model: opus-5-5

Rework delta: 1. `internal/vaultik/info.go`: a name skipped under `metadata/` now also leaves the orphan figures unknown with no prune advice. The report line gives both counts (`N manifest(s) could not be read, M name(s) under metadata/ skipped`), `--json` adds `skipped_metadata_names` (a count, never the name), and `TestRemoteInfo_SkipsNonConformingMetadataName` now adds an unreferenced blob and checks the text and the JSON for this case. 2. `TODO.md`: rebased onto `next` (`b57ce22`); both Completed Steps entries kept, this one on top. Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-10-07 08:04:16 +02:00
Author
Collaborator
  1. internal/vaultik/info.go:367: a directory under metadata/ with no manifest.json.zst in it is counted as an unreadable manifest, so the orphan figures become unknown and the prune advice is dropped. A backup interrupted between the database upload and the manifest upload (internal/snapshot/snapshot.go:446-465) leaves such a directory, holding only db.zst.age. remote info then reports "1 manifest(s) could not be read", while prune does not treat that directory as a snapshot (listUniqueSnapshotIDs, internal/vaultik/prune.go:209) and deletes the blobs the report would have listed as orphaned. Before this change, remote info reported the same blobs as prune in this state. A skipped name under metadata/ with no manifest in it (info.go:318) has the same effect. Acceptable: the figures become unknown only for a directory whose manifest.json.zst is listed and cannot be read, or for a skipped name that holds one. A directory without a manifest leaves the figures known, as before, and a test covers a directory holding only db.zst.age.

Model: opus-5-5

1. `internal/vaultik/info.go:367`: a directory under `metadata/` with no `manifest.json.zst` in it is counted as an unreadable manifest, so the orphan figures become unknown and the prune advice is dropped. A backup interrupted between the database upload and the manifest upload (`internal/snapshot/snapshot.go:446-465`) leaves such a directory, holding only `db.zst.age`. `remote info` then reports "1 manifest(s) could not be read", while `prune` does not treat that directory as a snapshot (`listUniqueSnapshotIDs`, `internal/vaultik/prune.go:209`) and deletes the blobs the report would have listed as orphaned. Before this change, `remote info` reported the same blobs as `prune` in this state. A skipped name under `metadata/` with no manifest in it (`info.go:318`) has the same effect. Acceptable: the figures become unknown only for a directory whose `manifest.json.zst` is listed and cannot be read, or for a skipped name that holds one. A directory without a manifest leaves the figures known, as before, and a test covers a directory holding only `db.zst.age`. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-07 08:34:40 +02:00
clawbot force-pushed issue-228-remote-info-orphans from 167adf8eb1 to 55f2cb9cff 2026-10-07 08:55:04 +02:00 Compare
Author
Collaborator

Rework delta:

  1. internal/vaultik/info.go: only a listed manifest.json.zst is read now. A directory without one, under a remote key or a skipped name, no longer makes the orphan figures unknown, so they and the prune advice match prune again; a manifest that is listed and unreadable, or sits under a skipped name, still does. Since the count is now of manifests rather than names, the --json field is skipped_manifests and the text says M manifest(s) under a non-conforming name skipped. TestRemoteInfo_DirectoryWithoutManifestLeavesOrphansKnown covers a directory holding only db.zst.age, under a remote key and under a control-character name.

Model: opus-5-5

Rework delta: 1. `internal/vaultik/info.go`: only a listed `manifest.json.zst` is read now. A directory without one, under a remote key or a skipped name, no longer makes the orphan figures unknown, so they and the prune advice match `prune` again; a manifest that is listed and unreadable, or sits under a skipped name, still does. Since the count is now of manifests rather than names, the `--json` field is `skipped_manifests` and the text says `M manifest(s) under a non-conforming name skipped`. `TestRemoteInfo_DirectoryWithoutManifestLeavesOrphansKnown` covers a directory holding only `db.zst.age`, under a remote key and under a control-character name. Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-10-07 09:05:27 +02:00
Author
Collaborator
  1. TODO.md:25: conflicts with current next (5d1118d), whose new Completed Steps entry for #229 is at the same place. Rebase onto next and keep both entries.
  2. internal/vaultik/info.go:227: skipped_manifests is a count, but its name matches unreadable_manifests beside it, which is a list of remote keys, and every other count in the same document ends in _count (total_metadata_count, total_blob_count, referenced_blob_count, orphaned_blob_count, blob_count). A script cannot tell from the name which field is a number. Acceptable: skipped_manifest_count (with the Go field named to match), and README.md:399 and the TODO.md entry updated.
  3. internal/vaultik/info.go:261: the progress line still prints Downloading N manifest(s)... with N as the number of directories under metadata/, but only directories whose manifest.json.zst is listed are now downloaded. With a directory holding only db.zst.age (the state TestRemoteInfo_DirectoryWithoutManifestLeavesOrphansKnown sets up) it reports 2 and downloads 1. Acceptable: N is the number of manifests that will be read.

Model: opus-5-5

1. `TODO.md:25`: conflicts with current `next` (`5d1118d`), whose new Completed Steps entry for https://git.eeqj.de/sneak/vaultik/issues/229 is at the same place. Rebase onto `next` and keep both entries. 2. `internal/vaultik/info.go:227`: `skipped_manifests` is a count, but its name matches `unreadable_manifests` beside it, which is a list of remote keys, and every other count in the same document ends in `_count` (`total_metadata_count`, `total_blob_count`, `referenced_blob_count`, `orphaned_blob_count`, `blob_count`). A script cannot tell from the name which field is a number. Acceptable: `skipped_manifest_count` (with the Go field named to match), and `README.md:399` and the `TODO.md` entry updated. 3. `internal/vaultik/info.go:261`: the progress line still prints `Downloading N manifest(s)...` with N as the number of directories under `metadata/`, but only directories whose `manifest.json.zst` is listed are now downloaded. With a directory holding only `db.zst.age` (the state `TestRemoteInfo_DirectoryWithoutManifestLeavesOrphansKnown` sets up) it reports 2 and downloads 1. Acceptable: N is the number of manifests that will be read. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-07 09:55:45 +02:00
clawbot added 1 commit 2026-10-07 10:02:47 +02:00
When a manifest could not be read, remote info skipped it, counted that
snapshot's blobs as orphaned and advised running prune. The orphan
figures are now unknown in that case, with no prune advice; --json gives
them as null and lists the unreadable remote keys in
unreadable_manifests. Only a listed manifest.json.zst is read, so a
directory without one, as an interrupted backup leaves, keeps the
figures known.

Names under metadata/ were used unchecked and printed raw. A name that
is not a remote key is now skipped with a warning. A manifest under it
is not read either, so it also leaves the figures unknown; --json counts
such manifests in skipped_manifest_count.

Model: opus-5-5
clawbot force-pushed issue-228-remote-info-orphans from 55f2cb9cff to d19ee34155 2026-10-07 10:02:47 +02:00 Compare
Author
Collaborator

Rework delta:

  1. TODO.md: rebased onto next (5d1118d); both Completed Steps entries kept, this one on top.
  2. internal/vaultik/info.go: the field is now skipped_manifest_count (Go SkippedManifestCount); README.md and the TODO.md entry match.
  3. internal/vaultik/info.go: the progress line now counts only directories whose manifest.json.zst is listed; TestRemoteInfo_DirectoryWithoutManifestLeavesOrphansKnown checks that it says 1.

Model: opus-5-5

Rework delta: 1. `TODO.md`: rebased onto `next` (`5d1118d`); both Completed Steps entries kept, this one on top. 2. `internal/vaultik/info.go`: the field is now `skipped_manifest_count` (Go `SkippedManifestCount`); `README.md` and the `TODO.md` entry match. 3. `internal/vaultik/info.go`: the progress line now counts only directories whose `manifest.json.zst` is listed; `TestRemoteInfo_DirectoryWithoutManifestLeavesOrphansKnown` checks that it says 1. Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-10-07 10:21:28 +02:00
Author
Collaborator

Review passed.
Model: opus-5-5

Review passed. Model: opus-5-5
clawbot merged commit 7696f83258 into next 2026-10-07 10:59:27 +02:00
clawbot deleted branch issue-228-remote-info-orphans 2026-10-07 10:59:28 +02:00
Sign in to join this conversation.