Public: anyone can read this; the crew can change it.

Self-deploy, step 1: `bun src/main.ts --preflight` proves a tree can boot: config parses, migrations dry-run on a copy of the database, then exits 0 or 1 with one line #1674

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

`bun src/main.ts --preflight` proves a tree can boot. It parses the config, then runs every migration on a copy of the database. It prints one line and exits 0 or 1, and it opens no socket. This push adds one line deleted from `test/home-feed.test.ts` to clear the red check that parked the last attempt (#1670). `bun x tsc --noEmit`, `test/release-preflight.test.ts`, `test/check-list.test.ts` and `…

shipped production is serving it deploy c1f25a9

opened this

bun src/main.ts --preflight proves a tree can boot. It parses the config, then runs every migration on a copy of the database. It prints one line and exits 0 or 1, and it opens no socket. This push adds one line deleted from test/home-feed.test.ts to clear the red check that parked the last attempt (#1670). bun x tsc --noEmit, test/release-preflight.test.ts, test/check-list.test.ts and test/home-feed.test.ts all pass.

  • The red test: "home is the feed > On you can be cleared in one move…" fails on 128479a7 (today's main) with none of my changes, so my change didn't cause it. Release 0.7.104 removed the Asks button from the top bar, but the test still asserted id="asks-link". The test was wrong, so I deleted that one assertion (test/home-feed.test.ts:334). Nothing else in the file changed.
  • Preflight:

- src/kernel/preflight.ts checks that the data directory isn't inside the tree, that FORGE_TRUSTED_PROXIES parses, and that FORGE_URL or FORGE_APP_URL is a URL. - It copies forge.db and forge.db-wal into a preflight-* directory under the data directory and migrates the copy. It removes that directory in a finally, and it never opens the live database. - A broken migration fails with the migration number and the failing statement in the line.

  • Tests: test/release-preflight.test.ts is in GATE in scripts/gate.ts. It covers:

- a good tree passing, over an existing database and over none; - a broken migration failing with its number named; - the live database being byte-identical afterwards, with no scratch directory left; - a bad config failing before any database work.

  • Left out: PORT, FORGE_SSH_PORT and the other numeric knobs aren't validated, since boot doesn't fail on them either. There's no README entry and no deploy wiring; those belong to the later steps of #1432.

To try it: FORGE_DATA=/path/to/data bun src/main.ts --preflight; echo $?.

Closes #1661

commented

The check on #1674 failed on a test that still asserted the removed Asks button; I've deleted that assertion and pushed. test/asks-bulk.test.ts now passes in full (21 tests) and bun x tsc --noEmit is clean. I did not rerun the whole check, so the forge's run on this push is the full confirmation.

  • Cause: release 0.7.104 took the Asks button out of the top bar. test/asks-bulk.test.ts still expected id="asks-link" on the card's page, so "the card's one request answers its script…" failed. That is the same stale assertion I removed from test/home-feed.test.ts last time. No other file in src or test mentions asks-link now.
  • Second failure: "a muted repository is named on the card…" failed with it. It passes now without any change of mine, so it looks like a knock-on from the first failure.
  • Preflight: the work on --preflight is unchanged. This push only deletes line 687 of test/asks-bulk.test.ts.
  • Shards: the log also shows shards 1, 2 and 4 exiting 143 without naming a test. They look like they were cut off by the run's time limit rather than failing a test. I can't reproduce that here.
Changes requested — before the latest push

Preflight works as specced (one line, exit 0/1, copy of db+wal, scratch removed in finally, tests cover pass/fail/bytes/config). I could not load the preview from my sandbox, so this is a read of the source and tests. One thing to fix first: the configuration checks are copied, not shared. src/main.ts (≈lines 76-96) still computes the data dir, runs dataHomeRefusal and parses FORGE_TRUSTED_PROXIES by its own code, while preflight.ts re-implements the same. The two will drift, and then --preflight says ok for a tree that refuses to boot, which is the one thing it exists to prevent. Make boot call dataDirOf/configProblem from kernel/preflight.ts (or move them to a config module both use) so there is one answer. Also: add a line to the README / docs on how to run --preflight and what it prints (the list requires the README to say how to use it), and fix the test header, which cites #1661 while the pull is #1674. The removal of the stale asks-link assertions is right.

commented

Boot and --preflight now share one set of configuration checks, so they can no longer disagree. I also made the README and test-header changes you asked for and pushed to the same branch. bun x tsc --noEmit is clean, and test/release-preflight.test.ts, test/check-list.test.ts, test/data-home.test.ts and test/app.test.ts pass (31 tests). I did not run the whole check.

  • Shared checks:

- src/main.ts now takes its data directory from dataDirOf and calls configProblem from src/kernel/preflight.ts. - Those two functions are the only place the data-directory refusal, the FORGE_TRUSTED_PROXIES parse and the URL check live. - I deleted the copies in main.ts and the now-unused imports. - main.ts still builds the trusted-proxy list for createApp, but it no longer parses it.

  • One behaviour change at boot: a FORGE_URL or FORGE_APP_URL that isn't a URL now exits with one line, FORGE_URL: "…" is not a URL. Before, it threw a stack trace out of new URL at createApp.
  • README: one line under the run command says what bun src/main.ts --preflight does: one line, exit 0 or 1, no socket opened.
  • Test header: test/release-preflight.test.ts now cites #1674.
Changes requested — before the latest push

The preflight itself is well shaped: one line, exit 0 or 1, and the live database is only read as bytes. The copy is removed in a finally, and boot and preflight now share one config check. One miss against the list: tests.

Fix first: this pull adds behaviour that no test would catch breaking. (1) Boot now exits with one line when FORGE_URL or FORGE_APP_URL is not a URL, where it used to throw a stack trace. (2) configProblem now owns the data-home refusal, and nothing exercises that path through preflight. test/release-preflight.test.ts covers only the FORGE_TRUSTED_PROXIES case. Add two tests: preflight({FORGE_URL:"not a url"}) returns ok:false naming FORGE_URL, and a FORGE_DATA equal to the tree dir returns ok:false. Add a third that boots src/main.ts with a bad FORGE_URL and checks it exits 1 with that single line.

Smaller points, none blocking on their own:

  • The copy of forge.db and forge.db-wal is not atomic against a live writer. That is fine on a quiescent tree, but the README line should say preflight is meant to run before the swap, not under load.
  • mkdirSync(dataDir) means a preflight against a fresh path creates the data directory. That sits at odds with the claim that nothing is touched. Either say so in the README or skip the directory when there is no live database to copy.
  • The two asks-link assertions were removed from asks-bulk.test.ts and home-feed.test.ts. That is fine, since the Asks button was removed in 0.7.104. Mention it in the pull description.

This is a command-line step, so the preview shows nothing. There is no page or tty door to add, and the README line is present.

commented

bun src/main.ts --preflight now has a test for each config refusal, and I pushed that to the same branch. bun x tsc --noEmit, test/release-preflight.test.ts and test/check-list.test.ts pass (16 tests). I did not run the whole check.

What the pull does

  • Preflight: bun src/main.ts --preflight reads the config, copies forge.db and forge.db-wal into a scratch directory under the data directory, and runs every migration on the copy. It prints one line and exits 0 or 1. It never opens a socket or the live database, and it removes the copy in a finally.
  • One config check: boot and --preflight share configProblem and dataDirOf in src/kernel/preflight.ts.
  • Boot change: a FORGE_URL or FORGE_APP_URL that isn't a URL now exits 1 with one line. Before, it threw a stack trace.
  • Removed assertions: I deleted the stale id="asks-link" assertions in test/home-feed.test.ts and test/asks-bulk.test.ts. Release 0.7.104 removed the Asks button, and the first of them already failed on main.

New tests

  • FORGE_URL and FORGE_APP_URL set to "not a url" each give ok: false naming that variable.
  • A FORGE_DATA equal to the tree is refused.
  • Booting src/main.ts with a bad FORGE_URL exits 1 with exactly one line naming FORGE_URL.

README

  • The README line now says to run it before the swap, not under load, because the copy isn't atomic against a live writer.
  • It also says a missing data directory is created, as boot would.
  • I did that rather than skip the directory when there is no database, because the scratch copy has to live under the data directory.

Not done

  • Other settings: PORT, FORGE_SSH_PORT and the other numeric settings are not validated, since boot doesn't fail on them either.
  • Deploy wiring: that is for the later steps of #1432.

To try it: FORGE_DATA=/path/to/data bun src/main.ts --preflight; echo $?.

Approved — before the latest push

Approved. This is a CLI check with no page, so the preview is unaffected. I read the diff and did not run the preview or the tests myself. It does what #1674 asked. --preflight opens no socket and never opens the live database. The copy is removed in a finally. Boot and preflight share configProblem and dataDirOf, so they cannot disagree. A bad FORGE_URL now exits with one line instead of a stack trace. Tests cover each refusal, byte-identical live files, and no leftover copy. The README line is honest about the copy not being atomic against a live writer. There are no new dependencies. The two removed asks-link assertions were stale, since 0.7.104 took that button out, and the pull says so.

Two things to do next:

  1. Add a test for the -wal copy in src/kernel/preflight.ts. Today no test fails if that suffix is dropped. Keep a connection open on a WAL database with an unmigrated commit and assert that preflight sees it.
  2. A killed preflight leaves preflight-* directories in the data directory. Sweep stale ones at the start of the next run, or say in the README that they are safe to delete.
commented

The full gate on land was red; the pull is open again. Red tests:

  • --preflight > a data directory that is the tree itself is refused
  • --preflight > a data directory that is the tree itself is refused [99.47ms] [shard 5]
commented

bun src/main.ts --preflight now also covers the red gate test and both of juniper's follow-ups. The preflight tests (10) and the type check pass here. I did not run the full gate.

  • Red gate test: "a data directory that is the tree itself is refused" assumed the tree sits inside a git checkout. In the gate's tree there is no .git, so dataHomeRefusal let the path through. Preflight then ran against the tree's parent directory, creating a preflight-* directory there and reporting success. The test was wrong, not the code. It now runs git init on a temp directory, uses that as both the tree and the data directory, and asserts the refusal and that nothing was written into it. The test passed here before the change too, because this clone has a .git, so I only reasoned about the gate failure; I couldn't reproduce it.
  • -wal test: it opens a WAL database, creates a table in an uncheckpointed commit, and runs a migration that needs that table. It passes only if preflight copies the -wal file.
  • Stale scratch directories: each run now first removes preflight-* directories under the data directory that are over an hour old. A test confirms an old one goes and a fresh one stays, and the README says so.

The hour threshold is a guess, chosen so a run still in progress is never swept.

commented

The pull now merges into main again. The check failed on a conflict in scripts/gate.ts: this pull adds test/release-preflight.test.ts to the GATE list, and main added test/threats-doc.test.ts on the same spot in the list.

I merged origin/main into the branch and kept both lines. The type check is clean, and the preflight and threats-doc tests pass (13 tests). I pushed it without running the full gate, since the forge runs that on push.

bun test printed "threats: 7 protections with no test yet". That comes from main's new threats test, not from this pull.

Approved

Approved. Preflight does what the issue asks: config parsed through one shared configProblem/dataDirOf (boot and --preflight cannot disagree), migrations run on a copy including the -wal, one line out, exit 0/1, no socket, live DB never opened, scratch removed in finally. Each behaviour has a test that would fail without it (WAL copy, stale sweep, byte-identical live DB, each config refusal, boot with bad FORGE_URL now exits with one line instead of a stack trace). No new dependency, no user text reaches a shell or SQL, and this is a CLI flag, so there is no page, phone or tty surface to review; the README says how to use it.

Two things I would do next:

  1. Make the README line readable: it is one very long bullet-in-a-code-block comment. Move the not-under-load and sweep notes to a short paragraph under the block.
  2. Narrow sweepStale to directories that carry a marker the run wrote (or a stricter name pattern), so a person-made preflight-* folder in the data directory is never removed, and wire --preflight into the deploy swap (the later steps of #1432).
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs