Summary
Remove foreign-show packages (and other unexpected com.tindrum.* apps) from already-deployed devices over WiFi, without re-provisioning. Closes the gap left by v1.2.0, where SHOW_REMOVE_PACKAGES only applies during the provision pass — devices already on the floor with a foreign show app aren't cleaned until re-provisioned.
Why this lives in the status tooling
ml_status.sh already (a) iterates the fleet in parallel, (b) detects foreign packages — tindrum_extra at ml_status.sh:320 = installed com.tindrum.* that isn't TARGET_PACKAGE/KIOSK_PACKAGE, written into the per-device run JSON, and (c) has the --fix apply machinery (fix_device, ml_status.sh:408; dispatch ml_status.sh:694). The scrub plan can be computed from the JSON already collected — no extra device round-trip.
Why a NEW flag, not --fix
CLAUDE.md is explicit: "behavior that's fine in the flash→provision→deploy chain (e.g. removing apps) is wrong for a standalone fleet remediation pass." --fix only nudges settings (non-destructive, safe on show day). Uninstalling packages must be a deliberate, separate, confirmed action.
CLI surface
ml_status.sh --scrub # dry-run: report what WOULD be removed (default)
ml_status.sh --scrub --apply # actually uninstall
ml_status.sh --scrub -d <ip> # single device
- Dry-run by default;
--apply is the trigger (mirrors --check muscle memory).
- Composable with
--failures; --json emits the plan/result.
What gets scrubbed
SHOW_REMOVE_PACKAGES (active show conf) — known foreign-show apps. Always in scope (BLUE → com.tindrum.kagamu).
- Detected extras — the existing
tindrum_extra set, gated behind --scrub-extras so an unexpected-but-maybe-wanted package isn't nuked by reflex.
Hard exclusions (never removable): active SHOW_PACKAGE and KIOSK_PACKAGE (ml_status.sh:69-70) — belt-and-suspenders even if SHOW_REMOVE_PACKAGES is misconfigured to list the active app.
Implementation notes (reuse, don't rebuild)
- Add
--scrub/--apply/--scrub-extras to the arg case (ml_status.sh:88).
- New
scrub_device() modeled on fix_device (ml_status.sh:408): read per-device JSON, intersect installed pkgs with SHOW_REMOVE_PACKAGES (+extras), minus exclusions. Uninstall via the proven two-step pm uninstall --user 0 <pkg> then pm uninstall <pkg> (ml_provision.sh:602-605). Log to logs/<ts>_<serial>.log.
- Dispatch parallels the
AUTO_FIX loop (ml_status.sh:694).
- Guard every capture with
|| true — see the set -e/pipefail regression in v1.2.1; an unguarded x=$(… grep …) aborts the whole run.
Safety / guardrails
--scrub --apply is destructive → must call show_confirm (like flash/provision/deploy), honoring ML_SHOW_CONFIRMED. Plain --scrub (dry-run) stays banner-only and read-only.
- Online-only (skip OFFLINE stubs); idempotent ("already gone" = no-op success); per-device + final
Scrubbed/Skipped/Failed counts; a failed uninstall is logged, never aborts the fleet run.
Reporting
- Table: post-run block of devices × removed packages.
- JSON: add
"scrub": { "planned":[…], "removed":[…], "failed":[…] } per device (additive; tindrum_extra already present).
- Dashboard:
fleet_dashboard.html already flags extra com.tindrum.*; rows clear on the next --json refresh — no dashboard change needed.
Testing (device-free, fits tests/)
Feed scrub_device a synthetic per-device JSON + fake adb/pm shim; assert: foreign app targeted, SHOW_PACKAGE/KIOSK_PACKAGE never targeted, blank config = no-op, dry-run issues zero uninstall calls.
Open decisions
- Dry-run default +
--apply (recommended) vs. act immediately with show_confirm as the only gate.
- Ship
--scrub-extras tier in v1, or SHOW_REMOVE_PACKAGES-only first.
- Flag name:
--scrub (preferred) vs --remove-foreign vs --clean.
Context: spun out of the Osaka BLUE bench work (v1.2.0 added SHOW_REMOVE_PACKAGES for the provision pass). Spec only — not scheduled.
🤖 Generated with Claude Code
Summary
Remove foreign-show packages (and other unexpected
com.tindrum.*apps) from already-deployed devices over WiFi, without re-provisioning. Closes the gap left by v1.2.0, whereSHOW_REMOVE_PACKAGESonly applies during the provision pass — devices already on the floor with a foreign show app aren't cleaned until re-provisioned.Why this lives in the status tooling
ml_status.shalready (a) iterates the fleet in parallel, (b) detects foreign packages —tindrum_extraatml_status.sh:320= installedcom.tindrum.*that isn'tTARGET_PACKAGE/KIOSK_PACKAGE, written into the per-device run JSON, and (c) has the--fixapply machinery (fix_device,ml_status.sh:408; dispatchml_status.sh:694). The scrub plan can be computed from the JSON already collected — no extra device round-trip.Why a NEW flag, not
--fixCLAUDE.md is explicit: "behavior that's fine in the flash→provision→deploy chain (e.g. removing apps) is wrong for a standalone fleet remediation pass."
--fixonly nudges settings (non-destructive, safe on show day). Uninstalling packages must be a deliberate, separate, confirmed action.CLI surface
--applyis the trigger (mirrors--checkmuscle memory).--failures;--jsonemits the plan/result.What gets scrubbed
SHOW_REMOVE_PACKAGES(active show conf) — known foreign-show apps. Always in scope (BLUE →com.tindrum.kagamu).tindrum_extraset, gated behind--scrub-extrasso an unexpected-but-maybe-wanted package isn't nuked by reflex.Hard exclusions (never removable): active
SHOW_PACKAGEandKIOSK_PACKAGE(ml_status.sh:69-70) — belt-and-suspenders even ifSHOW_REMOVE_PACKAGESis misconfigured to list the active app.Implementation notes (reuse, don't rebuild)
--scrub/--apply/--scrub-extrasto the argcase(ml_status.sh:88).scrub_device()modeled onfix_device(ml_status.sh:408): read per-device JSON, intersect installed pkgs withSHOW_REMOVE_PACKAGES(+extras), minus exclusions. Uninstall via the proven two-steppm uninstall --user 0 <pkg>thenpm uninstall <pkg>(ml_provision.sh:602-605). Log tologs/<ts>_<serial>.log.AUTO_FIXloop (ml_status.sh:694).|| true— see the set -e/pipefail regression in v1.2.1; an unguardedx=$(… grep …)aborts the whole run.Safety / guardrails
--scrub --applyis destructive → must callshow_confirm(like flash/provision/deploy), honoringML_SHOW_CONFIRMED. Plain--scrub(dry-run) stays banner-only and read-only.Scrubbed/Skipped/Failedcounts; a failed uninstall is logged, never aborts the fleet run.Reporting
"scrub": { "planned":[…], "removed":[…], "failed":[…] }per device (additive;tindrum_extraalready present).fleet_dashboard.htmlalready flags extracom.tindrum.*; rows clear on the next--jsonrefresh — no dashboard change needed.Testing (device-free, fits tests/)
Feed
scrub_devicea synthetic per-device JSON + fakeadb/pmshim; assert: foreign app targeted,SHOW_PACKAGE/KIOSK_PACKAGEnever targeted, blank config = no-op, dry-run issues zerouninstallcalls.Open decisions
--apply(recommended) vs. act immediately withshow_confirmas the only gate.--scrub-extrastier in v1, orSHOW_REMOVE_PACKAGES-only first.--scrub(preferred) vs--remove-foreignvs--clean.Context: spun out of the Osaka BLUE bench work (v1.2.0 added
SHOW_REMOVE_PACKAGESfor the provision pass). Spec only — not scheduled.🤖 Generated with Claude Code