Commit Graph

15 Commits

Author SHA1 Message Date
f15f00f3fe Re-point paths for the ~ reorganisation (home-tidy)
Also bumps the 16 meta/requirements pins the freshness gate flagged
(anthropic 1.5.0, numpy 2.5.3, pytest-randomly 5, flake8-bugbear 26.9.9,
ruff 0.16.7, pyright 1.1.414, selenium 4.49.0, hypothesis 6.168.0, lxml
6.1.3, pipdeptree 4.2.5, the two types-* stubs) and Flutter 3.47.3 in
meta/.fvmrc. One test still expected the pre-move ~/mcp-servers path.

The pre-commit hook fed .zsh files to shfmt and shellcheck, neither of
which parses zsh (SC2148/SC1071), so no zsh file could be committed since
the hook was tightened on 2026-08-22; those now get `zsh -n` instead. The
hook's own shfmt pass also staged its reformat of 8 shell files.

CI_GREEN_SKIP because main is red on exactly the dependency-freshness
finding this commit fixes, which is the documented use of that flag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AwtowrowDXx6ccmWVPkCaF
2026-09-11 20:50:19 +02:00
46c8f065e8 Wire the shell ratchet into pre-commit, and test the coverage tooling
check_shell_coverage.sh has existed for a while and was never in
.pre-commit-config.yaml, so nothing ran it: a new shell library with no
suite beside it could land freely. Wiring it was unaffordable while the
gate measured a jail-run 100% bar (--all took over 600s). After 578c4a6c
dropped that, --all is 0.31s and the per-file mode this hook uses is
milliseconds, so it goes in.

Also fixes a bug 578c4a6c introduced: the failure path still interpolated
${COVERAGE_BAR}, which that commit deleted, so every rejection printed
"COVERAGE_BAR: unbound variable" and advised a 100% line-coverage bar
that no longer exists. The message now describes what is enforced.

Three libs blocked the wiring -- meta/scripts/lib/shell_coverage_{bar,
case_runner,jail_setup}.sh, the code that BUILDS the jail, and so the one
corner of the repo the jail cannot measure itself. They are pure
file-writers, so they get a suite that calls them against a temp $JAIL
and asserts on what was written: 48 assertions, nothing mounted, no
unshare, no kcov.

Several assertions check that an emitted file contains an UNEXPANDED
${BASH_SOURCE} or $1 -- the whole point of the quoted heredocs the jail
relies on. Written as single-quoted literals that is SC2016 on every
line, so the needles compose the dollar from an exported constant
instead. No suppressions were added.

  check_shell_coverage.sh --all:  3 BLOCK  ->  0 BLOCK
  hook, repo-wide:                0.16s
  new suite:                      48 assertions, green

Proved the ratchet still bites: an untested lib dropped into
mtk_root/lib is refused with "needs: .../tests/run_all.sh". Probe removed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJrqKERAo5QUeEHWS5Asgp
2026-08-23 15:51:29 +02:00
578c4a6cdd Drop the 100% shell coverage bar; back to a presence ratchet
bca70b67 (2026-08-22) turned the shell ratchet from "a run_all.sh exists
beside this lib" into a measured 100% line-coverage bar, run through the
kcov+userns jail. It works -- dot_resolver_install.sh really does measure
39/39 -- but the price is one hand-written test file per lib: fixes/lib
took 34 test files for 22 libs, the jail runs serially at 37-90s a pass
(concurrent jails give false failures), and most libs need a production
seam added before they can be driven at all.

That put the remaining 84 exempt libs at several days of work, for a
repo of personal automation scripts. User's call today: not worth it.

python_pkg keeps its 100% branch bar, which is already met -- 2101 tests,
100.00%, zero missing statements or branches. The difference is the
instrument: pytest-cov is fast, reliable and parallel; the shell jail is
none of those.

Also tried and rejected, recorded in the file: a middle option where the
suite must MENTION the lib by name, closing the empty-suite hole the
100% bar was reaching for. It newly BLOCKED 68 libs sitting in
directories that already have suites -- 38 in utils/lib alone -- because
those passed the old presence check and so were never allowlisted, and
seed_allowlist refuses to grow by design. Tightening a ratchet with no
way to grandfather the existing tree turns it into a wall.

The jail is not deleted; it is still the tool for measuring a lib on
purpose, and ci_mirror.sh still runs jailed suites.

  check_shell_coverage.sh --all:  >600s  ->  0.31s
  BLOCK entries:                  3, all pre-existing meta/scripts/lib
                                  coverage-tooling libs
  allowlist:                      unchanged at 84 (--seed correctly
                                  refused to add the 3)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJrqKERAo5QUeEHWS5Asgp
2026-08-23 15:47:04 +02:00
0201d5460a Clear both hosts-guard libs; teach the instrument done <redirect> (88 -> 86)
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
hosts_guard_migrate.sh 81/81 and hosts_guard_rollback.sh 65/65, both
100.00% through run_all.sh at --min 100, and check_shell_coverage.sh
passes with each allowlist line already deleted.

82 assertions over 36 cases in three files sharing one harness: all four
validate_requirements refusals, plugin installation, the mount-collapse
loop in every arm (clears, fails, hits its 20-iteration bound, dry-run),
per-instance migration for both the bind-mounted and the plugin flavour,
and do_migrate's refusal to retire the legacy pacman hooks when nothing
registered -- the arm that exists so a failed migration cannot leave the
files with no unlock hook at all.

INSTRUMENT FIX: rollback measured 97.01% with lines 69 and 108
uncovered, both `done <redirect>` closing a loop. Reproduced minimally:
in a three-iteration loop ending `done >"$1"`, the body reports hits=3
and the `done` line hits=0, because bash attributes the redirected
compound statement to the loop's OPENING line. That is the same defect
_CLOSING_BRACE_OP already handles for `} |`, so _LOOP_END_OP is its
twin. The redirect is required by the pattern: a bare `done` stays in
the denominator, so genuinely uncovered loops are still caught.

Audited as the instrument notes demand -- every newly excluded line
printed with its text -- and it is exactly those two lines across all of
fixes/lib. Acceptance pair unchanged and exact: dot_resolver_install.sh
39/39, rpi_nc_ca.sh 73/73.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 11:50:13 +02:00
0e9629e110 Test thorium_repairs.sh to 100%, lift the gate timeout (89 -> 88)
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
75/75 lines through run_all.sh at --min 100, exit 0, and
check_shell_coverage.sh passes with the allowlist line already deleted
so the pass cannot come from the exemption.

55 assertions over 23 cases: all five profile repairs plus test_thorium,
including every dry-run arm, the partial singleton-lock case, a corrupt
database and the sqlite3-absent path. The lib needed NO production
change -- it already takes every path from $THORIUM_CONFIG_DIR, so
pointing that one variable at the tmpdir confines it completely.

The gate's per-case timeout moves from the jail's 120s default to 600s.
That default is a hang-catcher, not a performance budget, and this suite
has outgrown it: 76s bare at 25 test files, 93s at 27, and kcov roughly
doubles it. The symptom was a lib FAILING the gate while measuring
perfectly -- bt_audio.sh printed "127/127 lines = 100.00%" and then
"warn: case timed out after 120s", which --fail-on-case-error turned
into a non-zero exit. bt_audio.sh and arch_cpu.sh both pass now.

The cost, recorded in the evidence: a genuine hang takes 10 minutes per
pass to surface instead of 2. test_thorium's `read -r -p` is exactly
that shape, so every case here feeds it stdin explicitly.

This also settles the quarantine in b2779059. Re-adding the arch_sysctl
cases still yields "kcov instrumented no lines" for bt_audio at a 600s
timeout with no timeout warning, so runtime was never that file's
problem and the quarantine stands for the reason its header gives.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 11:05:56 +02:00
28f2925c30 Instrument: multi-line array elements are not statements; clear arch_perf_fixes
Fourth flavour of defect (a) found this session, after multi-line quotes,
`} |` and the backslash-continued assignment. bash reports a multi-line array
assignment at its OPENING line and never emits the element lines or the
closing paren, so kcov instrumenting them inflates the denominator:

    +L3 items=('one' 'two')      # opened line 3, closed line 6
                                 # lines 4, 5, 6: never emitted

The rule is therefore symmetric with the existing multi-line-quote case: the
opening line is the statement, everything up to the closing paren is data.
_closes_array() finds that paren by recomputing quote state per candidate, so
an element like "a(b)" does not end the literal early.

Measured effect through fixes/lib/tests/run_all.sh:

  arch_perf_fixes.sh  93.48% -> 100.00%  (43/43)
  bt_audio.sh        100.00% -> 100.00%  (127/127)
  arch_perf_report.sh 100.00% -> 100.00% (59/59)
  arch_perf_probes.sh 100.00% -> 100.00% (93/93)
  ubuntu_perf_more.sh 100.00% -> 100.00% (32/32)
  bt_report.sh       100.00% -> 100.00%  (37/37)
  bt_pairing.sh      100.00% -> 100.00%  (65/65)

One gained, six held, none went DOWN. Acceptance test unchanged:
dot_resolver_install.sh 39/39 and rpi_nc_ca.sh 73/73. arch_perf_fixes.sh
therefore comes off the allowlist in the same commit, confirmed by the real
gate with no exemption.

The fail-open check, since over-detection INFLATES coverage: the old and new
predicates were diffed over every allowlisted lib plus the six cleared ones,
printing each newly excluded line WITH its text. All 91 across 10 libs are
array elements or closing parens -- common.sh's tool tables, packages.sh's
associative arrays, arch_sysctl.sh's sysctl maps, thorium_repairs.sh's four
path lists, ubuntu_perf_fixes.sh's sssd units, and so on. No statement caught.

Tests cover both directions, including three negatives: a single-line
`x=(a b)`, an element containing a parenthesis, and `x=$(echo hi)` which is a
command substitution rather than an array. Module stays at 100% branch
coverage.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 20:13:45 +02:00
d989cd7ebf Coverage instrument: a closing brace plus an operator is not a statement
continuation_lines() excluded the continuation lines of multi-line QUOTED
arguments but nothing else, so a line holding only a group's closing brace
and an operator -- `} |`, `} >>"$REPORT_FILE"` -- stayed in the denominator
while bash attributed no statement to it. That capped four fixes/lib libs
below 100% with no test able to move them.

Why the anchor makes this safe: bash attributes every command INSIDE a group
to its own line, and a pipe's right-hand side to its own line, leaving the
brace-plus-operator line with nothing. Anchoring the pattern on a LEADING
`}` therefore excludes only lines that can hold no statement. A one-liner
`{ echo a; } >file` begins with `{` and is untouched; a bare `}` closing a
function has no operator and is untouched.

Measured effect, all through fixes/lib/tests/run_all.sh:

  bt_audio.sh          96.97% -> 100.00%  (127/127)
  arch_perf_report.sh  98.33% -> 100.00%  (59/59)
  bt_report.sh        100.00% -> 100.00%  (37/37)
  bt_pairing.sh       100.00% -> 100.00%  (65/65)
  bt_adapter.sh        98.55% ->  98.55%  (68/69, capped by a different flavour)

Two gained, three held, none went DOWN -- the handoff's regression signal.
That is not luck: _measure computes covered_set = (executed|traced) &
denominator, so for a lib already at 100% the covered set IS the denominator
and removing a subset cannot lower it. The five gated pacman_hook_stall libs
are safe by the same arithmetic.

Acceptance test from the handoff passes unchanged: dot_resolver_install.sh
39/39 and rpi_nc_ca.sh 73/73, both 100.00%.

The fail-open check that actually matters, since over-detection INFLATES
coverage and could promote an untested lib: the new predicate was run over
every lib in the allowlist plus the cleared ones, printing each newly
excluded line WITH its text. All 17 across 10 libs are genuine
brace-plus-operator lines; no ordinary statement was caught.

Tests cover both directions, including the two negative cases (a bare `}`
and a single-line `{ ...; } >file`), keeping the module at 100% branch
coverage. The quote scanner moved into _quote_after() to stay under the
C901 complexity limit rather than suppressing it.

Deliberately NOT fixed here: the backslash-continued ASSIGNMENT flavour that
caps bt_adapter.sh line 97. bash attributes those to the LAST line of the run
but a backslash-continued plain COMMAND to its FIRST, so that predicate needs
a command-vs-assignment distinction. One line in one lib; not worth the
landmine today.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 18:29:47 +02:00
81191f40e6 Fix both kcov defects: dwm_config 47.95% -> 98.48%, rpi_nc_install 11.36% -> 96.59%
The instrument, not the tests, was the bottleneck. Coverage is now
(PS4-traced lines | kcov hits) & (kcov line set - continuation lines).

The trace deliberately does NOT supply the denominator. A trace reports only
lines that RAN, so using it for both halves would make every subject 100.00%
and arm a repo-wide gate that checks nothing.

Three measured findings, each of which yields a silently wrong trace:

1. kcov's ptrace and bash's xtrace cannot share a process. Under
   SHELLOPTS=xtrace every line kcov recorded as hits="1" comes back hits="0"
   -- reproduced A/B on one subject. Hence two passes, two namespaces.
2. SHELLOPTS is readonly inside bash, so `export SHELLOPTS=xtrace` fails and
   tracing never turns on; and `set -x` does not reach child processes.
3. Decisive: bash under `unshare --map-root-user` runs PRIVILEGED and
   DISCARDS an inherited PS4, falling back to "+ " while still honouring
   xtrace. The trace loses its file:line prefix and every subject reads 0%.
   Fixed by delivering PS4 and `set -x` through a BASH_ENV file, which also
   solves child-process propagation.

A fourth trap, in the report: kcov records filename="dwm_config.sh", a bare
basename. The source path comes from the trace's ${BASH_SOURCE} or a unique
repo search; an ambiguous basename yields no exclusion rather than a guess.

Measured through run_all.sh, nothing down:

  dot_resolver_install.sh  39/39 = 100.00%  (acceptance, unchanged)
  rpi_nc_ca.sh             73/73 = 100.00%  (was 72/73 = 98.63%)
  dwm_config.sh            65/66 =  98.48%  (was 35/73 = 47.95%)
  rpi_nc_install.sh        85/88 =  96.59%  (was 10/88 = 11.36%)

rpi_nc_ca reaching 100% is the load-bearing check: its missing line 141 was
cross-process contagion, and the trace pass has no kcov in it to contaminate.

dwm_config's remaining line 39 is a `done < <(...)`. Left in the denominator
on purpose -- removing lines inflates coverage, the fail-open direction, so a
second exclusion rule needs per-line justification, not a pattern from one lib.

Both new Python modules hold 100% branch coverage (29 tests).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 14:24:25 +02:00
0091608b08 Make the gate read jail_args and refuse a suite that dies partway
Two defects found in review, both of which made checks PASS more easily, so
neither ever went red.

GATE: is_covered() hardcoded its jail flags and never read the jail_args
marker that ci_mirror.sh and shell-tests.yml both consume. The features/lib
suite needs /usr/local/share and /etc/pki and aborts under set -e without
them -- but the coverage number still printed, because kcov had already
recorded whatever the dead run reached. The gate would have graded 44 libs on
a suite that never finished. It now reads the marker, keeps the historical
defaults for suites without one, fails closed on an empty marker, and passes
--fail-on-case-error.

Negative control: stripping the binds makes the gate exit 1 and restoring
them returns exit 0. Before this commit both cases exited 0 -- the gate could
not fail on an aborting suite.

HASH CACHE: the _t_unstub helper written earlier today for the transcribe
harness was needed in features_harness.sh too. Two assertions were vacuous --
test_dwm_config's "verify still reports when dwm is absent", and
test_rpi_nc_ca's uname removal, where a cached uname left every later branch
running under a faked Darwin. All four `rm -f "$TEST_TMPDIR/bin/` sites in
the repo were audited; the other two are sound (one probes with [[ -f ]], the
other runs in a subshell).

No measured figure moved: dot_resolver_install.sh still 39/39 = 100.00% with
real absence enforced, so its off-allowlist clearance holds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016sxxsDHWgdRhmdmzNMA6XM
2026-08-22 13:36:54 +02:00
170f843481 Make the jailed lib/tests gate able to fail, without breaking measurement
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
The jail recorded each case's exit code but reported only timeouts,
discarding every other non-zero status. A suite with a failing assertion
exited 0 through the push gate -- the same blind spot that let an earlier
session ship 2 failing assertions in the nc_php suite. Found by negative
control, not by reading: a deliberately failing assertion still exited 0.

Propagating that status unconditionally was wrong, and caught before landing:
the jail's own usage example passes a `bogus` case that exits non-zero to
cover an error path, so a clean 5/5 = 100.00% run started failing. That is
the mode the 105-lib campaign runs in.

So the behaviour sits behind --fail-on-case-error, default off, passed only
by ci_mirror.sh and shell-tests.yml. Verified in all four quadrants:
measurement + error case = 0, gate + error case = 1, gate + clean suite = 0,
gate + broken assertion = 1.

Both gates also pass --min 0: with no --measure the jail was measuring
run_all.sh itself against its default --min 100 and failing the push at
54.55%, a coverage verdict neither gate asked for. ci_mirror.sh now prints
the jail's output on failure, so a red gate can be diagnosed.

The added flag pushed the jail over the 250-line cap, so the in-namespace
case runner moved to lib/shell_coverage_case_runner.sh, following the
existing lib/shell_coverage_jail_setup.sh split. All four quadrants were
re-run after the extraction.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016sxxsDHWgdRhmdmzNMA6XM
2026-08-22 12:16:18 +02:00
61ff5eacac Cover nc_php.sh (98.82%) and teach the jail to measure foreign platforms
nc_php.sh was the next-hardest library on the allowlist. It targets a
Debian Raspberry Pi -- it edits /etc/php/<ver>/apache2/php.ini, drives
occ as www-data and installs a crontab -- so none of its write targets
exist on this Arch host and the jail had nothing to mirror.

- --seed-dir / --seed-file create the paths a foreign-platform subject
  expects. Verified with a probe: /etc/php/8.2/apache2 exists inside
  the jail while /etc/php does not exist outside it.
- A failed bind is now FATAL, and missing mountpoints are created
  first. A bind that fails silently is indistinguishable from
  unreachable code in the report, which is the misdiagnosis this
  runner exists to prevent.
- Mirroring skips unreadable targets (/root as a normal user) rather
  than aborting under set -e.

nc_php.sh: 84/85 = 98.82%, 24 assertions. It STAYS on the allowlist,
because 98.82% is below the bar. Line 111 -- the crontab first-run
fallback -- hung the suite on both attempts to cover it, so it is left
uncovered and documented rather than tried a third time.

Two harness hazards found the hard way and written down: a top-level
stub function shadows the subject's own function for every later
assertion (this silently dropped coverage 83.53% -> 57.65% while the
tests still passed), and a stub for a piped-into command must drain
stdin or the writer takes SIGPIPE.

dot_resolver_install.sh still measures 39/39 = 100.00%.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbD4nco3EgL1UFoZKmKsLE
2026-08-22 11:30:32 +02:00
bca70b67b5 Shell coverage: convert the ratchet to a measured 100% bar
The gate was a presence check -- 'a run_all.sh exists beside this lib' --
which an empty suite that asserts nothing satisfies. It now measures.

100% is reachable with no suppressions and no production source changes:
run the suite inside a user+mount namespace and the sudo-writes, systemd
units and nftables rules execute for real against a throwaway /etc.

- meta/scripts/shell_coverage_jail.sh: the runner, promoted out of a
  session scratchpad. Two fixes it needed on the way: bind targets mirror
  the real directory tree (an empty bind makes writes fail with ENOENT,
  which reads as unreachable code), and sudo gets a pass-through stub
  because real sudo cannot setresuid inside a userns.
- It fingerprints git status before and after and fails the run if a
  write escapes the jail. Verified with a deliberate escapee.
- dot_resolver_install.sh: 39/39 = 100.00%, 28 assertions. Picked by
  scoring every allowlisted lib on root ops, system writes and
  destructive calls -- it scored highest in the repo. Allowlist 106 -> 105.
- jscpd was already over its own threshold at 2.45% and would have
  rejected every commit staging a .sh file. Excluding vendored .venv/
  shell alone gives 1.67%; also excluding lib/tests/ gives 1.47%.

The gate is deliberately NOT wired into the hook chain yet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbD4nco3EgL1UFoZKmKsLE
2026-08-22 11:20:58 +02:00
19e652dc30 Close three fail-open paths in the prefix trace harness
All three were found by review and reproduced before fixing. Each let a run
report success while proving nothing -- the shape this harness exists to
prevent.

A reused prefix defeated dropped-write detection entirely. Run 1's files were
still there when run 2's manifest was taken, so the manifest reported writes
that never happened and the fixture pair diffed CLEAN. The doc's own examples
demonstrated that workflow. A non-empty prefix is now refused -- not deleted,
since the path is user-supplied -- and the examples use distinct prefixes.

A missing bwrap was swallowed. trace_prefix_bwrap_argv returned 1, but the
caller reads it through `mapfile < <(...)` and a process substitution's status
is discarded, so runner ended up empty and the run continued with no sandbox
and no uid 0. Same swallow as the grep-status one fixed earlier, one layer up.
bwrap is now probed in validate_requirements, before anything runs.

The absolute-path scan only fired when no --bind-abs was given at all, so
binding one path and missing three produced no warning. It now runs whenever
--prefix is given and reports only what is not already covered. Fixing that
surfaced a second defect: --bind-abs on a FILE aborted with "Can't mkdir
/etc/profile: Not a directory", and binding its parent reintroduced the
wholesale /etc shadowing this change had already measured as harmful ($SUDO_USER
collapsing to /home//, the real /etc/profile unreadable). Files are now bound
file-over-file, seeded with a copy so a script that greps before appending sees
real content.

Also measured rather than argued: the XDG redirect. A new fixture run with a
real XDG_DATA_HOME pointing at a decoy shows all four writes landing in the
prefix and the decoy empty -- the rsync --delete data-loss path, proven closed
without running install_leechblock.sh. And no-prefix output was diffed against
cdf2a55 from a detached worktree: byte-identical, so existing baselines hold.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 23:38:38 +02:00
f6356d4725 Trace scripts that place files, via a prefix redirect
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Shell redirections are builtins, so no PATH stub can intercept
`cat > /etc/modprobe.d/x.conf`. That left installer scripts unverifiable:
stub cp/chmod/ln and the trace is nearly empty, don't and the run rewrites
the live system.

--prefix redirects HOME and every XDG_* base into a temp tree. XDG_DATA_HOME
is exported explicitly, not left to default off HOME: install_leechblock.sh
reads it directly and then runs `rsync -a --delete` at it.

--bind-abs binds hardcoded absolutes via bwrap --unshare-user --uid 0, leaf
directories only. uid 0 is load-bearing beyond permissions: common.sh's
require_root does `exec sudo "$0"`, and under the sudo stub that truncated
the whole run to one recorded call -- a trace that diffs clean against any
split. Binding /etc wholesale was measured to shadow /etc/passwd, collapse
$SUDO_USER to /home//, and mask a nested bind, so the scanner never emits it.

The trace gains a manifest of every file written, with content hashes. That
section is the point: with it, a dropped write in the fixture pair is a
one-line diff; with it stripped, the two traces are identical, i.e. the old
harness passed that broken split. Hashes normalize the prefix to @PREFIX@
because install_usage_monitoring.sh writes an unquoted heredoc that bakes
$HOME into the artifact.

Verified: ~/.config/systemd/user, ~/.local/bin, ~/.local/share and
/etc/modprobe.d byte-identical before and after; usage-report units still 2.
The three prior regressions are now committed fixtures rather than ad-hoc --
one was silently neutered mid-change when a trailing echo added for SC2034
became the function's last statement and turned exit 1 into exit 0.

Stub generation moved to lib/ because --prefix pushed the harness to 252
lines, over the cap it exists to serve.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 23:31:49 +02:00
5b6973e11d Split disk_cleanup_check.sh: extract the helpers and the output blocks
329 -> 230 + 124. The colour constants, human_readable, dir_size_kb,
report, confirm and try_clean move to lib/disk_cleanup_common.sh, along
with the report-only loop and the summary block as print_report_only and
print_summary.

The seam passes state -- report accumulates into TOTAL_RECLAIMABLE and
confirm reads CLEAN -- so both are set before the source line, and the
verification is a trace diff rather than a static check: identical exit
status, identical ordered du calls, identical stdout and stderr against a
detached worktree at the pre-split commit.

Also ran it for real from /tmp (not its own directory) to confirm
SCRIPT_DIR resolves the lib from any cwd: 303.7 GB reported reclaimable,
nothing deleted. The one shellcheck finding (SC2034 on RED) was fixed by
moving the function that uses it, not suppressed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 22:57:06 +02:00