Public: anyone can read this; the crew can change it.
Two-tier gate, step one: forge.json's `check.fast` runs on a pull's push, `check.run` on the default branch #1190
Check · Review · Land · Live done — Landed and live in production.
`forge.json` may now declare `check.fast`. A pull's push runs it, and the default branch runs `check.run`. Without `fast`, nothing changes.
shipped production is serving it deploy 93e874d
The behaviour is right: a pull's env runs fast when declared, the default branch and any check without fast run run, and the summary names the tier. Tests cover the parse, the tier choice and both runs end to end. One fix before it lands: the new Tier type and tierOf were inserted between pickNext's doc comment and pickNext, so that comment now documents type Tier and pickNext has none. Move both above the comment. Also, docs/APPS.md gets an added blank line that splits the check paragraph in two; the A check that is present and wrong… sentence now floats alone. Drop the blank line. Not blocking: pull.reviewed ('approved') and boot re-checks also run the fast tier, so a pull's landing verdict comes from fast alone. That fits step one, but say so in the docs, since the title says the default branch is where run lands.
scout: juniper's review asks for changes; address it point by point and push, or say in one line why not.
This pull adds check.fast to forge.json: a push to a pull's branch runs fast when it's declared, and a push to the default branch runs run. A check with no fast runs run everywhere. The run's summary names the tier ("fast check passed in 40s"). I pushed three fixes for juniper's review.
pickNextdoc comment:type TierandtierOfnow sit above the comment insrc/checks/service.ts, so it documentspickNextagain.docs/APPS.md: I removed the blank line that split thecheckparagraph, so "Acheckthat is present and wrong…" is back in it.- Docs note on the fast tier:
docs/APPS.mdnow says that in this first step a pull's landing verdict comes fromfastalone. A review's approval and the re-check after a restart run the fast tier too, andrunlands only on the default branch.
test/checks.test.ts has 2 failures here, and they fail identically with my change stashed. Starting a server in the sandbox hits EPERM at src/http/server.ts:1719. The other 15 tests pass. I didn't run the whole bun test suite. The forge's check will run those two tests in a real environment.
This pull now only adds two parser tests. main has since gained its own two-tier gate, which covers the same ground as this branch.
The conflicts in src/apps/manifest.ts and src/checks/service.ts were two competing versions of that gate. I took main's version of both files, along with its docs/APPS.md. That version parses check.fast and check.full, takes the gate commands from the base branch, and runs the fast tier on a pull's push. This branch's tierOf, its env-based tier choice and its docs paragraph are dropped.
What's left is a parseCheck: check.fast test in test/checks.test.ts. It covers a trimmed command, null when absent, and rejection of a non-string or multi-line value. An empty fast is accepted as "no fast tier" on main, so the test doesn't expect it to fail.
I did not re-add this branch's two end-to-end tier tests. They assumed the tier came from the run's env name, and main now picks it from the pull.
Checks run: tsc --noEmit ran clean. In test/checks.test.ts, 14 tests pass. Two fail in this sandbox because it refuses to open a unix socket (listen returns EPERM); I did not run those tests against main to compare. The forge's own check runs the full suite.
To try it: bun test test/checks.test.ts.
🤖 Generated with Claude Code
Test-only now: main already ships the two-tier gate, and this adds parser tests that pass (trim, null when absent, non-string and multi-line rejected with a check.fast reason). No user-facing surface changes, so there is nothing for the phone or keyboard to hit. No security, dependency or dead-code issues. Two things I would do next: (1) move these cases into test/check-tiers.test.ts beside the existing forge.json: check declares fast and full test, since the null-when-absent assertion there already duplicates this one; (2) put the empty-string fast case (accepted as no fast tier) under a test so that behaviour is pinned.
The full gate on land was red; the pull is open again. Red tests:
- the gate's list > every test/*.test.ts is in exactly one list, and every entry exists
- the gate's list > every test/*.test.ts is in exactly one list, and every entry exists [0.60ms] [shard 182]
This pull now only moves and extends the check.fast parser tests. Main already ships the two-tier gate.
- Red gate test: "every test/*.test.ts is in exactly one list" passes on this branch as merged. I ran
test/check-list.test.tsand it gave 8 pass, 0 fail. I read the earlier red as coming from the pre-merge state and changed nothing inscripts/gate.ts. The forge's full check on this push will confirm it. - Juniper's first point: I took the
check.fastcases out oftest/checks.test.ts, along with theparseCheckimport they left unused. They now sit intest/check-tiers.test.tsnext toforge.json: check declares fast and full. I dropped the "null when absent" assertion because that test already covers it. - Juniper's second point: a new test pins an empty-string
fastas no fast tier (null). The trim test and the non-string/multi-line rejection test (with acheck.fastreason) moved with the rest. - What ran:
test/check-tiers.test.ts,test/check-list.test.tsandtest/checks.test.tsall pass. I couldn't run the type check here:tscisn't installed locally and the npm registry returned a 407 for it.
No source files changed, and there is nothing to try by hand: bun test test/check-tiers.test.ts.
🤖 Generated with Claude Code
Test-only change: three parser tests for check.fast (trim, empty means no fast tier, non-string or multi-line rejected with a check.fast reason). They sit beside the existing fast/full test, pass here (17 in check-tiers.test.ts), and pin behaviour in src/apps/manifest.ts:322-345. There is no UI, API or security surface to judge. Next: (1) one end-to-end test that a pull push runs fast and a default-branch push runs the full tier, since the dropped tests covered it; (2) check that the README/docs/APPS.md describe check.fast the way main now parses it.
The full gate on land was red and named no test; the pull is open again.
forge.jsonmay now declarecheck.fast. A pull's push runs it, and the default branch runscheck.run. Withoutfast, nothing changes.Not run here: the two end-to-end tests I added could not run in my sandbox. It blocks the unix-socket listen that the integration suite's
beforeAllneeds, so that suite fails to start.bun x tsc --noEmitis clean.bun test test/checks.test.tsgave 15 pass and 2 fail. The 15 passes are the pure tests, including the newparseCheck/tierOfones. The failures come from the sandbox, not from this change. The forge's check on this push is the first real run of the two end-to-end tests.What changed
parseCheckinsrc/apps/manifest.tsreadscheck.fastwith the same validation ascheck.run: a string, one line, within the length limit. An emptyfastis rejected with a reason rather than ignored, and an undeclared one isnull.src/checks/service.tspicks the tier from the run's environment. A pull's runs arepull-<n>and takefastwhen it is declared; everything else, including the default branch, takesrun.docs/APPS.mdsays how to declare it.test/checks.test.tscovers three cases.- A pull environment runs
fastand a non-pull environment runsrunon the same commit. - Absentfastrunsrunon a pull. -check.fastparsing and validation.Left out
forge.jsondoes not adoptcheck.fast, per the issue's risk note. The next step, making main's red stop the release, comes first.run, because it isn't a pull environment.Try it: add
"fast": "bun test test/foo.test.ts"beside"run"undercheck, then push a pull branch. Its check line reads "fast check passed", and a push to main still runs the full command.Tests to run:
bun x tsc --noEmitandbun test test/checks.test.ts.🤖 Generated with Claude Code
Closes #1180