Files
crowd-funder-for-time-pwa/doc/CHANGES/history/PLAN-startup-db-failure-recovery.md
T

164 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Plan: Startup Database Failure — Diagnostics and Degraded Mode
## Status
Closed without building Parts A–C. The startup failure this plan prepared for
had a root cause, and the fix in `PLAN-startup-db-fix-crash.md` removed it: the
SQLite plugin's configuration key, migration recording, and Android backup
rules. A user who saw the error on every launch started normally after
installing a build with that fix.
The recovery screen from commit d6ccf0e4 remains the answer for any other
database failure. Parts A–C below are kept as a design to pick up if startup
database failures are reported again; nothing in them has been built.
## Goal
When the local database cannot be opened, a user should (1) get a useful
report to the developers with one tap, and (2) keep using whatever
parts of the app do not need local storage, with a clear path to recovery.
Requirements:
1. A failure report reaches the developers without the user composing an email,
and it carries enough to identify the cause on the first report.
2. The report is sent only with the user's consent and holds no keys, seed
phrases, or contact data.
3. A user whose database is down can browse public content (feed, projects,
claims) instead of being held on the recovery screen.
4. Every action that needs the database or an identity is visibly disabled with
the real reason, and points to the recovery screen.
5. Recovery steps never delete the database without keeping a copy.
## Startup failure behavior
- `CapacitorPlatformService._initialize()`
(`src/services/platforms/CapacitorPlatformService.ts`) opens the database in
three steps: "create connection", "open database", "run migrations". A
failure throws `Failed to initialize database (step: <step>): <cause>`.
- `src/utils/startupError.ts` recognizes that error by its message and hands it
to `StartView` through `sessionStorage`.
- Three places catch it and redirect to `/start`: the router `errorHandler`
and `beforeEach` guard (`src/router/index.ts`), and the Vue global error
handler (`src/main.common.ts`).
- The guard calls `retrieveAccountDids()` for every route except the
identity-setup routes, so with the database down every route except
`/account`, `/test` and `/logs` lands on `/start`.
- `StartView` shows the recovery screen: Try Again, Email Details, Copy
Details, "Save a Copy of My Data" (`shareDatabaseFile()`), and "Start Over"
(`resetDatabase()`, which copies the file aside before deleting it). When the
browser cannot run WebAssembly, the screen says so and points to the
settings that block it.
- API reads work without an identity: `getHeaders()`
(`src/libs/endorserServer.ts`) omits the Authorization header when there
is no DID.
- Every path that creates, saves, or signs with an identity reads the `secret`
and `accounts` tables (`src/libs/util.ts`: `saveNewIdentity`,
`retrieveFullyDecryptedAccount`), so no action that needs a DID can work while
the database is down.
## Part A: One-tap diagnostics report (not built)
Contents of the report:
- the startup error message (step and cause) and stack
- app version, platform, OS version, device model
- Android: WebView version and installer package (`getInstallerPackageName`)
- whether a database file existed before the first connection attempt of this
process, which shows whether Android restored a backup at install time
- whether the SQLite plugin loaded (a plugin load failure surfaces as a
`CapacitorSQLitePlugin:` prefix on the cause)
- the `migrations` table contents, when the database opens far enough to read it
Delivery: a POST to an endpoint on a server the project runs. The recovery
screen shows the exact payload and a Send button; the email option stays for
users who decline.
The database-existed flag needs a native-side or `Filesystem.stat` check before
`createConnection`; `databaseDirectoryUri()` resolves the directory on
Android and iOS.
## Part B: Degraded mode (not built)
- Change the router guard so that a database startup error records the error
and lets read-only routes through: home feed, projects, project and claim
views, and discovery. Identity-setup routes and anything that writes stay
redirected to `/start`.
- Show a persistent banner on every page while in degraded mode: "Your
device's storage isn't working. You can look around, but you can't act yet.
Fix it →", linking to the recovery screen.
- Disable buttons that need a DID or the database (give, offer, create project,
confirm, invite, contacts) with that same reason, instead of the
"someone must register you" text an unregistered user sees.
- Default `apiServer` in every settings fallback. `$accountSettings()`
(`src/utils/PlatformServiceMixin.ts`) returns the caller's `defaults` (often
`{}`) on its early-return and error paths, so `apiServer` can be undefined;
`HomeView` works around this on its own. This gap exists independently of
degraded mode. (Moved to PLAN.md)
- Order the recovery screen as a ladder: Try Again → Send Diagnostics → Save a
Copy → Start Over.
## Part C (optional): An identity that does not need SQLite (not built)
Key generation is pure (`generateSeed` → `deriveAddress` → `newIdentifier`).
In degraded mode the app could generate an identity, require the user to write
down the seed phrase, and keep the mnemonic in `@capacitor/preferences`
(installed, and independent of SQLite). The user would then have a DID someone
can register. When the database opens again, the app imports the identity from
Preferences and deletes the Preferences copy.
Trade-off: the mnemonic sits unencrypted in Preferences, about the same
protection as the database's secret stored next to the data it encrypts.
Worth building only if startup failures keep being reported with the plugin's
encryption setting (`androidIsEncryption: false`) in effect.
## Open questions
These stay open for whoever picks up Parts A–C.
- Which server and endpoint receive diagnostics, and how long reports are kept.
- Whether to quarantine a corrupt database automatically ("file is not a
database", "malformed") and start fresh. Automatic quarantine trades a
visible failure for a silent switch to a new identity, and some "malformed"
databases are recoverable with `REINDEX` or `.recover`; the manual Start Over
button covers this case.
- Whether Android backup should include `databases/`. Including it restores a
user's identity on a new phone; it also gives the Google backup a copy of the
keys together with the secret that decrypts them. The backup rules
(`android/app/src/main/res/xml/backup_rules.xml`,
`data_extraction_rules.xml`) include it and exclude only the plugin's
encrypted preference files.
## Rejected
- An IndexedDB (absurd-sql) database as a fallback inside the Capacitor
WebView: untested there, and two stores that both hold keys risk losing an
identity when they disagree.
## Checklist
Parts A–C are deferred, not scheduled.
- [ ] A1. Decide the diagnostics endpoint and payload retention.
- [ ] A2. Capture "database file existed before first connection" during startup.
- [ ] A3. Build the report payload and the Send Diagnostics button with preview.
- [ ] B1. Let read-only routes through the router guard on a database startup error.
- [ ] B2. Degraded-mode banner and disabled-action reasons.
- [ ] B3. Default `apiServer` in all `$accountSettings()` fallbacks.
- [ ] B4. Reorder the recovery screen.
- [ ] C1. (Optional) Preferences-backed identity with import on recovery.
## History
- 2026-10-10: commit d6ccf0e4 added the failed step and cause to the startup
error, the `startupError.ts` hand-off to `StartView`, and the recovery screen
(Try Again, Email/Copy Details, Save a Copy, Start Over).
- 2026-10-10: commit f41a807a fixed the root cause (see
`PLAN-startup-db-fix-crash.md`) and wrote this plan. Tagged 1.4.8; the CHANGELOG
records it under 1.4.9 as "Startup DB error for new installations".
- 2026-10-17: commit d2cdd578 added the WebAssembly explanation to the recovery
screen (CHANGELOG 1.4.10).
- After 1.4.9: a user who had the startup error on every launch reported
normal startup on a build with the fix. The plan was closed with Parts A–C
unbuilt.