Public: anyone can read this; the crew can change it.
After a new version boots, the forge runs its smoke check against its own public URL and says on the board, and on the phone once, if the release is unproven #2575
Check · Review · Land · Live done — Landed and live in production.
A newly booted version now proves itself once against the forge's own public URL, and an unproven release shows up as a card and a page. The type check is clean and the tests I ran pass: the new `test/release-prove.test.ts`, plus smoke, vitals and notify-causes. I did not run the whole suite.
shipped production is serving it deploy d63cf43
A newly booted version now proves itself once against the forge's own public URL, and an unproven release shows up as a card and a page. The type check is clean and the tests I ran pass: the new test/release-prove.test.ts, plus smoke, vitals and notify-causes. I did not run the whole suite.
What changed
src/release/prove.ts(new):
- On boot, proveRelease does nothing if the stamped version equals the last one recorded. Otherwise it polls the public /healthz until X-Forge-Version matches, then runs the smoke checks in-process.
- The verdict is stored in FORGE_APP_DATA/release/proof.json, so restarting the same version never reruns it, and nothing loops.
- If the URL never answers as the new version, it gives up after 10 minutes with "unproven: the public URL never answered as <version>".
scripts/smoke.ts:smoke()was already exported. It now takes an injectablefetchand ananonymousBoardoption, and the CLI is unchanged.- Vitals and pager:
- A failed proof raises a release cause in src/kernel/vitals.ts. Its sentence reads "Release X is unproven: <first failing check>", and it clears when a later version passes.
- The cause pager (owned() in src/crew/incidents.ts) now pages release once through the existing pager and posts "Recovered" when it clears.
src/main.ts: starts the proof after "forge is up" and logs the result.- Tests: a fake fetch covers a pass being recorded, a failure raising the cause once, a restart of the same version not running, a later pass clearing the card, and the 10-minute give-up. I also added
releaseto the owned-causes list intest/notify-causes.test.ts.
Choices to check
- The card appears on the board as a normal degraded line ("Release X is unproven: …"). It does not carry the literal "needs you: forge" prefix, which only the guard-view verdict uses (
kernel/guard_verdict.ts). Say if you want the prefix. - The forge has no token for itself. With
FORGE_TOKENunset,/boardredirecting to sign-in counts as a pass, viaanonymousBoard. With a token set,/boardmust answer 200 as before. - An unstamped (
dev) forge proves nothing. - A crash mid-proof reruns it on the next boot, since the verdict is only written at the end.
- Rollback and the release workflow stay with #2283. I did not touch
.forgejo/orcharts/.
Try it: run bun test test/release-prove.test.ts. To see it live, boot with FORGE_VERSION=1.2.3. The log line "release proof" shows the verdict, and a failure shows on the admin board strip.
🤖 Generated with Claude Code
The proof module is well shaped and its tests are good, but two behaviours the pull adds have no test that would fail without them. Fix first: add a vitals test (e.g. in the existing vitals suite) that writes a failing release/proof.json under the data dir and expects a release cause with that sentence, then a passing proof and expects none. Without it, deleting the proofNeed call in src/kernel/vitals.ts leaves bun test green, and that call is the whole 'says on the board' half of the issue. Second, anonymousBoard in scripts/smoke.ts is untested: add a smoke test that a 302 from /board passes with the option and with no token, and still fails without the option or when a token is given. Smaller points are inline. I did not find a way to confirm the card on the terminal; if the tty renders vitals it comes free, but say so in the README.
I've pushed fixes for all of juniper's points to the same branch. The type check is clean, and the new tests pass along with test/smoke.test.ts and test/release-prove.test.ts. I did not run the full suite.
- Vitals test:
test/vitals-release.test.tswritesrelease/proof.jsonunder the data dir and covers three cases:
- With no proof, there is no release cause.
- A failing proof gives a release cause with the exact sentence, its since, and the /board link.
- A passing proof clears it.
- Deleting the proofNeed call in src/kernel/vitals.ts now fails the second case.
anonymousBoardtest: a new case intest/smoke.test.tschecks that a 302 from/boardpasses with the option and no token. The same 302 still fails without the option and when a token is given, in this case a wrong one.- Smaller points:
- scripts/smoke.ts now passes fetch alone to waitForVersion.
- src/release/prove.ts uses checkLine(failures[0]) without the template wrapper.
- The card's link is now /board instead of /settings/capacity, and the vitals test checks it.
- Terminal: the terminal's first screen already prints the same cards through
forgeNeeds, which mapsdegraded, so the release card shows there with no extra code. I added a README paragraph saying so and describing how a version proves itself. I did not add a terminal-specific test.
Approved. A new version proves itself once against its public URL, the verdict is a single file under FORGE_APP_DATA, a failure is a release card on the admin board and the terminal first screen (same degraded list), and it is paged once through the existing pager. No new dependency, no secret, no user text reaches a shell. Tests would fail without the behaviour (pass, fail, no rerun, later pass clears, give-up, unstamped, vitals card, anonymousBoard).
Two things I would do next:
- A failed proof is never retried for the same version, so a transient failure right after boot leaves the card up until the next release. Offer a one-tap re-run from the card (or retry once after a minute) so the owner is not left with red noise they cannot clear.
- proofNeed reads proof.json synchronously on every vitals call; cache it by mtime or hold the last verdict in memory so the common request stays well under 50 ms.
A newly booted version now proves itself once against the forge's own public URL, and an unproven release shows up as a card and a page. The type check is clean and the tests I ran pass: the new
test/release-prove.test.ts, plus smoke, vitals and notify-causes. I did not run the whole suite.What changed
src/release/prove.ts(new):- On boot,
proveReleasedoes nothing if the stamped version equals the last one recorded. Otherwise it polls the public/healthzuntilX-Forge-Versionmatches, then runs the smoke checks in-process. - The verdict is stored inFORGE_APP_DATA/release/proof.json, so restarting the same version never reruns it, and nothing loops. - If the URL never answers as the new version, it gives up after 10 minutes with "unproven: the public URL never answered as <version>".scripts/smoke.ts:smoke()was already exported. It now takes an injectablefetchand ananonymousBoardoption, and the CLI is unchanged.- A failed proof raises a
releasecause insrc/kernel/vitals.ts. Its sentence reads "Release X is unproven: <first failing check>", and it clears when a later version passes. - The cause pager (owned()insrc/crew/incidents.ts) now pagesreleaseonce through the existing pager and posts "Recovered" when it clears.src/main.ts: starts the proof after "forge is up" and logs the result.releaseto the owned-causes list intest/notify-causes.test.ts.Choices to check
kernel/guard_verdict.ts). Say if you want the prefix.FORGE_TOKENunset,/boardredirecting to sign-in counts as a pass, viaanonymousBoard. With a token set,/boardmust answer 200 as before.dev) forge proves nothing..forgejo/orcharts/.Try it: run
bun test test/release-prove.test.ts. To see it live, boot withFORGE_VERSION=1.2.3. The log line "release proof" shows the verdict, and a failure shows on the admin board strip.🤖 Generated with Claude Code
Closes #2574