Implements #181: one album or file quak cannot decrypt no longer stops a refresh, so quak backup stores everything else.
Client.collectionsSince and filesSince return a row that does not decrypt or parse in undecryptable, with the reason, instead of throwing; the cursor still moves past it. refreshOnce leaves it out, an album with its files, and the store keeps that list in metadata.json until a later refresh gets the record again, decrypted or deleted.
lib.backup() records each such file in its scope in failures.json with an error starting could not be decrypted:, unless a copy in another album decrypted: it then stores the original from that copy, even when that album is outside onlyAlbumNames, and the album where it does not decrypt neither links nor lists it. Each such album is listed in the new BackupResult.undecryptableAlbums, and a run without --json prints one FAILED album line with its ID; quak backup exits 1 while either remains.
Not visible in the diff: a record that decrypted before and whose new copy does not is dropped from the cache like a deleted one, so its album links go while its original stays in the backup.
Disclosures:
Judgement call: undecryptable is optional on the page types, so the library's existing client stand-ins in the tests stay valid.
Judgement call: an album that cannot be decrypted is reported even by an onlyAlbumNames run, since its name is unknown.
Judgement call: a file left out is recorded with an empty title.
Judgement call: the README text is not in "Session handling" or "Retries", which do not cover decryption.
Model: opus-5-5
Implements https://git.eeqj.de/sneak/quak/issues/181: one album or file quak cannot decrypt no longer stops a refresh, so `quak backup` stores everything else.
`Client.collectionsSince` and `filesSince` return a row that does not decrypt or parse in `undecryptable`, with the reason, instead of throwing; the cursor still moves past it. `refreshOnce` leaves it out, an album with its files, and the store keeps that list in `metadata.json` until a later refresh gets the record again, decrypted or deleted.
`lib.backup()` records each such file in its scope in `failures.json` with an error starting `could not be decrypted:`, unless a copy in another album decrypted: it then stores the original from that copy, even when that album is outside `onlyAlbumNames`, and the album where it does not decrypt neither links nor lists it. Each such album is listed in the new `BackupResult.undecryptableAlbums`, and a run without `--json` prints one `FAILED album` line with its ID; `quak backup` exits 1 while either remains.
Not visible in the diff: a record that decrypted before and whose new copy does not is dropped from the cache like a deleted one, so its album links go while its original stays in the backup.
Disclosures:
- Judgement call: `undecryptable` is optional on the page types, so the library's existing client stand-ins in the tests stay valid.
- Judgement call: an album that cannot be decrypted is reported even by an `onlyAlbumNames` run, since its name is unknown.
- Judgement call: a file left out is recorded with an empty title.
- Judgement call: the README text is not in "Session handling" or "Retries", which do not cover decryption.
Model: opus-5-5
src/backup.ts, the loop over lib.undecryptable() in runLockedBackup: a file is skipped when a copy of it in any album decrypted, but only the albums in scope are backed up. In lib.backup({ onlyAlbumNames: ["Trip"] }), a file whose copy in Trip does not decrypt, and whose copy in an album outside the scope does, is neither stored nor recorded in failures.json, and the run reports no failure. That also makes the "Backup layout" sentence "A file in two albums is backed up as usual if its copy in the other album decrypted" untrue for such a run. Acceptable: back the file up from the copy that decrypted, or record it as failed, and add a test of the scoped run.
README.md, "Exit codes": "keeps the exit code at 1 until the server's copy changes" is wrong when the new copy still does not decrypt, because the exit code stays at 1. Acceptable: "until the server sends a copy that decrypts, or deletes it", as "Backup layout" says.
README.md, "Backup layout": "Each run prints one line for it" is untrue for a --json run, which prints no progress lines. Acceptable: say that a run without --json prints the line.
PR body: "An older metadata.json loads with an empty list, so the schema version is unchanged" plans for caches written before this change, and quak has no installed base. Acceptable: drop the sentence, and in src/library/store.ts declare StoredMetadata.undecryptable as required, like the other fields.
Model: opus-5-5
Review failed.
1. `src/backup.ts`, the loop over `lib.undecryptable()` in `runLockedBackup`: a file is skipped when a copy of it in any album decrypted, but only the albums in scope are backed up. In `lib.backup({ onlyAlbumNames: ["Trip"] })`, a file whose copy in Trip does not decrypt, and whose copy in an album outside the scope does, is neither stored nor recorded in `failures.json`, and the run reports no failure. That also makes the "Backup layout" sentence "A file in two albums is backed up as usual if its copy in the other album decrypted" untrue for such a run. Acceptable: back the file up from the copy that decrypted, or record it as failed, and add a test of the scoped run.
2. `README.md`, "Exit codes": "keeps the exit code at `1` until the server's copy changes" is wrong when the new copy still does not decrypt, because the exit code stays at 1. Acceptable: "until the server sends a copy that decrypts, or deletes it", as "Backup layout" says.
3. `README.md`, "Backup layout": "Each run prints one line for it" is untrue for a `--json` run, which prints no progress lines. Acceptable: say that a run without `--json` prints the line.
4. PR body: "An older `metadata.json` loads with an empty list, so the schema version is unchanged" plans for caches written before this change, and quak has no installed base. Acceptable: drop the sentence, and in `src/library/store.ts` declare `StoredMetadata.undecryptable` as required, like the other fields.
Model: opus-5-5
A file whose copy in an album in scope does not decrypt is now backed up from a copy that decrypted in any album, in scope or not; a new test covers a run limited to "Trip".
"Exit codes" now says the exit code stays at 1 until the server sends a copy that decrypts, or deletes it.
"Backup layout" now says a run without --json prints the FAILED album line.
The sentence is gone from the PR body, and StoredMetadata.undecryptable is required.
Model: opus-5-5
Rework:
1. A file whose copy in an album in scope does not decrypt is now backed up from a copy that decrypted in any album, in scope or not; a new test covers a run limited to "Trip".
2. "Exit codes" now says the exit code stays at `1` until the server sends a copy that decrypts, or deletes it.
3. "Backup layout" now says a run without `--json` prints the `FAILED album` line.
4. The sentence is gone from the PR body, and `StoredMetadata.undecryptable` is required.
Model: opus-5-5
README.md, "Default reads vs. fresh reads", the last sentence of the new paragraph: "lib.backup() reports each one" is untrue in two cases. A file left out in one album whose copy in another album decrypted is backed up and not reported; the test "backs up a file that does not decrypt in one album but does in another, and does not report it" expects exactly that. A file left out in an album outside onlyAlbumNames is not reported either. Acceptable: say which ones it reports, for example "lib.backup() reports each such album, and each such file in its scope that has no copy that decrypted (see "Backup layout")."
Model: opus-5-5
Review failed.
1. `README.md`, "Default reads vs. fresh reads", the last sentence of the new paragraph: "`lib.backup()` reports each one" is untrue in two cases. A file left out in one album whose copy in another album decrypted is backed up and not reported; the test "backs up a file that does not decrypt in one album but does in another, and does not report it" expects exactly that. A file left out in an album outside `onlyAlbumNames` is not reported either. Acceptable: say which ones it reports, for example "`lib.backup()` reports each such album, and each such file in its scope that has no copy that decrypted (see "Backup layout")."
Model: opus-5-5
Rework: the last sentence of the new paragraph in "Default reads vs. fresh reads" now says lib.backup() reports each such album, and each such file in its scope that has no copy that decrypted.
Model: opus-5-5
Rework: the last sentence of the new paragraph in "Default reads vs. fresh reads" now says `lib.backup()` reports each such album, and each such file in its scope that has no copy that decrypted.
Model: opus-5-5
test/cli/backup-undecryptable.test.ts: every test that expects exit 1 also has files in failures.json, so nothing checks that backupCommand (src/cli-commands.ts) exits 1 when the only thing left out is an album quak could not decrypt. Acceptable: a test whose only left-out record is an album, expecting exit 1, and 0 once the album decrypts or is deleted.
README.md, "Backup layout": "A file in two albums is backed up as usual if its copy in the other album decrypted" is not borne out, and neither is the same sentence in the new TODO.md entry. The album where its copy does not decrypt gets no link to it, its <name>.json does not list it, and the run reports nothing and can exit 0. Acceptable: say that its original is stored from the copy that decrypted, and that the album where it does not decrypt neither links nor lists it.
TODO.md, the new Completed Steps entry: "prints one line naming each such album's ID" is untrue for a --json run. Acceptable: say that a run without --json prints it, as "Backup layout" now does.
Model: opus-5-5
Review failed.
1. `test/cli/backup-undecryptable.test.ts`: every test that expects exit 1 also has files in `failures.json`, so nothing checks that `backupCommand` (`src/cli-commands.ts`) exits 1 when the only thing left out is an album quak could not decrypt. Acceptable: a test whose only left-out record is an album, expecting exit 1, and 0 once the album decrypts or is deleted.
2. `README.md`, "Backup layout": "A file in two albums is backed up as usual if its copy in the other album decrypted" is not borne out, and neither is the same sentence in the new `TODO.md` entry. The album where its copy does not decrypt gets no link to it, its `<name>.json` does not list it, and the run reports nothing and can exit 0. Acceptable: say that its original is stored from the copy that decrypted, and that the album where it does not decrypt neither links nor lists it.
3. `TODO.md`, the new Completed Steps entry: "prints one line naming each such album's ID" is untrue for a `--json` run. Acceptable: say that a run without `--json` prints it, as "Backup layout" now does.
Model: opus-5-5
collectionsSince and filesSince put a row that does not decrypt or parse in
an undecryptable list with the reason instead of throwing. The refresh
leaves it out, an album with its files, and the store keeps the list in
metadata.json until the server sends a copy that decrypts or deletes it,
since the next refresh asks only for changes. lib.backup() records each
such file in failures.json as "could not be decrypted: <reason>", unless a
copy in another album decrypted, logs one line per such album and lists
their IDs in undecryptableAlbums; quak backup exits 1 while any remain.
Tests use a stand-in Ente server with real encryption.
Model: opus-5-5
A run of lib.backup() limited to "Trip" must store a file whose copy in
Trip does not decrypt when its copy in an album the run skips does. Today
the run neither stores it nor records it as failed.
Model: opus-5-5
A file whose copy in an album in scope does not decrypt is now backed up
from a copy that decrypted in any album, not only one in scope; with no
such copy it is still recorded as failed. The README now says the exit
code stays at 1 until the server sends a copy that decrypts or deletes it,
and that only a run without --json prints the FAILED album line.
StoredMetadata.undecryptable is required, like the other fields.
Model: opus-5-5
A backup whose only record quak cannot decrypt is an album exits 1 with
nothing in failures.json, and 0 once the album decrypts or is deleted.
The test of a file in two albums now also checks that the album where its
copy does not decrypt neither links the original nor lists the file.
Model: opus-5-5
A file whose copy in another album decrypted is stored from that copy, and
the album where it does not decrypt neither links nor lists it. Only a run
without --json prints the line for an album. The exit-code and read-command
sentences no longer cover that file, and the failures.json sentence covers
only files in the backup's scope.
Model: opus-5-5
New tests: a backup whose only left-out record is an album exits 1 with nothing in failures.json, and 0 once the album decrypts or is deleted.
"Backup layout" and the TODO.md entry now say the original is stored from the copy that decrypted and the album where it does not decrypt neither links nor lists it; the two-album test now checks both.
The TODO.md entry now says the album line is printed without --json, and with --json the ID is in undecryptableAlbums.
Also, from rereading each sentence against the code: the "Exit codes" sentence now covers only an album and a file in failures.json, the read-command sentence says the command answers from what did decrypt, the "Backup layout" failures.json sentence covers only files in the backup's scope, "Every other file is backed up" is gone, and the PR body has the same corrections. The TODO.md entry is dated today.
Model: opus-5-5
Rework:
1. New tests: a backup whose only left-out record is an album exits 1 with nothing in `failures.json`, and 0 once the album decrypts or is deleted.
2. "Backup layout" and the `TODO.md` entry now say the original is stored from the copy that decrypted and the album where it does not decrypt neither links nor lists it; the two-album test now checks both.
3. The `TODO.md` entry now says the album line is printed without `--json`, and with `--json` the ID is in `undecryptableAlbums`.
Also, from rereading each sentence against the code: the "Exit codes" sentence now covers only an album and a file in `failures.json`, the read-command sentence says the command answers from what did decrypt, the "Backup layout" `failures.json` sentence covers only files in the backup's scope, "Every other file is backed up" is gone, and the PR body has the same corrections. The `TODO.md` entry is dated today.
Model: opus-5-5
src/backup.ts, runLockedBackup (the calls to removeStaleAlbumDirs and removeStaleLinks, lines 800 and 825): an album that decrypted in an earlier run and whose new copy does not has its folder and <name>.json deleted from the backup, and a file in the same case loses its link and its entry in its album's JSON. README.md "Backup layout" (line 795) says a run removes only the folders and JSON of albums that were deleted or renamed and touches nothing else in collections/, and no test covers a record that decrypted before. Acceptable: keep that album's folder and JSON, and the file's link, while the record is on the library's list of what it left out, or say in "Backup layout" that the run removes them; either way, a test of an album and a file that decrypted in an earlier run.
src/library/store.ts, MetadataStore.load (line 122): undecryptable is taken as it is, so a metadata.json whose undecryptable is not a list loads, and every later quak backup then stops with quak: this.undecryptable is not iterable until the cache is deleted, though load promises that a damaged file gives an empty store, never a thrown error. Acceptable: read it entry by entry, as collections and files are, so a damaged value discards the cache.
Model: opus-5-5
Review failed.
1. `src/backup.ts`, `runLockedBackup` (the calls to `removeStaleAlbumDirs` and `removeStaleLinks`, lines 800 and 825): an album that decrypted in an earlier run and whose new copy does not has its folder and `<name>.json` deleted from the backup, and a file in the same case loses its link and its entry in its album's JSON. `README.md` "Backup layout" (line 795) says a run removes only the folders and JSON of albums that were deleted or renamed and touches nothing else in `collections/`, and no test covers a record that decrypted before. Acceptable: keep that album's folder and JSON, and the file's link, while the record is on the library's list of what it left out, or say in "Backup layout" that the run removes them; either way, a test of an album and a file that decrypted in an earlier run.
2. `src/library/store.ts`, `MetadataStore.load` (line 122): `undecryptable` is taken as it is, so a `metadata.json` whose `undecryptable` is not a list loads, and every later `quak backup` then stops with `quak: this.undecryptable is not iterable` until the cache is deleted, though `load` promises that a damaged file gives an empty store, never a thrown error. Acceptable: read it entry by entry, as `collections` and `files` are, so a damaged value discards the cache.
Model: opus-5-5
Decision on finding 1 of the last review: take the second option. Album folders, album JSON and links are rebuilt from what the library holds on every run (README, "Backup layout"), so a run that cannot decrypt an album or a file's copy removes that folder, JSON and link, while the originals stay at their save paths and the run exits 1. "Backup layout" says so in a plain sentence, and a test covers an album and a file that decrypted in an earlier run and no longer do. Finding 2 is fixed as the review says.
Model: opus-5-5
Decision on finding 1 of the last review: take the second option. Album folders, album JSON and links are rebuilt from what the library holds on every run (README, "Backup layout"), so a run that cannot decrypt an album or a file's copy removes that folder, JSON and link, while the originals stay at their save paths and the run exits 1. "Backup layout" says so in a plain sentence, and a test covers an album and a file that decrypted in an earlier run and no longer do. Finding 2 is fixed as the review says.
Model: opus-5-5
A metadata.json whose list of what the refresh could not decrypt is
damaged must load as an empty store. A backup after an album and a
file's copy stop decrypting removes the album's folder and JSON and the
file's link in its album, and keeps the originals.
Model: opus-5-5
metadata.json's list of the albums and files the refresh could not
decrypt is read entry by entry, so a value that is not a list of records
discards the cache as other damage does, instead of failing every later
backup. "Backup layout" now says that an album, or a file's copy in an
album, that quak can no longer decrypt loses its folder, JSON or link as
a deleted one does, and the originals stay. It no longer mentions the
empty title in failures.json, which no test covers.
Model: opus-5-5
"Backup layout" now says an album quak can no longer decrypt loses its folder and JSON as a deleted one does, and a file's copy that no longer decrypts loses its link and its entry in that album's JSON, while the originals stay; a new test covers an album and a file that decrypted in an earlier run.
MetadataStore.load now reads undecryptable entry by entry, so a value that is not a list of records discards the cache; a new store test covers it.
Also, from rereading each sentence against the tests: "Backup layout" no longer mentions the empty title in failures.json, which no test checks.
Model: opus-5-5
Rework:
1. "Backup layout" now says an album quak can no longer decrypt loses its folder and JSON as a deleted one does, and a file's copy that no longer decrypts loses its link and its entry in that album's JSON, while the originals stay; a new test covers an album and a file that decrypted in an earlier run.
2. `MetadataStore.load` now reads `undecryptable` entry by entry, so a value that is not a list of records discards the cache; a new store test covers it.
Also, from rereading each sentence against the tests: "Backup layout" no longer mentions the empty title in `failures.json`, which no test checks.
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.
Implements #181: one album or file quak cannot decrypt no longer stops a refresh, so
quak backupstores everything else.Client.collectionsSinceandfilesSincereturn a row that does not decrypt or parse inundecryptable, with the reason, instead of throwing; the cursor still moves past it.refreshOnceleaves it out, an album with its files, and the store keeps that list inmetadata.jsonuntil a later refresh gets the record again, decrypted or deleted.lib.backup()records each such file in its scope infailures.jsonwith an error startingcould not be decrypted:, unless a copy in another album decrypted: it then stores the original from that copy, even when that album is outsideonlyAlbumNames, and the album where it does not decrypt neither links nor lists it. Each such album is listed in the newBackupResult.undecryptableAlbums, and a run without--jsonprints oneFAILED albumline with its ID;quak backupexits 1 while either remains.Not visible in the diff: a record that decrypted before and whose new copy does not is dropped from the cache like a deleted one, so its album links go while its original stays in the backup.
Disclosures:
undecryptableis optional on the page types, so the library's existing client stand-ins in the tests stay valid.onlyAlbumNamesrun, since its name is unknown.Model: opus-5-5
Review failed.
src/backup.ts, the loop overlib.undecryptable()inrunLockedBackup: a file is skipped when a copy of it in any album decrypted, but only the albums in scope are backed up. Inlib.backup({ onlyAlbumNames: ["Trip"] }), a file whose copy in Trip does not decrypt, and whose copy in an album outside the scope does, is neither stored nor recorded infailures.json, and the run reports no failure. That also makes the "Backup layout" sentence "A file in two albums is backed up as usual if its copy in the other album decrypted" untrue for such a run. Acceptable: back the file up from the copy that decrypted, or record it as failed, and add a test of the scoped run.README.md, "Exit codes": "keeps the exit code at1until the server's copy changes" is wrong when the new copy still does not decrypt, because the exit code stays at 1. Acceptable: "until the server sends a copy that decrypts, or deletes it", as "Backup layout" says.README.md, "Backup layout": "Each run prints one line for it" is untrue for a--jsonrun, which prints no progress lines. Acceptable: say that a run without--jsonprints the line.metadata.jsonloads with an empty list, so the schema version is unchanged" plans for caches written before this change, and quak has no installed base. Acceptable: drop the sentence, and insrc/library/store.tsdeclareStoredMetadata.undecryptableas required, like the other fields.Model: opus-5-5
Rework:
1until the server sends a copy that decrypts, or deletes it.--jsonprints theFAILED albumline.StoredMetadata.undecryptableis required.Model: opus-5-5
Review failed.
README.md, "Default reads vs. fresh reads", the last sentence of the new paragraph: "lib.backup()reports each one" is untrue in two cases. A file left out in one album whose copy in another album decrypted is backed up and not reported; the test "backs up a file that does not decrypt in one album but does in another, and does not report it" expects exactly that. A file left out in an album outsideonlyAlbumNamesis not reported either. Acceptable: say which ones it reports, for example "lib.backup()reports each such album, and each such file in its scope that has no copy that decrypted (see "Backup layout")."Model: opus-5-5
20b588728dto8bc542f1e5Rework: the last sentence of the new paragraph in "Default reads vs. fresh reads" now says
lib.backup()reports each such album, and each such file in its scope that has no copy that decrypted.Model: opus-5-5
Review failed.
test/cli/backup-undecryptable.test.ts: every test that expects exit 1 also has files infailures.json, so nothing checks thatbackupCommand(src/cli-commands.ts) exits 1 when the only thing left out is an album quak could not decrypt. Acceptable: a test whose only left-out record is an album, expecting exit 1, and 0 once the album decrypts or is deleted.README.md, "Backup layout": "A file in two albums is backed up as usual if its copy in the other album decrypted" is not borne out, and neither is the same sentence in the newTODO.mdentry. The album where its copy does not decrypt gets no link to it, its<name>.jsondoes not list it, and the run reports nothing and can exit 0. Acceptable: say that its original is stored from the copy that decrypted, and that the album where it does not decrypt neither links nor lists it.TODO.md, the new Completed Steps entry: "prints one line naming each such album's ID" is untrue for a--jsonrun. Acceptable: say that a run without--jsonprints it, as "Backup layout" now does.Model: opus-5-5
8bc542f1e5toc6bc1a6265Rework:
failures.json, and 0 once the album decrypts or is deleted.TODO.mdentry now say the original is stored from the copy that decrypted and the album where it does not decrypt neither links nor lists it; the two-album test now checks both.TODO.mdentry now says the album line is printed without--json, and with--jsonthe ID is inundecryptableAlbums.Also, from rereading each sentence against the code: the "Exit codes" sentence now covers only an album and a file in
failures.json, the read-command sentence says the command answers from what did decrypt, the "Backup layout"failures.jsonsentence covers only files in the backup's scope, "Every other file is backed up" is gone, and the PR body has the same corrections. TheTODO.mdentry is dated today.Model: opus-5-5
clawbot referenced this pull request2026-10-14 05:16:38 +02:00
Review failed.
src/backup.ts,runLockedBackup(the calls toremoveStaleAlbumDirsandremoveStaleLinks, lines 800 and 825): an album that decrypted in an earlier run and whose new copy does not has its folder and<name>.jsondeleted from the backup, and a file in the same case loses its link and its entry in its album's JSON.README.md"Backup layout" (line 795) says a run removes only the folders and JSON of albums that were deleted or renamed and touches nothing else incollections/, and no test covers a record that decrypted before. Acceptable: keep that album's folder and JSON, and the file's link, while the record is on the library's list of what it left out, or say in "Backup layout" that the run removes them; either way, a test of an album and a file that decrypted in an earlier run.src/library/store.ts,MetadataStore.load(line 122):undecryptableis taken as it is, so ametadata.jsonwhoseundecryptableis not a list loads, and every laterquak backupthen stops withquak: this.undecryptable is not iterableuntil the cache is deleted, thoughloadpromises that a damaged file gives an empty store, never a thrown error. Acceptable: read it entry by entry, ascollectionsandfilesare, so a damaged value discards the cache.Model: opus-5-5
Decision on finding 1 of the last review: take the second option. Album folders, album JSON and links are rebuilt from what the library holds on every run (README, "Backup layout"), so a run that cannot decrypt an album or a file's copy removes that folder, JSON and link, while the originals stay at their save paths and the run exits 1. "Backup layout" says so in a plain sentence, and a test covers an album and a file that decrypted in an earlier run and no longer do. Finding 2 is fixed as the review says.
Model: opus-5-5
Rework:
MetadataStore.loadnow readsundecryptableentry by entry, so a value that is not a list of records discards the cache; a new store test covers it.Also, from rereading each sentence against the tests: "Backup layout" no longer mentions the empty title in
failures.json, which no test checks.Model: opus-5-5
Review passed.
Model: opus-5-5