Skip to content

Complete SQLite migrations before marking the HTTP server ready #173

Description

@atomantic

Problem

The server starts accepting traffic before pending SQLite migrations finish, and migration failure is merely logged. Requests can therefore reach route handlers against an older schema; if a migration fails, the process remains healthy-looking and continues serving an incompatible database.

Evidence (quoted code + context)

Startup calls "httpServer.listen(PORT, HOST, () => {" at line 120, immediately logs that it is running (line 121), then begins "runMigrations().then(...)" at line 124. The catch only performs "logger.error('server', Migration error: ${err.message});" (lines 128-130), leaving the listener alive. runMigrations itself creates/uses the migration table then applies each migration asynchronously in a loop (lines 39-62), so there is an observable period in which migration.up() has not completed or has failed while routes are already mounted.

Impact

After deployment with a new migration, early requests can fail with missing tables/columns or operate on stale data. A permanent migration error does not fail readiness, so an orchestrator can route production traffic to an instance that cannot satisfy its database contract.

Implementation plan

Refactor bootstrap into an awaited async startup path: validate startup configuration, run SQLite migrations first, and only then bind/listen and report readiness. Treat a migration error as a failed startup with a non-zero exit (after closing any resources) so process supervision can surface it. Preserve a separate liveness/readiness distinction if the deployment environment needs it, and add transaction boundaries where individual migration semantics permit them.

Acceptance criteria

No HTTP listener is bound before runMigrations resolves; a rejection prevents readiness/listening and exits or rejects startup predictably; successful migration preserves the current route behavior; and startup logs clearly identify migration success/failure.

Verification

Add server Vitest tests with mocked runMigrations and httpServer.listen proving listen is deferred and rejected migration does not expose the server. Run npm test --prefix server -- --run for bootstrap/migration tests, npm run build --prefix server, and an integration smoke that starts with a pending migration then requests /api/health only after readiness.

Dependencies and related work

This concerns the active SQLite runtime only and must not duplicate or alter the planned staged PostgreSQL migration in #120 and #149-#155.

Scope (Complexity, files, non-goals)

Medium; server bootstrap, focused tests, and possibly migration transaction helpers. Non-goals: replacing SQLite or changing the PostgreSQL rollout plan.

Metadata

Metadata

Assignees

No one assigned

    Labels

    planClaimable backlog itemseverity:highHigh severitystack-specificStack-specific audit finding

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions