459d636ffb
A long-lived branch can add a migration whose timestamped filename sorts BEFORE migrations already applied in prod (#234's 20260627T130000-ai-chat-runs merged after 20260704T120000-client-metrics was live). Kysely's migrator with the default ordered setting then rejects the applied set as "corrupted migrations" (no longer a prefix of the sorted list), throws, and the app crash-loops on boot — exactly incident #361 (502s for ~11 min after a develop deploy). #119 and #120 (June branches) are the next such threats. Two levels, both: 1. CI migration-order gate (a new `migration-order` job in test.yml, PR-only): fails the PR when an added migration sorts at/before the newest migration on the base branch, with an actionable message to rename it to a current timestamp before merge. This is the primary defense — makes back-dating impossible to merge accidentally. 2. `allowUnorderedMigrations: true` on BOTH Migrators (migration.service.ts startup auto-migrate + migrate.ts CLI): the runtime safety net — Kysely applies a not-yet-applied older migration instead of bricking startup, so a back-dated migration that bypasses the gate (manual push / hotfix branch) still boots. Trade-off documented inline: apply order across instances may differ from lexicographic, so migrations must stay independent (ours each create their own objects); the CI gate remains the primary line. Verified: allowUnorderedMigrations is a valid Kysely 0.28.17 Migrator option; server tsc clean; the gate script rejects a back-dated filename and passes a current one. No new deps, no migration, no runtime behavior change beyond the migrator resilience. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>