check / check (push) Successful in 24s
Add `MetadataStore` (src/library/store.ts): one metadata.json holding the user id, schema version, collections cursor, and decrypted collection and file records. Loaded whole into RAM with Maps for id lookups; rewritten whole through the download layer's fsync atomic writer (temp, fsync, rename, dir fsync). Binary keys are base64-encoded on disk. A missing, unparseable, or wrong-schema file loads as an empty store, because the file is only a cache the refresh unit repopulates. Directory 0700, file 0600, matching session.json. No lock file; no sync() beyond the writer. This unit only stores; issue 42's refresh unit populates it. (closes #41) Model: opus-4-8
7.6 KiB
7.6 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
Update the README API reference section to match the current implementation.
Completed Steps
- 2026-09-22: Added the on-disk JSON metadata store (
src/library/store.ts, issue 41), phase 2 of the cache/API work. Onemetadata.jsonholds the user id, schema version, collections cursor, and the decrypted collection and file records, loaded whole into RAM withMaps for id lookups and rewritten whole through the download layer's fsync atomic writer. Missing, corrupt, or wrong-schema files load empty; the directory is 0700 and the file 0600. The store only stores — the refresh unit (issue 42) populates it. - 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
- Tag v1.0.0.
- 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.