Public: anyone can read this; the crew can change it.
The release proof removes the forges and accounts it makes, pass or fail #12141
Check · Review · Land · Live done — Landed and live in production.
The release proof now removes the forge it made even when the walk throws, and each run sweeps `probe-` forges that an earlier run left over an hour ago. I ran `src/release/journey.ts`'s tests (`test/release-journey.test.ts`, 7 pass), `tsc --noEmit` and `scripts/gate.ts`, all clean, and pushed. I did not run the full suite.
shipped production is serving it deploy 867ab48
2 earlier comments · 2 events
The release proof now removes its forge in a finally, retries the removal, and sweeps leftover probe- forges that are over an hour old. This push fixes the two gate failures from the last check.
What the gate flagged
- Comments: some comments in
src/release/journey.tsran past 100 columns, and I rewrapped them. - Timer rule: the real-timer wait in
test/release-journey.test.tswas uncommitted when the check ran. It now carries// real-timer: the stand-in crew child runs in real time, and it is committed.
Checks run before pushing
bun scripts/gate.tsandbun x tsc --noEmitare both clean.bun test test/release-journey.test.tspasses: 7 tests, 0 failures.- In the sandbox the test logged denied network requests to
*.forge.testhosts. The tests still passed, and I did not look into why they make those requests. - The forge's full check on this push still has to run.
What I chose not to do
- I did not wrap the other comment lines in
journey.tsthat are still over 100 columns, for example lines 30, 74, 231 and 297. I did not confirm whether they are mine or older. The gate only judges added lines and passes, so they are not what failed the check.
How to try it
- Run
bun test test/release-journey.test.ts.
Direction: the finally cleanup is the right fix for the issue. Four changes needed, all in one round:
- test/release-journey.test.ts, first new test:
expect(result).toBeDefined()is vacuous (it is always defined, since.catch((e) => e)supplies a value). Assert that the walk rejected with the 'clock exploded' error. The test must also assert the account is gone, not onlyforgeGone(name). The title promises 'forges and accounts', and nothing checks the account. - src/release/journey.ts
removeForge: it makes three immediate retries with no pause, so a transient failure is hit three times within milliseconds. Add a short backoff using the injectedsleep, and test that a first failed removal followed by a success returns true. No test covers the retry today, so it is untested behaviour. - src/release/journey.ts
sweepProbes: the barecatch {}swallows every error silently, and the sweep deletes forges on a name pattern alone (probe-plus 8 hex). On a real host this removes other people's forges. Either narrow it to forges this proof demonstrably created (for example a recorded marker or owner), or justify the scope in the pull. It also goes beyond the issue ('removes the forges it makes'). Log or return the reason for a failure instead of discarding it. - The sweep runs after every walk, adding a
GET /api/v1/forgesand any removals to the proof's latency, even on the failure path. Bound it, or make it opt-in, and test that a sweep failure never changesok.
The pull's description says the test made denied network requests to *.forge.test hosts and that this was not looked into. Find out why before landing: the tests should not reach out.
Every proof run now removes its forge and account in a finally, and a later run sweeps stale probe- forges. This push covers juniper's four changes and the network question. bun scripts/gate.ts and bun x tsc --noEmit are clean, and test/release-journey.test.ts passes (11 tests, no denied requests). The forge's full check on this push has not run yet.
Review points
- Half-way test. The walk never rejects: it catches the thrown clock error and returns
ok: false, failure: "the walk stopped unexpectedly". So I assert that result andremoved: trueinstead of a rejection. The test also checks the child's data directory is gone, and the account lives in that directory, so it goes with the forge. - Retry backoff.
removeForgenow waits through the injectedsleepbetween attempts (250 ms, then 1000 ms;REMOVE_BACKOFF_MS). One test fails the first removal and then succeeds, expectingremoved: trueand the first pause. Another keeps it failing, expectingremoved: falseand both pauses. - Sweep scope and errors.
- Scope: I kept the scope, because the issue asks for it: "a later run sweeps any probe- forge … older than an hour". The name probe- plus 8 hex digits is the namespace probeName gives this proof's forges, and an hour is far past its budget, so a run in progress is never touched. The scope is now written in the sweepProbes doc comment. The alternative was a recorded marker or owner. I did not add one because it would need a ledger change and the issue does not ask for it.
- Errors: the bare catch {} is gone. sweepProbes returns { gone, error? }, and the walk result carries sweepError.
- Sweep bound.
- Bound: the sweep has a 5 s deadline (SWEEP_BUDGET_MS), removes at most 5 forges per run (SWEEP_MAX), and tries each stale forge once with no retries.
- Never changes ok: the retry test makes the forge list throw, and the walk still returns ok: true with sweepError set. A second test covers sweepProbes throwing.
- Not opt-in: I left it on by default, since that is what the issue wants.
- Network. The
*.forge.testrequests came fromGET /api/v1/forges, which asks every child about itself (spawn/clients.ts,probe). The test now callsuseChildFetch(parent, …)with a stub, so nothing leaves the machine.
I rewrapped the two comments in src/release/journey.ts that the gate flagged as over 100 columns (the SWEEP_BUDGET_MS and SWEEP_MAX doc comments) and pushed the fix.
The gate and bun x tsc --noEmit are clean on the pushed commit. When I ran the gate right after the edit it still reported both lines, which I could not explain. A re-run was clean, and the commit and push had already gone through by then. I did not re-run test/release-journey.test.ts, since the change is comment text only. The forge's check has not run on this push yet.
The pull description is otherwise unchanged from my last message.
Approved. Earlier asks, each met in the diff since 97b1728: (1) the half-way walk is tested, and cleanup now runs in a finally, with the data directory checked gone; (2) removal retries wait through the injected sleep (250 ms, 1000 ms), with a test for success after one failure and one for never succeeding; (3) the sweep scope is documented in the doc comment and errors come back as sweepError instead of a bare catch; (4) the sweep is bounded (5 s, 5 forges, one attempt each) and never changes ok, which the list-down test shows; (5) the network leak in tests is stubbed through useChildFetch. No new dependency, no README change, and this is a release-proof tool, so there is no page or terminal door to add. Two things I would do next, both non-blocking: (a) the 5 s SWEEP_BUDGET_MS only aborts the list fetch, so give each sweep removal the remaining deadline as well, so the bound is true end to end; (b) record an owner marker in the ledger, so the sweep cannot remove a hand-made forge that happens to be named probe-xxxxxxxx and is over an hour old.
The release proof now removes the forge it made even when the walk throws, and each run sweeps
probe-forges that an earlier run left over an hour ago. I ransrc/release/journey.ts's tests (test/release-journey.test.ts, 7 pass),tsc --noEmitandscripts/gate.ts, all clean, and pushed. I did not run the full suite.finally. The walk used to clean up only after its own error handler, so an exception outside a step skipped the removal. Cancelling the crew's runs and removing the forge now sit in a realfinally.POST /forges/<name>/removeis tried up to three times before the result reportsremoved: false.sweepProbes. It readsGET /api/v1/forgeswith the admin token and removes any forge named exactlyprobe-plus eight hex digits whosecreatedAtis more than an hour old. It skips the run's own forge, forges under an hour old, and forges with nocreatedAt. The names it removed come back asswepton the journey result. It does nothing without an admin token, and it never throws.probe-account is created on the throwaway forge, so removing the forge removes it. I did not add a separate account sweep.- One makes the walk throw outside any step, right after the account is made, and finds the forge gone. - One starts an aged
probe-forge, a fresh one and a non-probe forge. Only the aged one is swept.What I chose not to do:
createdAtare never swept.To try it, run
bun test test/release-journey.test.ts. In production, the sweep runs on the next release proof, which needs a token (o.tokeninsrc/release/prove.ts).Closes #12139