docs: mark the offline-engine design + review as superseded

Both describe a full offline mail replica with a persistent cursor-based sync
engine. That scope was dropped in favour of "a SQLite index we can prompt
against" - see the notes prepended to each file for what shipped instead
(lib/mail-index/** + app/api/offline/{reindex,search}).

Kept rather than deleted because several findings are still accurate and still
load-bearing: the SQLCipher binding investigation, the PRAGMA-key
silent-no-op landmine, the safeStorage Linux basic_text hazard, the
hosted-deployment gate, and the codebase survey.

The review's note also records the disposition of every CRITICAL/HIGH finding.
Most became MOOT rather than fixed - C2, C3, C4, H1 and H2 were all
consequences of a long-lived worker holding credentials, and the new shape has
no worker. C1 (the Docker build breakage) and H2's env-vs-fd point were fixed
as specified, and the review's two corrections to the design (the
cipher_version check needing a non-empty string, getSelectedStorageBackend
being Linux-only) are both in the shipped code.

Also recorded: two things the design got wrong beyond the scope change - its
claim that the chosen process needs no new secret handling (the review was
right) and its assumption that Next's file tracing would carry the native
module (it does not).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Bernd Rodler
2026-08-04 23:44:57 +02:00
co-authored by Claude Sonnet 5
parent 0271df4338
commit 31b4ea2ecd
2 changed files with 64 additions and 2 deletions
+27
View File
@@ -1,3 +1,30 @@
> # ⚠️ SUPERSEDED — reviews a design that was not built
>
> This reviews `ELECTRON-OFFLINE-ENGINE-DESIGN.md`, which was **dropped**. Its findings were the
> direct cause: seeing them, the human narrowed the requirement from a full offline mail replica to
> *"a SQLite index we can prompt against"*, refreshed on each delivery/change event. What shipped is
> `lib/mail-index/**` + `app/api/offline/{reindex,search}` — see that doc's superseded note.
>
> **This review did its job.** Most of its severe findings were resolved by the scope change
> removing the thing they were about, which is the strongest outcome a review can have:
>
> | Finding | Outcome |
> |---|---|
> | **C1** — `@signalapp/sqlcipher` in `dependencies` breaks both Alpine `docker build`s | **FIXED as specified.** It is an `optionalDependencies` entry with a guarded runtime require (`lib/mail-index/binding.ts`). Both `docker build`s verified passing, and the require verified failing cleanly with MODULE_NOT_FOUND inside the musl image. |
> | **C2** — credentials are request-scoped, so no persistent worker can hold them | **MOOT.** There is no worker. Indexing is a normal API route using the request's own `jmap_stalwart_ctx` cookie, via the existing `lib/stalwart/credentials.ts`. |
> | **C3** — the OAuth-refresh mitigation is itself the bug | **MOOT, and avoided by construction.** The indexer never touches the refresh-token cookie; it only reads an already-minted auth header, so it cannot rotate a token into a response nobody reads. |
> | **C4** — shared `registry.json` breaks the multi-account safety premise | **MOOT.** No registry, no epochs, no concurrent workers. |
> | **H1** — a server-side engine can't read a renderer-only setting | **MOOT.** The renderer decides when to index. |
> | **H2** — key handoff sequencing, and a nonce via env is readable by same-user processes | **FIXED.** The key crosses on an **inherited file descriptor**, never env, and is fetched per job and zeroed after — not held. Sequencing is moot: the key is fetched when a job runs, not at spawn. |
> | **H3** — local unread-count arithmetic needs a coherence story | **MOOT.** A retrieval index does not need to stay coherent with live unread counts. |
> | **H4** — no cap on concurrent multi-account sync | **MOOT.** One request, one account. |
> | *medium/low:* `getSelectedStorageBackend()` is Linux-only and would crash elsewhere | **FIXED** — platform-guarded. |
> | *medium/low:* `cipher_version` check would pass vacuously on zero rows | **FIXED** — the shipped assertion requires a non-empty *string*, and a test reads the raw file bytes for a plaintext canary. |
> | *medium/low:* the two bindings are not "the same code either way" | **CONFIRMED true, the hard way.** `@signalapp/sqlcipher` rejects varargs params (`TypeError: Params must be either object or array`) where better-sqlite3 accepts them. Documented in `binding.ts`. |
>
> Reviewing this file's own accuracy: its two re-executed claims (the binding working in Electron 43,
> and `PRAGMA key` being a silent no-op) both held up and both shaped the shipped code.
# Adversarial review: `docs/ELECTRON-OFFLINE-ENGINE-DESIGN.md`
Reviewer: independent agent, fresh context, no relation to the design's author. 2026-08-04.