Skip to content

First-boot FK race + no single-flight leaves RBAC/plugins unseeded and latches the broken state (beta.25) #1017

Description

@mmcintosh

Summary

On a fresh database's first boot, bootstrapMiddleware seeds system data with a flat Promise.all([...]) that races a foreign-key dependency: two steps create document_types rows while two other steps insert documents that FK-reference those rows. When a consumer insert wins the race, D1 throws FOREIGN KEY constraint failed. The per-step .catch() swallows it, so RBAC roles/verbs and every core plugin are silently left unseeded — the admin then 403s and the site runs with no roles/plugins.

Two facets compound it:

  1. No single-flight guard. Two concurrent requests on a cold isolate both enter bootstrap and seed in parallel, re-opening the FK window across the two runs.
  2. The broken state latches. Bootstrap writes an unconditional KV skip-marker (24h TTL) even when seeding failed. Every subsequent request/isolate then takes the KV fast-path, skips D1 entirely, and the site stays broken until the marker expires or is cleared.

How it was hit (real, not theoretical)

Spinning up a clean greenfield stack (fresh D1, empty KV) on beta.25, the very first request logged:

✘ [ERROR] [PluginBootstrap] Error ensuring plugin Authentication System:
    Error: D1_ERROR: FOREIGN KEY constraint failed: SQLITE_CONSTRAINT_FOREIGNKEY
      at PluginService.installPlugin (services/plugin-service.ts)
      at async Promise.all (index 4)
✘ [ERROR] [Bootstrap] Error seeding RBAC documents:
    Error: D1_ERROR: FOREIGN KEY constraint failed: SQLITE_CONSTRAINT_FOREIGNKEY

Both failing steps are inside the await Promise.all([...]) in middleware/bootstrap.ts — the document-type producers had not finished when the FK consumers ran.

Proposed fix

(1) Order the seed so the two document-type producers run and finish before the FK consumers; (2) add an in-flight single-flight promise so concurrent cold-isolate requests await one bootstrap instead of racing; (3) make the "bootstrap complete" latch + KV skip-marker conditional on success, so a failed boot self-heals on the next request instead of latching for 24h.

A PR implementing this is ready — a surgical change to middleware/bootstrap.ts plus a real-DB regression test.

Note

This depends on the beta.25 test-infra fix (separate issue/PR) only for a green unit suite — the ~25 failures on this branch are the pre-existing set fixed there, and none of them touch bootstrap.ts.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions