build: remove dist/ when a release build fails (closes #333) #341

Merged
clawbot merged 1 commits from build/333-failed-release-wipes-dist into next 2026-08-23 15:39:04 +02:00
Collaborator

Closes #333, implementing the
decision recorded on it: a failed release build removes dist/.

What changed

  • New script/discard-dist-on-failure COMMAND [ARG...] (POSIX sh): runs one
    release-build step, and if it fails removes dist/, says on stderr that it
    did and why, and returns the step's own exit status. A removal it cannot
    complete is reported just as loudly, naming what is still on disk. Success
    removes nothing.
  • Makefile: every step of build: runs through it — the yarn run build
    step, the verify-build step, and the final check-censored --require-dist
    step. It composes with the existing mktemp'd receipt and its trap: the
    receipt is still removed on the way out (verified, no
    /tmp/autistmask-build-receipt.* left after either run below).
  • build-debug: is deliberately NOT wrapped — a failed debug build is not a
    mistakable artifact and its dist/ is the evidence.
  • script/verify-build is untouched. What it inspects is unchanged; this is
    only what happens after it says no.
  • README.md (Debug Builds, Entrypoints, make build/make build-debug) and
    TODO.md updated; make fmt run.

Both-directions proof

Real make build runs in this tree, not simulations.

Failing release build (AUTISTMASK_DEBUG=1 exported):

Build mode: DEBUG (INSECURE - hardcoded test mnemonic, do not ship)
verify-build: FAIL: dist/chrome/src/background/index.js is autistmask-build-debug=on
    but this build was told to expect autistmask-build-debug=off. ...
discard-dist-on-failure: the release build failed, so dist/ WAS REMOVED and no
    longer exists. A release build that fails has often already emitted a
    complete, loadable debug bundle - every wallet it creates gets the publicly
    committed test recovery phrase - so the failed build is not left behind to
    be loaded. ...
make: *** [Makefile:71: build] Error 1   (make exit 2)
ls: cannot access 'dist': No such file or directory   -> DIST GONE

Passing release build (same tree, no env):

Build mode: release (DEBUG off)
verify-build: 15 emitted file(s) verified against the receipt, 4 bundle(s) autistmask-build-debug=off
check-censored: 152 tracked file(s) inspected, 15 file(s) under dist/
make exit: 0
dist/ afterwards: chrome firefox styles.css   (15 files, intact)

Third direction, since the wrapper is on the release path only: a debug build
made to fail mid-write (chmod 500 dist/chrome, EACCES from build.js) exits
2 and keeps its dist/.

Also exercised for real: with dist/chrome unwritable, a failing release
build cannot complete the rm -rf and prints
dist/ COULD NOT BE REMOVED, so it is still on disk. Do not load it: ... Remove it by hand (make clean). — the fallback path is real, not hypothetical.

Test coverage

Four new script/test-verify-build cases assert the POST-RUN STATE OF dist/
directly (present-and-intact vs. absent), not the exit status alone: a failing
step removes it, a succeeding step leaves it alone, a failing step with no
dist/ says there was none, and a wrapper handed no command removes nothing.
Three make -n wiring cases assert the wrapper is on make build (including
around check-censored --require-dist) and ABSENT from make build-debug.

Mutation-checked both directions rather than assumed: disabling the rm fails
the "removes dist/" case (dist/ is kept after the run, wanted gone), and
dropping the success guard fails the "leaves dist/ alone" case (dist/ is gone after the run, wanted kept). Both restored.

Verification

make check green: 41 suites / 835 tests, test-verify-build: 46 case(s) passed (no skips; permission cases enabled), check-censored 152 files, lint
and fmt-check in the Docker lint stage. No containers left behind
(docker ps -a empty); no prune of any kind.

Closes [#333](https://git.eeqj.de/sneak/AutistMask/issues/333), implementing the decision recorded on it: **a failed release build removes `dist/`**. ## What changed - New `script/discard-dist-on-failure COMMAND [ARG...]` (POSIX sh): runs one release-build step, and if it fails removes `dist/`, says on stderr that it did and why, and returns the step's own exit status. A removal it cannot complete is reported just as loudly, naming what is still on disk. Success removes nothing. - `Makefile`: every step of `build:` runs through it — the `yarn run build` step, the `verify-build` step, and the final `check-censored --require-dist` step. It composes with the existing mktemp'd receipt and its `trap`: the receipt is still removed on the way out (verified, no `/tmp/autistmask-build-receipt.*` left after either run below). - `build-debug:` is deliberately NOT wrapped — a failed debug build is not a mistakable artifact and its `dist/` is the evidence. - `script/verify-build` is untouched. What it inspects is unchanged; this is only what happens after it says no. - `README.md` (Debug Builds, Entrypoints, `make build`/`make build-debug`) and `TODO.md` updated; `make fmt` run. ## Both-directions proof Real `make build` runs in this tree, not simulations. Failing release build (`AUTISTMASK_DEBUG=1` exported): ``` Build mode: DEBUG (INSECURE - hardcoded test mnemonic, do not ship) verify-build: FAIL: dist/chrome/src/background/index.js is autistmask-build-debug=on but this build was told to expect autistmask-build-debug=off. ... discard-dist-on-failure: the release build failed, so dist/ WAS REMOVED and no longer exists. A release build that fails has often already emitted a complete, loadable debug bundle - every wallet it creates gets the publicly committed test recovery phrase - so the failed build is not left behind to be loaded. ... make: *** [Makefile:71: build] Error 1 (make exit 2) ls: cannot access 'dist': No such file or directory -> DIST GONE ``` Passing release build (same tree, no env): ``` Build mode: release (DEBUG off) verify-build: 15 emitted file(s) verified against the receipt, 4 bundle(s) autistmask-build-debug=off check-censored: 152 tracked file(s) inspected, 15 file(s) under dist/ make exit: 0 dist/ afterwards: chrome firefox styles.css (15 files, intact) ``` Third direction, since the wrapper is on the release path only: a debug build made to fail mid-write (`chmod 500 dist/chrome`, EACCES from `build.js`) exits 2 and **keeps** its `dist/`. Also exercised for real: with `dist/chrome` unwritable, a failing *release* build cannot complete the `rm -rf` and prints `dist/ COULD NOT BE REMOVED, so it is still on disk. Do not load it: ... Remove it by hand (make clean).` — the fallback path is real, not hypothetical. ## Test coverage Four new `script/test-verify-build` cases assert the POST-RUN STATE OF `dist/` directly (present-and-intact vs. absent), not the exit status alone: a failing step removes it, a succeeding step leaves it alone, a failing step with no `dist/` says there was none, and a wrapper handed no command removes nothing. Three `make -n` wiring cases assert the wrapper is on `make build` (including around `check-censored --require-dist`) and ABSENT from `make build-debug`. Mutation-checked both directions rather than assumed: disabling the `rm` fails the "removes dist/" case (`dist/ is kept after the run, wanted gone`), and dropping the success guard fails the "leaves dist/ alone" case (`dist/ is gone after the run, wanted kept`). Both restored. ## Verification `make check` green: 41 suites / 835 tests, `test-verify-build: 46 case(s) passed` (no skips; permission cases enabled), `check-censored` 152 files, lint and fmt-check in the Docker `lint` stage. No containers left behind (`docker ps -a` empty); no prune of any kind.
clawbot added the needs-review label 2026-08-23 15:26:09 +02:00
clawbot added 1 commit 2026-08-23 15:26:09 +02:00
build: remove dist/ when a release build fails (closes #333)
All checks were successful
check / check (push) Successful in 50s
e2e / e2e-chrome (push) Successful in 1m25s
e2e / e2e-firefox (push) Successful in 43s
177f592094
With AUTISTMASK_DEBUG=1 exported in the calling shell, make build compiled a
debug bundle and failed on it in script/verify-build, but left the bundle in
dist/: loadable, with every wallet it creates using the publicly committed test
recovery phrase from src/shared/constants.js. A failed release build that leaves
a loadable debug build behind is the trap the verifier exists to close.

Every step of make build now runs through script/discard-dist-on-failure, which
removes dist/ when its step fails and says on stderr that it did and why, then
returns the step's own exit status. A removal it cannot complete is reported as
loudly as one it can, naming what is still on disk. A step that succeeds removes
nothing, including the final check-censored --require-dist pass. It composes
with the existing receipt trap: the receipt is still deleted on the way out.

make build-debug is deliberately not wrapped. A debug build that failed is not
producing an artifact mistakable for a release one, and its dist/ is the
evidence of what went wrong.

script/test-verify-build asserts the state of dist/ on disk after a failing and
a succeeding step rather than the exit status alone, plus a step that fails with
no dist/ and a wrapper handed no command, and reads make -n to check the wrapper
is on the release path and absent from the debug one. Both directions were also
run end to end: AUTISTMASK_DEBUG=1 make build fails and leaves no dist/, plain
make build passes with all 15 emitted files intact, and a debug build failed
mid-write keeps its dist/.

verify-build itself is unchanged; this is only what happens after it says no.
clawbot self-assigned this 2026-08-23 15:26:19 +02:00
Author
Collaborator

PASS — independently verified in a fresh clone: the definition of done in #333 is met, all four discard cases are mutation-proven (disabling the rm, dropping the success guard, unwrapping check-censored, and wrapping build-debug each fail exactly one case), make check green with the Docker lint stage genuinely executing (4.7s, real eslint/prettier output, not CACHED), 46 cases / 0 skips, CI green on 177f592, merges cleanly into next, script/verify-build untouched, POSIX-clean under dash.

Destructive-path probes actually executed, all safe: invoked from an unrelated cwd (removes only the script's own $ROOT/dist), repo root containing a space and an apostrophe, dist a symlink to a populated tree (link removed, target intact), dangling symlink, absent dist, missing command (exit 127), and non-1/2 statuses (3, 4, 5, 7, 8, 9) all propagated.

Two trivial nits, neither blocking:

  • README.md:165 — the script/discard-dist-on-failure bullet ends ...is reported as loudly as one that was with no terminal period; every other sentence-form bullet in that list has one. Prettier cannot catch it.
  • script/discard-dist-on-failure:52-58 — if stderr is closed, the final echo fails, set -eu aborts before exit "$_status", and the wrapper exits 2 instead of the step's status (measured: ... sh -c 'exit 6' 2>&- returns 2). Contradicts the documented "returns the step's own exit status", but stderr is never closed on the make build path. Same applies to the "there was no dist/" branch.

Disclosure, not a defect: on a real SIGINT to the process group mid-make build, the wrapper is killed by the signal, so discard_dist never runs and dist/ is left partially written with no message. The receipt trap still fires (no /tmp/autistmask-build-receipt.* left after any of my runs). Not in scope for #333, and an operator who interrupts a build knows they did, but nothing states it.

Also confirmed by real make build runs, not by reading: failing release build (AUTISTMASK_DEBUG=1) exits 2 and leaves no dist/; passing release build keeps all 15 emitted files; make build-debug failed mid-write with EACCES keeps its dist/; and with dist/chrome unwritable a failing release build prints the COULD NOT BE REMOVED fallback, which is accurate — rm -rf had removed what it could and the remaining tree really is still on disk.

PASS — independently verified in a fresh clone: the definition of done in [#333](https://git.eeqj.de/sneak/AutistMask/issues/333) is met, all four discard cases are mutation-proven (disabling the `rm`, dropping the success guard, unwrapping `check-censored`, and wrapping `build-debug` each fail exactly one case), `make check` green with the Docker lint stage genuinely executing (4.7s, real `eslint`/`prettier` output, not `CACHED`), 46 cases / 0 skips, CI green on `177f592`, merges cleanly into `next`, `script/verify-build` untouched, POSIX-clean under `dash`. Destructive-path probes actually executed, all safe: invoked from an unrelated cwd (removes only the script's own `$ROOT/dist`), repo root containing a space and an apostrophe, `dist` a symlink to a populated tree (link removed, target intact), dangling symlink, absent `dist`, missing command (exit 127), and non-1/2 statuses (3, 4, 5, 7, 8, 9) all propagated. Two trivial nits, neither blocking: - `README.md:165` — the `script/discard-dist-on-failure` bullet ends `...is reported as loudly as one that was` with no terminal period; every other sentence-form bullet in that list has one. Prettier cannot catch it. - `script/discard-dist-on-failure:52-58` — if stderr is closed, the final `echo` fails, `set -eu` aborts before `exit "$_status"`, and the wrapper exits 2 instead of the step's status (measured: `... sh -c 'exit 6' 2>&-` returns 2). Contradicts the documented "returns the step's own exit status", but stderr is never closed on the `make build` path. Same applies to the "there was no dist/" branch. Disclosure, not a defect: on a real SIGINT to the process group mid-`make build`, the wrapper is killed by the signal, so `discard_dist` never runs and `dist/` is left partially written with no message. The receipt `trap` still fires (no `/tmp/autistmask-build-receipt.*` left after any of my runs). Not in scope for [#333](https://git.eeqj.de/sneak/AutistMask/issues/333), and an operator who interrupts a build knows they did, but nothing states it. Also confirmed by real `make build` runs, not by reading: failing release build (`AUTISTMASK_DEBUG=1`) exits 2 and leaves no `dist/`; passing release build keeps all 15 emitted files; `make build-debug` failed mid-write with EACCES keeps its `dist/`; and with `dist/chrome` unwritable a failing release build prints the `COULD NOT BE REMOVED` fallback, which is accurate — `rm -rf` had removed what it could and the remaining tree really is still on disk.
clawbot added needs-rebase and removed needs-review labels 2026-08-23 15:35:24 +02:00
clawbot force-pushed build/333-failed-release-wipes-dist from 177f592094 to 92318cb60e 2026-08-23 15:38:27 +02:00 Compare
clawbot merged commit 12b0c4d1c6 into next 2026-08-23 15:39:04 +02:00
clawbot deleted branch build/333-failed-release-wipes-dist 2026-08-23 15:39:04 +02:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/AutistMask#341