diff --git a/TODO.md b/TODO.md index 78c8ceb..49e356f 100644 --- a/TODO.md +++ b/TODO.md @@ -44,6 +44,11 @@ undefined identifiers, which is how # Completed Steps +- 2026-08-11: `script/verify-build` diagnostics corrected: the both-markers + message now states what is and is not proven, an unreadable bundle is + diagnosed as an I/O fault rather than as changed output, and the `*.js` + assumption lives only in `build.js` + ([#180](https://git.eeqj.de/sneak/AutistMask/issues/180)). - 2026-08-11: Wallet deletion repairs its own state — `hasWallet` follows the remaining wallets, the selection only moves when it was deleted, and the active-address change is broadcast to connected sites diff --git a/build.js b/build.js index 0ecdad4..1e2551f 100644 --- a/build.js +++ b/build.js @@ -29,6 +29,11 @@ function repoRelative(p) { // reports every input that contributed to an output in the metafile, which is // the authoritative answer to "is constants.js in this bundle" — unlike // searching the minified text, it does not depend on what survived minification. +// +// The ".js" filter below is the only place that assumption lives: +// script/verify-build searches every file under dist/ for a marker, without +// filtering by extension, so a bundle emitted under some other extension fails +// there as unlisted rather than escaping both checks at once. function outputsContainingAuditedModule(metafile) { return Object.entries(metafile.outputs) .filter(([outFile, info]) => { diff --git a/script/verify-build b/script/verify-build index 23b1b7c..b294cd6 100755 --- a/script/verify-build +++ b/script/verify-build @@ -34,16 +34,31 @@ fail() { exit 1 } +# Is the literal $1 present in the file $2? Match (grep exit 0) and no-match +# (exit 1) are answers about the emitted output. Anything else (exit 2: the +# file could not be read) is not an answer at all, and must not be reported as +# "no marker" — that would blame the bundle for a permissions or I/O fault. has_marker() { - grep -q -F "$1" "$2" 2>/dev/null + _hm_status=0 + grep -q -F -e "$1" -- "$2" || _hm_status=$? + case "$_hm_status" in + 0) return 0 ;; + 1) return 1 ;; + *) + fail "grep exited $_hm_status reading $2, so the file could not be + searched and its DEBUG state was not checked at all. That is a permissions + or I/O fault on the artifact, not a change in the emitted output. Refusing + to report success." + ;; + esac } # Read one bundle's DEBUG state into MARKER. Exactly one marker must be # present. Both means the ternary in constants.js was never folded, which is # what happens when the __BUILD_DEBUG__ define goes missing from build.js: -# DEBUG stops being known at build time and the debug branch is live again. -# Neither means we are reading output we do not understand. Both are hard -# failures; neither is ever treated as absence of a problem. +# DEBUG stops being known at build time. Neither means we are reading output +# we do not understand. Both are hard failures; neither is ever treated as +# absence of a problem. read_marker() { _file="$1" _on=no @@ -52,9 +67,14 @@ read_marker() { if has_marker "$MARKER_OFF" "$_file"; then _off=yes; fi if [ "$_on" = yes ] && [ "$_off" = yes ]; then - fail "$_file carries both debug markers, so the build-time DEBUG value - was never resolved and the debug branch is still live. Check that build.js - still defines __BUILD_DEBUG__." + fail "$_file carries both debug markers, so DEBUG was not resolved at + build time: the ternary in src/shared/constants.js survived into the + emitted output. This does not mean the debug branch is live in this + artifact: an unresolved __BUILD_DEBUG__ is undeclared in extension + context, so DEBUG evaluates to false at runtime. It does mean the + release/debug distinction is no longer enforced at build time, and which + way that fallback happens to evaluate is then an accident a refactor can + flip. Check that build.js still defines __BUILD_DEBUG__." fi if [ "$_on" = no ] && [ "$_off" = no ]; then fail "$_file carries no debug marker, so its DEBUG state cannot be @@ -70,13 +90,20 @@ read_marker() { } # The manifest says which bundles must carry a marker. This says no other -# emitted bundle may carry one, which catches a manifest that has gone stale +# emitted file may carry one, which catches a manifest that has gone stale # or short rather than trusting whatever it happens to list. +# +# Deliberately unfiltered by extension. build.js selects manifest entries with +# an endsWith(".js") test; repeating that literal here would mean a bundle +# emitted under some other extension escaped the manifest AND this check at +# once, which is the correlated blind spot the two-source design exists to +# avoid. Every file under dist/ is searched, so build.js's filter is the only +# place the assumption lives and this check is what catches it being wrong. check_unlisted_bundles() { - _listing="$(find dist -type f -name '*.js' | sort)" + _listing="$(find dist -type f | sort)" while read -r _file; do [ -n "$_file" ] || continue - if grep -q -x -F "$_file" "$MANIFEST"; then + if grep -q -x -F -e "$_file" -- "$MANIFEST"; then continue fi if has_marker "$MARKER_ON" "$_file" || @@ -113,12 +140,18 @@ main() { fail "$MANIFEST is empty, so no emitted bundle was found to contain src/shared/constants.js. That is never correct, so it is a failure and not a pass." + [ -r "$MANIFEST" ] || + fail "$MANIFEST is not readable, so nothing was inspected. That is a + permissions or I/O fault, not a pass." count=0 while read -r file; do [ -n "$file" ] || continue [ -f "$file" ] || fail "$MANIFEST lists $file, which does not exist." + [ -s "$file" ] || + fail "$MANIFEST lists $file, which is empty. An empty bundle + carries no marker and proves nothing, so this is a failure and not a pass." read_marker "$file" [ "$MARKER" = "$expected" ] || fail "$file is $MARKER but this build expects $expected."