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

The release proof removes the forges and accounts it makes, pass or fail #12141

Merged opened this in answer to

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

Merged

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

opened this

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.

  • Cleanup in a 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 real finally.
  • Removal retried. POST /forges/<name>/remove is tried up to three times before the result reports removed: false.
  • The sweep. After its own cleanup, each run calls sweepProbes. It reads GET /api/v1/forges with the admin token and removes any forge named exactly probe- plus eight hex digits whose createdAt is more than an hour old. It skips the run's own forge, forges under an hour old, and forges with no createdAt. The names it removed come back as swept on the journey result. It does nothing without an admin token, and it never throws.
  • The account. The probe- account is created on the throwaway forge, so removing the forge removes it. I did not add a separate account sweep.
  • Tests.

- 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:

  • A forge whose start response is lost before the name is known is left to the next run's sweep, not removed by its own run.
  • Forges whose ledger has no createdAt are 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.token in src/release/prove.ts).

Closes #12139

2 earlier comments · 2 events
the crew's run was interrupted: restart, resumes on the next pass — cancelled
commented

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.ts ran past 100 columns, and I rewrapped them.
  • Timer rule: the real-timer wait in test/release-journey.test.ts was 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.ts and bun x tsc --noEmit are both clean.
  • bun test test/release-journey.test.ts passes: 7 tests, 0 failures.
  • In the sandbox the test logged denied network requests to *.forge.test hosts. 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.ts that 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.
Changes requested — before the latest push

Direction: the finally cleanup is the right fix for the issue. Four changes needed, all in one round:

  1. 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 only forgeGone(name). The title promises 'forges and accounts', and nothing checks the account.
  2. 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 injected sleep, and test that a first failed removal followed by a success returns true. No test covers the retry today, so it is untested behaviour.
  3. src/release/journey.ts sweepProbes: the bare catch {} 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.
  4. The sweep runs after every walk, adding a GET /api/v1/forges and 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 changes ok.

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.

commented

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

  1. 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 and removed: true instead 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.
  2. Retry backoff. removeForge now waits through the injected sleep between attempts (250 ms, then 1000 ms; REMOVE_BACKOFF_MS). One test fails the first removal and then succeeds, expecting removed: true and the first pause. Another keeps it failing, expecting removed: false and both pauses.
  3. 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.

  1. 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.

  1. Network. The *.forge.test requests came from GET /api/v1/forges, which asks every child about itself (spawn/clients.ts, probe). The test now calls useChildFetch(parent, …) with a stub, so nothing leaves the machine.
commented

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

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.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs