Mirrors the source split in the previous commit: the six tests exercising
adb_verify_trusted_identity and adb_forget_trusted_device move to
test_adb_trusted.sh, beside the adb_trusted.sh they cover.
Shared fixtures go to adb_test_harness.sh (76) -- counters, run_test, the adb()
mock, the injection payloads, and the XDG_STATE_HOME redirect into a mktemp
dir. That redirect had to travel with the mock: a test file sourcing the
library without it writes to live phone state.
10 + 6 = 16, the pre-split count. That sum is the check that matters -- a
dropped test or a file that failed to source its fixtures would lower the
total while both files still printed success. Also verified both exit 0
explicitly (the summary line prints either way), and that a copy with one
assertion replaced by `return 1` still exits 1, so neither file is vacuously
green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
adb_trusted.sh (136) the enrolled-device record: save, list, forget, match,
and refuse to act on an unenrolled phone
adb_locking.sh (76) the single-instance run lock and the cooldown markers
Both are sourced at the end of adb_common.sh from its own directory, so every
consumer keeps getting the whole API from the one source line it already has.
No consumer was edited. Nothing deploys a copy of this lib elsewhere, so the
deployed-copy blocker that stops install_leechblock.sh does not apply.
Picked the seam by coverage, not size: 13 of 23 functions are named by
test_adb_common.sh, and both extracted groups sit inside that covered set.
One real edit. _matches_trusted_record used to shadow TRUSTED_DEVICE_FILE with
a `local` so _load_trusted_device_values would read it; across a file boundary
shellcheck can't see that and reported SC2034. Fixed by making the function
take the path as an argument, which is what the caller meant -- not by a
suppression. shellcheck passes with none added, at a hook that runs with no
severity filter.
16/16 tests still pass, and sourcing adb_common.sh alone still defines every
function from all three files.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
`pm uninstall --user 0` unregisters a package for one user while leaving
the APK on the system image. It needs no root and no factory reset, and
unlike `pm suspend` and `pm disable-user` -- both measured to be cleared
by a reboot on this device -- it survives one. Applied to the YouTube
app, YouTube Music, and Chrome; `--restore` puts any of them back with no
download.
focus_owner could not have done this: its sweep filters to third-party
packages, and YouTube ships at /product/app/YouTube with FLAG_SYSTEM, so
the Device Owner path would skip it even after the wipe.
Verified on 23181JEGR08034 across two reboots: all three read
installed=false, and in Firefox m.youtube.com, www.youtube.com, a /watch
URL and a youtu.be short link all fail to connect while example.com
loads. The web half of that is RethinkDNS domain rules, which live on the
device and are not in this commit; docs/youtube-block-unrooted.md records
how to rebuild them, and what this does NOT resist -- the VPN is a few
taps from off, which only Device Owner can prevent.
The trusted-device record becomes one file per device, so managing a
second phone no longer evicts the first. Unenrolled devices still abort,
and a changed model or build fingerprint aborts rather than falling
through to the next record.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JxNrcMDcizNB3XrrYpZF23
"Pre-commit checks" has failed on every push since b83ce5d removed
shellcheck's --severity filter. The findings never appeared locally: CI
installs an older shellcheck than this machine, and 0.11.0 no longer emits
SC2002/SC2015/SC2119 at all. Reproduced by fetching shellcheck 0.9.0, which
matches CI's output exactly, and verified against both versions.
SC2015 x6 -- rewrote `A && B || C` as an explicit if. Each keeps its trailing
`|| true`, so a failing side effect still cannot abort the caller under set -e.
The first draft dropped it in run_phone.sh; a review pass caught that and it
was restored, since that call is the last statement of a function.
SC2119 x3 -- require_imagemagick and adb_select_device take an OPTIONAL first
argument, so the bare calls were already correct. Following shellcheck's
literal advice and passing "$@" would forward the script's own arguments as a
tool preference, which would be a real defect. Passing an explicit "" states
the same intent without changing behaviour, confirmed by running both forms.
SC2002 x2 -- `cat FILE | tr ...` becomes `tr ... < FILE`, keeping 2>/dev/null
so a missing file still yields an empty string.
No suppressions, no --severity filter, no exclusions added. Verified: 0
findings under both shellcheck versions, `pre-commit run shellcheck
--all-files` passes, test_adb_common 13/13, test_boot_repair 31/31, 1193
Python tests at 100% coverage.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ReVsw7qovYkkGbqcU9tn7
The shellcheck hook ran with --severity=warning, so info- and style-level
findings never failed a commit. 222 had accumulated across 94 of 244 shell
files. They were not all style noise -- six were bugs:
- Five scripts in single_use/misc/testsAndMisc-bash/ sourced
$SCRIPT_DIR/../../lib/common.sh, one directory too shallow. Nothing exists
at that path, so all five failed at runtime.
- setup_personal_website.sh and setup_kcd2_dice_solver.sh called usage()
(which ends in `exit 0`) and then `exit 1` for an unknown command. The
exit 1 was unreachable, so a typo'd subcommand reported SUCCESS.
- Six i3blocks scripts had a `# shellcheck source=` directive missing the
scripts/periodic_background/ path segment. The stale path had silently
disabled source-following for all of them.
- diagnose_arch_performance.sh word-split a newline-joined file list; when
the find came back empty, awk got no file operands, fell through to
reading stdin, and the diagnostic hung.
- lib/common.sh's handle_arg_help_or_unknown ended in an unreachable
`return 0` while documenting "Returns: 0 if handled" -- a contract no code
path could satisfy.
- deploy.sh's SCRIPT_DIR is reassigned by config.sh, which it sources at top
level, and config.sh can repoint it at the on-device path
/data/local/tmp/focus_mode. Renamed to DEPLOY_DIR.
The SC1091 bulk (81 findings) was resolved rather than silenced: .shellcheckrc
sets external-sources + source-path=SCRIPTDIR so sourced files are actually
followed and analysed. That is what surfaced the i3blocks and deploy.sh bugs.
17 `# shellcheck disable=` directives remain, each with a written reason, and
only where the finding is provably wrong: injection-test payloads that must not
expand, literal source text being grepped for, POSIX sh scripts on the phone
where arrays do not exist, and source paths that only exist at runtime.
Everything else was fixed in code.
Verified in a CLEAN worktree, not just my own tree. config.sh sources the
gitignored config_secrets.sh, which exists locally but not in a fresh checkout;
a working-tree-only check passed while ci_mirror.sh -- which lints HEAD in a
clean worktree, exactly as CI does -- failed on it.
The gate is verified to bite: a file whose only finding is SC2016 (info) fails
the hook, while the old --severity=warning gate exits 0 on it.
third_party/ is excluded -- vendored upstream, matching the prettier hook.
Not fixed, reported: check_polling_antipatterns.sh carries a pre-existing
SC2034 disable that hides a real bug -- its `patterns` array is never used, so
the checker detects nothing. Confirmed unchanged by this commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The focus system only ever filtered this phone's own traffic: /system/etc/hosts
via the system resolver, dns_enforcer and the curfew net layer via OUTPUT. A
tethered second phone's packets are FORWARDed + NAT'd through us on a path none
of that covers, so a second phone on the hotspot browses freely on our mobile
data and defeats focus mode.
Add tether_enforcer.sh: an always-on daemon that acts while focus mode is ON
(at home) or a force-test file is present, converging three levers each tick:
1. disable Android tether offload (settings global tether_offload_disabled=1,
snapshot/restore) so forwarded traffic goes through netfilter;
2. blanket REJECT of the FORWARD chain (iptables + ip6tables, FOCUS_TETHER_BLOCK
pinned at position 1), rebuilt only when tampered via a chain_intact gate so
it does not fork an `iptables -L` every second (that overheated the phone
during curfew-net tuning). Covers WiFi, USB and BT tethering;
3. best-effort `cmd wifi stop-softap` (Android 11+) so the hotspot visibly
flips off.
Reverts offload + tears down FORWARD on transition away from home / on SIGTERM.
Wiring: config.sh flags, focus_ctl.sh (tether-status/start/stop/log/test-on/off),
magisk_service.sh boot autostart, deploy.sh push/cp/restart. README section and
a working subprocess regression test (lib/tests/test_tether_enforcer.sh, 17/17).
Verified on-device (BL9000, Android 13): our REJECT jump sits ABOVE Android's
tetherctrl_FORWARD chain, tether_offload_disabled=1 took effect, and
`cmd wifi stop-softap` reports "Soft AP stopped successfully".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q6i3cdhKBoUrKxy4rmx6hH
Root cause of the zero-runs-ever CI mystery: GitHub Actions was disabled
at the repo level (actions/permissions enabled:false). Re-enabled it via
the API, which immediately surfaced a backlog of pre-existing issues
that local pre-commit runs (scoped to changed files only) never caught:
- pylama is unused (grep confirms zero references outside
requirements.txt) and its pytest plugin auto-loads via setuptools
entry points, crashing the pytest-coverage hook with
ModuleNotFoundError: pkg_resources on the CI runner. Removed it.
- zsh-syntax hook had no zsh binary on the CI image; added an install
step.
- _load_trusted_device_values() in adb_common.sh had an unused optional
arg (shellcheck SC2120); confirmed via grep it's never called with one,
including in tests, and simplified it away.
- claude-code-review.yml/claude.yml had a stray trailing blank line.
None of this is related to the wake_alarm gatelock migration committed
earlier; it's purely the CI/lint backlog that surfaced once Actions
actually started running for the first time in the repo's history.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A7vbgtFfZmfxJtN5DdtJky