Adds MetadataStore (src/library/store.ts), phase 2 of the cache/API work (#36): the on-disk JSON metadata store the library reads and writes.
What it does:
One metadata.json holds userID, schemaVersion, the collections-list cursor, and the decrypted collection and file records (per collection: key, type, name, three magic layers, timestamp, sharing; per file: metadata, both blob headers, key, content hash, file/thumbnail sizes).
Loaded whole into RAM with Maps for id lookups; rewritten whole through the download layer's exported fsync atomic writer (temp, fsync, rename, dir fsync) — not re-implemented.
Binary keys are base64 on disk via Node Buffer (no sodium init needed; the store does no crypto).
Missing, unparseable, or wrong-schema-version files load as an empty store, because the file is only a cache the refresh unit rebuilds. Directory 0700, file 0600, matching session.json. No lock file; no sync() in any public surface.
File records are keyed per membership (collectionID, fileID): the same file in two collections is two records with distinct keys, and deleting a collection drops its file records.
This unit only stores; the refresh unit (#42) populates it.
Traps worth knowing:
Permissions are set with chmod after the atomic writer's rename, since the writer takes no mode argument; the 0700 directory keeps other users out during the brief window.
Not wired into src/index.ts public exports — that belongs with the consumer, out of scope here.
TODO.md: its linear Next Step points at an unrelated task, so I appended to Completed Steps rather than rotating the pointer.
make check is green (298 tests, 10 new; docker lint clean).
Model: opus-4-8
Adds `MetadataStore` (`src/library/store.ts`), phase 2 of the cache/API work (https://git.eeqj.de/sneak/quak/issues/36): the on-disk JSON metadata store the library reads and writes.
What it does:
- One `metadata.json` holds `userID`, `schemaVersion`, the collections-list cursor, and the decrypted collection and file records (per collection: key, type, name, three magic layers, timestamp, sharing; per file: metadata, both blob headers, key, content hash, file/thumbnail sizes).
- Loaded whole into RAM with `Map`s for id lookups; rewritten whole through the download layer's exported fsync atomic writer (temp, fsync, rename, dir fsync) — not re-implemented.
- Binary keys are base64 on disk via Node `Buffer` (no sodium init needed; the store does no crypto).
- Missing, unparseable, or wrong-schema-version files load as an empty store, because the file is only a cache the refresh unit rebuilds. Directory `0700`, file `0600`, matching `session.json`. No lock file; no `sync()` in any public surface.
File records are keyed per membership `(collectionID, fileID)`: the same file in two collections is two records with distinct keys, and deleting a collection drops its file records.
This unit only stores; the refresh unit (https://git.eeqj.de/sneak/quak/issues/42) populates it.
Traps worth knowing:
- Permissions are set with `chmod` after the atomic writer's rename, since the writer takes no mode argument; the `0700` directory keeps other users out during the brief window.
- Not wired into `src/index.ts` public exports — that belongs with the consumer, out of scope here.
- `TODO.md`: its linear Next Step points at an unrelated task, so I appended to Completed Steps rather than rotating the pointer.
`make check` is green (298 tests, 10 new; docker lint clean).
Model: opus-4-8
The on-disk JSON metadata store meets the issue's definition of done. It loads metadata.json whole into RAM with id-lookup Maps and rewrites it whole through the exported fsync atomic writer that landed with #39 (temp file, fsync, rename, dir fsync) rather than re-implementing one. A missing, unparseable, or wrong-schema file loads as an empty store without throwing, because the file is only a cache. The directory is created 0700 and the file 0600; there is no lock file and no sync() in any public method. The round-trip (keys and all), empty-on-corrupt/missing, and 0700/0600 permission tests are meaningful — each fails when its behaviour is broken. make check is green here on the PR head rebased onto current next, and the change is comprehensible in one reading with no coined terms.
Disclosure (judgement call, not gated): the PR body runs ~266 words, marginally over the ~250-word guideline; it does not affect the merged code.
Model: opus-4-8
**PASS**
The on-disk JSON metadata store meets the issue's definition of done. It loads `metadata.json` whole into RAM with id-lookup `Map`s and rewrites it whole through the exported fsync atomic writer that landed with https://git.eeqj.de/sneak/quak/pulls/39 (temp file, fsync, rename, dir fsync) rather than re-implementing one. A missing, unparseable, or wrong-schema file loads as an empty store without throwing, because the file is only a cache. The directory is created `0700` and the file `0600`; there is no lock file and no `sync()` in any public method. The round-trip (keys and all), empty-on-corrupt/missing, and `0700`/`0600` permission tests are meaningful — each fails when its behaviour is broken. `make check` is green here on the PR head rebased onto current `next`, and the change is comprehensible in one reading with no coined terms.
Disclosure (judgement call, not gated): the PR body runs ~266 words, marginally over the ~250-word guideline; it does not affect the merged code.
Model: opus-4-8
Failing first: the on-disk JSON metadata store does not exist yet. These
tests fix its contract before the implementation lands — round-trip
fidelity including the binary keys, missing/corrupt files loading empty, a
schema-version mismatch discarded as stale cache, atomic writes leaving no
temp sibling, and directory 0700 / file 0600 permissions.
Model: opus-4-8
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
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.
Adds
MetadataStore(src/library/store.ts), phase 2 of the cache/API work (#36): the on-disk JSON metadata store the library reads and writes.What it does:
metadata.jsonholdsuserID,schemaVersion, the collections-list cursor, and the decrypted collection and file records (per collection: key, type, name, three magic layers, timestamp, sharing; per file: metadata, both blob headers, key, content hash, file/thumbnail sizes).Maps for id lookups; rewritten whole through the download layer's exported fsync atomic writer (temp, fsync, rename, dir fsync) — not re-implemented.Buffer(no sodium init needed; the store does no crypto).0700, file0600, matchingsession.json. No lock file; nosync()in any public surface.File records are keyed per membership
(collectionID, fileID): the same file in two collections is two records with distinct keys, and deleting a collection drops its file records.This unit only stores; the refresh unit (#42) populates it.
Traps worth knowing:
chmodafter the atomic writer's rename, since the writer takes no mode argument; the0700directory keeps other users out during the brief window.src/index.tspublic exports — that belongs with the consumer, out of scope here.TODO.md: its linear Next Step points at an unrelated task, so I appended to Completed Steps rather than rotating the pointer.make checkis green (298 tests, 10 new; docker lint clean).Model: opus-4-8
PASS
The on-disk JSON metadata store meets the issue's definition of done. It loads
metadata.jsonwhole into RAM with id-lookupMaps and rewrites it whole through the exported fsync atomic writer that landed with #39 (temp file, fsync, rename, dir fsync) rather than re-implementing one. A missing, unparseable, or wrong-schema file loads as an empty store without throwing, because the file is only a cache. The directory is created0700and the file0600; there is no lock file and nosync()in any public method. The round-trip (keys and all), empty-on-corrupt/missing, and0700/0600permission tests are meaningful — each fails when its behaviour is broken.make checkis green here on the PR head rebased onto currentnext, and the change is comprehensible in one reading with no coined terms.Disclosure (judgement call, not gated): the PR body runs ~266 words, marginally over the ~250-word guideline; it does not affect the merged code.
Model: opus-4-8
893cc95ab1to76141ae919