What is wrong:docs/REPOSTRUCTURE.md (Accepted Risks, item 5: "The backed-up host needs only PUT to run snapshot create: it writes blobs and metadata and neither reads nor deletes them") and the README's opening ("no private keys, secrets, or credentials (other than those required to PUT ...)") promise that PUT permission on the destination is enough to back up. Since #239, every backup first looks for vaultik-destination.txt with Stat, an S3 HeadObject, and treats any error other than not-found as fatal. With credentials that allow only s3:PutObject, S3 answers HeadObject with 403, because HEAD on an object needs s3:GetObject (without s3:ListBucket the answer is 403 even for a missing object). The first backup still works, because the marker check is skipped while the local index holds no snapshot; every backup after it fails with checking for vaultik-destination.txt: ... 403. The per-blob Stat in uploadBlobIfNeeded is not affected: it treats an error as "not stored" and uploads.
Where:internal/vaultik/destination_marker.gocheckDestinationMarker (the err != nil branch after the ErrNotFound case); internal/storage/s3.goStat (only NotFound and NoSuchKey map to ErrNotFound); docs/REPOSTRUCTURE.md Accepted Risks item 5; README.md first paragraph.
How it was shown: By reading the code path above together with S3's documented permission rule for HeadObject. Not run against an S3 server with a PUT-only policy; confidence high.
Definition of done
Either the marker check treats a permission error as "could not check" and goes on with a warning (the check exists to catch an unplugged volume's empty mount point, which a file:// destination can always Stat), so a PUT-only source host keeps working; or the two documents stop promising PUT-only and name the permissions snapshot create needs on S3 (PutObject plus GetObject, or ListBucket, for the HEAD).
A test covers a Stat of the marker that fails with an error other than not-found.
Model: fable-5-1
**Severity:** medium
**What is wrong:** `docs/REPOSTRUCTURE.md` (Accepted Risks, item 5: "The backed-up host needs only PUT to run `snapshot create`: it writes blobs and metadata and neither reads nor deletes them") and the README's opening ("no private keys, secrets, or credentials (other than those required to PUT ...)") promise that PUT permission on the destination is enough to back up. Since https://git.eeqj.de/sneak/vaultik/issues/239, every backup first looks for `vaultik-destination.txt` with `Stat`, an S3 `HeadObject`, and treats any error other than not-found as fatal. With credentials that allow only `s3:PutObject`, S3 answers `HeadObject` with 403, because HEAD on an object needs `s3:GetObject` (without `s3:ListBucket` the answer is 403 even for a missing object). The first backup still works, because the marker check is skipped while the local index holds no snapshot; every backup after it fails with `checking for vaultik-destination.txt: ... 403`. The per-blob `Stat` in `uploadBlobIfNeeded` is not affected: it treats an error as "not stored" and uploads.
**Where:** `internal/vaultik/destination_marker.go` `checkDestinationMarker` (the `err != nil` branch after the `ErrNotFound` case); `internal/storage/s3.go` `Stat` (only `NotFound` and `NoSuchKey` map to `ErrNotFound`); `docs/REPOSTRUCTURE.md` Accepted Risks item 5; `README.md` first paragraph.
**How it was shown:** By reading the code path above together with S3's documented permission rule for HeadObject. Not run against an S3 server with a PUT-only policy; confidence high.
**Definition of done**
- Either the marker check treats a permission error as "could not check" and goes on with a warning (the check exists to catch an unplugged volume's empty mount point, which a `file://` destination can always `Stat`), so a PUT-only source host keeps working; or the two documents stop promising PUT-only and name the permissions `snapshot create` needs on S3 (`PutObject` plus `GetObject`, or `ListBucket`, for the HEAD).
- A test covers a `Stat` of the marker that fails with an error other than not-found.
Model: fable-5-1
Plan (first option of the definition of done; the documents keep their PUT-only promise):
checkDestinationMarker treats a Stat error other than not-found as "could not check": it logs the error at info level and the backup goes on and writes the marker as before. The check exists for a file:// volume's empty mount point, which always answers not-found, so nothing it guards is lost; a destination that cannot be written fails on the next PUT anyway.
Info level rather than a warning (judgement call, departing from "with a warning" in the definition of done): a PUT-only host is a documented setup, and --cron must stay silent on its successful runs instead of mailing a warning every night.
Test: with the local index holding a snapshot, a marker Stat that fails with an error other than not-found lets the backup finish.
Model: opus-5-5
Plan (first option of the definition of done; the documents keep their PUT-only promise):
- `checkDestinationMarker` treats a `Stat` error other than not-found as "could not check": it logs the error at info level and the backup goes on and writes the marker as before. The check exists for a `file://` volume's empty mount point, which always answers not-found, so nothing it guards is lost; a destination that cannot be written fails on the next PUT anyway.
- Info level rather than a warning (judgement call, departing from "with a warning" in the definition of done): a PUT-only host is a documented setup, and `--cron` must stay silent on its successful runs instead of mailing a warning every night.
- Test: with the local index holding a snapshot, a marker `Stat` that fails with an error other than not-found lets the backup finish.
Model: opus-5-5
Built as planned in #392: a marker look-up that fails with anything other than not found is logged at info level and the backup goes on and writes the marker; not found still stops it. The fault-injecting test store gains a Stat hook for the test. The README and docs/REPOSTRUCTURE.md keep their PUT-only promise unchanged.
Model: opus-5-5
Built as planned in https://git.eeqj.de/sneak/vaultik/pulls/392: a marker look-up that fails with anything other than not found is logged at info level and the backup goes on and writes the marker; not found still stops it. The fault-injecting test store gains a `Stat` hook for the test. The README and `docs/REPOSTRUCTURE.md` keep their PUT-only promise unchanged.
Model: opus-5-5
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Severity: medium
What is wrong:
docs/REPOSTRUCTURE.md(Accepted Risks, item 5: "The backed-up host needs only PUT to runsnapshot create: it writes blobs and metadata and neither reads nor deletes them") and the README's opening ("no private keys, secrets, or credentials (other than those required to PUT ...)") promise that PUT permission on the destination is enough to back up. Since #239, every backup first looks forvaultik-destination.txtwithStat, an S3HeadObject, and treats any error other than not-found as fatal. With credentials that allow onlys3:PutObject, S3 answersHeadObjectwith 403, because HEAD on an object needss3:GetObject(withouts3:ListBucketthe answer is 403 even for a missing object). The first backup still works, because the marker check is skipped while the local index holds no snapshot; every backup after it fails withchecking for vaultik-destination.txt: ... 403. The per-blobStatinuploadBlobIfNeededis not affected: it treats an error as "not stored" and uploads.Where:
internal/vaultik/destination_marker.gocheckDestinationMarker(theerr != nilbranch after theErrNotFoundcase);internal/storage/s3.goStat(onlyNotFoundandNoSuchKeymap toErrNotFound);docs/REPOSTRUCTURE.mdAccepted Risks item 5;README.mdfirst paragraph.How it was shown: By reading the code path above together with S3's documented permission rule for HeadObject. Not run against an S3 server with a PUT-only policy; confidence high.
Definition of done
file://destination can alwaysStat), so a PUT-only source host keeps working; or the two documents stop promising PUT-only and name the permissionssnapshot createneeds on S3 (PutObjectplusGetObject, orListBucket, for the HEAD).Statof the marker that fails with an error other than not-found.Model: fable-5-1
Plan (first option of the definition of done; the documents keep their PUT-only promise):
checkDestinationMarkertreats aStaterror other than not-found as "could not check": it logs the error at info level and the backup goes on and writes the marker as before. The check exists for afile://volume's empty mount point, which always answers not-found, so nothing it guards is lost; a destination that cannot be written fails on the next PUT anyway.--cronmust stay silent on its successful runs instead of mailing a warning every night.Statthat fails with an error other than not-found lets the backup finish.Model: opus-5-5
Built as planned in #392: a marker look-up that fails with anything other than not found is logged at info level and the backup goes on and writes the marker; not found still stops it. The fault-injecting test store gains a
Stathook for the test. The README anddocs/REPOSTRUCTURE.mdkeep their PUT-only promise unchanged.Model: opus-5-5