check / check (push) Successful in 27s
A POST or PUT is replayed only when every errno in the cause chain is a connect errno and the walk reached the end of the chain, and postJSON/putJSON no longer follow redirects, so a redirect is an ApiError that is not retried. getRetryOptions() returns a copy. New tests pin every errno the classifier names, the cause-chain depth limit, a chain deeper than the limit, a two-error cycle, and a fresh deadline per attempt for every retrying entry point. The README endpoint list is now the one place naming the requests the replay rule covers; code comments point to it. Model: opus-5-5
10 KiB
10 KiB
Workflow
- branch (from
main) - do the work in Next Step
- move Next Step to the top of Completed Steps
- move the top item of Future Steps into Next Step
- commit (
TODO.mdchanges in the same commit as the work) - merge to
mainif the branch is not protected, otherwise open a PR - push
Status
pre-1.0
Next Step
Tag v1.0.0.
Completed Steps
- 2026-09-22: Hardened the retry classifier (issue 80). A
POSTorPUTis replayed only when every errno in the cause chain is a connect errno, and it no longer follows redirects.getRetryOptions()returns a copy. Tests pin every errno the classifier names, the cause-chain depth limit, cycle termination, and a fresh deadline per attempt for every retrying entry point. The README's endpoint list is the one place that names the requests the replay rule covers. - 2026-09-22: Stopped
make testcollecting tests from checkouts nested under.claude/(issue 25). vitest ignores.gitignorewhen finding tests, so a nested checkout ran the whole suite again;vitest.config.tsnow adds.claude/**to vitest's default excludes, andtest/packaging/nested-checkout.test.tsplants a nested checkout in a temp directory and fails if vitest would collect it. - 2026-09-22: Dropped the deprecated
@types/libsodium-wrappers-sumostub fromdevDependencies(issue 27). It shipped no declarations; the types come fromlibsodium-wrappers-sumoitself.yarn.lockregenerated byyarn remove. - 2026-09-22: Hardened the client session lifecycle (issue 10).
Client.fromJSONchecks every snapshot field and each key's decoded length and names the bad field;toJSONreads the token throughApiClient.getAuthTokenand throws when there is none;logoutzeroes the key buffers, andcollectionsSincere-checks for logout after its request so it never decrypts with zeroed keys. The CLI reports a corrupt session file separately from a missing one (src/cli-session.ts). - 2026-09-22: Sanitized file names taken from server metadata (issue 9). A new
src/filename.tsholds the one sanitizer, used byquak get/get-thumbwithout--out,downloadFile/downloadThumbnailwithoutoutPath, and the backup and metadata backup trees; it removes separators, control characters, leading dots and Windows device names, and falls back to a name built from the ID for an empty title. Originals-cache extensions are letters and digits only, else.bin. A user-supplied path is used as is.decryptFilereads a missing or non-string title as "" and rejects metadata that is not a JSON object. - 2026-09-22: Rewrote the README API reference (and the Getting Started / usage
snippets) to match the shipped cache/API library on
next(issue 53, issue 13). DocumentedLibrary.openand its options, the default-read vsfresh()distinction (and that the CLI's read commands are fresh), the record types andsnapshot()/subscribe(), thealbums/photos/timelineread surface,Photocontent methods,thumbnails.ensure, themldatasearch surface,backup(), the three request pools (10/5/25), and the on-disk cache layout; noted the deferred content-hash integrity check (issue 68). Docs-only; no code changed. - 2026-09-22: Added resumable, deletion-aware enumeration to
Client(issue 38, closes issue 7).collectionsSince/filesSincetake a starting cursor, decrypt live records, surface tombstoned ids in a separatedeletedlist (a tombstone has nothing to decrypt, so it is a bare id, not a hollow record), and return the maxupdationTimeseen as the cursor to resume from.filesSincerefuses to loop when the diff reportshasMorewithout advancing the cursor (issue 7).listCollections/listFilesare now thin wrappers that enumerate fromsinceTime: 0and drop deletions, so existing callers are unaffected. - 2026-09-22: Carried file size, thumbnail size, and the deletion flag through
decryptFile(issue 37, foundation for the cache/API design). Live files now populatefile.size/thumbnail.sizefrom the server'sinfo(leftundefinedwhen the server omits it), andisDeletedis carried from the diff row ontoEnteFile. No caller change:listFilesstill filters deleted rows before decrypting. Surfacing a tombstone through decryption belongs to the enumeration unit (issue 38). - 2026-08-10: Made
lint-once.test.tsenforce what its header claims. It walkedmake checkonly, so it never readDockerfile— the image CI builds throughscript/cibuild— and a secondprettier --check .could be added there with the suite staying green. The walk now also starts at.gitea/workflows/check.ymland follows itsrun:steps, so the graph under test is the one CI executes rather than the one someone assumed it executes. The lockfile assertion was a substring check against the whole ofscript/bootstrap, which has two install sites and so reported the branch the containers never take; the two branches are now resolved separately and everyyarn installin each is required to be--frozen-lockfile. Prettier is counted per occurrence instead of per line, so two invocations chained with&&no longer read as one, and edges are followed on counted lines instead of being skipped. Every way for the walk to reach nothing — an unknown target, an unknown script, a missing file, a node with no commands, an unknown node kind — is a thrown error rather than a quiet zero. Every assertion in the file was mutation-tested individually. - 2026-08-10: Stopped
make checkrunningprettier --check .twice. Since linting moved into Docker, the duplicate was one container pass and one host pass of the same check:script/lintbuildsDockerfile.lint, which runs prettier as a build step, andscript/checkthen calledscript/fmt-checkas well. The host call is gone fromscript/checkand fromscript/precommit; the container keeps checking formatting, because a successfulDockerfile.lintbuild is what CI treats as proof of a clean tree, and it is also what still fails the pre-commit hook on a badly formatted tree.script/fmt-checksurvives as a standalone entrypoint, whose verdict cannot drift from the container's. A test walks the invocation graph from each entrypoint — through the Makefile shims, thescript/calls and thedocker build— and asserts the prettier count, so the duplication cannot come back unnoticed. - 2026-08-10: Moved all linting into Docker.
script/lintbuilds a new rootDockerfile.lint, which copies the repo into the digest-pinned node image and runs eslint and prettier as build steps, so a successful build is a clean lint; no host lint path remains andyarn lintis gone frompackage.json. A fail-closedLINT_EPOCHguard stops Docker serving the linter layers from cache, which is how a lint build returns success in under a second having linted nothing. The lint stage insideDockerfileand itsCOPY --from=lintordering hack are gone: that image now runsmake testandmake buildonly, becausescript/checkcallsscript/lintand running it in a container would mean docker inside docker.script/cibuildbuilds the lint image first, then the test and build image. - 2026-08-09: Made
make dockergreen and policy-conformant. Multi-stage Dockerfile: a lint stage runsmake fmt-checkandmake lint, and the check stage takes aCOPY --from=lintdependency on it before runningmake checkandmake build.CHECK_EPOCHand a fail-closed guard stop Docker serving those two layers from cache, which is what let a build report success without running the suite.script/projectnamesaysquak, so the image is taggedquak;script/bootstrapupdates apt lists before installing, so a Debian base works;.dockerignoreno longer ships the compiled binary, the caches or agent worktrees into the build context, and keeps.gitignorein it for prettier. - 2026-08-09: Fixed the TypeScript build.
rootDiris the repo root, sobin/compiles alongsidesrc/instead of failing with TS6059; output isdist/src/anddist/bin/, which is wheremain,typesandbin.quaknow point.script/buildverifies the declared entrypoints exist after the compiler runs and makes the CLI executable, the Dockerfile runsmake buildas well asmake check, and aquakscript makes the README'syarn quak <command>examples work. - 2026-08-09: Retry policy: no retry on 4xx (except
408and429), exponential backoff with full jitter on 5xx, transport failures and truncated transfers, under per-attempt deadlines that cover the response body as well as the request. Downloads retry request, stream consumption and decryption as one unit;postJSONandputJSONare replayed only when the connection was never established. - 2026-08-09: Downloads verify the secretstream terminated on
TAG_FINALand write output atomically: a truncated body is rejected instead of landing on disk as a short file, and plaintext is staged in a sibling temp file and renamed into place, so a failed download leaves the destination untouched. - 2026-07-07 Adopted scripts-to-rule-them-all:
script/entrypoints, Makefile shims, README Entrypoints section - 2026-06-10: Decrypted collections shared by other users (sealed-box keys); listCollections drops deleted-collection tombstones.
- 2026-06-10: Login hardening: dual-2FA empty-string fields handled, TOTP preferred when a passkey is also enrolled, interactive input via @inquirer/prompts.
- 2026-06-10: Replaced sharp with pure JS (jpeg-js + exif-reader); added single-binary bun build and make install.
- 2026-06-09: Added backup-metadata command (ML data always included, --exif opt-in); rewrote README to match the implementation; added thumbnail helper tests.
- 2026-05-13: Full CLI surface: login, backup with dedup symlink layout, collections, files, get, get-thumb, thumbnail repair helpers.
- 2026-05-13: Client OO API with literate usage tests; file download and decryption; all three metadata layers decrypted and persisted; renamed quack to quak.
- 2026-05-11: SRP login flow (email OTP + TOTP) and ApiClient.
Future Steps
- Future desktop client, separate repo:
- Electron app skeleton consuming this library.
- Local SQLite cache keyed on (collectionID, fileID, updationTime).
- Background sync worker streaming new files into the cache.
- Gallery UI: thumbnails, full-image view, basic search.
- Upload, delete, and share operations in the library.