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.
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.
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
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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #333, implementing the
decision recorded on it: a failed release build removes
dist/.What changed
script/discard-dist-on-failure COMMAND [ARG...](POSIX sh): runs onerelease-build step, and if it fails removes
dist/, says on stderr that itdid 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 ofbuild:runs through it — theyarn run buildstep, the
verify-buildstep, and the finalcheck-censored --require-diststep. It composes with the existing mktemp'd receipt and its
trap: thereceipt 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 amistakable artifact and its
dist/is the evidence.script/verify-buildis untouched. What it inspects is unchanged; this isonly what happens after it says no.
README.md(Debug Builds, Entrypoints,make build/make build-debug) andTODO.mdupdated;make fmtrun.Both-directions proof
Real
make buildruns in this tree, not simulations.Failing release build (
AUTISTMASK_DEBUG=1exported):Passing release build (same tree, no env):
Third direction, since the wrapper is on the release path only: a debug build
made to fail mid-write (
chmod 500 dist/chrome, EACCES frombuild.js) exits2 and keeps its
dist/.Also exercised for real: with
dist/chromeunwritable, a failing releasebuild cannot complete the
rm -rfand printsdist/ 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-buildcases assert the POST-RUN STATE OFdist/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 -nwiring cases assert the wrapper is onmake build(includingaround
check-censored --require-dist) and ABSENT frommake build-debug.Mutation-checked both directions rather than assumed: disabling the
rmfailsthe "removes dist/" case (
dist/ is kept after the run, wanted gone), anddropping the success guard fails the "leaves dist/ alone" case (
dist/ is gone after the run, wanted kept). Both restored.Verification
make checkgreen: 41 suites / 835 tests,test-verify-build: 46 case(s) passed(no skips; permission cases enabled),check-censored152 files, lintand fmt-check in the Docker
lintstage. No containers left behind(
docker ps -aempty); no prune of any kind.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, unwrappingcheck-censored, and wrappingbuild-debugeach fail exactly one case),make checkgreen with the Docker lint stage genuinely executing (4.7s, realeslint/prettieroutput, notCACHED), 46 cases / 0 skips, CI green on177f592, merges cleanly intonext,script/verify-builduntouched, POSIX-clean underdash.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,dista symlink to a populated tree (link removed, target intact), dangling symlink, absentdist, 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— thescript/discard-dist-on-failurebullet ends...is reported as loudly as one that waswith 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 finalechofails,set -euaborts beforeexit "$_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 themake buildpath. 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, sodiscard_distnever runs anddist/is left partially written with no message. The receipttrapstill 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 buildruns, not by reading: failing release build (AUTISTMASK_DEBUG=1) exits 2 and leaves nodist/; passing release build keeps all 15 emitted files;make build-debugfailed mid-write with EACCES keeps itsdist/; and withdist/chromeunwritable a failing release build prints theCOULD NOT BE REMOVEDfallback, which is accurate —rm -rfhad removed what it could and the remaining tree really is still on disk.177f592094to92318cb60e