check_unlisted_bundles in script/verify-build read dist/ line by line
(find -print into while read -r), so two unlisted, marker-carrying paths
were never checked while the script still exited 0.
The walk is now find dist \( -type f -o -type l \) -print0 into a mktemp
listing, whose find exit status is still checked separately (the status
cannot be read off a pipeline), and xargs -0 hands the paths back to this
script as arguments, where the same is_listed and has_marker run on
them. The per-path half is a distinct internal entry point
(--scan-dist-paths) rather than a second copy of those two functions, so
they cannot drift. The listing is removed by an EXIT trap.
is_listed now answers "not listed" for any path containing a newline
without asking grep. This is load-bearing and was not obvious: NUL
delimiting alone did not fix the newline case. grep reads a pattern
containing a newline as two patterns, so the part before the newline still
matched the listed line and the file was skipped. The manifest is
line-delimited and cannot name such a path at all, so "not listed" is the
only true answer. Proven below — the first fixed run still passed this case.
dist/ being a symlink is now its own check with its own message, asserted
in main before anything reads through it. Previously it failed only
because GNU grep exits 2 on a directory (see the pre-fix output below,
which literally reports grep: dist: Is a directory); under a grep that
exits 1 instead, the whole cross-check would have collapsed into a pass.
The comment claiming "Every file under dist/ is searched" now states what
is actually guaranteed — every regular file and every symlink, which is the
whole of what a build emits — and lists the four properties that coverage
rests on as enforced rather than assumed. Other file types are excluded on
purpose: a build emits none of them, and grep on a fifo would hang rather
than fail.
Everything #180 hardened is
preserved: the non-zero find status guard, symlinks walked rather than
skipped (dangling and directory-pointing links failing closed through has_marker's exit-2 path), and the exit-2 treatment in has_marker and is_listed.
Only the sort step is gone, and not for portability. It existed for
deterministic reporting order, and that order does not matter to the only
consumer: traversal order decides nothing but which offender is named first
when there are several. sort -z is a GNU extension, but so are find -print0
and xargs -0, which this change adopts because the issue mandates them, so
portability was never the reason to drop it.
sh -n and dash -n clean; /bin/sh is dash on the machine this ran on, so
the battery below executed under dash. Also run under bash once as a
portability check.
Evidence
Every case below was constructed against a real make build tree and run,
as a non-root user (uid 1000 — under root chmod 000 does not stop find).
Full battery: 18 cases, 1 control that must pass and 17 that must fail.
Before the fix (script/verify-build as it is on next)
Trailing space — passes, which is the bug:
===== CASE: NEW 1: unlisted marker-carrying file, trailing space in name =====
Verifying emitted bundles (expecting autistmask-build-debug=off)...
ok: dist/chrome/src/background/index.js (autistmask-build-debug=off)
ok: dist/chrome/src/popup/index.js (autistmask-build-debug=off)
ok: dist/firefox/src/background/index.js (autistmask-build-debug=off)
ok: dist/firefox/src/popup/index.js (autistmask-build-debug=off)
verify-build: 4 bundle(s) verified autistmask-build-debug=off
--- exit: 0
Newline in the name — same, exit 0.
dist/ replaced by a symlink — fails, but only incidentally, and the output
names the reason:
===== CASE: NEW 3: dist/ replaced by a symlink =====
...
grep: dist: Is a directory
verify-build: FAIL: grep exited 2 reading dist, so the file could not be
searched and its DEBUG state was not checked at all. ...
--- exit: 1
After the fix — the three new cases
NEW 1, a marker-carrying copy at dist/chrome/src/popup/index.js plus a
trailing space:
===== CASE: NEW 1: unlisted marker-carrying file, trailing space in name =====
Verifying emitted bundles (expecting autistmask-build-debug=off)...
ok: dist/chrome/src/background/index.js (autistmask-build-debug=off)
ok: dist/chrome/src/popup/index.js (autistmask-build-debug=off)
ok: dist/firefox/src/background/index.js (autistmask-build-debug=off)
ok: dist/firefox/src/popup/index.js (autistmask-build-debug=off)
verify-build: FAIL: dist/chrome/src/popup/index.js carries a debug marker but is absent from dist/constants-bundles.txt,
so the manifest no longer describes the emitted bundles.
verify-build: FAIL: the unlisted-bundle scan exited 123: either a path
under dist/ failed the check reported above, or the scan could not be run
at all. Refusing to report success.
--- exit: 1
NEW 2, a copy whose name is the listed path plus a literal newline (the blank
second line in the message is that newline):
===== CASE: NEW 2: unlisted marker-carrying file, newline in name =====
Verifying emitted bundles (expecting autistmask-build-debug=off)...
ok: dist/chrome/src/background/index.js (autistmask-build-debug=off)
ok: dist/chrome/src/popup/index.js (autistmask-build-debug=off)
ok: dist/firefox/src/background/index.js (autistmask-build-debug=off)
ok: dist/firefox/src/popup/index.js (autistmask-build-debug=off)
verify-build: FAIL: dist/chrome/src/popup/index.js
carries a debug marker but is absent from dist/constants-bundles.txt,
so the manifest no longer describes the emitted bundles.
verify-build: FAIL: the unlisted-bundle scan exited 123: either a path
under dist/ failed the check reported above, or the scan could not be run
at all. Refusing to report success.
--- exit: 1
NEW 3, dist/ moved aside and replaced by a symlink to it — now the script's
own check, and grep is never reached:
===== CASE: NEW 3: dist/ replaced by a symlink =====
Verifying emitted bundles (expecting autistmask-build-debug=off)...
verify-build: FAIL: dist is a symlink, not a directory. find does not follow a
symlink named on its own command line, so the unlisted-bundle cross-check
would see one entry instead of the emitted tree and establish nothing about
it. Refusing to report success.
--- exit: 1
After the fix — full battery
Case, then exit status. Every failure case still fails; the control still
passes.
control: untouched dist (must PASS, exit 0) exit: 0
NEW 1: unlisted marker-carrying file, trailing space in name exit: 1
NEW 2: unlisted marker-carrying file, newline in name exit: 1
NEW 3: dist/ replaced by a symlink exit: 1
manifest missing exit: 1
manifest empty exit: 1
manifest unreadable (chmod 000) exit: 1
listed bundle missing exit: 1
listed bundle empty exit: 1
listed bundle unreadable (chmod 000) exit: 1
unlisted .mjs carrying a marker exit: 1
unwalkable subtree (chmod 000 dist/chrome/src/content) exit: 1
dangling symlink in dist/ exit: 1
symlink to a directory in dist/ exit: 1
symlink to a listed bundle under an unlisted path exit: 1
listed bundle carries no marker exit: 1
listed bundle carries both markers exit: 1
wrong marker (off build, AUTISTMASK_DEBUG=1) exit: 1
Each keeps the diagnostic it had before, e.g. the walk guard:
===== CASE: unwalkable subtree (chmod 000 dist/chrome/src/content) =====
...
find: 'dist/chrome/src/content': Permission denied
verify-build: FAIL: find exited 1 enumerating dist/, so part of the tree
was never walked and nothing was established about the files in it. ...
--- exit: 1
and the symlink cases still fail closed through has_marker's exit-2 path
(grep: dist/chrome/dangling.js: No such file or directory, grep: dist/chrome/link-to-dir: Is a directory).
The unwalkable-subtree case uses dist/chrome/src/content, which the manifest
does not list, so main's per-bundle loop passes and the find guard is the
thing under test.
The battery was run against a freshly rebuilt tree (make clean, then make build) at next = 86cdea5, with no temporary listing file left behind
on either the passing or the failing path. It was not re-run after the rebase
onto next = 12acf4d, because that rebase touched only TODO.md: script/verify-build is byte-identical to the version the battery ran against.
Verification
make check: green, re-run after rebasing onto next = 12acf4d — 10 test
suites, 252 tests (251 passed, 1 skipped), prettier clean. The earlier body
said "157 tests", which was wrong; this is a real run on the current base.
make clean then make build: green, all 4 bundles verified autistmask-build-debug=off.
sh -n script/verify-build and dash -n script/verify-build: clean.
Notes
The battery is a manual harness kept outside the repo, as in #180; nothing about it is
committed here. make test is jest, which cannot host a fixture tree whose
filenames contain newlines and 000-mode directories.
Branch name is fix/issue-223-verify-build-nul-walk rather than the issue-<N>-<slug> form in TODO.md, as instructed when this unit was
assigned.
Closes [#223](https://git.eeqj.de/sneak/AutistMask/issues/223).
## What changed
`check_unlisted_bundles` in `script/verify-build` read `dist/` line by line
(`find -print` into `while read -r`), so two unlisted, marker-carrying paths
were never checked while the script still exited 0.
- The walk is now `find dist \( -type f -o -type l \) -print0` into a `mktemp`
listing, whose `find` exit status is still checked separately (the status
cannot be read off a pipeline), and `xargs -0` hands the paths back to this
script as **arguments**, where the same `is_listed` and `has_marker` run on
them. The per-path half is a distinct internal entry point
(`--scan-dist-paths`) rather than a second copy of those two functions, so
they cannot drift. The listing is removed by an `EXIT` trap.
- `is_listed` now answers "not listed" for any path containing a newline
without asking `grep`. This is load-bearing and was not obvious: NUL
delimiting alone did **not** fix the newline case. `grep` reads a pattern
containing a newline as *two* patterns, so the part before the newline still
matched the listed line and the file was skipped. The manifest is
line-delimited and cannot name such a path at all, so "not listed" is the
only true answer. Proven below — the first fixed run still passed this case.
- `dist/` being a symlink is now its own check with its own message, asserted
in `main` before anything reads through it. Previously it failed **only**
because GNU `grep` exits 2 on a directory (see the pre-fix output below,
which literally reports `grep: dist: Is a directory`); under a `grep` that
exits 1 instead, the whole cross-check would have collapsed into a pass.
- The comment claiming "Every file under `dist/` is searched" now states what
is actually guaranteed — every regular file and every symlink, which is the
whole of what a build emits — and lists the four properties that coverage
rests on as enforced rather than assumed. Other file types are excluded on
purpose: a build emits none of them, and `grep` on a fifo would hang rather
than fail.
Everything [#180](https://git.eeqj.de/sneak/AutistMask/issues/180) hardened is
preserved: the non-zero `find` status guard, symlinks walked rather than
skipped (dangling and directory-pointing links failing closed through
`has_marker`'s exit-2 path), and the exit-2 treatment in `has_marker` and
`is_listed`.
Only the `sort` step is gone, and not for portability. It existed for
deterministic reporting order, and that order does not matter to the only
consumer: traversal order decides nothing but which offender is named first
when there are several. `sort -z` is a GNU extension, but so are `find -print0`
and `xargs -0`, which this change adopts because the issue mandates them, so
portability was never the reason to drop it.
`sh -n` and `dash -n` clean; `/bin/sh` is `dash` on the machine this ran on, so
the battery below executed under `dash`. Also run under `bash` once as a
portability check.
## Evidence
Every case below was constructed against a real `make build` tree and run,
as a non-root user (uid 1000 — under root `chmod 000` does not stop `find`).
Full battery: 18 cases, 1 control that must pass and 17 that must fail.
### Before the fix (`script/verify-build` as it is on `next`)
Trailing space — passes, which is the bug:
```
===== CASE: NEW 1: unlisted marker-carrying file, trailing space in name =====
Verifying emitted bundles (expecting autistmask-build-debug=off)...
ok: dist/chrome/src/background/index.js (autistmask-build-debug=off)
ok: dist/chrome/src/popup/index.js (autistmask-build-debug=off)
ok: dist/firefox/src/background/index.js (autistmask-build-debug=off)
ok: dist/firefox/src/popup/index.js (autistmask-build-debug=off)
verify-build: 4 bundle(s) verified autistmask-build-debug=off
--- exit: 0
```
Newline in the name — same, exit 0.
`dist/` replaced by a symlink — fails, but only incidentally, and the output
names the reason:
```
===== CASE: NEW 3: dist/ replaced by a symlink =====
...
grep: dist: Is a directory
verify-build: FAIL: grep exited 2 reading dist, so the file could not be
searched and its DEBUG state was not checked at all. ...
--- exit: 1
```
### After the fix — the three new cases
NEW 1, a marker-carrying copy at `dist/chrome/src/popup/index.js` plus a
trailing space:
```
===== CASE: NEW 1: unlisted marker-carrying file, trailing space in name =====
Verifying emitted bundles (expecting autistmask-build-debug=off)...
ok: dist/chrome/src/background/index.js (autistmask-build-debug=off)
ok: dist/chrome/src/popup/index.js (autistmask-build-debug=off)
ok: dist/firefox/src/background/index.js (autistmask-build-debug=off)
ok: dist/firefox/src/popup/index.js (autistmask-build-debug=off)
verify-build: FAIL: dist/chrome/src/popup/index.js carries a debug marker but is absent from dist/constants-bundles.txt,
so the manifest no longer describes the emitted bundles.
verify-build: FAIL: the unlisted-bundle scan exited 123: either a path
under dist/ failed the check reported above, or the scan could not be run
at all. Refusing to report success.
--- exit: 1
```
NEW 2, a copy whose name is the listed path plus a literal newline (the blank
second line in the message is that newline):
```
===== CASE: NEW 2: unlisted marker-carrying file, newline in name =====
Verifying emitted bundles (expecting autistmask-build-debug=off)...
ok: dist/chrome/src/background/index.js (autistmask-build-debug=off)
ok: dist/chrome/src/popup/index.js (autistmask-build-debug=off)
ok: dist/firefox/src/background/index.js (autistmask-build-debug=off)
ok: dist/firefox/src/popup/index.js (autistmask-build-debug=off)
verify-build: FAIL: dist/chrome/src/popup/index.js
carries a debug marker but is absent from dist/constants-bundles.txt,
so the manifest no longer describes the emitted bundles.
verify-build: FAIL: the unlisted-bundle scan exited 123: either a path
under dist/ failed the check reported above, or the scan could not be run
at all. Refusing to report success.
--- exit: 1
```
NEW 3, `dist/` moved aside and replaced by a symlink to it — now the script's
own check, and `grep` is never reached:
```
===== CASE: NEW 3: dist/ replaced by a symlink =====
Verifying emitted bundles (expecting autistmask-build-debug=off)...
verify-build: FAIL: dist is a symlink, not a directory. find does not follow a
symlink named on its own command line, so the unlisted-bundle cross-check
would see one entry instead of the emitted tree and establish nothing about
it. Refusing to report success.
--- exit: 1
```
### After the fix — full battery
Case, then exit status. Every failure case still fails; the control still
passes.
```
control: untouched dist (must PASS, exit 0) exit: 0
NEW 1: unlisted marker-carrying file, trailing space in name exit: 1
NEW 2: unlisted marker-carrying file, newline in name exit: 1
NEW 3: dist/ replaced by a symlink exit: 1
manifest missing exit: 1
manifest empty exit: 1
manifest unreadable (chmod 000) exit: 1
listed bundle missing exit: 1
listed bundle empty exit: 1
listed bundle unreadable (chmod 000) exit: 1
unlisted .mjs carrying a marker exit: 1
unwalkable subtree (chmod 000 dist/chrome/src/content) exit: 1
dangling symlink in dist/ exit: 1
symlink to a directory in dist/ exit: 1
symlink to a listed bundle under an unlisted path exit: 1
listed bundle carries no marker exit: 1
listed bundle carries both markers exit: 1
wrong marker (off build, AUTISTMASK_DEBUG=1) exit: 1
```
Each keeps the diagnostic it had before, e.g. the walk guard:
```
===== CASE: unwalkable subtree (chmod 000 dist/chrome/src/content) =====
...
find: 'dist/chrome/src/content': Permission denied
verify-build: FAIL: find exited 1 enumerating dist/, so part of the tree
was never walked and nothing was established about the files in it. ...
--- exit: 1
```
and the symlink cases still fail closed through `has_marker`'s exit-2 path
(`grep: dist/chrome/dangling.js: No such file or directory`,
`grep: dist/chrome/link-to-dir: Is a directory`).
The unwalkable-subtree case uses `dist/chrome/src/content`, which the manifest
does not list, so `main`'s per-bundle loop passes and the `find` guard is the
thing under test.
The battery was run against a freshly rebuilt tree (`make clean`, then
`make build`) at `next` = `86cdea5`, with no temporary listing file left behind
on either the passing or the failing path. It was not re-run after the rebase
onto `next` = `12acf4d`, because that rebase touched only `TODO.md`:
`script/verify-build` is byte-identical to the version the battery ran against.
## Verification
- `make check`: green, re-run after rebasing onto `next` = `12acf4d` — 10 test
suites, 252 tests (251 passed, 1 skipped), prettier clean. The earlier body
said "157 tests", which was wrong; this is a real run on the current base.
- `make clean` then `make build`: green, all 4 bundles verified
`autistmask-build-debug=off`.
- `sh -n script/verify-build` and `dash -n script/verify-build`: clean.
## Notes
- The battery is a manual harness kept outside the repo, as in
[#180](https://git.eeqj.de/sneak/AutistMask/issues/180); nothing about it is
committed here. `make test` is `jest`, which cannot host a fixture tree whose
filenames contain newlines and 000-mode directories.
- Branch name is `fix/issue-223-verify-build-nul-walk` rather than the
`issue-<N>-<slug>` form in `TODO.md`, as instructed when this unit was
assigned.
check_unlisted_bundles read dist/ line by line, so two unlisted paths
carrying a debug marker went unchecked while the script still exited 0:
a name with a trailing space (read stripped it and the remnant matched a
manifest line) and a name containing a newline (find printed it as a
listed path plus an empty one).
The walk is now find -print0 into a temporary listing, checked for find's
exit status as before, and xargs -0 hands the paths back to this script as
arguments, where the same is_listed and has_marker run on them. is_listed
also answers "not listed" for any path containing a newline without asking
grep, which would otherwise read such a path as two patterns and match on
the first half — the escape survives NUL delimiting on its own.
dist/ being a symlink is now its own check with its own message. It failed
before only because GNU grep exits 2 on a directory, so a grep that exits 1
instead would have turned the whole cross-check into a pass.
The comment claiming every file under dist/ is searched now says what is
actually guaranteed: every regular file and every symlink, with the four
properties that coverage rests on stated as enforced rather than assumed.
clawbot
self-assigned this 2026-08-11 15:06:49 +02:00
PASS — 30-case battery re-run independently under dash and bash as uid 1000 (control 0, every failure case non-zero, no temp file left on any path), plus 12 added attacks (CR/tab/quote/backslash/leading-dash names, newline in a directory component, a name whose first line is exactly a listed manifest entry, 40k-file xargs batching, missing/unwritable TMPDIR, absent xargs, non-executable $SELF, relative/PATH/symlinked-dir invocation) — no vacuous pass constructible; make check green here (173 tests executed, prettier clean); merges clean into next with both TODO entries retained.
Two disclosures, neither a defect: the sort removal is justified as avoiding a GNU extension while find -print0/xargs -0 adopted in the same change are equally non-POSIX (the issue mandated them, and nothing reads the listing but the per-path loop, so ordering is genuinely load-free); and the Verification section's "157 tests" does not reproduce — the same commit yields 173 here, so that figure looks stale rather than wrong in kind.
PASS — 30-case battery re-run independently under `dash` and `bash` as uid 1000 (control 0, every failure case non-zero, no temp file left on any path), plus 12 added attacks (CR/tab/quote/backslash/leading-dash names, newline in a directory component, a name whose first line is exactly a listed manifest entry, 40k-file `xargs` batching, missing/unwritable `TMPDIR`, absent `xargs`, non-executable `$SELF`, relative/PATH/symlinked-dir invocation) — no vacuous pass constructible; `make check` green here (173 tests executed, prettier clean); merges clean into `next` with both TODO entries retained.
Two disclosures, neither a defect: the `sort` removal is justified as avoiding a GNU extension while `find -print0`/`xargs -0` adopted in the same change are equally non-POSIX (the issue mandated them, and nothing reads the listing but the per-path loop, so ordering is genuinely load-free); and the Verification section's "157 tests" does not reproduce — the same commit yields 173 here, so that figure looks stale rather than wrong in kind.
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 #223.
What changed
check_unlisted_bundlesinscript/verify-buildreaddist/line by line(
find -printintowhile read -r), so two unlisted, marker-carrying pathswere never checked while the script still exited 0.
find dist \( -type f -o -type l \) -print0into amktemplisting, whose
findexit status is still checked separately (the statuscannot be read off a pipeline), and
xargs -0hands the paths back to thisscript as arguments, where the same
is_listedandhas_markerrun onthem. The per-path half is a distinct internal entry point
(
--scan-dist-paths) rather than a second copy of those two functions, sothey cannot drift. The listing is removed by an
EXITtrap.is_listednow answers "not listed" for any path containing a newlinewithout asking
grep. This is load-bearing and was not obvious: NULdelimiting alone did not fix the newline case.
grepreads a patterncontaining a newline as two patterns, so the part before the newline still
matched the listed line and the file was skipped. The manifest is
line-delimited and cannot name such a path at all, so "not listed" is the
only true answer. Proven below — the first fixed run still passed this case.
dist/being a symlink is now its own check with its own message, assertedin
mainbefore anything reads through it. Previously it failed onlybecause GNU
grepexits 2 on a directory (see the pre-fix output below,which literally reports
grep: dist: Is a directory); under agrepthatexits 1 instead, the whole cross-check would have collapsed into a pass.
dist/is searched" now states whatis actually guaranteed — every regular file and every symlink, which is the
whole of what a build emits — and lists the four properties that coverage
rests on as enforced rather than assumed. Other file types are excluded on
purpose: a build emits none of them, and
grepon a fifo would hang ratherthan fail.
Everything #180 hardened is
preserved: the non-zero
findstatus guard, symlinks walked rather thanskipped (dangling and directory-pointing links failing closed through
has_marker's exit-2 path), and the exit-2 treatment inhas_markerandis_listed.Only the
sortstep is gone, and not for portability. It existed fordeterministic reporting order, and that order does not matter to the only
consumer: traversal order decides nothing but which offender is named first
when there are several.
sort -zis a GNU extension, but so arefind -print0and
xargs -0, which this change adopts because the issue mandates them, soportability was never the reason to drop it.
sh -nanddash -nclean;/bin/shisdashon the machine this ran on, sothe battery below executed under
dash. Also run underbashonce as aportability check.
Evidence
Every case below was constructed against a real
make buildtree and run,as a non-root user (uid 1000 — under root
chmod 000does not stopfind).Full battery: 18 cases, 1 control that must pass and 17 that must fail.
Before the fix (
script/verify-buildas it is onnext)Trailing space — passes, which is the bug:
Newline in the name — same, exit 0.
dist/replaced by a symlink — fails, but only incidentally, and the outputnames the reason:
After the fix — the three new cases
NEW 1, a marker-carrying copy at
dist/chrome/src/popup/index.jsplus atrailing space:
NEW 2, a copy whose name is the listed path plus a literal newline (the blank
second line in the message is that newline):
NEW 3,
dist/moved aside and replaced by a symlink to it — now the script'sown check, and
grepis never reached:After the fix — full battery
Case, then exit status. Every failure case still fails; the control still
passes.
Each keeps the diagnostic it had before, e.g. the walk guard:
and the symlink cases still fail closed through
has_marker's exit-2 path(
grep: dist/chrome/dangling.js: No such file or directory,grep: dist/chrome/link-to-dir: Is a directory).The unwalkable-subtree case uses
dist/chrome/src/content, which the manifestdoes not list, so
main's per-bundle loop passes and thefindguard is thething under test.
The battery was run against a freshly rebuilt tree (
make clean, thenmake build) atnext=86cdea5, with no temporary listing file left behindon either the passing or the failing path. It was not re-run after the rebase
onto
next=12acf4d, because that rebase touched onlyTODO.md:script/verify-buildis byte-identical to the version the battery ran against.Verification
make check: green, re-run after rebasing ontonext=12acf4d— 10 testsuites, 252 tests (251 passed, 1 skipped), prettier clean. The earlier body
said "157 tests", which was wrong; this is a real run on the current base.
make cleanthenmake build: green, all 4 bundles verifiedautistmask-build-debug=off.sh -n script/verify-buildanddash -n script/verify-build: clean.Notes
#180; nothing about it is
committed here.
make testisjest, which cannot host a fixture tree whosefilenames contain newlines and 000-mode directories.
fix/issue-223-verify-build-nul-walkrather than theissue-<N>-<slug>form inTODO.md, as instructed when this unit wasassigned.
PASS — 30-case battery re-run independently under
dashandbashas uid 1000 (control 0, every failure case non-zero, no temp file left on any path), plus 12 added attacks (CR/tab/quote/backslash/leading-dash names, newline in a directory component, a name whose first line is exactly a listed manifest entry, 40k-filexargsbatching, missing/unwritableTMPDIR, absentxargs, non-executable$SELF, relative/PATH/symlinked-dir invocation) — no vacuous pass constructible;make checkgreen here (173 tests executed, prettier clean); merges clean intonextwith both TODO entries retained.Two disclosures, neither a defect: the
sortremoval is justified as avoiding a GNU extension whilefind -print0/xargs -0adopted in the same change are equally non-POSIX (the issue mandated them, and nothing reads the listing but the per-path loop, so ordering is genuinely load-free); and the Verification section's "157 tests" does not reproduce — the same commit yields 173 here, so that figure looks stale rather than wrong in kind.22428cd556toc0a9b58e0c