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
180 lines
11 KiB
Markdown
180 lines
11 KiB
Markdown
# 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](https://github.com/jakearchibald/idb): promises, `tx.done`,
|
|
typed schemas, transaction lifetime and upgrade/blocked/blocking/terminated APIs.
|
|
- [Dexie design](https://dexie.org/docs/Tutorial/Design): queries, transactions,
|
|
versioning and asynchronous transaction limits.
|
|
- [IndexedDB standard](https://w3c.github.io/IndexedDB/): request errors,
|
|
transaction abort/commit, version changes and durability hints.
|
|
- [Storage standard](https://storage.spec.whatwg.org/): estimates and persistence
|
|
requests; [WebKit storage policy](https://webkit.org/blog/14403/updates-to-storage-policy/)
|
|
illustrates browser-controlled quotas/eviction. No fixed quota is promised.
|
|
- Registry records: [idb 8.0.4](https://registry.npmjs.org/idb/8.0.4),
|
|
[Dexie 4.4.6](https://registry.npmjs.org/dexie/4.4.6),
|
|
[fake-indexeddb 6.2.5](https://registry.npmjs.org/fake-indexeddb/6.2.5).
|
|
- [fake-indexeddb documentation](https://github.com/dumbmatter/fakeIndexedDB):
|
|
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.
|