164 lines
8.1 KiB
Markdown
164 lines
8.1 KiB
Markdown
# 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.
|