Commit Graph

1150 Commits

Author SHA1 Message Date
4cfc3fdde1 Test bt_report.sh to 100%: allowlist 98 -> 97
First of the 16 untested fixes/lib libs. Measured 37/37 = 100.00% through
lib/tests/run_all.sh, twice, the second run at --min 100 (exit 0), then
removed from the allowlist and confirmed with the real gate and no
exemption.

Adds two reusable pieces rather than a per-family harness clone:

- lib_test_core.sh -- PASS/FAIL tally, _t_eq/_t_contains/_t_lacks, a
  mktemp -d sandbox wiped on EXIT, a FAKE_BIN stub dir on PATH, and
  _t_stub/_t_unstub. _t_unstub runs `hash -r`: deleting a stub file alone
  does not drop bash's cached executable path, so the real binary would
  never be reached again.
- bt_harness.sh -- sources the core, sources the real scripts/lib/common.sh
  for the production log_*/has_cmd helpers (verified inert at source time),
  and redefines _btctl and apply_fix verbatim from fix_bluetooth.sh, since
  those live in the entry script rather than in any lib. Same trade-off
  pacman_hook_stall_harness.sh already makes with log_size.

pacman_hook_stall_harness.sh is deliberately left alone: its six libs are
already off the allowlist, i.e. gated at 100% with no exemption, so
refactoring it onto the new core would risk banked work for no gain. jscpd
reads 1.6% against the hook's own flags, and **/lib/tests/** is excluded
from that scan anyway, so the duplication budget is not at stake.

_t_only_path exists because every tool these libs call (usbreset, pactl,
lsusb, ...) genuinely exists on an Arch desktop: a prepended stub dir
cannot exercise a "not installed" branch, so those cases drop PATH to
FAKE_BIN alone.

The three coverage runs were made in a detached worktree: a second session
was writing to this repo concurrently, and the jail aborts on any
working-tree delta during a run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 16:33:33 +02:00
98ca3ac56a Whitelist com.kuhy.lyricanki in focus mode
Focus Owner hides every package absent from the allowlist, so a newly
installed lyricanki was installed=true but hidden=true: absent from the
launcher and from `pm list packages`, and `am start` reported the activity
did not exist even though dumpsys listed its MAIN/LAUNCHER filter. Nothing
about the app itself was wrong.

policy.json is regenerated from config.sh by python_pkg.focus_policy, never
hand-edited.

Also drops com.android.launcher3 and com.android.camera2 from
SYSTEM_NEVER_DISABLE. That list is prefix-matched and both are strict
supersets of entries already in it, so coverage is byte-identical; this
keeps config.sh under the 250-line cap without weakening the policy.
com.android.settings stays: its only shorter match is com.android.se, an
accidental substring rather than intent.

Verified on the phone: signer matched so the Device Owner upgraded in
place, dpm still reports DeviceOwner, lyricanki flipped to hidden=false,
and workout_app + the dialer are untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RAEChhK3sWGxd6ucsUQmcT
2026-08-22 16:26:52 +02:00
503ba1fcca Handoff: the 16 untested fixes/lib libs, ordered smallest-first
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
Retargets the handoff from 're-measure the allowlist' (done) to the work that
is actually left: writing test suites for the 16 libs in fixes/lib that have
none, to 100% line coverage.

Includes the size-ordered table, the family groupings (bluetooth four, arch
perf six, hosts guard two) so shared stubs get reused, the two entry scripts
that source them, and what they actually call -- systemctl, bluetoothctl,
pacman, sysctl, nvidia-*, apt-get.

Says explicitly NOT to build a new harness: fixes/lib/tests already has a
205-line lib harness, a 142-line entry harness and eight test files to copy
from, and jscpd fails the commit above 2% duplication.

Records the three-step bar the five cleared libs met -- measure 100.00%,
re-measure at --min 100, then run the real gate with no exemption -- so the
next session does not have to re-derive why one reading is not enough.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 16:11:13 +02:00
105d869776 Handoff: record the five cleared libs and the bar used to clear them
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
Allowlist 103 -> 98. Each of the five was measured twice, the second run
gating at --min 100, then confirmed through the real check_shell_coverage.sh
with no exemption -- all exit 0.

States the reusable bar explicitly (two matching runs, the second at
--min 100, then the real gate) so the next session does not have to re-derive
why one reading is not enough.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 15:06:43 +02:00
83de41f470 Clear five pacman_hook_stall libs: allowlist 103 -> 98
Each measured 100.00% through fixes/lib/tests/run_all.sh, twice, with the
second run gating at --min 100 so a shortfall would exit non-zero rather than
print a number I might misread:

  pacman_hook_stall_capture.sh   59/59
  pacman_hook_stall_load.sh      25/25
  pacman_hook_stall_summary.sh   18/18
  pacman_hook_stall_watch.sh     24/24
  pacman_hook_stall_usage.sh       1/1

Two matching runs was the bar because this directory's suites drive
watch_forever timing loops, and a timing-dependent suite can vary its line
set between runs -- dwm_config.sh was observed doing exactly that. These five
did not vary.

pacman_hook_stall_setup.sh stays: 26/27 = 96.30%, uncovered line 7.

usage.sh's denominator of 1 is honest rather than broken. The lib is a single
usage() whose body is one `cat <<EOF` heredoc plus `exit 0`, and heredoc
content is data, not statements. Its tests assert on the rendered text, so
the behaviour is exercised even though only one line is instrumentable.

summary.sh has one off-set line (36). That cannot inflate the figure -- an
off-set line is by construction one the trace SAW EXECUTE -- but it does mean
the lib is measured over a subset of its statements.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 14:57:01 +02:00
f65d247c23 Record the fixes/lib measurements and two traps that each cost a run
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Python tests / test (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Adds the measured table so the next session starts from fourteen known
numbers instead of re-deriving them. Seven libs are at 100.00% and are
allowlist-removal candidates; they are deliberately NOT removed yet, because
fixes/lib's suites drive watch_forever timing loops and a timing-dependent
suite can vary its line set run to run (dwm_config.sh was seen to). Reproduce
each at --min 100 twice before editing the allowlist.

Also settles a question the run_all.sh commit raised: allowlist entries are
static per-file lines, so adding fixes/lib/tests/run_all.sh did NOT start
failing the 16 untested libs there. check_shell_coverage.sh on one of them
still exits 0. The allowlist header's "every file in that directory is
enforced" describes intent, not current mechanics.

Two traps recorded, both hit this session:

- Editing ANY file while a measurement runs aborts it with "a write escaped
  the jail". The jail fingerprints git status before each case, so an
  unrelated doc edit trips it. False alarm; cost two runs, the second time
  after I had already diagnosed the first.
- A sweep's output file buffers, so pgrep is the liveness signal, not file
  growth. A measurement silent for minutes is usually still running.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 14:48:06 +02:00
347222b921 Add fixes/lib run_all.sh; the previous handoff was wrong about that directory
The handoff recorded fixes/lib as having "no harness at all". It already had
8 test files and 2 harnesses. What was missing is run_all.sh -- the subject
is_covered() actually builds, and the only thing the gate measures through.
All 8 test files pass through it.

No jail_args beside it, deliberately: the harness confines every write to its
own `mktemp -d` and intercepts external commands with a PATH stub dir, and
grep finds no absolute write to /etc, /var or /usr in any of the tests. So it
runs bare, like the transcribe suite.

First measurement through it: pacman_hook_stall_usage.sh is 1/1 = 100.00%.
That denominator is honest, not broken -- the lib is a single usage() whose
body is one `cat <<EOF` heredoc plus `exit 0`, and heredoc content is data
rather than statements.

16 of the 22 libs there still have no tests at all. That is now the largest
block of real test-writing work left in the campaign.

Also rewrites NEXT_SESSION_PROMPT.md, whose central claim ("the instrument is
the bottleneck, not the tests") stopped being true this session. The new one
leads with re-measuring the 103 allowlisted libs on the fixed instrument
before writing any new test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 14:40:31 +02:00
d58910d916 Apply prettier formatting to docs/kcov-under-report.md
The pre-push prettier hook rejected the table and list formatting introduced
by the two preceding commits. Formatting only -- no content, number or claim
is changed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 14:35:35 +02:00
11aea86920 Land the token_audit standing-cost checkup (116 tests, 100% branch coverage)
Answers "what is billed on every turn regardless of use" by measurement
instead of recall, which is the part earlier audits kept getting wrong.

A surface is reported dead only when unused in BOTH windows. One earlier
audit's seven-day dead list included six servers that had been used within
thirty days, so a single window is not evidence. Shares are weighted with
cache-read weight 0.1, so a cached surface is not billed as though it were
re-read in full each turn.

Verified by running it, not just by tests: the report renders over 44,311
turns, putting rules/ at 4,907 tokens (1.77%), MEMORY.md at 4,312 (1.55%),
memories/ at 3,758 (1.35%), skill descriptions at 3,279 (1.18%) and CLAUDE.md
at 2,057 (0.74%), with per-MCP 7d/30d call counts behind each verdict.

The files were found untracked in the tree from an earlier session; every
module is already under the 250-line cap, so nothing needed splitting, and
the package was already at 100% branch coverage, so nothing needed new tests.
This lands the instrument only -- no surface is unregistered on its say-so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 14:33:03 +02:00
29b8c5a788 Name the lines outside kcov's line set; bound defect (c) as fail-closed
The report printed a COUNT of traced lines that kcov never listed as
instrumentable. A count cannot be acted on: deciding whether such a line is a
real statement or a function declaration needs the line number. It now lists
them, the way it already lists uncovered lines.

Reading them across every lib with a harness splits them cleanly:

  benign  dwm_config.sh:27 (`heal_config() {`), transcribe_deps.sh:9
          (one-line function declaration), clean_audio_filters.sh:197,200
          (`((running++))`) -- correctly not statements
  real    aw_autostart.sh 68,70,73,79-81,83-84,87-88,94 -- plain echo, local,
          if, `sudo -u ... mkdir`, `cat >` heredoc. kcov lists nothing after
          the `{ ... } >>"$file"` command group at lines 61-65.

So kcov's LINE SET is incomplete too, not just its hit counts. This does NOT
inflate anything: a line reaches that list only because the TRACE SAW IT RUN,
so folding all 11 into both halves moves aw_autostart from 80/82 = 97.56% to
91/93 = 97.85% -- up. An off-set line is by construction an executed line, so
it can only ever be a missing COVERED line, never a missing uncovered one.

What it does mean is that such a subject is measured over a subset of its
statements. The percentage is honest about the subset, not the whole file,
which is why the lines are now printed rather than tallied.

Separately: nc_php.sh reports "kcov instrumented no lines" on the PRE-FIX
instrument too -- checked at 078d3463 in a throwaway worktree -- so it is not
a regression from the two-pass change. The 84/85 = 98.82% in earlier notes
does not reproduce and is recorded as unverified.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 14:30:39 +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
078d3463bd Prove kcov's under-report is TWO defects, one of them a numerator bug
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
Settles a question the campaign has worked around for three sessions:
rpi_nc_install.sh's 11.36% is an instrument failure, not a testing gap.

Measured against an independent instrument. Driving the lib under
PS4='+PS4:${BASH_SOURCE##*/}:${LINENO} ' with set -x traces 55 distinct lines
as executed, and 45 of them are lines kcov reports as UNCOVERED -- including
61, 62, 63, 66, 68, 102, 118 and 146. The lines run; kcov does not record
them.

That splits the problem in two, and they need different fixes:

  (a) WRONG DENOMINATOR -- continuation lines of a multi-line quoted argument
      counted as statements, which bash -x never calls executable at all.
      Fixable in shell_coverage_report.py. Holds dwm_config.sh down.
  (b) WRONG NUMERATOR -- ordinary statements executing unrecorded. Caps
      rpi_nc_install.sh, and no test can move it.

The distinction is the point. A denominator filter is the obvious fix and I
nearly built one last session; it would have done nothing for (b).

Also confirmed no test can raise rpi_nc_install.sh: the file is one 227-line
function called twice, an `exit 88` sentinel proves the suite runs to
completion, and its assertions on save_config (line 219) and the password
message (line 227) PASS while kcov calls both lines uncovered.

kcov 43 is the current release, so upgrading is not an available fix.

The kcov material moved to docs/kcov-under-report.md (the 250-line cap), and
NEXT_SESSION_PROMPT.md now leads with the instrument work rather than the
next lib -- 201 in-scope libs, 98 passing, 103 exempt, and the instrument is
the bottleneck.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016sxxsDHWgdRhmdmzNMA6XM
2026-08-22 13:57:33 +02:00
bcb3e59fa7 Refresh the handoff: allowlist 105 -> 103, gate is ready, kcov bounded
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
Records what changed this session so the next one does not re-derive it:

- The allowlist dropped to 103. unity_handler.sh and transcribe_venv.sh both
  measure 100% and are off it, verified through check_shell_coverage.sh with
  the exemption deleted rather than by the measurement alone.
- The gate itself is now ready to arm: is_covered() reads jail_args and
  passes --fail-on-case-error. Only the allowlist remains.
- Two directories have harnesses where one had none.
- The kcov problem is bounded, not solved: one artifact CONFIRMED, six
  hypotheses DISPROVEN and listed so nobody retries them.

The trap list is rewritten around what actually cost time: measuring through
run_all.sh rather than a single file, --fail-on-case-error, the exit-sentinel
technique, and the two ways a stub silently fails to hide a tool.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016sxxsDHWgdRhmdmzNMA6XM
2026-08-22 13:47:13 +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
d277369aca Cover clean_audio_filters.sh (89.25%), completing testsAndMisc-bash/lib
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
A 38-assertion suite for the last lib in the directory without one. Every
one of its five libs now has a suite; at the start of the session it had
none. 118 assertions across the directory, 0 failures.

build_filters is asserted on the EXACT filter string it emits, because that
string is the lib's entire product. An arnndn chain missing its mandatory
48 kHz resample, or a preset silently falling through to no dynamics, yields
worse audio and never an error -- nothing else would catch it.

Every exit code is pinned: 1 for a missing command, 11 for "RNNoise required
but unavailable" (refusing to quietly fall back to afftdn), 8 for an ffmpeg
whose afftdn lacks md=.

Measured 83/93 = 89.25%; it stays on the allowlist. All ten uncovered lines
are the documented under-report, not gaps: an `exit 66` sentinel proves
require_cmd's lines 37-38 execute, 52-58 are a multi-line array literal (the
confirmed continuation-line artifact), and 129 is an empty `*) ;;` arm whose
branch has a passing assertion. The gate uses the measured figure, so the
exemption stands regardless.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016sxxsDHWgdRhmdmzNMA6XM
2026-08-22 13:27:44 +02:00
154b416617 Clear transcribe_venv.sh: 100%, off the allowlist, 104 -> 103
A 29-assertion suite covering all six functions. transcribe_venv.sh measures
53/53 = 100.00% through the directory runner, and check_shell_coverage.sh
passes it with the exemption deleted.

Assertions are on recorded ARGV because which index a pip install points at
IS the behaviour: the OpenNMT cu12 wheel index on the NVIDIA path, the
CPU-only torch index for diarization (forced to avoid mismatched CUDA
builds). "pip was called" cannot see either.

generate_test_audio's four-deep fallback chain is covered by failing each
generator in turn. Every stub materialises the wav, because the chain
advances on `[[ ! -s $tmpwav ]]` -- a record-only stub would silently
exercise the branch after the one under test.

One assertion was wrong and got corrected rather than forced: I claimed a
wholly-failing pip still returns 0, but the final `python "$PY_HELPERS"
deps-installed` carries no `|| log`, so it does not. The suite now asserts
what the subject actually guarantees -- all four warnings emitted, no earlier
failure aborting the run.

SC2016 fixed at the source with a quoted heredoc, not suppressed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016sxxsDHWgdRhmdmzNMA6XM
2026-08-22 13:24:28 +02:00
b91be3099d Clear unity_handler.sh: 100%, off the allowlist, 105 -> 104
The first allowlist removal of the campaign this session. unity_handler.sh
measures 32/32 = 100.00% through its directory's runner, and
check_shell_coverage.sh passes it with the exemption DELETED -- the real
gate, not just the measurement.

HARNESS BUG found getting there: bash caches executable locations in a hash
table, so `command -v foo` keeps succeeding after foo is removed from the
stub dir. Every "tool is missing" test was therefore running as a "tool is
present" test, asserting the opposite of its own name while looking
plausible. Reproduced minimally: after rm, STILL FOUND; after `hash -r`,
gone.

A _t_unstub helper now removes the stub and clears the table. The two suites
committed an hour ago had the same defect and are corrected here --
test_transcribe_pkgmgr.sh goes from 15 to 18 passing assertions, so three of
its checks had been vacuous. This is exactly the failure the campaign exists
to catch, found in its own test code.

`basename` joins the restricted PATH because the SUBJECT calls it; the suite
avoids needing `tail` via a parameter expansion instead, since every binary
in that PATH is one more thing a stub's shebang can trip over.

transcribe_deps.sh (92.31%) and transcribe_pkgmgr.sh (98.00%) re-measured
unchanged and stay on the allowlist.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016sxxsDHWgdRhmdmzNMA6XM
2026-08-22 13:20:54 +02:00
ac1cf57656 Give testsAndMisc-bash/lib a harness; cover transcribe_deps and _pkgmgr
The directory held five allowlisted libs and no tests. It now has a harness,
a runner, and suites for two libs: transcribe_deps.sh (the fourth
hardest-first target, 48/52 = 92.31%) and transcribe_pkgmgr.sh (49/50 =
98.00%). Both stay on the allowlist. 15 + 18 assertions, 0 failures.

No jail is needed here and no jail_args marker is created: every branch turns
on `command -v <manager>` and every install goes through sudo, both
intercepted by a PATH stub dir. Verified the runner exits 0 when invoked
bare, which is how both gates call it.

The harness REPLACES PATH rather than prepending. This host really has
pacman, and a prepended stub dir cannot HIDE a binary, so every "manager not
installed" case would have asserted nothing. `bash` and `env` are in the
private coreutils dir for a non-obvious reason: every stub's
`#!/usr/bin/env bash` shebang resolves through that restricted PATH, and
without them each stub died with "env: 'bash': No such file or directory"
while looking exactly like a coverage gap.

Assertions are on recorded ARGV rather than on the call happening, because
which packages a branch installs is what differs between distros --
pacman gets python-virtualenv where apt gets python3-venv.

Three assertions aimed at the libsndfile branch were REMOVED rather than left
green-but-vacuous: /usr/lib/libsndfile.so exists on this host, so
need_libsndfile is legitimately 0 and lines 19/52/53/55 are unreachable
without a jail that hides the library. The limitation is written into the
suite instead of asserted around.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016sxxsDHWgdRhmdmzNMA6XM
2026-08-22 13:14:02 +02:00
b830734cad Correct the cause of rpi_nc_ca.sh's missing line: it is cross-process
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 previous commit attributed rpi_nc_ca.sh's 72/73 to a heredoc BODY being
counted in the denominator. That was wrong. Line 141 is `cat <<'EOF'` -- the
heredoc's OPENING line, an ordinary statement. The body starts at 142.

The real cause is that the kcov under-report is contagious across processes
sharing one jail. Bisected over the five sibling suites: run
test_dwm_config.sh before test_rpi_nc_ca.sh and line 141 is lost; run
test_aw_autostart.sh or test_dot_resolver_install.sh first and it is kept.
Alone the CA suite reports 73/73 = 100.00%; through run_all.sh it reports
72/73 = 98.63%. Both figures reproduce twice, so this is deterministic
contamination rather than flakiness.

Nothing is untested: with dwm_config running first, the CA suite still exits
0 with every assertion passing, show_help included. The line executes and is
not traced.

A heredoc-body detector was prototyped and deleted once it showed line 141 is
not a body line -- a fix aimed at the wrong defect.

The operational rule this implies is now written down: is_covered() measures
through run_all.sh, so the runner's number is the lib's coverage, and a
suite's own single-file figure must never be reported in its place.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016sxxsDHWgdRhmdmzNMA6XM
2026-08-22 13:05:52 +02:00
92678c9506 Cover rpi_nc_ca.sh (98.63%) and bind the two paths its suite needed
A 26-assertion suite for rpi_nc_ca.sh, third in the hardest-first order. All
five OS branches are exercised by seeding the marker file each one tests for
inside the jailed /etc, plus a uname stub for Darwin; both NSS stores and all
three Firefox outcomes are covered.

SHARED-FILE BUG: the Debian branch does `mkdir -p
/usr/local/share/ca-certificates` and jail_args bound only /usr/local/bin, so
the suite aborted there under set -e. /usr/local/share and /etc/pki are now
bound. jail_args is shared by every suite in the directory, so all five libs
were re-measured: dot_resolver_install.sh still 39/39 = 100.00%, and
rpi_nc_install.sh, dwm_config.sh and aw_autostart.sh are unchanged. An
over-aggressive mount looks exactly like unreachable code, so this check is
not optional.

That bind also unblocked the runner for the whole directory: with the old
args the run exited 1 before nc_php.sh could be measured at all.

Measured 72/73 = 98.63% through run_all.sh, which is the exact subject
is_covered() builds -- the gate's own view -- so the lib STAYS on the
allowlist. The single test file alone reports 73/73 = 100.00%, both figures
reproduced twice. The one-line delta is line 141, the first line of
show_help's heredoc BODY: not a statement, so the denominator is wrong rather
than a test missing. Excluding heredoc bodies from the denominator is the
next piece of work; it is what stands between this suite and the bar.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016sxxsDHWgdRhmdmzNMA6XM
2026-08-22 12:59:51 +02:00
26afe4390c Cover aw_autostart.sh (82.93%), executing the script it generates
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
A 25-assertion suite for aw_autostart.sh, second in the hardest-first order.

The generated i3blocks status script is asserted by RUNNING it against
stubbed pacman/pgrep across all four of its branches -- uninstalled,
installed-but-stopped, running, installed-via-package -- rather than by
grepping the heredoc's text. A heredoc that emits syntactically broken bash
passes a text check and fails in front of the user.

One assertion was silently testing nothing at first: aw-qt is genuinely
installed at /usr/bin/aw-qt on this host, so the "not installed" case found
the real binary through the PREPENDED stub dir. PATH is now REPLACED for that
one case. The suite went 24-passed-1-failed -> 25-passed-0-failed, and the
gate caught it rather than the number.

Measured 68/82 = 82.93%; it STAYS on the allowlist. The residue is the
`[[ $EUID -eq 0 ]]` false branches, which are unreachable inside the jail
because the process is uid 0 there, plus the documented kcov under-report --
an `exit 44` sentinel proves create_i3blocks_status runs to completion while
its lines 145-162 report zero hits. Third lib to show it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016sxxsDHWgdRhmdmzNMA6XM
2026-08-22 12:49:51 +02:00
768e73249d Cover dwm_config.sh and fix its unreachable upstream-drift warning
A 27-assertion suite for dwm_config.sh, the highest-scored remaining
allowlisted lib. It drives the lib for real inside the jail: rewriting dwm.c
with perl, appending to config.h, compiling with cc and installing into
/usr/local/bin. The generated content is asserted, not just the call.

LATENT BUG, found by writing the test: apply_fullscreen_confine_hook does
`on=$(grep -c 'pconfine-auto on' "$src")`, and grep -c exits 1 when the count
is zero. The caller runs `set -euo pipefail`, so the function aborted at that
line exactly when the count was zero -- which is precisely when the
"setfullscreen/unmanage were likely refactored upstream" warning three lines
below was the thing needed. The warn branch was dead code in the only
situation it exists for. Both call sites now use `|| true`, and the suite
asserts the warning fires.

Measured: dwm_config.sh 35/73 = 47.95%. It STAYS on the allowlist -- the bar
is 100% and it is measured, not asserted.

That number is an under-report. An `exit 43` sentinel proves build_install,
build_pointer_confine, write_session_files and verify all execute inside the
jail while kcov reports their bodies at zero hits. One kcov artifact is now
CONFIRMED against a minimal repro: continuation lines inside a multi-line
quoted argument (the `perl -0777 -pe '...'` blocks) are counted as
statements that never run, which is the wrong denominator. A sixth
hypothesis -- that the exec-ing sudo shim or a pipeline loses the trace --
was tested and disproven. The two-strike time-box on this hunt is spent;
docs/shell-split-verification.md records what is known, including the
exit-sentinel technique for telling a tracing failure from an aborted suite.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016sxxsDHWgdRhmdmzNMA6XM
2026-08-22 12:44:12 +02:00
6ebb3462ab CI: skip the kcov-jail suites instead of failing to install kcov
The previous commit added kcov to the shell-tests apt step on the assumption
it was packaged for Ubuntu. It is not -- CI went red in 34s on "E: Unable to
locate package kcov" -- and upstream ships no prebuilt binary either, so it
would have to be built from source on every run.

CI now skips jail_args-marked suites with a printed reason, the same trade
the arch-tests job already makes for Arch-only tests: declare what this
runner cannot do rather than pretend to check it. Enforcement is not lost --
ci_mirror.sh runs those suites through the real jail on every push, and that
gate is already proven to fail on a broken assertion. An empty jail_args is
still a hard failure.

Simulated over all six discovered runners: 5 PASS, 1 SKIP, none dropped.
Committed with CI_GREEN_SKIP=1, the ci-baseline-green hook's documented
escape hatch for a commit that IS the fix for the red baseline.

Also records the kcov instrument defect in docs/shell-split-verification.md.
rpi_nc_install.sh measures 11.36% while its 29 assertions pass and its writes
land on disk. Five hypotheses are now disproven against minimal
reproductions -- the three from the previous session, plus `cd`-relocation
and a heredoc piped into a stubbed external. The cause is still unknown, so
the rule is documented instead: a suspicious percentage means re-measure, not
re-write the test. The failure only ever under-reports, so it can keep a lib
on the allowlist but can never promote an untested one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016sxxsDHWgdRhmdmzNMA6XM
2026-08-22 12:31:36 +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
08b58794e2 Teach both lib/tests gates to run jail-requiring suites
ci_mirror.sh and shell-tests.yml discovered every */lib/tests/run_all.sh and
invoked it bare. The features/lib suite refuses to run bare -- that guard is
what stops it rewriting the real /etc -- so the pre-push gate blocked all six
pending commits, and CI would have gone red on the same suite the moment they
landed.

A suite now DECLARES its jail requirement with a jail_args file beside its
run_all.sh, holding the --bind/--seed-* flags it needs. Both consumers branch
on the marker. The 105 still-allowlisted libs were scored on root ops and
system writes, so most future suites will need the jail too; naming the path
in two runners would drift, which is the failure ci_mirror.sh's own comments
say it exists to prevent.

Both branches fail closed: a marker present but empty is an error, not a skip.
Verified the extracted flags reproduce the independently measured
dot_resolver_install.sh 39/39 = 100.00%, and that the other five suites still
pass bare.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016sxxsDHWgdRhmdmzNMA6XM
2026-08-22 12:05:45 +02:00
1bd9e6140e Replace the handoff: the ratchet is converted, the campaign continues
The old prompt still described Phase 3 as 'finish wiring the ratchet'
and presented the three scoping questions as open with a recommendation
to answer them as a presence-based lib-only ratchet. The user answered
all of them the other way on 2026-08-22 -- repo-wide, 100%, ALL files,
all three gates, hardest first -- and the ratchet has since been
converted to a measured 100% bar. A handoff that re-proposes a vetoed
design is worse than no handoff.

The new one carries what the next session actually needs: the settled
answers, the exact jail invocation, the four hardest remaining targets,
and the traps that cost this session real time -- stub shadowing that
drops coverage while tests still pass, stubs that must drain stdin or
materialise what the real tool creates, shfmt pushing a file over the
250-line cap after your own check passed, and the two-strike rule.

It also records the one UNSOLVED problem honestly: kcov mis-measures
rpi_nc_install.sh at 11.36% while the code provably runs, and the three
obvious explanations were each disproven against a minimal
reproduction. That is a hole in the campaign's primary instrument and
the next session should know before trusting a number.

Every figure in it was verified against the tree, not recalled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbD4nco3EgL1UFoZKmKsLE
2026-08-22 11:53:12 +02:00
0e843c2a5f Cover rpi_nc_install.sh and fix two latent bugs in the nc_php suite
rpi_nc_install.sh is one 200-line function that apt-installs 24
packages, seeds MariaDB, rm -rf's /var/www/nextcloud, rewrites Apache
and PHP config and installs a www-data crontab. 29 assertions, run for
real inside the jail. The destructive step is the point: a bug in
`rm -rf /var/www/nextcloud` is invisible to any check that does not
actually run it.

Two latent bugs in the ALREADY-COMMITTED nc_php suite, both found by
finally reading its assertion results instead of only its coverage
percentage. It was failing 2 of 29 in isolation:

- the cron entry arrives on crontab's STDIN, not in argv, so asserting
  on the call log could never have passed for the right reason. The
  stub now records stdin.
- configure_mariadb's stdout is eaten by `db_password=$(...)`, so the
  phase-order marker now goes to stderr and the capture reads both.

Suites now: 28 + 30 + 29 = 87 assertions, 0 failures.

KNOWN AND UNEXPLAINED: kcov mis-measures rpi_nc_install.sh at 10/88 =
11.36%, recording lines 13-54 and nothing after -- yet the code past
line 54 provably runs, since /root/.nextcloud_db_password and
/etc/apache2/sites-available/nextcloud.conf both exist afterwards. The
three obvious culprits were each tested against a minimal reproduction
and DISPROVEN: kcov traces fine past a heredoc, into a $( ) command
substitution, and past a heredoc-fed stub reading stdin via $(cat).
The file stays on the allowlist and the suite is kept for its
assertions, not its percentage. Do not 'fix' the number by weakening a
test.

dot_resolver_install.sh 39/39 = 100.00%; nc_php.sh 84/85 = 98.82%.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbD4nco3EgL1UFoZKmKsLE
2026-08-22 11:49:47 +02:00
b7523d1691 Bound every coverage-jail case with a timeout and closed stdin
Investigated whether this session caused the 11:33 hard freeze. It did
not, and the evidence is recorded in the evidence artifact:

- No OOM kill, no oom_reaper, no hung-task or soft-lockup message, and
  no kernel message AT ALL after 11:20. The log stops mid-line at
  11:33:15 -- a hard hang, not a process the kernel was fighting.
- Last commit 11:30:32; last jail trace 11:11:57, 21 minutes earlier.
  The session was idle when the machine died.
- Steam wrote a crash minidump at 11:31:10 and at-spi2 disabled an
  unresponsive app at 11:32:06, on an RTX 3090. Correlation only -- a
  hard hang leaves no log to prove cause.
- Nothing survived the reboot: no stray unshare/kcov, no leftover
  tmpdirs, no leaked mounts, and the live /etc and /usr/local/bin
  files keep their original dates.

Hardened anyway, because the jail had one genuinely unbounded
behaviour: a case could run forever. Two hangs yesterday came from a
`crontab -` fallback inheriting the caller's stdin.

Each case now runs under `timeout --kill-after=10s` (default 120s,
--timeout to override) with stdin from /dev/null, and a timed-out case
WARNS by name instead of being swallowed -- a silent timeout looks
identical to an unreachable line in the report. Verified: a `sleep 600`
subject now ends in 5s with --timeout 5s.

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:40:16 +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
3338033cf2 Refresh the ratchet handoff after the first allowlist batch
115 -> 106 everywhere the count appears, 4 -> 5 suites, and the
"chipping away" section rewritten around what the scripts/lib batch
actually measured: the per-directory cost, the two isolation traps
(source-time $HOME writes, readonly double-source), and the reason to
mutation-test before reseeding rather than after.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 23:58:35 +02:00
d42d8ff20d Shrink the shell-coverage allowlist 115 -> 106: cover scripts/lib
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
Gives linux_configuration/scripts/lib a real test suite, which takes all
nine of its libraries off meta/shell-coverage-allowlist.txt at once —
coverage is per-directory, so this is the highest-ratio first batch.

139 assertions across four suites, all offline: the mtk toolkit is driven
through its existing MTK_ROOT_FIXTURE seam, and adb is shimmed onto a
narrowed PATH. Nothing reads the wall clock, this host's udev rules, or a
real device, because the suite runs in shell-tests.yml and again on every
pre-push via ci-mirror — a host-dependent assertion would be a flake that
blocks unrelated pushes.

The gate only checks that tests/run_all.sh EXISTS, so an empty one would
mark all nine libraries covered while testing nothing. Each of the four
load-bearing rules was therefore mutation-tested and the suite required to
go red: classify_device's && -> ||, discover_partitions' init_boot/boot
ordering, mtk_select_device's refusal to guess between two phones, and
is_hour_in_range's 10#$ octal guard.

That last one initially SURVIVED. Dropping 10#$ makes bash abort with
"value too great for base", a non-zero exit indistinguishable from an
honest out-of-range result, so the status-only assertion passed either
way. It now asserts stderr is empty.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 23:52:09 +02:00
50854e7837 Hand off the shell-ratchet hook paste, with the tests/ exclusion measured
The .pre-commit-config.yaml edit has been denied twice as a sensitive file,
so the paste belongs to the user. Record the exact block, its 6-space
indentation, its anchor point, and the measured gate behaviour so the next
session does not re-derive any of it.

Adds the one case the earlier table missed: `files: \.sh$` also matches
lib/tests/ and lib/payloads/ files, but the script self-filters them, so the
block needs no `exclude:` and cannot block its own test suites.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 23:42:52 +02:00
7845ddfe12 Shell ratchet: fix tests/lib misread, enforce shrink-only, correct docs
Three review findings, all before the hook is wired:

is_shell_lib excluded */lib/tests/* but not */tests/lib/*. Both are test
code; the second nesting made the gate demand tests/lib/tests/run_all.sh
for 10 helper files. It would have fired precisely while editing test
helpers to write the suites this ratchet asks for. Reseeded 125 -> 115.

--seed now refuses (exit 1) when a reseed would ADD an entry, instead of
a header comment asking nicely. It reads tracked files only, so an
unstaged probe seeds normally -- staging it produces the refusal with
the allowlist left byte-identical.

The parent commit's docs claimed an exempt file loses its exemption when
next modified. It does not: check_files exempts allowlisted paths
unconditionally, verified by staging an edit and getting exit 0. Fixed
the text, not the code -- making an edit trip the gate would block
routine work across 115 of 198 libraries, which is the repo-wide gate
this ratchet exists to avoid.

Also corrects the "a percentage is unreachable" rationale: commit
53e80462 reached 100% on an entry script with no suppressions. Presence
is still the right bar, because that took per-file mount analysis.
2026-08-21 17:09:20 +02:00
53e80462c5 Phase 3: prove 100% shell coverage is reachable via a namespace jail
Measurement-only. No production file changed.

The gating question was whether the campaign's constraints are jointly
satisfiable: 100% line coverage over ALL .sh files with no suppressions.
Phase 1 measured effecting code at 0% under PATH shims, which implied
either forbidden suppressions or source seams in 17+46 files.

They are satisfiable. A user+mount namespace reaches 100% with zero
source changes: --map-root-user makes require_root fall through, and
bind-mounting each write target makes installer writes land in the jail.
setup_night_lockdown.sh went 81.58% (PATH shims) -> 86.84% (userns) ->
100.00% (38/38, all mounts).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3PzGc75gBcdxP3PkHXGcV
2026-08-21 17:08:05 +02:00
0048293a2b NEXT_SESSION_PROMPT.md: record Phase 3 as built-but-unenforced
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
Replaces the Q1/Q2/Q3 grilling block with the answers the ratchet was
built under, flagged as cheaply vetoable since the hook is not yet
wired. Documents the one remaining step (the .pre-commit-config.yaml
entry, denied as a sensitive file) and corrects the file-count method:
`find` reports 565 .sh because it walks .venv and node_modules, which
is how the stale "roughly 487" got in here.
2026-08-21 17:02:31 +02:00
3278bebd03 Phase 3: ratchet shell libraries onto test suites
A shell library under a lib/ directory must now sit beside a
tests/run_all.sh. This is a presence gate, not a coverage percentage:
Phase 1 measured these libs' decision logic at ~90% under test but
their effecting code at 0%, because it sudo-writes, pkills and installs.
A numeric bar would only be reachable by shimming every external for
every installer, or by the suppressions this repo forbids.

Seeded at 125 exempt libraries out of 208 tracked; the other 83 already
have a suite. The allowlist shrinks only — a modified library loses its
exemption and must bring tests.

Verified standalone: blocks a new untested lib (exit 1), and does NOT
block covered libs, exempt libs, or unrelated entry scripts. That last
case is the point of a ratchet over the repo-wide gate.

The .pre-commit-config.yaml entry is deliberately NOT included — the
edit was denied as a sensitive file, so the gate is runnable but not
yet enforced.
2026-08-21 17:00:26 +02:00
09af51d462 NEXT_SESSION_PROMPT.md: rewrite for Phase 3
Phases 1 and 2 are done — the cap is at zero and enforced, and both CI gates
are wired — so the handoff now carries what a Phase 3 session needs instead.

Phase 3 opens with questions rather than work, because the scale (roughly 487
.sh files against a handful of lib/tests/ dirs) makes scope the user's call.
The coverage numbers Phase 1 actually measured are included so the "what bar"
question gets answered with evidence: decision logic reached 94%, while every
function that pkills, sudo-writes or installs sat at 0%. A 100% bar means
shimming sudo/pkill/systemctl/curl/npm across every installer, and that cost
belongs in front of the user before anyone starts writing tests.

Also records the traps that cost real time and are all still live: shfmt
corrupting hyphenated array subscripts, `# shellcheck` at the start of a
comment being parsed as a directive, verify_shell_split.sh being blind to
heredoc-internal functions, and the exec-bit staging rule.

Notes that Python tests has been red since 2026-08-17 from a pip
resolution-too-deep failure that predates this campaign and is unrelated to
it — which is why python-tests.yml is not in check_ci_green.sh's required list.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3PzGc75gBcdxP3PkHXGcV
2026-08-21 16:50:53 +02:00
18c4a4c6af glyph-art evidence: describe the commit that actually landed
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Python tests / test (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
The artifact was written for an earlier draft of the change and did not match
what shipped. It claimed _glyph_art.py went 253 -> 208 when HEAD was 220 and
the committed file is 238, omitted apps.py entirely, and carried the line
"Non-goal: any change to the glyph artwork itself" — false, since the whole
point of the commit was adding the note-ascender glyph and the lyricanki
AppIcon entry.

Corrected the line counts, added apps.py to scope, replaced the false non-goal
with the real one (the twelve pre-existing glyphs and style.py are untouched),
and recorded the linux=False check: ~/lyricanki has android/ and no linux/, so
there is no desktop hicolor set to install. Evidence that misdescribes its own
diff is worse than none — it is what a future reader bisects against.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 16:43:01 +02:00
1d7271f023 Phase 2: enforce the 250-line cap and stop pushing red
Two pre-commit hooks and one pre-push extension, plus the _glyph_art split
that had to land first — the cap gate cannot be wired while any file is over
it, and the lyricanki note-ascender glyph had taken _glyph_art.py to 253.

file-length-cap runs check_file_length.sh --all repo-wide rather than on
staged files, so a file that grows past the cap in some other commit is still
caught. Proven to block, not assumed: with a deliberate 261-line file staged
the hook exits 1 and names it; with the file removed it passes.

ci-baseline-green is the user's option (a): refuse to build on a main that is
already red. It fails closed on a definitively failing baseline and warns-and-
passes on anything it cannot determine — offline, no gh, unauthenticated, no
settled runs — each call capped at 5s. Blocking every commit on network
reachability buys nothing when (b) is the real per-change guarantee.
CI_GREEN_SKIP=1 is the escape for a fix-forward commit. python-tests.yml is
excluded on purpose: it is path-filtered, so a shell-only commit never
triggers it and its "latest run" would produce false reds.

This hook already earned its place. Run against a red main earlier in this
session it refused with "Shell tests (failure)", which is how the broken
SETUP_SOURCES was found instead of being pushed over.

ci_mirror gains the shell-tests workflow's side-effect-free half — every
lib/tests/run_all.sh plus the workflow's own test list, the user's option (b).
That list is parsed out of shell-tests.yml rather than copied: a hardcoded
duplicate drifts the moment someone adds a test, and a mirror that quietly
checks less than CI is worse than no mirror because it reports safe. The
Arch-container half stays unmirrored — it wants root and an Arch userland.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3PzGc75gBcdxP3PkHXGcV
2026-08-21 16:41:31 +02:00
40e2e22359 test_shutdown_timer_monitor: actually populate SETUP_SOURCES
The template checks were grepping an empty argument list. SETUP_SOURCES was
initialised as SETUP_SOURCES=("${SETUP_SOURCES[@]}") — a self-reference to an
array that did not exist yet — so it started empty, never contained
$SETUP_SCRIPT, and the lib-discovery loop ran dirname on nothing.

CI reported "setup_midnight_shutdown.sh should install the D-Bus monitor
helper". Reproduced locally before changing anything, so the fix is verified
against the real failure.

Seeding it with ("$SETUP_SCRIPT") restores the intent: search the entry
script together with lib/ms_*.sh, since the monitor template moved into
lib/ms_monitor.sh during the split.

test_pacman_wrapper_security.sh and test_hosts_guard_pacman_integration.sh
re-checked at the same time and both pass, so the Shell tests set is green
again before the file-length gate goes in.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3PzGc75gBcdxP3PkHXGcV
2026-08-21 16:32:51 +02:00
d72f03ea93 setup_midnight_shutdown: split into lib/ — the cap is now zero
1734 -> 109 lines across eight libs plus one payload file. This was the last
file over the 250-line cap; check_file_length.sh --all now reports "all
checked files are within 250 lines".

This file needed a different proof from the others. Its product is scripts
generated into /usr/local/bin via heredocs, so "did the functions move
verbatim" is not the question that matters — "does it still emit the same
bytes" is. Every heredoc body was hashed before the split: 11 of 12 are
byte-identical afterwards, and the twelfth (the 224-line config-unlock body)
now lives in lib/payloads/shutdown-config-unlock.sh.in at hash
1a5c67cfd0244101, exactly matching what it replaced. No new bodies appeared.

The "~39 top-level functions" this file is documented as having are not real.
log(), now_epoch(), cmd_add() and friends sit at column 0 because they are
inside the generated scripts. The brace-matching extractor keeps each heredoc
with its owning generator, so they moved as part of it. That is also why
verify_shell_split.sh reports a bare "-log": it cannot tell a heredoc-internal
definition from a top-level one. log() is not lost — it is line 17 of the
payload, which is where it always ran from.

$_MS_PAYLOAD_DIR is defined in the same file as its use, directly above it,
following the _NL_PAYLOAD_DIR lesson from earlier in this campaign.

--help is byte-identical, shellcheck is clean with no suppression added.
`setup` itself is not executed — it installs systemd units, makes the config
immutable and arms a shutdown timer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3PzGc75gBcdxP3PkHXGcV
2026-08-21 16:27:54 +02:00
ef12e9c6ad pacman tests: follow the wrapper split so Shell tests goes green
Shell tests has been red since fbe9134c. Three assertions across two
files grep pacman_wrapper.sh for functions the split moved into the flat
pw_*.sh libs: pacman_hooks_manage_guard_lib and
should_use_wrapper_guard_lib_fallback (pw_guard.sh),
verify_policy_integrity and the two -U helpers (pw_policy.sh), and
auto_remove_virtualbox_vms plus VBoxManage (pw_cleanup.sh).

Each grep now searches the entry script and its libs together, via a
pw_*.sh glob rather than a named lib, so a future re-partition does not
break them again. The sed extraction in the security test reads
pw_policy.sh directly, since it runs those two functions in isolation.

Deliberately left narrow: the two assert_order checks still target the
entry script alone. They assert the order of two call sites, and both
calls are still there -- widening them would turn a positional check
into a meaningless one. The 'CRITICAL near verify_policy_integrity' and
auto_remove_virtualbox_vms call-site greps were also left alone after
confirming they still match in the entry script.

Both files now exit 0. Of the two other suites still failing locally,
neither is from this commit: test_security_hardening's remaining failure
is this machine's unwrapped signal-desktop/discord and passes in CI, and
test_shutdown_timer_monitor is failing on the uncommitted
setup_midnight_shutdown split in the working tree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 16:25:15 +02:00
fbe9134c22 pacman_wrapper: split into flat libs under the 250-line cap
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Python tests / test (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
929 -> 217 lines across five pw_*.sh libraries. This file IS /usr/bin/pacman
here (the symlink points at the deployed copy), so the design constraint was
not readability but that no failure mode may leave the machine unable to
install packages.

Fail OPEN, unlike the other splits in this campaign. A missing lib warns on
stderr and execs real pacman unwrapped. Failing closed would mean a missing
lib leaves no way to install anything, including the missing lib. This is the
same choice the existing pacman_lock_lib.sh block already makes, and it is
verified rather than asserted: with pw_guard.sh removed from a scratch-dir
copy, the wrapper warns, execs, and returns real `-Qi bash` output at rc=0.

The libs load at the very top, before the fast path, because the fast path
calls needs_unlock() to decide whether to exec straight through, and that
function now lives in pw_guard.sh.

They are deployed flat beside the wrapper — the shape pacman_lock_lib.sh and
heavy_job_lock.sh already use in /usr/local/bin — as required copies, and are
added to the integrity manifest. Tamper detection was round-tripped in a fake
root: 8 manifest entries verify clean, and appending one line to
pw_challenge.sh is detected and the transaction denied.

Verified by execution, not just textually: real `-Qi bash` and `-Q` through
the split wrapper are byte-identical to the pre-split baseline, same 2824
packages. verify_shell_split.sh reports all 14 top-level functions moved
verbatim.

The deployed copy is deliberately NOT updated by this commit and still hashes
548fc2b5... exactly as before the work started; live pacman is unaffected.
A live `pacman -S` remains unverified — the Bash tool deadlocks on db.lck and
a failed experiment costs the package manager.

Cap is now down to one file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3PzGc75gBcdxP3PkHXGcV
2026-08-21 16:19:26 +02:00
12e9f5916f extract_shell_functions: recognise the function name() spelling
The detection regex matched only `name() {`. bash also accepts
`function name() {` and `function name {`, and pacman_wrapper.sh uses the
keyword form for 17 of its 30 functions — so a split of that file would have
left every one of them behind in the entry script while the tool reported
success. Silent, and exactly the kind of half-done split the verifier is
meant to catch afterwards rather than the extractor causing.

Parentheses stay mandatory in the non-keyword branch. Making them optional
everywhere also matches brace groups and control constructs, which would
sweep non-function code into the library — the opposite failure, and worse.
Nine negative tests pin that boundary.

The function name now comes from a named capture group rather than
splitting on "(", which returned "function name" for the keyword form and
would have broken both --functions selection and the verifier's name matching.

Verified against the already-split files first: cco_state.sh still yields 13
functions and leechblock_fetch.sh 3, so the completed splits are unaffected.
pacman_wrapper.sh goes from 13 detected to 31.

First tests for this script: 20 cases covering every accepted spelling,
brace matching across nested bodies, and single-line functions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3PzGc75gBcdxP3PkHXGcV
2026-08-21 16:11:29 +02:00
ddd3738976 security_apps: follow the cco split so Shell tests goes green
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Python tests / test (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Shell tests has been red on main since 72e1b6a8. Two assertions grep
block_compulsive_opening.sh for AUTO_CLOSE_TIMEOUT_MINUTES and
launch_with_timer -- both of which the 250-line-cap split moved into
lib/cco_wrapper.sh. The code is fine; the test's idea of where it lives
was stale, and it reported "Function not found" for something that had
simply moved one file over.

Both greps now search the entry script plus every lib/cco_*.sh. The glob
means the next split does not break them again, and the assertions stay
about the feature existing, which is what they were always for.

CI's only failure on the red run was launch_with_timer, so this targets
the actual break. The one failure that remains locally, "Compulsive
block wrappers installed", is this machine's state -- signal-desktop and
discord are installed but unwrapped, which the blocker's own status
reports independently -- and it passes in CI, where neither app exists.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 16:06:28 +02:00
c266b3cd42 test_security_hardening: look for the blocker's code where it now lives
The block_compulsive_opening split moved launch_with_timer into
lib/cco_wrapper.sh, and tests 17 and 18 grep the entry script alone. CI went
red on "launch_with_timer function exists — Not found at expected location":
a false failure about code that is present and working, one file over.

Both assertions are about a feature existing, so they now search the entry
script together with lib/cco_*.sh. That restores what they were always
checking and does not go stale on the next split.

The lib list is built with a loop and an -f test rather than a bare glob, so
an unmatched pattern cannot land in the array as a literal and no shellcheck
suppression is needed.

Verified by running the suite: both checks PASS, 18 passed. The one remaining
local failure is test 19, which inspects /usr/bin on this machine — signal
and discord are not currently wrapped here and beeper is not installed. That
is machine state, predates this change, and is not what CI reported.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3PzGc75gBcdxP3PkHXGcV
2026-08-21 16:06:08 +02:00
79a49846b9 setup_night_lockdown: define _NL_PAYLOAD_DIR where it is used
The split referenced $_NL_PAYLOAD_DIR from both install functions but
defined it nowhere, so `setup_night_lockdown.sh setup` would have died
with an unbound variable partway through installing the lockdown. My
earlier scratch verification set the variable by hand, which is exactly
what hid the bug: the emitted artifacts hashed correctly while the real
entry path could not have reached them.

It is now defined in each lib that uses it, eight lines above the use,
via `: "${_NL_PAYLOAD_DIR:=...}"` so an explicit override still wins. A
definition placed in the entry script was silently stripped by an
autoformat pass three times during this work, and a half-run lockdown
installer is not an acceptable failure mode.

Also drops two zero-byte payload files left behind by the .sh -> .sh.in
rename.

Re-verified without presetting anything: install_enter_script and
install_unlock_script emit mode-0755 files hashing 4baaf405... and
eafce28d..., byte-identical to the live /usr/local/bin copies. help and
an unknown command match their baselines; status differs only by the
05:00 countdown ticking 13h to 12h between runs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 16:02:59 +02:00
f6bfdff76a setup_night_lockdown: split into lib/ under the 250-line cap
918 -> 138 lines, across five libs plus two data-file payloads.

The two generated scripts became data files rather than heredocs.
ENTER_EOF alone was 279 lines, so any lib containing it would have been
over the cap however the rest was arranged. Both heredocs were quoted,
so their bodies were already literal text with no build-time
interpolation, and copying them verbatim is exactly equivalent.

The payloads are named *.sh.in on purpose. The pre-commit hook globs
staged files by '\.(sh|bash|zsh)$', runs shfmt -w on them and re-stages
the result -- and shfmt does reformat these bodies. That would have
changed their bytes and destroyed the property this split is verified
by. The .in extension keeps them out of every .sh-globbing hook without
a suppression or an exclude regex.

cat + chmod 0755 became a single `install -m 0755`, closing the window
the original left between creating the file and making it executable.

The load-bearing check: running install_enter_script and
install_unlock_script against a scratch directory produces mode-0755
files whose sha256 sums are identical to the copies already deployed in
/usr/local/bin on this machine. The split provably emits the exact bytes
that are running today, and it touched no system file -- sha256sum -c on
both deployed artifacts passes unchanged.

help, status and an unknown command are byte-identical to their
pre-split baselines. A line-multiset check leaves exactly six lines
unaccounted for: the two cat/heredoc openers, the two terminators and
the two chmod calls that install -m now performs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 15:59:35 +02:00
72e1b6a8b1 block_compulsive_opening: test the allow/block decision
21 assertions for cco_state.sh, the lib that decides whether a launch is
permitted. Coverage measured: 62/66 lines = 93.94%. The only uncovered lines
are kill_app's pkill/pkill -9 pair, which must never run in a test on a
machine whose apps are live. cco_wrapper/cco_install/cco_report stay at 0%:
every function in them moves system binaries, writes /etc/pacman.d/hooks or
kills processes.

The assertion that matters most is the one in the negative direction: a
PREVIOUS hour's stamp must NOT keep blocking. If that decision inverted, a
legitimately-closed app could never be reopened, and the failure would look
like the tool working. Both directions are pinned, plus the empty-stamp case
and stale suspend-marker cleanup.

Also generalises run_all.sh from test_leechblock_*.sh to test_*.sh. This
directory now holds suites for more than one entry script, and a prefix glob
silently skips every suite added after it — including inside the CI discovery
step added in fbec4656, which runs this file.

The harness asserts its own fixture is complete rather than letting a typo in
a global surface as a confusing unbound-variable from inside the code under
test. That check is also what makes the fixture's globals legible to
shellcheck, which cannot see them being read across the source boundary — a
real check that happens to remove SC2034, not a suppression.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3PzGc75gBcdxP3PkHXGcV
2026-08-21 15:53:54 +02:00