Skip to content

Reset-gate follow-ups from #596: local-first Safari fallback, encryption-remanence clearData() propagation, IdbConnectionManager test-closer leak #602

Description

@qnbs

Tracking issue for three genuinely-valid, out-of-scope findings surfaced during PR #596's review (the #532 E2E-determinism / IDB reset-quiescence work), deliberately NOT fixed in that PR per explicit scoping decision. This repo's branch protection requires all review conversation threads to be resolved before merge (required_conversation_resolution: true), so the five threads below were resolved on #596 with a reply citing this tracking issue — resolving is a GitHub merge-mechanics step, not a claim that the underlying issue is fixed. Full original disposition remains permanently visible in each thread's comment history.

1. Local-first Safari/old-browser fallback omits dynamic worldscript-localfirst-* databases

Threads: CodeAnt, CodeRabbit, and cubic each independently flagged services/factoryResetService.ts around the native-indexedDB.databases()-unavailable fallback path.

Disposition (confirmed valid): When native database enumeration is unavailable (Safari) or throws, deleteAllIndexedDBDatabases() falls back to the static KNOWN_DB_NAMES list, which cannot include per-project worldscript-localfirst-<projectId> databases (dynamically named, only discoverable via indexedDB.databases()). A factory reset on an affected browser can therefore reload as a "fresh install" while local-first project shadow data survives.

Why not fixed in #596: local-first sync is behind enableLocalFirstSync, a user opt-in flag defaulting OFF — narrowing the real-world blast radius to Safari/old-browser users who have explicitly enabled an experimental flag. A real fix requires enumerating known project IDs (e.g. via dbService.listProjects()) before beginIdbReset() closes that connection, then deriving candidate worldscript-localfirst-* names for the fallback -- a call-ordering restructure of wipeAllAppData(), not a local one-line fix, and risks reintroducing a connection-timing race the whole PR exists to close.

Suggested approach: capture the project-ID list up front in wipeAllAppData(), before beginIdbReset() runs, and thread it into deleteAllIndexedDBDatabases()'s fallback branch.

2. Encryption-transition clearData() failure is swallowed before the caller can react

Thread: CodeRabbit on app/listenerMiddleware.ts's reconcileLocalFirstHandle().

Disposition (confirmed valid): When at-rest encryption becomes active after a plaintext-persisting local-first handle was created, reconcileLocalFirstHandle() calls persistence.clearData() to wipe the plaintext before discarding the handle. DocPersistence.clearData() (in services/localFirst/docPersistence.ts) always resolves successfully by design (so its many defensive callers never need to branch on failure), meaning a genuine wipe failure is invisible to the caller, which proceeds to discard the handle as if the wipe succeeded.

Why not fixed in #596: this is the same root architectural gap as an earlier pre-existing thread on this feature (encryption-transition remanence in the "dead reference" branch) -- fixing it properly requires the SAME kind of raw-vs-swallowed split already applied to destroy()/beginDestroy() in this PR (see services/localFirst/docPersistence.ts), but for clearData(), plus a design decision on what reconcileLocalFirstHandle() should actually DO on a proven wipe failure (retry? surface a user-facing warning? refuse to discard the handle?) -- that's a deliberate lifecycle-design call, not a one-line fix, and this PR's scope is reset-gate quiescence, not local-first encryption lifecycle redesign.

Suggested approach: mirror the beginDestroy()/destroy() split for clearData(), then decide (separately) what reconcileLocalFirstHandle() does with a genuine wipe failure.

3. IdbConnectionManager discards its unregister function, leaking closers in tests

Thread: cubic on services/storage/idbCore.ts's IdbConnectionManager constructor.

Disposition (confirmed valid): registerIdbConnectionCloser()'s returned unregister function is discarded in IdbConnectionManager's constructor. In production every subclass is a long-lived module-level singleton (constructed once), so this doesn't leak there -- but multiple test files instantiate new IdbProjectStore() (or similar) repeatedly within the same file, each accumulating a new closer in the shared idbResetGate module registry with no way to remove the stale one from a previous instance.

Why not fixed in #596: each leaked closer is a harmless no-op (this.stateDb?.close() etc. on an already-closed/null reference), so this is a test-hygiene issue, not a production correctness bug. A real fix means exposing a disposal API and updating roughly ten existing test files (idbStoreEncryption.test.ts and others) that aren't in this PR's already-large scope.

Suggested approach: store unregister on the instance, expose a dispose()/similar method, and update affected test files to call it between instantiations.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions