From 2ab9effa4fd62f82d057c494db52079611b6bb71 Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Sun, 23 Aug 2026 14:22:10 +0200 Subject: [PATCH] Handoff: fast push gate, then close the real coverage holes Two jobs for the next session. Job 1 is the push gate: pre-push is ~130s, almost all of it ci-mirror, while pre-commit is already 4.8s. The prompt says plainly that a clean-venv install plus --all-files plus two suites cannot fit in 10s, and asks for stage timings BEFORE proposing anything, then lists four options with what each actually costs -- three that keep the guarantee and one (drop ci-mirror from pre-push) that trades it. Job 2 is the coverage ask. It leads with the finding that ~12 gated shell libs outside fixes/lib fail the gate today, which is worse than an exemption: the gate believes it is checking them. Then the 84 allowlist entries, then Python. It also says to report the real scope first, because "100% covered" is several days of work and the user should see the shape before it is spent. Carries forward what cost hours last session: verify an edit landed before trusting it, run the suite from a clean checkout before pushing, never run two jails at once, and audit any instrument predicate repo-wide rather than in one directory. Co-Authored-By: Claude Opus 5 (1M context) --- NEXT_SESSION_PROMPT.md | 618 +++++++++++++++++++---------------------- 1 file changed, 289 insertions(+), 329 deletions(-) diff --git a/NEXT_SESSION_PROMPT.md b/NEXT_SESSION_PROMPT.md index e47e2004..6bcebb3d 100644 --- a/NEXT_SESSION_PROMPT.md +++ b/NEXT_SESSION_PROMPT.md @@ -1,399 +1,359 @@ -# Next session: the last 2 untested `fixes/lib` libs +# Next session: make the push gate fast, then close the coverage holes > **Paste this whole file into a fresh Claude session opened in `~/testsAndMisc`.** > It is self-contained. Do not go looking for the previous session's context. -## The job +There are **two jobs** here. Do them in order — job 1 is small and makes job 2 +bearable, because job 2 involves many commits. -`linux_configuration/scripts/single_use/fixes/lib/` has 22 libs. **Two still -have no tests.** Write them, to 100% line coverage, and take each one off -`meta/shell-coverage-allowlist.txt`. +--- -| lines | lib | entry script | harness | -| ----- | ---------------------- | --------------------------- | ----------------------------------------- | -| 215 | `ubuntu_perf_fixes.sh` | `fix_ubuntu_performance.sh` | `ubuntu_perf_harness.sh` — ALREADY EXISTS | -| 180 | `nvidia_config.sh` | `nvidia_troubleshoot.sh` | needs a new one | +# JOB 1: get `git push` under 10 seconds -**Do `ubuntu_perf_fixes.sh` first** — its harness already exists and already -exports `JOURNALD_CONF_DIR` and `UNDO_DIR`, so you extend rather than build. +## What is slow -Allowlist is at **86** entries — count with `grep -vc '^#'`, since the file -also holds comment lines. +`git push` currently takes **~130 seconds**. Nearly all of it is the +`ci-mirror` pre-push hook (`meta/scripts/ci_mirror.sh`), which deliberately +re-runs CI locally so a red push cannot reach GitHub. It does four things +inside a fresh `git worktree` checkout of HEAD: -## What the previous session finished +| stage | what it costs | +| ----------------------------------------- | ------------------------------------- | +| venv install from `requirements.txt` | ~0s when cached, ~40s on a change | +| `pre-commit run --all-files` | the bulk of it | +| `pytest_changed_packages.py` in that venv | seconds to minutes, package-dependent | +| `run_shell_test_gate` (the shell suites) | ~40s+ | -Allowlist **91 -> 86**, six commits. Five libs cleared: +For comparison: **pre-commit is already fast — 4.8s measured.** Do not touch +it. The whole problem is pre-push. -| lib | coverage | -| ------------------------- | ------------------- | -| `arch_cpu.sh` | **57/57 = 100.00%** | -| `arch_hardware.sh` | **94/94 = 100.00%** | -| `thorium_repairs.sh` | **75/75 = 100.00%** | -| `hosts_guard_migrate.sh` | **81/81 = 100.00%** | -| `hosts_guard_rollback.sh` | **65/65 = 100.00%** | +## The target, and the honest tradeoff -`arch_sysctl.sh` has 23 green assertions but is NOT cleared — see the -quarantine section below. +The user asked for **under 10 seconds, ideally ~10s or less, total**. -## Everything you need already exists — do NOT build a new harness +**Be straight with them about this:** a clean-venv install plus `--all-files` +plus two test suites cannot fit in 10s. Something must actually be given up. +The decision is theirs, so **measure first, then present the options and their +real costs, then implement the one they pick.** -`fixes/lib/tests/` is a working suite: **28 test files, all green, all -shellcheck-clean with zero suppressions.** Layers: +### Step 1 — measure each stage separately -- `run_all.sh` — the runner. **The subject the gate measures through.** -- `lib_test_core.sh` — shared primitives. Read it first. **240 lines, close to - the 250-line cap: the next helper added here forces a split.** - - `_t_eq` / `_t_contains` / `_t_lacks` / `_t_pass` / `_t_fail` - - `_t_stub NAME BODY`, plus `_t_stub_stdin` (body from a quoted heredoc), - `_t_stub_cat NAME FIXTURE`, `_t_stub_writes NAME ARGNUM CONTENT`. Use - these instead of `_t_stub foo '...$1...'`, which trips SC2016 every time. - - **`_t_run FUNC [ARG...]`** — runs FUNC in the CURRENT shell, puts combined - output in `$out`, returns its status. **Use this, never `out="$(func)"`:** - command substitution forks a subshell and kcov then does not register the - sourced lib at all (measured: 0 lines via `$( )`, 54 via a direct call). - - `_t_unstub NAME...` (runs `hash -r`; deleting the file alone does nothing) - - **`_t_hide TOOL...`** — rebuilds PATH from symlinks to every real binary - EXCEPT the named ones. It KEEPS `$FAKE_BIN` on PATH, so **`_t_unstub` the - tool first** or the harness's own default stub still satisfies `has_cmd` - and the "not installed" branch never runs. The farm is cached by tool - list; it symlinks ~13.5k binaries and rebuilding it per call cost enough - runtime to blow a jail timeout. - - `_t_calls` / `_t_reset_calls` — what the stubs recorded. -- **Five family harnesses**: `bt_harness.sh`, `arch_perf_harness.sh`, - `arch_desktop_harness.sh`, `ubuntu_perf_harness.sh`, `thorium_harness.sh`, - `hosts_guard_harness.sh`. Each sources the core, sources the real - `scripts/lib/common.sh`, and redefines the helpers that live in the **entry - script** rather than any lib. - -**To add a family:** copy `thorium_harness.sh`'s shape (~124 lines). jscpd -already excludes `**/lib/tests/**`, so shared test setup carries no -duplication risk. - -## The loop, per lib +Do this before proposing anything. Numbers, not guesses: ```bash -# 1. write tests/test_.sh, chmod +x it -# 2. run the suite bare first -- much faster than the jail -bash linux_configuration/scripts/single_use/fixes/lib/tests/run_all.sh +cd /home/kuhy/testsAndMisc +SP=$(mktemp -d); git worktree add --detach --quiet "$SP/co" HEAD -# 3. then measure through the runner, in a detached worktree -timeout 900s bash meta/scripts/shell_coverage_jail.sh --timeout 600s \ - --subject linux_configuration/scripts/single_use/fixes/lib/tests/run_all.sh \ - --measure .sh --min 1 --fail-on-case-error -- "" +time (cd "$SP/co" && SKIP=pytest-coverage pre-commit run --all-files) +time (cd "$SP/co" && python meta/scripts/pytest_changed_packages.py) +time bash linux_configuration/scripts/single_use/fixes/lib/tests/run_all.sh -# 4. close the gap, repeat until 100.00% +git worktree remove --force "$SP/co" ``` -**Always measure through `run_all.sh`, never a single test file.** -**Always pass `--timeout 600s`** — see the timeout section below. - -## The bar for taking a lib off the allowlist - -1. Measure 100.00% through `run_all.sh`. -2. **Measure again at `--min 100`** so a shortfall is a non-zero exit. -3. Remove the line from `meta/shell-coverage-allowlist.txt`, then run the gate - with **the line already deleted** and confirm exit 0: - ```bash - bash meta/scripts/check_shell_coverage.sh .sh - # expect: "every checked shell library meets the coverage bar" - ``` - Running it BEFORE deleting the line proves nothing — it passes via the - exemption. - -## NEW: the case timeout is now the binding constraint on suite size - -`meta/scripts/lib/shell_coverage_bar.sh` now passes **`--timeout 600s`** to the -jail. The jail's own default is 120s, and this suite outgrew it: **98s bare** -at 28 test files, and kcov instrumentation roughly doubles that. - -The failure mode is nasty and worth recognising instantly: the jail prints a -correct percentage AND a timeout warning, and `--fail-on-case-error` turns that -into a failed gate. It looked like this: - -``` -warn: case timed out after 120s: -bt_audio.sh: 127/127 lines = 100.00% -Error: 1 case(s) exited non-zero -``` - -A lib measuring a perfect 100% was failing the gate. If you add enough test -files to approach 600s instrumented, raise it again — the flag is a hang -catcher, not a performance budget. The cost is that a genuine hang now takes -10 minutes per pass to surface. - -## NEVER run two coverage jails at once - -Every false `FAIL` in the previous session came from CPU contention: two jails, -or a jail plus `bash run_all.sh`, and one of them times out. `bt_pairing.sh`, -`bt_audio.sh` and `bt_report.sh` each "failed" a sweep and then passed solo. - -When a sweep is running, do **zero-CPU work only** — write docs, read source. -Do not run the suite, the gate, or another jail. - -## NEW: adding a test file can break an ALREADY-CLEARED lib - -This is the most important operational lesson of the session, and it is -**unexplained**. - -Adding `test_arch_sysctl.sh` to the suite made kcov stop registering -`bt_audio.sh` entirely — "kcov instrumented no lines" — for a lib that is off -the allowlist at 127/127. That turned the gate red on `main`. Bisected: - -- `a2b23521` (before) — bt_audio 127/127 -- `afe6ac3a` (shfmt reformat) — bt_audio 127/127 -- `7c14cfca` (the file lands) — bt_audio "instrumented no lines" - -Removing that one file restores 127/127 with every other new test file still -present. Ruled out, each by reverting it alone in a throwaway worktree: -`export out`, the `_t_hide` cache, and **runtime** (it still fails at a 600s -timeout with no timeout warning, so it is NOT the timeout issue above). - -**Therefore: after adding any test file to this suite, re-measure an -already-cleared lib, not just your new one.** `bt_audio.sh` is the right canary -— it is the one that broke, it is gated, and it sorts late in the glob. - -## The quarantined file - -`tests/arch_sysctl_quarantined_cases.sh` holds **23 green assertions** for -`arch_sysctl.sh`. It is deliberately OUTSIDE the `test_*.sh` glob `run_all.sh` -iterates, because of the contagion above. Run it directly: +Then find which individual pre-commit hooks dominate `--all-files`: ```bash -bash linux_configuration/scripts/single_use/fixes/lib/tests/arch_sysctl_quarantined_cases.sh +cd "$SP/co" && for h in $(grep -oP '(?<=id: ).*' .pre-commit-config.yaml | sort -u); do + s=$(date +%s%N); SKIP='' pre-commit run "$h" --all-files >/dev/null 2>&1 + printf '%6d ms %s\n' $(( ($(date +%s%N)-s)/1000000 )) "$h" +done | sort -rn | head -15 ``` -`arch_sysctl.sh` stays on the allowlist. It has never measured through the jail -anyway — it reports "instrumented no lines" on its own too, like `nc_php.sh`. -Its header records what to re-verify before un-quarantining it. +### Step 2 — the options, in the order I would try them -## The instrument +1. **Only check what changed.** The single biggest win and the least loss. + `--all-files` is the expensive part; `pre-commit run --from-ref +origin/main --to-ref HEAD` checks the pushed range instead. The clean + worktree already guarantees you are checking committed content. + Similarly, only run a shell suite when the push touches shell files. +2. **Parallelise the stages.** `pre-commit --all-files`, pytest and the shell + gate are independent. Run them concurrently and wait. Roughly the cost of + the slowest rather than the sum. **Note:** two coverage jails must never run + at once (see the warning in job 2), so if the shell gate is jailed, keep it + serial with respect to any other jail. +3. **Cache on tree state.** Skip the whole mirror when + `git rev-parse HEAD:` (the tree hash) matches the last green run, recorded + in a dotfile. A re-push of an unchanged tree then costs ~0s. +4. **Move ci-mirror off pre-push entirely.** Pre-push drops to ~5s. The cost + is real and must be stated plainly: **a red push can then reach GitHub**, + and CI catches it minutes later instead of the hook catching it instantly. + The repo's `refuse-red-ci` hook already blocks the NEXT commit on a red + baseline, so this is less dangerous than it sounds — but it is a genuine + change in guarantees, not a free win. -Coverage = `(PS4-traced lines | kcov hits) & (kcov line set - continuation lines)` +Options 1-3 are optimisations that keep the guarantee. Option 4 trades the +guarantee for speed. **1+2+3 together may well reach ~10s; try them before +proposing 4.** If they land at, say, 25s, say so honestly rather than reaching +for 4 without asking. -`continuation_lines()` in `meta/scripts/lib/shell_coverage_lines.py` excludes -lines kcov instruments but bash attributes no statement to: - -- continuation lines of a multi-line **quoted** argument; -- **multi-line array literal** elements and the closing paren (`_ARRAY_OPEN`); -- **a closing brace plus an operator** — `} |`, `} >>file` (`_CLOSING_BRACE_OP`); -- **NEW: a loop terminator plus a redirect** — `done >file`, `done "$1"`, the body reports hits=3 and the - `done` line hits=0. This took `hosts_guard_rollback.sh` 97.01% -> 100%. - The redirect is REQUIRED by the pattern — a bare `done` stays in the - denominator, so genuinely uncovered loops are still caught. - -Acceptance test if you touch it: `dot_resolver_install.sh` = 39/39 and -`rpi_nc_ca.sh` = 73/73, **through `features/lib/tests/run_all.sh` WITH the -`jail_args` file beside it** (that suite refuses to run bare, and `mapfile` is -bash-only — wrap in `bash -c`). And **no lib's percentage may go DOWN**. - -A lib already at 100.00% **cannot** regress from excluding more lines -(`covered_set = (executed|traced) & denominator`). Over-exclusion is the only -real risk. Audit any new predicate by printing every newly-excluded line **with -its text** — and **grep the WHOLE repo, not just `fixes/lib`**. `_LOOP_END_OP` -looked like it touched 2 lines when scoped to `fixes/lib`; repo-wide it touches -~75, of which 14 are in gated non-exempt libs. - -### The one known-unfixed artifact - -`bt_adapter.sh` sits at **98.55% (68/69), still exempt**, capped by line 97: -the opening line of a backslash-continued **assignment** -(`result=$(dbus-send ... \`). bash attributes that statement to the LAST line -of the run, so the opening line can never be covered. - -**The predicate is narrower than "has a backslash".** A backslash-continued -plain **command** (`apply_fix "..." \`) is attributed to its **FIRST** line and -covers normally. Only the assignment flavour caps a lib: +### Step 3 — prove it ```bash -grep -nE '^[[:space:]]*(local[[:space:]]+)?[A-Za-z_][A-Za-z0-9_]*=.*\\$' +time git push # the number that matters ``` -Across all 22 libs that matches only `bt_adapter.sh` (1) and `bt_audio.sh` (2), -and bt_audio still reached 100%. **Neither remaining lib matches it**, so it -does not block this job. +And prove the gate still catches things: deliberately break something +(a failing assertion in a shell test), confirm the push is refused, then +revert. A fast gate that no longer gates is worse than a slow one. -## Work the jail in a detached worktree +## Do not -The jail fingerprints `git status --porcelain` and aborts with "a write escaped -the jail" if the tree changes **during** a run — it compares a delta, so a -pre-existing dirty file is fine but any concurrent write is not. **A `git mv` -you have not committed yet also trips it.** +- Do not weaken or delete a test to make a gate faster. +- Do not add `--no-verify` anywhere, or suggest it. It is forbidden by the + user's rules. +- Do not touch pre-commit's 4.8s — it is already at target. -Use a throwaway worktree and **copy one-way, INTO it, never out**: +--- + +# JOB 2: actually get the repo to 100% coverage + +The user asked to "make the repo truly 100% test covered". Be careful with +that phrasing: **it is a large campaign, not one task.** Scope it, report the +real size, and then work through it. Do not report it done until it is. + +## Where things actually stand + +### Shell: `meta/shell-coverage-allowlist.txt` has **84 exempt libs** ```bash -SP=/tmp/claude-.../scratchpad -git worktree add --detach "$SP/cov" HEAD -cp "$SP/cov/" # never the reverse +grep -vc '^#' meta/shell-coverage-allowlist.txt # 84 ``` -Copying _out_ of the worktree is how an earlier session silently reverted its -own uncommitted harness edit, twice. +Each line is a shell library allowed to sit below 100% line coverage. The +just-finished campaign took this from 91 to 84 by clearing seven libs in +`fixes/lib`. The remaining 84 are the job. -## Production bugs keep turning up. Expect more. +### ~12 gated libs OUTSIDE `fixes/lib` fail the gate RIGHT NOW -Three so far, all dead branches no test could cover without noticing: +`check_shell_coverage.sh` is **not wired into pre-commit**, so these have been +failing silently. Confirmed pre-existing (verified against an earlier commit — +they are not caused by recent work): -- `bt_audio.sh:21` — `timeout 3 _run_as_user wpctl status`. `timeout` is a - binary and cannot invoke a shell function, so it always returned 127. -- `arch_perf_report.sh:36,65` — journal-size regex required a space before the - unit (`4.2 G`), but `journalctl` prints `305.5M`. -- `arch_hardware.sh` — **the same regex bug again**, found this session. The - vacuum was unreachable; a multi-gigabyte journal would never be trimmed. +``` +lib/common_packages.sh FAIL +periodic_background/digital_wellbeing/lib/ms_override_mgr.sh FAIL +periodic_background/digital_wellbeing/lib/ms_scripts.sh FAIL +periodic_background/lib/periodic_browser.sh FAIL +single_use/utils/lib/analyze_repo_symbols.sh FAIL +single_use/utils/lib/bl9000_backup.sh FAIL +single_use/utils/lib/download_media.sh FAIL +single_use/utils/lib/idle_inhibit.sh FAIL +single_use/utils/lib/offline_index.sh FAIL +single_use/utils/lib/steam_api.sh FAIL +single_use/utils/lib/steam_report.sh FAIL +``` -**When a branch looks untestable, check whether it is actually reachable in -production before writing a test around it.** The user's standing answer is -"fix it in this commit". +`hosts_protect_custom.sh` and `hosts_protect_unblock.sh` PASS. -## Traps that cost real time +**Start here.** These are worse than an exemption: they are libs the gate +believes it is checking and which do not meet the bar. `single_use/utils/lib/` +is most of the list and shares one `tests/` directory, so one harness covers +several. -- **`shfmt` runs on every staged shell file and WILL reformat your lib**, - moving the denominator and invalidating a measurement taken minutes earlier. - Run `shfmt -l ` FIRST and land any reformat as its own commit. -- **The 250-line cap aborts the commit**, and a task notification can report - "completed (exit code 0)" for a commit that actually failed. Every test file - for a lib this size has needed splitting into two. `lib_test_core.sh` is at - 240 — the next helper added there forces a split. -- **A stub must consume what the real tool consumes.** A `bluetoothctl` stub - that read one line and exited closed the pipe, so the writing block took - SIGPIPE partway through. -- **`_t_stub` already records the invocation** into `$DEV/calls`. A custom body - that also records double-counts every call. -- **Check the argument position before writing a stub.** `pacman -Qi ` - puts the package in `$2`, not `$3`; two assertions passed for the wrong - reason until that was fixed. -- **A function that appends to a global array must not be called inside `$( )`.** -- **A top-level stub function SHADOWS the subject's own** for every later - assertion. Put redefinitions in a subshell, and bind them with - `eval '_real_name() { _my_recorder "$@"; }'` so shellcheck still sees the - recorder as called (SC2329). -- **Do not `pgrep -f ` in a wait loop** — the loop's own command line - matches, so it never exits. It also makes `pgrep -c` report a phantom match. -- **`--fail-on-case-error` or you are flying blind.** The jail suppresses the - suite's stdout, so assertions fail invisibly without it. -- **Do not pipe jail output through `grep 'lines ='`** — that filter hides the - `warn: case timed out` line, which is exactly the diagnosis you need. -- **`mapfile` is bash-only**; the Bash tool's shell is zsh. Wrap in `bash -c`. -- **`git stash` is blocked by a hook** — even a bare `git stash list` inside a - compound command trips the guard. Use a detached worktree. -- **Stage narrowly.** A `git add` of a directory swept another agent's - untracked file into a commit and broke the push on a prettier check. -- **Commits exceed a 2-minute foreground timeout.** Background them and - **check the result**. -- **Two-strike rule.** Two failed attempts at the same thing -> stop, document, - keep the working state. +### Python: `python_pkg/` is 25 packages at `fail_under = 100` -## Rules that will bite you +Branch coverage is already enforced at 100% by `meta/pyproject.toml`, so this +is likely in decent shape. **Verify before assuming** — and note the user's +standing rule: never exclude a package from coverage, write the tests. -- **No suppressions, ever.** Fix SC2016/SC2034/SC2329/C901 at the source. When - three arrays in `hosts_guard_harness.sh` tripped SC2034 because only the - libs read them, the fix was a `_t_table` helper the tests actually call — - bash cannot export an array. -- **Never weaken an assertion to move a number.** One asserted - `_t_lacks ... "hosts-guard.path="` against a line reading - `hosts-guard.path=absent`; the fix was `grep -c '=$'`, not deletion. -- **Every commit touching code needs evidence** in - `docs/superpowers/evidence/-.json` (validate with - `python3 meta/scripts/validate_evidence.py `), **plus a contract** in - `docs/superpowers/contracts/` once **>=4 code files** are staged. `.sh` - counts; so does the allowlist. -- **Put the MEASURED number in it**, never a rounded or hoped-for one. A - `"result": "fail"` entry is legitimate and `validate_evidence.py` accepts it. -- **New test files need the exec bit** — `chmod +x` the working tree. -- Work directly on `main`. Branch creation is blocked. +```bash +timeout 1800 python -m pytest python_pkg --cov=python_pkg --cov-report=term-missing -q 2>&1 | tail -30 +``` -## Seams: what each remaining lib needs +### Other languages -Neither lib can be tested without redirecting its writes. The established -idiom is an override **defaulting to the real path**, so production behaviour -is byte-identical: +`billsplit/` (Dart), `reverse_survivors/`, `poker-stakes/` (TS) have their own +CI. Check their gates before claiming anything about them. + +## Report the real scope first + +Before writing a single test, produce a count: N shell libs exempt, M gated +libs failing, Python status, other languages. Give the user that number and a +suggested order. "100% covered" spans several days of work; they should see +the shape before you spend it. + +## The order I would work in + +1. The ~12 gated-but-failing shell libs (worst: the gate is lying today). +2. Wire `check_shell_coverage.sh` into pre-commit so it cannot rot again — + **but only after 1**, or every commit is blocked. Mind job 1: this hook is + jailed and slow, so scope it to changed files. +3. The 84 allowlist entries, cheapest first. +4. Python, if the run above shows gaps. + +--- + +# How to do shell coverage work here (read before starting job 2) + +## The suite and its harnesses + +`linux_configuration/scripts/single_use/fixes/lib/tests/` is the reference +suite: **30 test files, 645 assertions, all green, zero suppressions, 37s.** +Copy its shape. Layers: + +- `run_all.sh` — the runner, and the subject the gate measures through. +- `lib_test_core.sh` (194 lines) — assertions and stubs. +- `lib_test_path.sh` (67 lines) — `_t_hide` and the PATH shim farm. +- Six family harnesses, one per entry script. + +Key primitives: + +- `_t_eq` / `_t_contains` / `_t_lacks` +- `_t_stub NAME BODY`, plus `_t_stub_stdin` (quoted heredoc), `_t_stub_cat`, + `_t_stub_writes`. Use these rather than `_t_stub foo '...$1...'`, which + trips SC2016 every time. +- **`_t_run FUNC [ARG...]`** — runs FUNC in the CURRENT shell, output in + `$out`. **Never `out="$(func)"`:** command substitution forks a subshell and + kcov then does not register the sourced lib at all (measured: 0 lines via + `$( )`, 54 via a direct call). +- **`_t_hide TOOL...`** — makes a tool genuinely unfindable. It KEEPS + `$FAKE_BIN` on PATH, so **`_t_unstub` the tool first** or the harness's own + default stub still satisfies `has_cmd` and the branch never runs. +- `_t_unstub` (runs `hash -r`; deleting the file alone does nothing). + +## Production seams + +Most libs write to `/etc`. The idiom is an override **defaulting to the real +path**, so production behaviour is byte-identical: ```bash local dropin="${SYSCTL_DROPIN_DIR:-/etc/sysctl.d}/99-performance-tuning.conf" ``` -Reuse the names already in use elsewhere rather than inventing parallel ones: -`SYSTEMD_UNIT_DIR`, `JOURNALD_CONF_DIR`, `UDEV_RULES_DIR`, `SYSCTL_DROPIN_DIR`. -Grep the entry script for a name before picking it — a collision is silent. +Reuse existing names — `SYSTEMD_UNIT_DIR`, `JOURNALD_CONF_DIR`, +`UDEV_RULES_DIR`, `SYSCTL_DROPIN_DIR` — and grep the entry script before +picking one; a collision is silent. -- **`ubuntu_perf_fixes.sh`** (4 seams): `/etc/sysctl.d/99-performance-tuning.conf`, - `/etc/systemd/system/nvidia-persistence.service`, - `/etc/systemd/system/nvidia-persistence-mode.service`, `/etc/default/earlyoom`. - At 215 lines, check `wc -l` after patching — seam comments run 3-4 lines each. -- **`nvidia_config.sh`** (3+ seams): `XORG_CONF`, `XORG_CONF_D`, `PROFILE_FILE` - (`/etc/profile`). **Check whether these are `local` or plain globals** — if - they are assigned inside a function, a harness `export` will not survive the - assignment and they still need the `${VAR:-default}` form. +**Watch for globals ASSIGNED inside a function** (`XORG_CONF="/etc/..."` with +no `local`). Exporting from the harness does not survive the assignment; the +default must live at the assignment itself. -Verify containment after the first run: `ls -la` the real paths and confirm -the mtimes did not change. +After the first run, `ls -la` the real paths and confirm the mtimes did not +move. -## PRE-EXISTING: ~12 gated libs OUTSIDE fixes/lib already fail the gate - -`check_shell_coverage.sh` is **not wired into pre-commit**, so a lib can sit -below the bar indefinitely without anything going red. Measured this session, -and confirmed by re-running at an earlier commit that these are **not** caused -by the `_LOOP_END_OP` instrument change: - -``` -lib/common_packages.sh FAIL (pre-existing) -periodic_background/digital_wellbeing/lib/ms_override_mgr.sh FAIL -periodic_background/digital_wellbeing/lib/ms_scripts.sh FAIL -periodic_background/lib/periodic_browser.sh FAIL (pre-existing) -single_use/utils/lib/analyze_repo_symbols.sh FAIL -single_use/utils/lib/bl9000_backup.sh FAIL -single_use/utils/lib/download_media.sh FAIL -single_use/utils/lib/idle_inhibit.sh FAIL -single_use/utils/lib/offline_index.sh FAIL -single_use/utils/lib/steam_api.sh FAIL (pre-existing) -single_use/utils/lib/steam_report.sh FAIL -``` - -`hosts_protect_custom.sh` and `hosts_protect_unblock.sh` PASS. - -This is a separate, larger job than the `fixes/lib` campaign — `utils/lib/` -alone is most of it. **Do not fold it into this task without asking**; it is -listed here so the next session recognises these as known and pre-existing -rather than something it just broke. - -## Finish with a serial sweep - -Once both libs are committed, run **one** sweep over every off-allowlist lib in -`fixes/lib`, serially, with nothing else running: +## The measurement loop ```bash -for lib in bt_report bt_pairing bt_audio arch_perf_report arch_perf_probes \ - arch_perf_fixes ubuntu_perf_more arch_cpu arch_hardware \ - thorium_repairs hosts_guard_migrate hosts_guard_rollback \ - ubuntu_perf_fixes nvidia_config; do - printf '%-24s ' "$lib.sh" - timeout 900 bash meta/scripts/check_shell_coverage.sh \ - "linux_configuration/scripts/single_use/fixes/lib/$lib.sh" \ - >/dev/null 2>&1 && echo PASS || echo "*** FAIL ***" -done +# 1. write the tests, chmod +x +# 2. run bare first -- far faster than the jail +bash /run_all.sh + +# 3. measure through the RUNNER, in a detached worktree +timeout 900s bash meta/scripts/shell_coverage_jail.sh --timeout 600s \ + --subject /run_all.sh --measure .sh --min 1 \ + --fail-on-case-error -- "" ``` -At ~2 min/lib that is ~30 minutes. Background it and poll the log, but do not -fill the gap with anything that forks. +Then the three-step bar to clear an exemption: + +1. 100.00% through `run_all.sh`. +2. Again at `--min 100`, so a shortfall is a non-zero exit. +3. Delete the allowlist line, **then** run + `bash meta/scripts/check_shell_coverage.sh ` and confirm exit 0. + Running it before deleting proves nothing — it passes via the exemption. + +## Traps that cost real hours last session + +- **Verify an edit landed.** A seam edit reported success, was measured, and + never reached the commit; ci-mirror caught it only because it runs from a + clean checkout. **After editing, `grep` the file for what you just wrote.** +- **Run the suite from a clean checkout before pushing.** It catches exactly + the above: + ```bash + SP=$(mktemp -d); git worktree add --detach "$SP/c" HEAD + bash "$SP/c//run_all.sh"; git worktree remove --force "$SP/c" + ``` +- **NEVER run two coverage jails at once.** Every false FAIL last session was + CPU contention — a jail plus another jail, or a jail plus `run_all.sh`. + Libs "failed" a sweep and passed solo. While a sweep runs, do zero-CPU work. +- **Another agent may be committing to this repo concurrently.** The jail + fingerprints `git status --porcelain` and aborts with "a write escaped the + jail" on THEIR edits. Measure in a detached worktree, and re-check a + transient gate failure before believing it. +- **Adding a test file can break an ALREADY-CLEARED lib.** Unexplained: + `test_arch_sysctl.sh` made kcov stop registering `bt_audio.sh` entirely + ("instrumented no lines"), turning the gate red. Ruled out: `export out`, + the `_t_hide` cache, and runtime (still fails at a 600s timeout). The file + is quarantined as `arch_sysctl_quarantined_cases.sh`, outside the + `test_*.sh` glob, with 23 still-green assertions. + **After adding any test file, re-measure an already-cleared lib** — + `bt_audio.sh` is the canary. +- **`shfmt` reformats staged shell files**, moving the denominator and + invalidating a measurement. Run `shfmt -l` first and land reformats + separately. +- **The 250-line cap aborts commits**, and a task notification can report + "completed (exit code 0)" for a commit that actually failed. Check. +- **Stage narrowly.** A `git add` of a directory swept another agent's + untracked file into a commit and broke the push. +- **`mapfile` is bash-only**; the tool shell is zsh. Wrap in `bash -c`. +- **`git stash` is blocked** — even `git stash list` inside a compound + command trips the guard. Use a detached worktree. +- **Do not pipe jail output through `grep 'lines ='`** — that hides the + `warn: case timed out` line, which is the diagnosis you need. +- **Do not `pgrep -f ` in a wait loop** — the loop's own command line + matches, so it never exits. + +## The coverage instrument + +`coverage = (PS4-traced | kcov hits) & (kcov line set - continuation lines)` + +`continuation_lines()` in `meta/scripts/lib/shell_coverage_lines.py` excludes +lines kcov instruments but bash attributes no statement to: multi-line quoted +arguments, multi-line array literals, `} |` / `} >>file`, and (added last +session) `done >file` / `done +``` + +### Libs that do not measure at all + +`arch_sysctl.sh` and `nc_php.sh` both report "kcov instrumented no lines" on +every version of the instrument. Treat as unverified; they stay exempt. + +--- ## State as of 2026-08-23 -Allowlist **86**, down from 91. The `fixes/lib/tests` suite is 28 test files -plus 6 harnesses and the core, ~470 assertions, all green, all shellcheck-clean -with zero suppressions. Suite runtime **98s bare**. +- Allowlist **84** (was 91 at the start of the previous session). +- `fixes/lib` cleared: `arch_cpu` 57/57, `arch_hardware` 94/94, + `thorium_repairs` 75/75, `hosts_guard_migrate` 81/81, + `hosts_guard_rollback` 65/65, `ubuntu_perf_fixes` 81/81, + `nvidia_config` 76/76. +- The `fixes/lib` suite: 30 files, 645 assertions, 37s, zero suppressions. +- Three production bugs were found by writing tests, all dead branches: + `bt_audio.sh:21` (`timeout` cannot invoke a shell function), + `arch_perf_report.sh:36,65` and `arch_hardware.sh` (journal-size regex + demanded a space before the unit, but `journalctl` prints `305.5M`, so the + vacuum was unreachable). **Expect more. When a branch looks untestable, + check whether it is reachable in production first** — the user's standing + answer is "fix it in this commit". -Still exempt and measured by an earlier session, **all predate the instrument -fixes and may now measure higher — re-measure before writing a single test**: +Still exempt and measured by an older session; **all predate the instrument +fixes and may now measure higher — re-measure before writing any test**: `dwm_config.sh` 98.48%, `pacman_hook_stall_setup.sh` 96.30%, `aw_autostart.sh` 97.56%, `rpi_nc_install.sh` 96.59%, `transcribe_pkgmgr.sh` 98.00%, `clean_audio_filters.sh` 91.40%, `transcribe_deps.sh` 92.31%. -`rpi_nc_ca.sh` (73/73) and `dot_resolver_install.sh` (39/39) are at 100% and -could come off the allowlist whenever you want, by the three-step bar. - -`nc_php.sh` does not measure at all — "kcov instrumented no lines", on every -version of the instrument. Same bucket as `arch_sysctl.sh`; treat as unverified. +`rpi_nc_ca.sh` (73/73) and `dot_resolver_install.sh` (39/39) are already at +100% and can come off the allowlist whenever you want, by the three-step bar. +That is two free entries.