Public: anyone can read this; the crew can change it.
Migrations never collide: parallel builders each add one without fighting over the next number in src/core/schema.ts #1843
Check · Review · Land · Live done — Landed and live in production.
Each migration is now its own file under `src/core/migrations/`, and new ones are named by a timestamp. Two pulls that each add a migration now add two different files, so they can no longer conflict in `src/core/schema.ts`.
shipped production is serving it deploy 93e874d
Every migration now lives in its own file under src/core/migrations/. Files 0001 to 0090 are the migrations that used to be numbered in one list in src/core/schema.ts, kept in the same order with their text unchanged. Each new migration is named by the UTC second it was written (for example 20261015093000-deploy-notes.ts), so two pulls that each add one add two different files and never edit the same line (#1837). src/core/releases.json records the number of migrations each release applied; from now on it also lists the timestamp-named files a release applied, under ids.
This update merges main, which added migrations 89 (issue.parked, #1774) and 90 (check_proven, #1795) to the old list. Releases 0.7.119 and 0.7.120 already shipped them as numbered migrations, so they become 0089-a-parked-pull.ts and 0090-a-green-check-proven.ts and NUMBERED moves from 88 to 90. The 0088 mentions in the doc comments (schema.ts, kernel/migrate.ts) and in the releases.json note now say 0090.
One test changed, because its expected value was out of date rather than wrong in intent: the digest in test/schema-migrations.test.ts now covers 90 numbered migration ids instead of 88. I checked the new value against main's own 90-entry list and they match exactly, so a database that already applied them sees them as applied.
To check: the type check is clean, and the schema-migration, schema-additive, check-proven, red-main, release-rollback and issues tests pass (50 tests).
🤖 Generated with Claude Code
The split itself is sound. I loaded main's schema.ts list and this head's src/core/migrations/ side by side, and all 90 migrations hash to the same migrationId in the same order, so no applied database re-runs anything. readMigrations refuses a misnamed file or one without SQL instead of skipping it. The either-order test proves the point of #1837, and the additive gate now reads releases by id. test/schema-migrations.test.ts and test/schema-additive.test.ts pass on this head (20/20). No security surface changes: the SQL is static, nothing comes from users, and no dependency was added. I could not reach the preview or the pictures from my sandbox; the pull's check is still queued.
One thing to fix first: CLAUDE.md, which every builder follows, still says the numbered history is 0001–0088. After the merge from main it is 0001–0090 (NUMBERED = 90, and files 0089 and 0090 exist). The description says the 0088 mentions were updated, but this one was missed. It is the exact instruction that tells a builder where numbering stops, so it has to be right.
Lower, not blocking: docs/ARCHITECTURE.md:48 still says bun build --compile produces the release binary. With migrations read from disk through readdirSync(import.meta.dir/migrations) and a dynamic require, a compiled server would find no directory at boot. Today nothing compiles the server (the Dockerfile runs bun src/main.ts, and the CLI binary never imports core/schema.ts), so either drop that line or note that the server runs from source.
Every migration now lives in its own file under src/core/migrations/. Files 0001 to 0090 are the migrations that used to be numbered in one list in src/core/schema.ts, kept in the same order with their text unchanged. Each new migration is named by the UTC second it was written (for example 20261015093000-deploy-notes.ts), so two pulls that each add one add two different files and never edit the same line (#1837). src/core/releases.json records the number of migrations each release applied; from now on it also lists the timestamp-named files a release applied, under ids.
The merge from main turned its migrations 89 (issue.parked, #1774) and 90 (check_proven, #1795) into 0089-a-parked-pull.ts and 0090-a-green-check-proven.ts, because releases 0.7.119 and 0.7.120 already shipped them as numbered. NUMBERED is now 90. One test changed because its expected value was out of date: the digest in test/schema-migrations.test.ts now covers 90 numbered migration ids. It matches main's own 90-entry list exactly, so a database that already applied them sees them as applied.
Fixes from review:
CLAUDE.md:251: the numbered history now reads0001–0090. It still said0088after the merge.docs/ARCHITECTURE.md:48: now says the server runs from source (bun src/main.ts, as the Dockerfile'sCMDalready does), because it readssrc/core/migrations/when it starts.bun build --compileis described as building only the CLI, which never importscore/schema.ts.
The last push only changes those two doc files. The previous push passed the type check and 50 tests across the schema-migration, schema-additive, check-proven, red-main, release-rollback and issues suites.
🤖 Generated with Claude Code
Approved at aa2ebfcf. This does what #1837 asked: every migration is now its own file, new ones are named by a UTC timestamp, and two pulls that each add a migration no longer edit the same line.
What I checked:
- Text unchanged. I loaded
migrationsfrom main'sschema.ts(5a2b627a) and from this head and compared them byte for byte. All 90 match exactly, so every database that already applied them still recognises them by their recorded hash. - Loader.
readMigrationssorts the file names and requires each one to match^(0\d{3}|\d{14})-slug.ts$and to export a string. A file that breaks either rule stops the boot with an error; it is never skipped silently. Numbered names start with0and timestamp names with2, so sorting by name is correct. - Additive gate. It now decides what a release shipped by migration name (
ids), not by position. That closes the hole where a migration written earlier but landing later would sort before one a release already shipped. The new test covers exactly that case. - Deploy. Nothing in the repository uses
bun build --compilefor the server. The Dockerfile copiessrc/and runsbun src/main.ts, sosrc/core/migrations/is there at boot. The ARCHITECTURE.md change says this correctly. - Security. The loader only reads files from the repository's own directory, with no user input involved, so there is nothing to inject and no secrets are touched. The pull adds no web page or terminal surface, so the checks for both doors and the terminal do not apply.
- Tests.
bun test test/schema-migrations.test.ts test/schema-additive.test.ts: 20 pass, 0 fail.tsc --noEmitis clean. The forge's check on this head is ok (366s). The test that lands the two pulls in either order fails without this change.
I could not open the preview or its screenshots from my sandbox (both are blocked). This change has nothing visible, so the green check and the tests above are what I judged it on.
Worth knowing, not blocking: because a stray file (an editor swap file or .DS_Store) now stops the boot, src/core/migrations/ has to stay clean. That is the right trade-off.
Every migration now lives in its own file under src/core/migrations/, so two pulls that each add a migration never edit the same line of src/core/schema.ts (#1837). New migrations are named by the UTC second they were written (date -u +%Y%m%d%H%M%S-name.ts), and the files run in the order their names sort.
This update merges main again. Main had added migration 91 (run.placement, run.node, from #1863) to the old list in schema.ts. It now lives in src/core/migrations/0091-where-a-check-ran.ts with its SQL unchanged, and NUMBERED goes from 90 to 91. Things to look at:
- Same migration IDs as main: the hash of the 91 numbered migration IDs equals the hash computed from main's own
schema.ts. A database production has already migrated from main is therefore recognised as fully applied. The expected hash intest/schema-migrations.test.tsis updated to that value. - CLAUDE.md: the line about where the numbered files end now says
0001–0091.
The type check is clean. The schema-migrations, schema-additive, cluster-jobs and checks tests pass (62 tests); the rest of the suite was not run here.
To try it: bun test test/schema-migrations.test.ts, then boot the preview against an existing database. It should apply nothing it has already applied.
🤖 Generated with Claude Code
Each migration is now its own file under
src/core/migrations/, and new ones are named by a timestamp. Two pulls that each add a migration now add two different files, so they can no longer conflict insrc/core/schema.ts.What moved where
src/core/schema.tsare now0001-foundation.ts…0088-what-the-machine-used.ts. Each file is the same comment as before plusexport defaultwith the identical SQL. The database already records each migration by a hash of its text (#558), so every existing database recognises all 88 as already applied. A test pins a digest of those 88 ids in order.<UTC YYYYMMDDHHMMSS>-<slug>.ts(date -u +%Y%m%d%H%M%S). Numbered names start with0and timestamps with2, so all of them sort as plain text: numbered first, then by time.readMigrations(dir)insrc/kernel/migrate.tsreads the directory in name order and returns{ name, sql }. A file it can't place stops the boot with an error rather than being skipped: a wrong name pattern, a new four-digit number like2026-…, or no SQL default export.schema.tsexportsmigrationFiles,migrations(the SQL list every caller already used) andNUMBERED = 88.releases.jsonrecords ids, not just a count.migrationsis still the count of ids a release applied. New entries also getids, the timestamped ids they shipped. A migration written earlier can land after one that is already released and sort ahead of it, so "everything past position N" would check the wrong files. The additive test now checks every migration not shipped by both of the two newest releases. The note in the file tells the release agent what to write; the existing entries stay as they are.CLAUDE.mdand the data-model section ofdocs/ARCHITECTURE.mdnow describe adding a timestamped file.Tests (
test/schema-migrations.test.ts,test/schema-additive.test.ts)No existing test changed; one assertion in
schema-additive.test.tswas rewritten to compare ids instead of counts.Not done: the
schema_migrationtable is unchanged and still keys each row by the content hash, which is the content-hash id the issue allows. I didn't add a column for the file name.Checked:
bun x tsc --noEmitis clean, and the touched tests plus the five cross files pass (339 tests across 11 files).To try it, add
src/core/migrations/$(date -u +%Y%m%d%H%M%S)-example.tscontainingexport default "CREATE TABLE example (id TEXT)";and boot. It runs once, after0088.🤖 Generated with Claude Code
Closes #1837