Changes: Accept ADR 0004 and record independent S00-005 review, experiment status and handoff; preserve explicit native browser and durability limitations. Validation: Fresh-cache npm ci and npm run check passed on Node 24.21.0/npm 11.19.0; exact lock pins, HTTPS/SHA512, installed license texts and runtime refusal checks passed. make check passed 72 tooling tests. Scoped diff and whitespace reviewed; native browser quota and process restart were not tested. Task: S00-005 Agent: auto-qa-4078580caf39 Role: qa Action: review Status: done Task title: Выбрать IndexedDB wrapper и migration policy
11 KiB
ADR 0004: IndexedDB wrapper and migration policy
- Status: accepted; bounded storage design independently reviewed on 2026-10-15.
- Task / requirements: S00-005 / STORE-001, STORE-004.
- Date: 2026-10-15.
- Decision owner: auto-storage-0ce544ac4b40 (storage).
- Reviewer: auto-qa-4078580caf39 (qa); evidence:
state/evidence/S00-005-review.md.
Context and required capabilities
Choose browser persistence tooling and policy, using accepted ADR 0001 (TypeScript,
ESM, Node 24.21.0/npm 11.19.0). Account history, crypto state and settings require
isolation, transactional writes and honest failure reporting. This decision does
not define application record schemas or port signatures. The registry reserves
docs/contracts/persistence.md for S01; that file does not exist yet. No current
experiment needs a new shared interface. Downstream implementation must wait for
its accepted models/lifecycle/persistence contracts.
Candidates and primary sources
Documentation and registry metadata inspected on 2026-10-15:
| Candidate | Benefits | Cost / outcome |
|---|---|---|
| Native IndexedDB | Standard API, no package | Event/request handling requires more adapter code; reference behavior, not selected ergonomics |
| idb 8.0.4, ISC | Promise API, typed schemas, explicit transaction completion and upgrade callbacks | Native transaction lifetime still applies; selected and executed |
| Dexie 4.4.6, Apache-2.0 | Query helpers, declarative versions and transaction API | Broader abstraction than this bounded adapter needs; researched, not installed or benchmarked |
Select idb 8.0.4. Its small API surface keeps native transaction boundaries
visible while providing promises and TypeScript declarations. Dexie remains a
viable option if measured query requirements justify it. No speed or security
superiority is claimed. npm view verified exact candidate versions/licenses;
installed idb metadata has no runtime dependencies. Only the isolated experiment
manifest/lock changes here; production adoption belongs to later scoped tasks.
Sources:
- idb documentation: promises,
tx.done, typed schemas, transaction lifetime and upgrade/blocked/blocking/terminated APIs. - Dexie design: queries, transactions, versioning and asynchronous transaction limits.
- IndexedDB standard: request errors, transaction abort/commit, version changes and durability hints.
- Storage standard: estimates and persistence requests; WebKit storage policy illustrates browser-controlled quotas/eviction. No fixed quota is promised.
- Registry records: idb 8.0.4, Dexie 4.4.6, fake-indexeddb 6.2.5.
- fake-indexeddb documentation: in-memory testing implementation, without disk persistence.
Transaction and account policy
Use one versioned application database per origin with distinct history, crypto, settings and metadata stores. Every account-owned key and index starts with the canonical accountId; unscoped scans are forbidden in account APIs. AccountId is an opaque application identity, not a password or a raw JID database name. Capture account/generation before asynchronous work and validate it at application entry; do not resolve an account from whichever context is active after awaiting.
History mutations and their archive checkpoint share one readwrite transaction. Any coupled crypto mutation must use that same database transaction when required by the future crypto contract. Separate stores allow this; separate databases would not. Cache is disposable and must never be the only copy of crypto state. Network/worker/crypto preparation finishes before starting a transaction. Await only IDB requests within it; any other asynchronous work risks auto-commit.
Success is published only after tx.done resolves. Synchronous exceptions,
request failures and completion failures all produce a failed save; abort an
active transaction and observe completion before returning. Never swallow a
request error and then report success. External XMPP delivery is not part of the
local transaction: reconcile delivery separately rather than blindly resend.
Use strict durability hints for critical state where supported/verified, with
default durability explicitly recorded otherwise; neither is a hardware vault
or proof against device failure, eviction or user clearing.
Schema upgrade and connection ownership
Use monotonically increasing integer versions, sequential oldVersion steps and one versionchange transaction. A fresh database runs every needed step. Modify existing stores via the upgrade transaction; do not recreate them accidentally. Upgrade code is deterministic and local: no network, worker waits, secrets or unbounded whole-history transformation. Large resumable transformations require an independently designed recovery/checkpoint contract before implementation.
Each tab/worker owns its connection. On blocking/versionchange, stop accepting new writes, let current work finish/abort, close the connection and request reload to the supported schema. On blocked, show an upgrade-wait state with instructions to close/reload other tabs; do not delete the database or claim readiness. A wait deadline changes UI state, not the upgrade result. IndexedDB open cannot simply be cancelled: a late success for a retired attempt must be closed and ignored. On terminated, invalidate the handle and reopen under lifecycle control.
An aborted migration leaves the prior version/data available for investigation and a corrected forward retry. No automatic wipe, schema downgrade or silent rebuild of non-recoverable state. An older build receiving VersionError asks for an update; release rollback must retain a compatible reader or ship a corrective forward migration. Tests must cover fresh creation and each supported old version.
Quota, eviction and key ownership
Estimates are advisory and race with other origin storage. Request persistent storage through supported StorageManager APIs and report the actual granted state; denial does not mean storage is unavailable. Quota/unknown disk failures must stop the batch without advancing checkpoint. UI reports history not saved locally and retains bounded pending work in memory under the later queue policy. Retry is explicit/bounded after freeing disposable media/cache; never remove crypto, trust state or unsynced history to make space automatically. Reload or origin clearing may lose pending memory and local data; startup must detect missing schema/account markers and request a real recovery path rather than imply success. Browser testing still must verify native exhaustion, eviction and restart behavior.
Storage owns byte persistence and isolation, not cryptographic key generation, trust or unlock policy. Crypto owns serialization/versioning of opaque state and any wrapping/unlock material under its accepted ADR/contract. This task does not authorize saving raw private keys. Passwords remain memory-only; no password, unlock secret or reusable wrapping secret in localStorage/IndexedDB. Persistent unlock needs its own accepted decision. Same-origin script access is a security boundary: IndexedDB alone provides neither XSS resistance nor hardware backing. Account removal must delete its records across all owned stores atomically, invalidate in-flight context and release in-memory crypto; exact deletion and suppression semantics remain S01/application work, not an implemented API here.
Focused experiment and actual result
experiments/0004-storage-design uses exact idb 8.0.4 and fake-indexeddb 6.2.5,
locked HTTPS/SHA512 dependencies and the accepted Node/npm pins. Fresh-cache
npm ci --ignore-scripts --no-audit --no-fund and npm run check passed.
- Real fixture ConstraintError during v1→v2 unique-index migration aborts open; v1 store/data survive, new store/index disappear. Corrected retry reaches v2, is queryable and survives connection reopen in the same process.
- Explicit QuotaExceededError injection after successful history/checkpoint requests returns failed save, aborts both mutations and preserves account B. A clean retry commits both. This tests policy, not native quota generation.
- Holding v1 produces versionchange then blocked; upgrade has not executed. Closing v1 allows v2 and preserves checkpoint. Opening v1 again fails VersionError.
These are synthetic API experiments, not production adapters. Browser inventory was empty. Native disk quota, crash/process restart, browser multi-tab, eviction, persistence grants and crypto unlock were not tested. No browser readiness or application durability acceptance is implied. See README and task evidence for commands and limits; preserved RECOVERY.md describes the historical checkpoint.
License / browser constraints and consequences
idb's ISC notice is retained in the experiment; fake-indexeddb's Apache-2.0 text is retained as a test-only dependency notice. Neither package declares runtime dependencies in this lock graph. Installs disable lifecycle scripts and enforce normal TLS; audit/provenance assessment was not performed. Future distributions must retain notices for the actual bundled graph.
idb targets modern Chrome/Firefox/Safari/Edge and excludes IE. TypeScript types do not polyfill IndexedDB, Proxy or storage capabilities. ADR 0001 syntax floors are not a browser test matrix; S00-006 owns product support. If IndexedDB opening fails (private mode, policy, permissions or security errors), expose unavailable storage and fail saves honestly. No fake-indexeddb production fallback.
S01 must define exact keys/indexes, normalized account identifiers, save errors, cancellation, crypto ownership and valid/invalid fixtures before adapter work. Future tests must add account deletion/suppression, cancelled generations, every supported migration and native browser quota/restart coverage. New query requirements, browser incompatibility, unacceptable migration cost or changed crypto atomicity requirements trigger a bounded ADR revision, never an implicit wrapper/stack replacement.
Independent acceptance
QA reviewer auto-qa-4078580caf39 independently repeated fresh-cache npm ci and all rollback, injected quota-policy and blocked-upgrade assertions on Linux, Node 24.21.0/npm 11.19.0. Exact lock pins, integrity metadata, installed license texts and runtime refusal were checked; make check passed 72 tooling tests. Accepted only the wrapper decision and documented storage policies/fixture experiments. Native browser quota, disk/crash durability, process restart, multi-tab behavior, eviction, persistence grants and crypto unlock remain unverified. S01 contracts and production adapters still require their own work. See the independent review report for commands, source inspection and limits. Coordinator owns the review commit.