build: remove dist/ when a release build fails (closes #333)
All checks were successful
check / check (push) Successful in 52s
e2e / e2e-chrome (push) Successful in 1m25s
e2e / e2e-firefox (push) Successful in 40s

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.
This commit is contained in:
2026-08-23 13:25:41 +00:00
parent c36d8b6ddf
commit 92318cb60e
5 changed files with 325 additions and 31 deletions

View File

@@ -63,8 +63,12 @@ that runs `make build`, that target compiles a debug bundle and then **fails**,
because it tells `script/verify-build` in so many words that it was supposed to
produce a release build. It used to be that the verifier read the same variable
out of its own environment, agreed with itself, and reported a debug artifact as
verified. The build prints which mode it used. See the
[DEBUG Mode Policy](#debug-mode-policy) for what the flag changes. **Never
verified. The build prints which mode it used. A release build that fails also
**removes `dist/`**, and says so: the bundle it had already written is loadable,
and a loud failure is no protection against someone loading `dist/chrome/`
anyway. `make build-debug` keeps its `dist/` on failure — that output is not
mistakable for a release build, and it is the evidence of what went wrong. See
the [DEBUG Mode Policy](#debug-mode-policy) for what the flag changes. **Never
distribute a debug build** — every wallet it creates gets the same publicly
known test recovery phrase.
@@ -158,16 +162,24 @@ provide:
`make build` and `make build-debug`; fails loudly rather than passing whenever
it cannot determine something. Not part of `make check`, which does not depend
on build artifacts existing.
- `script/discard-dist-on-failure COMMAND [ARG...]` — run one step of the
**release** build and, if it fails, remove `dist/` before returning that
step's exit status, saying on stderr that it did and why. Every step of
`make build` runs through it; `make build-debug` runs none of them through it.
A step that succeeds removes nothing, and a removal that cannot be completed
is reported as loudly as one that was
- `script/test-verify-build` — exercise every failure mode of
`script/verify-build` against a fixture tree in a temp dir, asserting the exit
status and the message of each, and read the `make build` and
status and the message of each, assert the state of `dist/` on disk after a
failing and a succeeding release build step, and read the `make build` and
`make build-debug` recipes back out of `make -n` to check that they pass the
mode as an argument on a scrubbed environment. Part of `make check`; it reads
no build artifacts and writes nothing under `dist/`. The cases that depend on
file permissions cannot mean anything for a process that is not subject to
them, so the harness proves its runner against a mode-000 file before counting
them, dropping to an unprivileged user when run as root; if it cannot, it
skips those cases and says so in a banner rather than passing them.
mode as an argument on a scrubbed environment and wrap only the release path.
Part of `make check`; it reads no build artifacts and writes nothing under
`dist/`. The cases that depend on file permissions cannot mean anything for a
process that is not subject to them, so the harness proves its runner against
a mode-000 file before counting them, dropping to an unprivileged user when
run as root; if it cannot, it skips those cases and says so in a banner rather
than passing them.
- `script/docker` — build the Docker image tagged via `script/projectname`
- `script/cibuild` — CI entrypoint: plain `docker build .`
- `script/precommit` — run by the git pre-commit hook; runs `script/check`
@@ -181,9 +193,11 @@ The Makefile shims to those. It also carries a few targets that have no
silently rewritten. Use `make setup` for a fresh clone.
- `make hooks` — shims to `script/install-precommit`
- `make build` — build the extension into `dist/chrome/` and `dist/firefox/`,
then verify the result against the build's receipt as a release build
then verify the result against the build's receipt as a release build. A
failure at any step removes `dist/`
- `make build-debug` — the same build with `AUTISTMASK_DEBUG=1`, verified as a
debug build (see [Debug Builds](#debug-builds))
debug build, and keeping its `dist/` on failure (see
[Debug Builds](#debug-builds))
- `make clean` — remove `dist/`
- `make dev` — build in watch mode