diff options
Diffstat (limited to 'todo.org')
| -rw-r--r-- | todo.org | 295 |
1 files changed, 61 insertions, 234 deletions
@@ -21,63 +21,6 @@ The vocabulary is open — topic tags are coined as needed — so these are conv - *Effort / autonomy*: =:quick:= a spare-moment fix (minutes, not a sitting); =:solo:= Claude can carry it end to end — there's a build path, a test path, and no upfront decision needed (a leftover manual spot-check doesn't disqualify it). - *Topic / area* (open): the subsystem a task touches — e.g. =:hyprland:= =:waybar:= =:mpd:= =:music:= =:network:= =:tooling:= =:llm:= =:eask:= =:pocketbook:= =:cmail:=. Coin a new one when it aids filtering. * Archsetup Open Work -** DONE [#B] Maintenance console build :feature: -CLOSED: [2026-07-08 Wed] -:PROPERTIES: -:SPEC_ID: 9d9df833-c592-4aec-a7df-50d588e943ce -:END: -Build the maintenance console per [[file:docs/specs/2026-07-07-maintenance-console-spec.org][maintenance-console-spec.org]] (DOING; review incorporated 2026-07-07 — two rounds, all 13 decisions DONE, all 10 findings DONE, verdict Ready; Craig approved config paths + sysmon right-click re-homing). Design source of truth: [[file:docs/design/maintenance-console-design-ideas.org][maintenance-console-design-ideas.org]]; pixel reference: [[file:docs/prototypes/2026-07-07-maint-console-E5-selector-subpanel.html][E5 prototype]]. CLI-first =maint= package in dotfiles (archsetup-owns-dotfiles applies: edit, test, commit, push, note dotfiles inbox per phase); thresholds TOML + VM harness in archsetup. Each phase gates on green tests before its commit. - -*** 2026-07-07 Tue @ 20:11:23 -0500 Built Phase 1 — package skeleton, thresholds, contract (dotfiles 43a39ac) -maint/ package + shim + tests/maint/ (48 tests, fake-tool harness); thresholds two-layer merge with disable flags + env overrides; capability probe (battery ⇔ type==Battery, verified against ratio's Mains+USB tree); =maint status --json= with disk-usage/pacman-cache/failed-units pilots. Seeded configs/maintenance-thresholds.toml in archsetup from the workflow's values and installed a copy to ~/.config/archsetup/ on ratio (installer wiring stays Phase 13). Full make test green (50 suites). Live on ratio: worst=warn from the real 10.75 GB cache; du grades on its printed total, not its exit code (root-owned partial-download dirs make it exit 1) — pinned in a test. - -*** 2026-07-07 Tue @ 20:49:05 -0500 Built Phase 2 — storage & snapshot collectors (dotfiles 94e8371) -All Phase 2 collectors TDD'd against the fake-tool harness (argv-matched cases added): btrfs unallocated/scrub-age/device-errors (SMART cross-check), per-disk SMART health + last self-test, fstrim posture (discard mount option = continuous trim, ratio's real setup), disk top-consumers on a new slow-local cache (=maint scan --slow=, ~/.local/state/maint), snapper counts split timeline·single·pre-post (split keys on cleanup, not type), full ZFS family. =priv.py= pulled forward from Phase 6 (read-only verbs =smart_json= + =btrfs_scrub_status=, validated args, MAINT_SUDO) so probes stay elevation-free. maint suites 48→118; make test green (51). Live read-only verified on BOTH hosts: ratio worst=warn (10.75 GB cache; scrub 0d, SMART ok), velox over tailscale shows the ZFS family live (ONLINE/autotrim-on/scrub 1d/231 snaps) with a genuine fstrim warn (timer off — Phase 6 Confirm remedy). TOML gained storage keys (btrfs_unalloc_warn_gb, smart_*, fstrim_stale_days, hog_*); installed copies refreshed on ratio + velox. - -*** 2026-07-07 Tue @ 21:30:59 -0500 Built Phase 3 — packages, security, systemd collectors (dotfiles 3eaab6e) -All Phase 3 collectors TDD'd: orphans name+size with [curation.kept_orphans], pacnew safe-delete vs needs-merge, keyring freshness, reboot-required (uname -r vs modules dir), -Qkk on =maint scan --slow= with a noise split (unverifiable-as-user reads, mtime-only, [curation.qkk_known] paths tallied but never counted — an unfiltered live run sat permanently red at 68 "altered" files; classified, ratio shows 10 real findings); =maint scan --net= writes checkupdates/yay/arch-audit/fwupd caches (yay rc-1-silent = AUR-clean zero; failures never clobber good cache; malformed payloads degrade to unprobed), readers grade with age + stale bump; is-system-running with state-aware cause, failed-unit roster since/exit/journalctl hint, maintenance-timers meta-metric (capability-derived expected set, @-prefix instance match), taint letters decoded. Prereq done: arch-audit added to archsetup deps + installed both hosts. Review subagent (live-verified) found 2 Important (yay AUR-clean rc quirk, cache-shape envelope kill) + 2 minor — all fixed red-first; archsetup now enables btrfs-scrub@-.timer on btrfs installs (was ratio-only out-of-band, the meta-metric expects it). maint suites 118→169; full make test green. Live on ratio: 23-metric envelope, worst=warn from real findings (10.75GB cache, 8 orphans, mirrorlist.pacnew, 713 repo updates, 32 CVEs, 1 firmware). - -*** 2026-07-07 Tue @ 21:59:27 -0500 Built Phase 4 — logs + mem·pwr collectors (dotfiles 40c448b) -All Phase 4 collectors TDD'd: journal error digest (identifier groups w/ first/last seen + journalctl hint, known-noise curation tallied but never counted, byte-array MESSAGE decoded), coredumps by binary over a TOML window, kernel/hw event classes (MCE/EDAC/IO/thermal/GPU — generic driver noise excluded), journald disk usage, app-log staleness mirroring the log-cleanup cron's filename-date rules; memory pressure + top-5 RSS, OOM kills (kill-verdict lines only — one event spans several matching lines), swap/zram, temps via sensors -j (k10temp Tctl / coretemp package / amdgpu edge), Intel thermal-throttle count, EPP read, battery capacity/health/charge-cap, unclean-shutdown rate from wtmp (last -x pairing). Throttle/EPP/battery self-gate on sysfs and drop out of the envelope. Review subagent (live-verified) found 1 Blocking — windowed-empty coredumpctl exits 1 (my earlier --since-now pin was wrong), which left the metric permanently unprobed on healthy hosts — plus OOM ~3× overcount and a malformed-curation-entry envelope crash; all fixed red-first, re-review Approve. maint suites 169→227; full make test green. Live read-only both hosts: ratio 34 metrics (telega-server crashing 3× in window — real find), velox 38 (genuine 75% battery-health warn; charge_control_end_threshold ABSENT on velox's cros_ec hardware → Phase 9's SET 80% lever must gate on the file). TOML +journal_disk_warn_gb/coredump_window_days, new [memory], [power] temp/health/unclean keys; installed both hosts. - -*** 2026-07-07 Tue @ 22:35:28 -0500 Built Phase 5 — network posture + services collectors (dotfiles 2e92a9f) -All Phase 5 collectors TDD'd after live-pinning every tool format on both hosts (velox presence sweep over tailscale: full tool set present): nm_state (passive — NM's own connectivity check covers DNS+HTTP, so status never generates traffic), chronyc offset vs [network].ntp_offset_warn_ms, tailscale Health + peer tally, fail2ban per-jail bans, listeners digest via priv ss (expected-entry curation tallied-not-counted, unexpected+wildcard=crit, v4/v6 dedup, multi-holder sockets match any name), firewall (priv ufw status; fallback requires unit-active AND ufw.conf ENABLED=yes; names "ufw down — N public binds exposed" from the digest), rsyncshot freshness (1MB log-tail parse; hourly graded only where the log shows hourly runs — velox is daily-only; daily ≥48h/missing = crit), docker df JSONL + stopped containers w/ [curation.expected_containers], libvirt roster (queried only when libvirtd already active — a read must not socket-activate), cron drift (root rsyncshot expectations gate on backup-source). priv.py +4 read-only verbs; thresholds.curation_entries() tolerant helper. Review subagent (live-verified): Request Changes — 1 Important (malformed fail2ban counts raise ValueError into the envelope) + ufw-disable unit-state false positive, docker unknown-unit sizes reading 0, unfloatable chrony offset; all fixed red-first, re-review Approve. maint suites 227→297; full make test green (54 suites, unpiped exit 0 — an earlier tail-piped run masked make's exit code, rerun properly). Live both hosts: ratio 45 metrics, velox 49. REAL FINDS: listeners crit on both (ratio postgres/8080 wildcard binds, velox syncthing *:8384/*:22000) — curation material for Craig; backups stale on both since 2026-07-06 (truenas down for repairs, Craig confirmed — clears when it returns). TOML +[network]/hourly-backup/cron keys, installed both hosts. - -*** 2026-07-07 Tue @ 23:14:56 -0500 Built Phase 6 — remedy layer, priv verbs, guard, doctor CLI (dotfiles e171c08) -The full remedy layer TDD'd in one pass (no 6a/6b split needed): remedies.py allowlisted table (~30 remedies — exact argv, tiers per the design tables as amended by the dated decisions, re_probe_ids, long flags, RECLAIM SPACE macro resolving steps against the envelope incl. per-config snapper expansion); priv.py +28 write verbs (closed builders, type-validated args, per-verb timeouts; =build()= returns the exact sudo-prefixed argv so dry-run and the GUI arm-press share one string); guard.py pure live-update matcher over new TOML =[updates] guard_patterns= (fnmatch, case-sensitive, pairwise-tested; cold pending-cache is surfaced as a note, never silent); curation.py user-layer writes (mark/unmark/clear + KEEP/MARK KNOWN/EXPECTED wrappers, disable-flags for shipped defaults, self-verifying TOML emitter); doctor.py clean|review + iter_fix streaming the event feed (begin/ok/fail/guard/note; failures red without aborting; re-probed truth after each action); cli =maint fix <id> [--dry-run] [--force]= + =maint doctor clean|review= wall renderer; status envelope now stamps =levers= (always-on for free controls: epp_set, charge_limit, update, topgrade). KILL four guards: pid:name victim, TERM-only verb, session-critical denylist ([curation.session_critical], new TOML table) at preview, comm revalidation + denylist re-check at fire. Live-pinned before coding: coredumpctl HAS NO clean verb (find -mtime +N -delete via priv), log-cleanup at ~/.local/bin/cron/, rsyncshot takes <mode> <keep>, ufw enable needs --force, EPP value enum. Review subagent (live-verified): Request Changes — 1 Blocking (json.dumps astral-char surrogate pairs = invalid TOML: one emoji in a MARK KNOWN example would brick thresholds.load() for the whole console → ensure_ascii=False + write self-verifies by re-parsing before replace) + 1 Important (KeyError from sparse installed TOML escapes the event stream → caught, degrades to a red wall entry) + 2 Minor (cold-cache guard note, comm-vs-denylist re-check); all fixed red-first (6 new tests), re-review Approve. maint suites 297→393 (96 in the new suite); full make test green ×2 (unpiped, exit 0). VERIFIED LIVE on ratio: doctor clean executed the Auto tier for real — paccache -ruk0 freed 223.91 MiB and the re-probe measured the cache drop exactly; the live-update guard trips on ratio's genuinely-pending mesa/vulkan-radeon set. TOML +guard_patterns/+session_critical/+timeline_quarterly installed both hosts. - -*** 2026-07-07 Tue @ 23:52:40 -0500 Built Phase 7 — GUI shell (dotfiles d28435e) -The GTK4 window lands as the fourth panel sibling (net.cjennings.maintpanel, humble-object layering: GTK-free PanelModel presenter + pure viewmodel formatters + thin gui.py view). Read-only shell: faceplate (worst lamp, CVE/ATTN badges, MNT·01, close), inert doctor/update keys, updates strip (pending/CVE/AUR/firmware from the strip ids), eight-band selector (lamp + fixable/watch split from the envelope's levers; updates/security categories fold into PACKAGES), metric-row subpanel per band with per-id value formatting (bytes/percent/days/°C/ms/bool). Hydration off-thread; 3s live tier (build_live: cheap mem·pwr reads) + 30s full rebuild, both visibility-gated; MAINT_PANEL_FIXTURE=good|bad renders shipped envelope fixtures (reshaped from the E5 prototype by tests/maint/gen_fixtures.py, deterministic, real remedy ids) and freezes both tiers so the degraded board stays put on a healthy machine. Review subagent: Request Changes — 1 Blocking (build_live returned bare metrics, so the 3s merge stripped the levers the full envelope established and the band splits oscillated between tiers → attach_levers in build_live) + 2 Minor (synthetic error envelope missing worst read as healthy green → panel.error_envelope with worst=unprobed; smoke leaked the panel process on mid-run assertion errors → try/finally reap); all fixed red-first, re-review Approve (+1 hardening: merge into an error envelope is a no-op). maint suites 393→447 (54 in test_panel.py); full make test green (56 suites, unpiped exit 0). AT-SPI smoke PASS on live Hyprland (faceplate, badges, strip, all 8 tiles, band switch, clean close; window parked on a special workspace, never the active one). Screenshots off-workspace via headless output: bad fixture + live board vs the E5 reference. Live board on ratio honest: 32 CVE / 13 ATTN, NETWORK red from the real listeners crit, SERVICES fixable from the truenas-down backup staleness. GOTCHAS: windowrules need hyprctl reload before first launch (panel opens tiled otherwise); pkill -f "maint panel" self-matches the invoking shell — anchor with "maint panel$". - -*** 2026-07-08 Wed @ 00:39:23 -0500 Built Phase 8 — GUI levers: digests, armed keys, rotary band, live strip (dotfiles 388521e) -The storage/snapshots/packages subpanels grew their evidence digests (disk hogs, per-drive SMART, device errors, snapper by-type, orphans KEEP/REMOVE, pacnew DELETE/MERGE, CVE advisories), packages got the E5 rotary band selector (ORPHANS/PACNEW/ADVISORIES), and the updates strip went live: state-tiered border, UPDATE/TOPGRADE behind the arm-to-override live-update guard, REBOOT offered when required or after an update lands. Every lever is arm-then-fire — first press shows the exact argv on the new act line (identical to =maint fix --dry-run=, the parity criterion), second press fires through =doctor.iter_fix= with the re-probe landing back on the act line; one key armed at a time, band/deck switch disarms; fixture boards always dry-run and never write curation, which is what lets the AT-SPI smoke exercise the full press-press path safely. Engine support: snapper probe emits =stale_singles= (DELETE STALE's candidates, newest kept), both scrub probes parse running-%, =doctor.pending_updates()= public for the arm-press guard read, fixtures regenerated with deterministic evidence. Review subagent (live-verified): Request Changes — 2 Important (refresh tiers raced a firing remedy: a 30s tick could probe the package DB mid-transaction and land stale data after the fire's own re-probe → tiers pause on =firing= + post-fire rebuild retries past an in-flight tick; digest builders KeyError'd on identity-less evidence rows replayed from cache files → skip-and-degrade, 8 new red-first tests) + 4 Minor (all taken: broadened strip-key except, scrub key offers first idle target instead of vanishing while any runs, busy-refused press no longer consumes the arm, fire-time guard refusal names the tripped set in GUI wording). Re-review: Approve. maint suites 447→516; full =make test= green (exit 0, unpiped); AT-SPI smoke PASS end-to-end on the bad fixture; headless-output screenshots vs E5; live board honest on ratio (real orphans w/ KEEP/REMOVE, 10.5 GB cache CLEAN, arm-press names the genuinely pending mesa set). Velox pulled to 388521e (no restow/TOML/windowrule changes). Scroll-position-on-arm check added to Manual testing. - -*** 2026-07-08 Wed @ 01:51:26 -0500 Built Phase 9 — GUI: systemd, logs, mem·pwr, network, services (dotfiles 9a2985f + a3da942) -Shipped in two gated halves. 9a (9a2985f): failed-unit + timer rosters (armed RESTART/RESET per unit; ENABLE only on present-but-inactive timers), logs rotary deck (SIGNAL / KNOWN NOISE / COREDUMPS / KERNEL·HW) with the journal MARK KNOWN lifecycle (arm-then-fire mark bound to identifier + sample, UNMARK, armed CLEAR MARKS, empty-sample withhold), OPEN JOURNAL terminal delegation, row levers VACUUM/CLEAR/RUN. 9b (a3da942): CPU-mode segmented control (current + "default" excluded — priv's closed verb refuses "default"), top-memory guarded KILL (session-critical disabled pre-press, arm refusal behind it, fire-time comm revalidation behind both), battery SET 80% gated on the sysfs knob (velox: unsupported → withheld, verified live), listeners curation (probe emits pid + owning unit from the cgroup v2 line; STOP for system.slice services, KILL otherwise, "?" watch-only), firewall ENABLE / tailscale UP / chrony STEP row levers, services deck (containers MARK EXPECTED + START, docker df, cron roster + backups w/ worst-of chip lamp). RECLAIM SPACE deferred to Phase 10's wall by design — the act line can't honestly render a 7-step stream. Reviews: 9a Request Changes (curation done-callbacks bypassed the busy-retry rebuild → routed through _refresh_after_fire) → Approve; 9b Request Changes (DEFAULT EPP dead control + cgroup hybrid-hierarchy, "?"-curation, cron chip lamp, unprobed ✓ minors — all fixed red-first) → Approve. maint 516→612 tests; make test 58 suites exit 0; AT-SPI smoke PASS across all 8 bands; headless screenshots vs E5; live envelopes verified both hosts. -**** 2026-07-08 Wed @ 01:13:48 -0500 Built 9a — systemd + logs subpanels (dotfiles 9a2985f) -Failed-unit roster (armed RESTART/RESET per unit, since/exit context, journalctl hint) + expected-timer roster (ENABLE only on present-but-inactive timers); logs rotary deck (SIGNAL / KNOWN NOISE / COREDUMPS / KERNEL·HW) with the journal MARK KNOWN lifecycle (arm-then-fire mark bound to identifier + sample, UNMARK, armed CLEAR MARKS, empty-sample withhold), OPEN JOURNAL terminal delegation, and row levers VACUUM / CLEAR / RUN. Review subagent (live-verified): Request Changes — 1 Important (curation done-callbacks called refresh_full, which silently no-ops while a tick refresh holds busy → a mid-tick write left a pre-write envelope on the board up to 30s while the act line claimed success; routed all four incl. Phase 8's keep through _refresh_after_fire) + minors taken (single _mark_token definition, three test gaps closed, docstring truthfulness, stale arm wording cleared on band/deck switch — my own find during screenshot eyeballing). Re-review: Approve. maint 516→562 tests; make test 58 suites exit 0 unpiped; AT-SPI smoke PASS incl. the new rosters/deck/mark path; headless screenshots vs E5. velox pulled (no restow). Accepted gap: KNOWN NOISE subsec lists the live curation table on a fixture board — fixture-sourced entries list is later polish. - -*** 2026-07-08 Wed @ 02:36:54 -0500 Built Phase 10 — results wall + doctor keys (dotfiles cc2fb5d) -Results wall shipped as the session action log: doctor events stream per-event (begin opens a running entry with the exact argv, ok/fail resolves in place, re-probe notes follow), 3.5-visible-row scroll cap with autoscroll, HIDE/COPY (COPY emits CLI-wall text), curation writes logged (fixture-suppressed as dim notes). CLEAN UP streams the auto tier with no arm press; REVIEW & FIX swaps the subpanel to the confirm-tier roster (item-less remedies get armed FIX keys, per-item ones point at their subpanel; review events carry item + worst severity). RECLAIM SPACE macro landed on the disk-usage row (deferred from 9b); macro arm line shows first step + remaining count; zero-step macros refuse by name. Review (live-verified): Request Changes — 1 Important: the roster's FIX for update/topgrade could never override the live-update guard (fire-time re-arm used the bare rid while the FIX key arms on "rid:", and _on_lever never passed force — an infinite arm→guard loop); fixed by threading the pressed token through _fire and sharing the strip's guard arming (_guard_arm_line + panel.guarded). Minors all taken: wall_abort resolves stranded running entries red when a stream dies, empty-macro IndexError → named RemedyError, test predicate tightened. Re-review: Approve, no residuals. maint 612→645; make test 59 suites exit 0 ×2; AT-SPI smoke extended (wall, HIDE/SHOW, CLEAN UP stream, review roster, 14-line RECLAIM stream) PASS; screenshots vs E5; LIVE verify on ratio: real CLEAN UP on the live board streamed "3 done" with real re-probes (coredumps warn 5, disk_usage ok 60). Velox pulled cc2fb5d. - -*** 2026-07-08 Wed @ 03:24:44 -0500 Built Phase 11 — waybar glyph + timers (dotfiles 10033be) -custom/maint replaced custom/sysmon: waybar-maint renders from the state file maint-scan.timer writes every 30 min; color tracks the worst diagnostic (lever-less) metric only, actionable findings never color the bar; missing/stale scan data dims to an honest unprobed state naming the timer. Battery hosts show live battery % + charging icon from sysfs on waybar's interval; low charge forces red. Left-click toggles the console (new maint-panel wrapper); right-click keeps the btop scratchpad. Bare =maint scan= became the glyph scan (envelope → state → RTMIN+12); maint-net-scan.timer drives =--net --slow= hourly. Retired same commit: waybar-sysmon, sysmon-cycle, both suites, #custom-sysmon CSS ×3 — plus unnamed collateral the grep caught: waybar-collapse's collapsed base set (maint glyph stays on battery hosts — it IS the battery display now) and a stale dotfiles todo. Review (live-verified): Approve with 4 Minor, all taken red-first — scope!=Device gate so a wireless mouse's battery can't masquerade as the machine's (shared capability.device_scope), unprobed diagnostics named in the tooltip instead of "all clear" under a dimmed glyph, comment truth, dead state field dropped; re-confirm clean (677 maint tests). Full make test exit 0; smoke PASS (first run's pipe masked a transient AT-SPI race — rerun unpiped clean). LIVE both hosts: ratio timer fired for real (glyph amber from genuine diagnostics: firmware update + journal errors; 11 actionable NOT coloring), velox pulled + one-time stow + battery 100% text w/ warning class. velox's user systemd manager found wedged (pre-existing) — timers enabled via manual symlinks + state seeded; Craig rebooting to clear. - -*** 2026-07-08 Wed @ 04:15:45 -0500 Built Phase 11b — prototype fidelity pass (dotfiles 3ee22a8) -Rebuilt the GUI presentation to the E5 instrument-card idiom. Each band renders a 4-column card grid: big-number readouts (viewmodel.card_spec registry — one builder per bespoke metric id, count-card fallback for the rest), cairo progress bars w/ threshold ticks (cache scaled to 1.5× the warn line), radial gauges (scrub cadence, keyring/topgrade age, temperatures — sweep scaled to the metric's own threshold), status chips (PASSED/FAILING/INACTIVE/YES; firewall chips the state word, exposure detail moves to the caption), and the CPU-mode segmented control promoted from digest section to card (current mode lit, alternatives as joined armed keys — same DIGEST_ONLY epp_set arm-then-fire). Evidence digests stay full-width below the grid. Selector became a two-row 4-col grid of wide tiles w/ count chips (3! · 7✓, crit reddens) + severity left borders; subpanel header carries the attention/ok/fixable/watch summary right-aligned beside a gold band title. The scoped NVMe wear dial was skipped honestly — no standalone wear metric exists (wear rides SMART evidence rows). Presentation only: engine/remedies/doctor/curation and the lever contracts unchanged. New 42-test suite (card kinds, threshold scales, running-scrub override, unprobed degrade incl. tick drop, seg exclusions, chips/subhead/band_counts, both-fixture sweep). Review (live-verified): Approve w/ 1 Important (pre-existing: chrony "not running" string crashed format_value's ms branch — fixed w/ card guard) + 4 Minor all taken red-first (disk None guard, _pct negative clamp, unprobed tick, dead row_detail/split_label/.maint-val removed); re-review Approve. make test 61 suites exit 0 ×2 unpiped; AT-SPI smoke 92 checks PASS (new gauge/chip/subhead assertions — DrawingAreas carry accessible_role=IMG + names so the smoke can see them); all 8 bands + review roster screenshotted on both fixtures + the live board against the settled headless-Chrome prototype render. Velox pulled (no restow). Gotcha pinned: a card body's vexpand propagates up a Gtk.Grid — a one-card band ballooned until grid.set_vexpand(False). - -*** 2026-07-08 Wed @ 05:05:20 -0500 Ran the granted /refactor sweep over the maint package (dotfiles a178470..73e9d94) -Four parallel read-only scans (complexity, duplication, dead-code, simplification) converged on the same hotspots; ten behavior-preserving commits applied and pushed. Headlines: six curation handlers folded into one _curation helper (~150 lines of repeated fixture/done plumbing, act/wall strings byte-identical), _on_lever/_on_strip_key merged into _press_lever, _on_fired split (_rearm_after_guard + _fire_summary), the "tool: not found / exit N" wording single-sited in cmd.why() across 20 probe sites, exact clones deduped (packages curation_entries, _parse_asctime, double VERSION), ten uniform digest sections folded into _evidence_digest, _card_body split per kind, listener key rule named, plus a small-simplifications batch. Deliberately skipped: format_value's flat chain (readable as-is), _journal_lines generalization (rc-semantics risk), test-panel-maint Makefile target (Phase 12 scope). Verification: suite parity exact pre/post (59 suites / 2490 tests, exit 0 unpiped), AT-SPI smoke PASS end-to-end, live ratio envelope 45 metrics zero unprobed, review subagent walked the full diff against 3ee22a8 and approved (no behavior change; accessible names untouched; its one latent-robustness minor pinned as an invariant comment). gui.py 1680→1605; net −103 lines. Velox pulled. - -*** 2026-07-08 Wed @ 05:46:02 -0500 Built Phase 12 — VM remedy scenarios + AT-SPI target (archsetup d6993d3, dotfiles 0636554) -Scenario orchestration landed as =run-maint-scenarios.sh= over the existing vm-utils.sh snapshot primitives: nine break → =maint fix= → assert scenarios in three groups (logs, packages, systemd), batched per Decision 11 (one boot per group, maint-ready snapshot restored between groups, clean-install restored at the end). =run-maint-nspawn.sh= is the fast lane — the packages group against a cached pacstrap rootfs in seconds. Plan layer is pure (=--list=, no KVM) with a 19-test unit suite; =make test-maint= wires it in. Dotfiles side: panel smoke now runs GOOD + BAD fixture passes (healthy render, badge tallies from the fixture, no-REBOOT, leak check) and =make test-panel-maint= is enumerated. Verified: full VM run 9/9 green from pristine snapshot ×2 (test-results/maint-20260708-054029), nspawn 4/4, smoke 100 checks exit 0, unit suite green under both FS_PROFILE values. Review subagent: Request Changes (FS_PROFILE env leak in the unit suite, journal-vacuum assert vacuous both directions) — all six findings fixed red-first, re-review Approve. FS_PROFILE=zfs scenarios: filtered but unexercised — the zfs base image fails to build (see the zfs DKMS task). - -*** 2026-07-08 Wed @ 06:18:58 -0500 Built Phase 13 — install wiring, workflow move, docs (archsetup bef7053 + 18c081f, dotfiles 9a3f0c7) -Installer: install_maintenance_config in user_customizations installs the shipped TOML to ~/.config/archsetup/ (re-run refreshes it, ~/.config/maint/ user layer never touched) and enables maint-scan/maint-net-scan timers via wants-symlinks (hyprland-only — the units live in that stow tier; no session bus during install, syncthing idiom); orchestrator + dispatch-branch tests pin the wiring. Deps swept from the probes' argv: expac, lm_sensors, fwupd added (arch-audit was already in from Phase 3; the rest ride base/archinstall or existing sections). maint/README.md in dotfiles covers user (CLI, panel, glyph, config layers, guard, privilege) + developer (module map, env seams, four test layers, add-a-metric recipe). system-health-check.org moved home → archsetup docs/workflows/ and rewired: TOML-authoritative severity section, threshold lines cite TOML keys (TOML wins on disagreement), inventory paths → docs/homelab-inventory/, maint-CLI fast-path note; only the four #+HOSTNAME: host inventories moved (+ the truenas specs asset they link) — the personal gear records stay in home per the domain split; discoverability via .ai/project-workflows symlink + notes.org entry; handoff note in home's inbox (2026-07-08-0607) lists home-side removals and flags strix-soak-watch as a candidate to move later. Verified: fresh-shell =maint status= green from installed paths on ratio; make test-unit exit 0; dotfiles make test exit 0; shellcheck count unchanged; review subagent Approve (2 Minor doc nits, both taken). - -*** 2026-07-08 Wed @ 06:18:58 -0500 Flipped the spec to IMPLEMENTED -Spec keyword DOING → IMPLEMENTED with a dated history line naming the closing commits (phases 1-13, dotfiles 43a39ac..9a3f0c7, archsetup d6993d3/bef7053/18c081f) and the Status mirror updated. Residuals tracked as their own tasks: manual-testing checklist, zfs base-image DKMS bug [#C], vNext [#D]. - ** TODO [#B] Manual testing and validation: maintenance console :test: Promoted from the build parent when it closed 2026-07-08 — Craig's checklist, runs once in person. Collects everything not agent-verifiable; populate per verification.md as phases land. Known so far: panel look-and-feel vs the E5 prototype (Craig's eyeball); arm-press wording reads right at the moment of use; results-wall readability during a real doctor run; a real UPDATE through the armed guard (and the TTY path once); REBOOT offer after an update; velox in-person glyph check (battery %, charging glyph, low-charge red); SET 80% charge limit sticks across a charge cycle; KILL on a real memory hog. Phase 8 additions: subpanel scroll position survives an arm press on a long digest (MAINT_PANEL_FIXTURE=bad, PACKAGES band, scroll into the 51-orphan list, press REMOVE — the list should stay put and the key read REMOVE?); a KEEP on a real orphan dims it into the kept section and UNKEEP restores it; MERGE on a needs-merge pacnew opens pacdiff in a foot terminal. @@ -92,6 +35,11 @@ Craig's first testing find: TOPGRADE exited 2 on every launch — topgrade 17.6. ** TODO [#D] Maintenance console vNext :feature: Deferred from the [[file:docs/specs/2026-07-07-maintenance-console-spec.org][spec]] (v1 ships determinate remedies only): AI/workflow assistance on read-only metrics (the vLater tier — failed-unit diagnosis, journal-error fix suggestions, OOM investigation); SIGKILL escalation for TERM-survivors on the KILL lever; live streaming meters on evidence rows where a count could be a level. +** TODO [#D] Retention-repair remedy unreachable from the panel GUI :bug:dotfiles: +snapshot_retention_repair (WRITE SANE LIMITS — =snapper -c <config> set-config TIMELINE_CREATE=yes TIMELINE_LIMIT_*=<TOML values>=) sits in the maint remedy table but is wired into no GUI layer (no reference in panel.py, viewmodel.py, or gui.py) — it's reachable only via =maint doctor review= / =maint fix=. Worse, the REVIEW & FIX roster's wording for item-bearing remedies says "per-item — on its subpanel", which for this remedy points at a key that doesn't exist. Found 2026-07-08 while walking the SNAPSHOTS subpanel with Craig. + +Two fix shapes, decide at work time: (1) wire a digest key — e.g. on the timeline row when the config's installed TIMELINE_LIMIT_* drift from the TOML values (needs the probe to read the installed limits for comparison); or (2) keep it CLI-only deliberately and special-case the roster wording for remedies with no panel key ("CLI only — maint fix"). Option 2 is a few lines; option 1 makes limit drift visible on the board, which is the 2026-05-26 pile-up's root cause. Severity minor × rare edge = P4 per the bug matrix. + ** TODO [#C] minimal tier zsh login PATH gap :bug:dotfiles:quick:solo: The 2026-07-08 fix gave common/ a .zprofile (zsh logins never read .profile, so ~/.local/bin was missing from TTY/ssh PATH — dotfiles 39cef40). The minimal/ tier has the same gap: it carries .zshrc and .profile but no .zprofile, so headless installs (DESKTOP_ENV=none stows minimal/ instead of common/) still get zsh logins without ~/.local/bin. Add a matching minimal/.zprofile (PATH-only prepend, same rationale comment). Verify: fresh minimal stow, zsh -lc 'echo $PATH' shows ~/.local/bin first. The installer itself is clean — it writes no shell profiles; the stow layer owns this. @@ -1129,144 +1077,6 @@ Verified: the only =eval= left in =archsetup= is line 578 in =retry_install=, an * Archsetup Resolved -** DONE [#B] Guard against live mesa/hyprland/wayland-runtime updates :hyprland: -CLOSED: [2026-06-28 Sun] -:PROPERTIES: -:LAST_REVIEWED: 2026-06-09 -:END: -A live =pacman -Syu= that swaps mesa/hyprland/wayland runtime libs out from under a running Hyprland session can crash the compositor: the next GPU-lib call hits a now-"(deleted)" library and SIGABRTs, taking the Wayland clients down with it. Hit ratio 2026-06-07 (mesa 26.0.6 -> 26.1.2 + hyprland upgraded live; Hyprland SIGABRT took down awww/insync/emacs). Likely the driver behind ratio's high lifetime unsafe-shutdown ratio — a crashed compositor forces a hard reset. - -Shipped as a pacman PreTransaction hook rather than a wrapper, so it fires no matter how the upgrade is launched (pacman, yay, topgrade). =scripts/hypr-live-update-guard= aborts the transaction before any package is swapped when the GPU/compositor runtime set is being upgraded AND Hyprland is running, pointing the user to re-run from a TTY with the session stopped; it stays quiet when Hyprland isn't running (the safe from-a-TTY path). Override via =HYPR_ALLOW_LIVE_UPDATE=1= or by touching the sentinel file named in the abort message. archsetup installs the script to =/usr/local/bin= and the hook to =/etc/pacman.d/hooks/= in the hyprland path. Decision logic unit-tested (=tests/hypr-live-update-guard=, 9 cases). Live firing test filed under Manual testing and validation. Commits: archsetup (this session). -** DONE [#B] ZFS pre-pacman snapshot installer step (ZFS-root) :feature:zfs: -CLOSED: [2026-06-30 Tue] -Add a ZFS-root-gated installer step that installs the pre-pacman snapshot pacman hook plus a self-pruning =/usr/local/bin/zfs-pre-snapshot= (KEEP=10). The script is hand-placed on velox, not authored by archsetup, so a reinstall loses it; snapshots accumulated unbounded (53 since April) because nothing prunes them and Sanoid ignores non-autosnap_ names. Gate to ZFS-root (velox; ratio is btrfs). Also correct the stale 2026-01-17 security-doc line claiming it's "already in install-archzfs". Needs the hook file (source from velox) and a ZFS-root VM test. - -Shipped: =configure_pre_pacman_snapshots()= in boot_ux (late, ZFS-gated) + =scripts/zfs-pre-snapshot=; unit tests for pruning + a Testinfra assertion. VM-verified ZFS install passed 97/0 (test_zfs_pre_pacman_snapshot_hook PASSED). The "stale doc" turned out accurate (it's an install-archzfs archive) — left as-is. Design notes and the KEEP=10 script: [[file:docs/design/2026-06-29-zfs-pre-snapshot-installer.org]]. Origin: home handoff 2026-06-29. -** DONE [#B] Waybar timer module :waybar: -CLOSED: [2026-06-29 Mon] -:PROPERTIES: -:LAST_REVIEWED: 2026-05-26 -:END: -Shipped as =wtimer= in the dotfiles repo (=134d61e=), a single always-visible module right of the battery/resource readout, non-collapsible. Covers all four modes (timer / alarm / stopwatch / pomodoro) with multiple running at once: the bar shows the most urgent item with a per-type glyph + "+N" badge, the tooltip lists them all. Left-click creates (fuzzel), middle-click pauses, right-click cancels, scroll cycles the primary; notify fires on completion and pomodoro phase changes. Pure-functions-over-injected-clock design; CLI serializes state with flock + atomic write so the 1s render and click handlers never lose an update or double-fire. TDD: 86 cases, 95% line coverage. Design spec: [[file:docs/design/2026-06-29-waybar-timer-module-spec.org][docs/design/2026-06-29-waybar-timer-module-spec.org]]. Live-verified on velox (glyph renders, position, countdown); the color states + click interactions filed under Manual testing and validation. - -A custom waybar module providing three time-keeping functions, surfaced in the bar with click/scroll controls and dunst notifications on completion. - -- *Alarm* — fire a notification at a wall-clock time (e.g. 2:00pm). Builds on the existing =notify= + =at= pattern from protocols.org. -- *Timer* — count down a duration (e.g. 25m) and notify when it elapses. -- *Pomodoro* — alternating work/break cycles (default 25/5, long break after 4) with the bar showing phase + remaining time. - -Implementation notes (to flesh out when picked up): waybar =custom= module(s) with =exec= polling or a persistent =exec= script emitting JSON; click actions to start/pause/reset; a small state file under =~/.local/state= or =~/.local/var=. Lives in the hyprland tier (=dotfiles/hyprland/.config/waybar/= + a backing script in =hyprland/.local/bin/=). TDD the backing script per testing.md. - -*** 2026-06-24 Wed @ 17:32:37 -0400 Scope expansion from roam capture (folded duplicate) -A roam-inbox capture asked for the same widget and expands the scope, so folding it in here rather than duplicating: -- *One panel, mode-selectable* — a single component where you choose timer / stopwatch / alarm; the icon changes to reflect the selected mode. -- *Stopwatch* — a count-up (the third function alongside the alarm/timer/pomodoro above), hover shows start time ("Stopwatch started: 12:22pm"). -- Hover text per mode: timer "Timer: 5 min", alarm "Alarm: 12:15pm", stopwatch "Stopwatch started: 12:22pm". -- *Multiple simultaneous* — several timers/alarms/stopwatches set and displayed at once, in one panel. -- Deliverable includes proposing a few panel designs and recommending one before building. -** DONE [#B] Sysmon module right-click cycles the visible metric :feature:waybar:solo: -CLOSED: [2026-06-28 Sun] -Shipped in the dotfiles repo (=f7b6896=, implemented from this archsetup session per Craig). =waybar-sysmon= reads a selected metric from =$XDG_RUNTIME_DIR/waybar/sysmon-metric= (absent = host default, so the old behavior is preserved); the new =sysmon-cycle= helper advances through a host-appropriate ring (battery only on a laptop), wraps, and refreshes waybar via signal 12 wired to =on-click-right=. Left-click stays the btop popup. Added cpu/temp/mem icons + thresholds. TDD: 13 new =waybar-sysmon= selection cases + a 9-case =sysmon-cycle= suite, full dotfiles suite green (29 suites). =sysmon-cycle= symlinked into =~/.local/bin= on velox. Live visual/relogin check filed under "Manual testing and validation". Handoff sent to the dotfiles inbox. -Builds on the just-shipped =custom/sysmon= collapse (dotfiles be7469b). Right-clicking the module rotates which metric is the visible one, in a fixed order: battery → cpu → temp → mem → disk → back to battery. Each click advances one step and wraps around. The host default (battery on a laptop, disk on a desktop) is the starting/reset metric; the tooltip keeps showing all metrics regardless. Left-click stays =pypr toggle monitor= (the btop popup) — the cycle lives on =on-click-right=. - -Implementation notes: =waybar-sysmon= needs a persisted selection (a state file in =$XDG_RUNTIME_DIR/waybar/=, absent = host default) that it reads to pick the visible metric. A new =sysmon-cycle= helper bumps the index and signals the module to refresh (add a =signal= to =custom/sysmon=, like the other custom modules; wire =sysmon-cycle= to =on-click-right=). TDD both — extend =tests/waybar-sysmon= for selection-driven output, add a =tests/sysmon-cycle= for the index advance/wrap and the signal. -** DONE [#B] Network module: enterprise WiFi add/edit deferred to vNext :waybar:network: -CLOSED: [2026-06-29 Mon] -Decided 2026-06-29 (Craig): keep v1 to open + WPA-PSK add/edit; the -WPA-Enterprise / 802.1X add/edit form is vNext, not a v1 phase. v1 still -*activates* any saved enterprise profile and points editing at nmtui/nmcli. -Evidence that settled it: 24 saved profiles on velox, 18 WPA-PSK, 0 enterprise — -no 802.1X network in Craig's history, so the form would be unused UI. If one ever -appears, nmtui adds it once and the module activates it thereafter. Spec: -[[file:docs/design/2026-06-29-waybar-network-module-spec.org][2026-06-29-waybar-network-module-spec.org]]. -** CANCELLED [#B] Migrate terminal emulator from foot to ghostty :tooling: -CLOSED: [2026-06-28 Sun 13:58] -:PROPERTIES: -:LAST_REVIEWED: 2026-06-24 -:END: -Decision (Craig, 2026-06-24): switch from foot to ghostty. Drivers: ligatures (foot won't add them) and kitty-graphics + sixel image support (foot is sixel-only, no kitty-graphics plans). ghostty is pure-Wayland on Hyprland, declarative config that fits the theme system, runtime config reload (keybind / SIGUSR2 since 1.2). Trade-off accepted: slightly higher input latency than foot. Already in use as Emacs's terminal renderer, so the config + rendering are familiar and the 06-18 tmux theme was tuned against that surface. Full evaluation: [[file:docs/2026-06-10-terminal-emulator-evaluation.org][docs/2026-06-10-terminal-emulator-evaluation.org]]. - -Migration scope: -- archsetup: add =ghostty= to the package list; decide whether to keep =foot= installed as a fallback or drop it. -- dotfiles: port =foot.ini= → ghostty config (flat key=value). The shared foot.ini sets no font (per-host via =host.ini= include) — replicate that per-host font split for ghostty. -- Themes: the dupre/hudson =themes/<name>/= dirs hold foot configs; add ghostty theme files and teach =set-theme= to write + reload the ghostty config. Watch the reload-clobbers-OSC-10/11 bug (ghostty #2795) when wiring runtime theme switch. -- hyprland.conf: default-terminal keybind, pyprland scratchpad terminals, and any other =foot= references → ghostty. -- Verify on velox + ratio: ligatures render, latency acceptable in tmux+vterm use, dupre theme correct, sixel/kitty-graphics previews work. -** DONE [#C] Scratchpad launch turns on focus-follows-mouse :bug:hyprland: -CLOSED: [2026-06-28 Sun] -:PROPERTIES: -:LAST_REVIEWED: 2026-06-28 -:END: -Root cause: =float_switch_override_focus = 1= in hyprland.conf. With =follow_mouse = 0=, focus still jumped to the window under the pointer when it crossed a floating-tiled boundary, so launching a floating scratchpad re-enabled focus-follows-mouse onto tiled windows. Fixed by setting it to 0 (dotfiles =5619342=). Not a pyprland side effect. - -Imported from roam inbox 2026-06-25. Repro: with two tiled windows, moving the mouse over the other tile does nothing (focus-follows-mouse off, as expected). Then launch a terminal (scratchpad), move the mouse over a tile, and focus now switches to the window under the pointer. Something about the scratchpad/terminal launch flips focus-follows-mouse on. Find what re-enables it (likely a Hyprland focus/input setting or a pyprland scratchpad side effect) and keep it off. -** DONE [#B] mod+J/K focus navigation: raise to front, reach floating, monocle fix :feature:bug:hyprland: -CLOSED: [2026-06-29 Mon] -Three improvements to =layout-navigate= (mod+J/K), validated live on velox: -- Raise the focused window to the front on focus navigation, so focusing a window behind an overlapping floating one brings it forward (dotfiles =5619342=, bundled with the =float_switch_override_focus = 0= scratchpad fix tracked above). -- Cycle into floating windows, so you can navigate back to a scratchpad like any window instead of it being a one-way trip (dotfiles =f2107f7=). -- Fixed a monocle regression from that change: the =cyclenext= dispatcher no-ops between monocle-stacked tiles, so focus navigation now computes the workspace window list and focuses the next/prev by address — layout-independent and floating-inclusive (dotfiles =09815f3=). -** CANCELLED [#C] archsetup Waybar Wi-Fi module should show no-internet state :feature:waybar: -CLOSED: [2026-06-29 Mon] -Consolidated, not dropped: the no-internet/captive indicator + the diagnostics/ -bounce/speed-test scope are now Phase 1 + Phase 3 of the unified -[[*Waybar network module — custom/net][Waybar network module — custom/net]] parent. The work continues there; -this separate entry is retired so it's tracked in one place. Spec: -[[file:docs/design/2026-06-29-waybar-network-module-spec.org][2026-06-29-waybar-network-module-spec.org]]. -** CANCELLED [#B] Audit dotfiles/common directory -CLOSED: [2026-06-28 Sun] -Refiled to the standalone =~/.dotfiles= repo, which owns this content since the 2026-06-16 split. Handoff sent 2026-06-28: =~/.dotfiles/inbox/2026-06-28-1335-from-archsetup-refiled-from-archsetup-task-audit-2026.org=. The three sub-tasks (review ~/.local/bin scripts, remove orphaned configs, verify stowed files are used) travel with it. Cancelled here, not abandoned. -** CANCELLED [#C] Zoom launches in a tiny window :bug:hyprland: -CLOSED: [2026-06-28 Sun 13:56] -:PROPERTIES: -:LAST_REVIEWED: 2026-06-24 -:END: -From the roam inbox: Zoom opens at a tiny size. Needs diagnosis (HiDPI scaling vs a window rule vs XWayland) and live verification with Zoom actually running — held for a Craig-driven debug pass, not a blind fix. -** DONE [#B] btrfs base VM unbuildable — archangel ISO bakes zfs-auto-snapshot :bug:test: -CLOSED: [2026-06-28 Sun] -Resolved: archangel shipped a fixed ISO (2026-06-27) that conditions the baked AUR list on the filesystem, so a btrfs install no longer drags in =zfs-auto-snapshot=. The btrfs base rebuilt and went green in the 2026-06-28 VM run (97/0, zero attributed issues). The EFI removable-fallback hardening is archangel-side and optional. -=make test-vm-base= (btrfs) fails in archangel's installer: the ISO bakes a fixed -AUR list ("downgrade yay informant zrepl pacman-cleanup-hook zfs-auto-snapshot -topgrade ventoy-bin") into every install regardless of =FILESYSTEM=. On a btrfs -install =zfs= isn't present, so =zfs-auto-snapshot='s =zfs= dependency can't -resolve and the unattended pacstrap aborts ("unable to satisfy dependency 'zfs' -required by zfs-auto-snapshot"). This is an archangel ISO bug (the baked list isn't -controllable from =archsetup-test.conf=), so it blocks btrfs-profile VM testing -until archangel ships an ISO that conditions the AUR list on the filesystem (or -drops zfs tooling from non-zfs installs). The 2026-06-27 btrfs base regen attempt -also wiped the prior (unbootable) btrfs base, so there's no btrfs base image until -this is fixed. zfs-profile testing works (=make test FS_PROFILE=zfs=). - -Companion hardening (defense-in-depth, archangel-side): install the bootloader -with a removable =\EFI\BOOT\BOOTX64.EFI= fallback so a base boots even from -fresh/empty NVRAM, and real installs survive firmware that drops boot entries. -** DONE [#B] Network panel UI — review findings :feature:waybar:network: -CLOSED: [2026-07-01 Wed] -Full UI review 2026-07-01 (visual walk of every state + code pass over the view logic), 30 findings: 21 from the agent review + color audit, 9 from Craig. All fixed the same night in a no-approvals speedrun, five commits, each test-gated (33 suites) and the whole panel re-verified via AT-SPI smoke + screenshots. - -*** 2026-07-02 Wed @ 00:00 -0400 Theme pass: contrast, hierarchy, focus, hover, destructive, dialogs (dotfiles 82aad0b, 998829b) -Selected-row captions turned cream (the dim gray measured 2.2:1 on the slate fill, a WCAG fail on the auto-selected active row). Names gained weight and captions dropped a size (type hierarchy beyond color). Focus rings gold, scrollbars slim slate, rows got a hover wash. Disconnect and Forget became terracotta destructive-action (red = off). The Join/Add dialogs carry the dupre contract now (ground, mono, styled entries with a gold focus border, no stock blue anywhere), the Add dialog disables its action button until the SSID is non-empty, its password field gained the peek icon, and long SSIDs ellipsize. - -*** 2026-07-02 Wed @ 00:00 -0400 Connections logic: lies, gaps, races (dotfiles 693f820) -Saved profiles stopped claiming "open" (security shows only when the scan knows it; subtitles are view-aware — Available adds signal %, Saved says just "out of range"). An active wired connection pins above the wifi scan in Available. Add connects immediately (Craig's decision), both add dialogs' action reads Connect. Rescan went through the op state machine (the dead guard let double-clicks double-scan). A failed initial load shows in the boxes with a retry instead of stranding "Loading…". Available's first message is "Scanning networks…". Live-info ages humanize (7m, not 445s). A held portal replaces the internet line (stable row height under the poll). The poll pauses on other tabs. VPN profiles got a VPN glyph. Forgetting the active network warns it will disconnect you. - -*** 2026-07-02 Wed @ 00:00 -0400 Diagnostics restructure: selector, live speed test, streamed verdicts, leaner chrome (dotfiles 787b475) -The six-button wall became a dropdown tool selector (full tool names, a description of the selection, one Run button) revealed under Advanced. Speed Test became Network Performance: a reveal with Run Speedtest + Stop (no button-flips-to-Cancel), a once-a-second elapsed ticker while running, and Download / Upload / Ping (high-latency warn) / Server / Tip rows in the diagnose aesthetic. Get Me Online streams the diagnose rows first, announces each attempt ("Trying Reset Connection…"), then its result, and closes with a bold verdict row — as do diagnose, the single tools, and the portal login; verdicts left the status line. Get Me Online at a held captive portal opens the login flow (doctor runs the safe, reversible portal-login when fix is requested). Connecting to a network that turns out captive sends a desktop heads-up, waits a beat, then opens the login page. Diagnose evidence humanized ("open internet (HTTP 204)", "names resolve (captive.apple.com)"). The title row and Close button are gone (Esc + focus-loss auto-hide cover a transient popup) and the status line became a self-clearing toast. The AT-SPI driver anchors on the Diagnostics tab now. -** DONE [#B] Advanced repair buttons: half width, two per row :feature:waybar:network:quick: -CLOSED: [2026-07-01 Wed] -The wide Advanced buttons shrink the panel and leave the diagnostics output impossible to read. Make each half width, two to a row, and rename where needed to fit. Origin: roam inbox capture. - -Done 2026-07-01 (dotfiles aca6827): the Advanced reveal became a 3-column, 2-row grid (Diagnose, Unblock WiFi, Reset / Restart, Test DNS, Force Portal) per Craig's follow-up; labels shortened, tooltips carry the full descriptions. Verified live + AT-SPI smoke. -** DONE [#B] Panel action-button rows fill the panel width :feature:waybar:network:quick: -CLOSED: [2026-07-01 Wed] -Disconnect / Rescan / Add / Add Hidden, and the Saved row, should be as wide as the panel and the buttons above them. Apply the same homogeneous full-width treatment used on the Available / Saved sub-tabs. Origin: roam inbox capture. - -Done 2026-07-01 (dotfiles aca6827): both Connections action rows are homogeneous full-width, which also fixed the panel resizing when Connect flips to Disconnect. Verified live. -** DONE [#B] Live connection info in the row subtitle :feature:waybar:network: -CLOSED: [2026-07-01 Wed] -The live connection information shown in the row hover should also appear in the small print under the connection name, updated in realtime like the hover. Origin: roam inbox capture. - -Done 2026-07-01 (dotfiles aca6827): a 1.5s poll fills the active row's subtitle with the bar-tooltip fields minus the SSID (signal, interface, internet + age, portal note, throughput), sharing the bar's per-field formatters. Verified live. ** DONE [#B] Right-click date/time: ntp sync + timezone update :feature:waybar: CLOSED: [2026-07-02 Thu] Right-click on the date updates the clock from ntpd (or whatever keeps the clock in sync); right-click on the time runs the update-timezone script. Neither opens a terminal — both run transparently in the background. Errors surface as notifications. The module should detect whether we're online and, if not, show a message instead of running the script. Origin: roam inbox capture 2026-07-02. @@ -1306,35 +1116,6 @@ CLOSED: [2026-07-02 Thu] The end of a wtimer timer didn't fire a desktop notification. Needs reproduction to confirm frequency; priority follows the severity-by-frequency matrix once known (a reliably-missing timer-end alert would rate higher). Origin: roam inbox capture. Root-caused 2026-07-02 (dotfiles ca35642). The pipeline works (a live 3s timer fired and persisted), but notify sent everything --urgency=normal and dunstrc delays normal-urgency popups while a fullscreen window has focus — a timer ending mid-video sat invisible until fullscreen exit. Alarms now go critical urgency, which rides the fullscreen_show_critical rule; verified CRITICAL in dunst history. The alarm sound (paplay, separate from dunst) was never affected. -** DONE [#B] Bake captive-portal login into the net panel :feature:network: -CLOSED: [2026-07-01 Wed] -Make the captive-portal login a first-class net-panel feature instead of the one-off =~/.local/bin/hotel-wifi= script. When the engine sees a held portal, offer "Log in to this network" that runs the plain-DNS + clean-browser flow reversibly (disable DoT -> recover the portal URL from the redirect -> open a clean Chrome profile -> restore DoT when online). Reconcile with the existing =net portal= / =captive= helper, whose DNS-hijack-to-gateway model did NOT match the real Hyatt portal. - -Full mechanism writeup, the working script, and the integration plan: [[file:docs/design/2026-06-30-captive-portal-login.org]]. From the 2026-06-30 Hyatt saga. - -*** 2026-06-30 Tue @ 11:40 -0400 Engine core landed (dotfiles a7d7559) -Replaced =net portal='s old captive-helper hand-off with a =portal-login= repair tier: drop DoT to plain DNS, probe the portal URL (302 / meta-refresh), open a throwaway browser profile, spawn a detached watcher that restores DoT once online (or on timeout). =net portal --restore= is the manual fallback. 7 tests. So =net doctor= / the bar's =net portal= hookups already run the real flow now. Remaining: (1) name the DoT-blocking cause in =net diagnose=; (2) a dedicated "Log in to this network" button in the panel's Diagnose/Repair tab (today it rides the generic =net portal=); (3) live validation against a real captive portal (unit-tested only — didn't run it live to avoid disrupting a meeting). - -*** 2026-07-01 Wed @ 22:41:51 -0400 Live-validated end to end against a local captive simulator (dotfiles c1401db) -The last remainder. tests/net/captive_sim.py is a local redirect portal (302s to a login page until "logged in", then a clean 204). NET_PROBE_URL and NET_PORTAL_TRIGGERS point the whole flow at it (an overridden probe skips the interface binding, which can't reach loopback). Ran live on velox, both restore paths verified: online-detect (login click, watcher saw the 204, DoT drop-in restored within ~2s, clean exit) and the timeout fallback (a watcher that never saw online restored DoT at its 300s deadline). Real sudo mv, real resolved restarts, real redirect URL recovery, real clean-profile Chrome — against a temp drop-in dir, so live DNS was untouched. All three remainders are done; the task is closed. The remaining what-if is a real venue's walled-garden quirks, which only an actual portal exercises. - -*** 2026-07-01 Wed @ 21:44:05 -0400 Diagnose names the DoT block; panel gained Log in to This Network (dotfiles 51e0e2d) -Remainders 1 and 2 landed. The dns-resolve step names the DoT pin when resolution is dead and the drop-in exists (sysio.dot_forced), and routes next_action to the portal login. The panel's hidden Open Portal button became a first-class suggested-action "Log in to This Network", shown whenever the report holds a portal signal (portal step with or without a URL, or the DoT-blocked resolution) via the unit-tested viewmodel.wants_portal_login. TDD, 33 suites green. Remainder 3 (live validation against a real portal) still open. - -*** 2026-06-30 Tue @ 14:59:53 -0400 Live test on velox surfaced two fixed bugs + a deeper follow-up -Force portal (panel Repair tab) = =net-popup net portal= = the same portal-login tier. Tested live on @Hyatt_WiFi (already authorized, so no real intercept). Two bugs fixed in dotfiles (TDD, full suite green): -- Chrome first-run wizard fired on every launch — =_open_portal= made a fresh tempfile profile but passed no first-run flags. Added =--no-first-run --no-default-browser-check= + a unit test. -- Flashing sudo prompt for the DoT drop + pointless resolved restart on velox, where the DoT drop-in the code looks for (=/etc/systemd/resolved.conf.d/dns-over-tls.conf=) doesn't exist. Guarded =_disable_dot=/=_restore_dot= to be true no-ops (no sudo, no restart) when there's no DoT drop-in to move; tests assert no systemctl call fires. -** DONE [#B] Consistent red=off across waybar toggle modules :waybar: -CLOSED: [2026-07-01 Wed] -Extend the red=off convention (just added to the touchpad/mouse indicator) to the other toggles — sound volume, microphone mute, and caffeine — so a disabled / muted / off state reads red across the board. Skip the "cross"/slash; the color alone carries it. Origin: roam inbox capture. - -Already implemented (verified 2026-07-01): =style.css= gives =#pulseaudio.muted=, =#pulseaudio.mic.source-muted=, and =#custom-caffeine.inhibited= the off-state color =#d47c59=, matching =#custom-touchpad.disabled=. Note: caffeine's red fires on =.inhibited= (caffeine ON / staying awake), which is arguably the inverse of "off" — leave as-is unless you want strict off=red semantics there. -** DONE [#B] Microphone-mute keybind :feature:waybar:quick: -CLOSED: [2026-07-01 Wed] -A keyboard shortcut to toggle the mic mute. The pulseaudio#mic module shows the state but there's no hotkey to flip it. Wire a hyprland bind to a mic-mute toggle. Origin: roam inbox capture. - -Already implemented (verified 2026-07-01): hyprland.conf binds both =XF86AudioMicMute= and =Super+Shift+A= to =mic-toggle= (no conflict — airplane is Super+Shift+X). ** DONE [#C] Keybind hints in waybar module tooltips :waybar: CLOSED: [2026-07-02 Thu] Every module's hover tooltip should list its keyboard shortcut(s), for discoverability. Audit the modules and add the bindings to each tooltip. Origin: roam inbox capture. @@ -1350,16 +1131,6 @@ CLOSED: [2026-07-02 Thu] Let each wtimer item carry an optional short text label. The data model already supports it (=add_timer/add_alarm/add_stopwatch/add_pomodoro= all take =label=""=, and =_describe= shows =label or type=); the gap is the fuzzel-driven creation flow, which doesn't prompt for a label. Add the optional label prompt on create. Origin: roam inbox capture. Shipped 2026-07-02 (dotfiles ca35642): =wtimer new= gained a "label (optional)" fuzzel prompt after the type/value prompts; empty keeps the unlabeled default. 2 new tests (89 total in the suite). -** DONE [#C] Alarm tooltip shows time remaining, not alarm time :bug:waybar:quick: -CLOSED: [2026-07-01 Wed] -The =wtimer= alarm tooltip displays the countdown (time remaining) instead of the alarm's wall-clock fire time. For an alarm set to 2:00pm, the tooltip should name the target time, not "1h 23m left". Fix the tooltip rendering in =wtimer= (dotfiles repo). Origin: roam inbox capture. - -Fixed 2026-07-01 (dotfiles): =_describe= now renders an alarm's wall-clock target via a new =format_clock= helper instead of =format_time(remaining)=. TDD test added; full wtimer suite (87) green. -** DONE [#C] Waybar right-cluster module order :waybar:quick: -CLOSED: [2026-07-01 Wed] -Move the timer module to the rightmost position, just left of the systray, and move the battery/sysmonitor module to second-to-rightmost. Config edit in the waybar config (dotfiles hyprland tier). Origin: roam inbox capture. - -Done 2026-07-01 (dotfiles waybar config): =custom/timer= now sits just left of =tray= with =custom/sysmon= second-to-rightmost. waybar regenerated + reloaded live on velox; visual confirmation pending Craig. ** DONE [#B] Pocketbook finish-or-cancel decision :pocketbook: CLOSED: [2026-07-02 Thu] :PROPERTIES: @@ -1864,3 +1635,59 @@ Stable within each backend group. dotfiles 2671472. tailscale is a mesh overlay, not a full tunnel, so it never conflicts. dotfiles 307a0fe. *** 2026-07-05 Sun @ 17:49:51 -0400 Net panel dedupes Proton's own wireguard row The proton CLI's =ProtonVPN <server>= NM profile no longer shows alongside the proton backend row. dotfiles dbc9ee8. +** DONE [#B] Maintenance console build :feature: +CLOSED: [2026-07-08 Wed] +:PROPERTIES: +:SPEC_ID: 9d9df833-c592-4aec-a7df-50d588e943ce +:END: +Build the maintenance console per [[file:docs/specs/2026-07-07-maintenance-console-spec.org][maintenance-console-spec.org]] (DOING; review incorporated 2026-07-07 — two rounds, all 13 decisions DONE, all 10 findings DONE, verdict Ready; Craig approved config paths + sysmon right-click re-homing). Design source of truth: [[file:docs/design/maintenance-console-design-ideas.org][maintenance-console-design-ideas.org]]; pixel reference: [[file:docs/prototypes/2026-07-07-maint-console-E5-selector-subpanel.html][E5 prototype]]. CLI-first =maint= package in dotfiles (archsetup-owns-dotfiles applies: edit, test, commit, push, note dotfiles inbox per phase); thresholds TOML + VM harness in archsetup. Each phase gates on green tests before its commit. + +*** 2026-07-07 Tue @ 20:11:23 -0500 Built Phase 1 — package skeleton, thresholds, contract (dotfiles 43a39ac) +maint/ package + shim + tests/maint/ (48 tests, fake-tool harness); thresholds two-layer merge with disable flags + env overrides; capability probe (battery ⇔ type==Battery, verified against ratio's Mains+USB tree); =maint status --json= with disk-usage/pacman-cache/failed-units pilots. Seeded configs/maintenance-thresholds.toml in archsetup from the workflow's values and installed a copy to ~/.config/archsetup/ on ratio (installer wiring stays Phase 13). Full make test green (50 suites). Live on ratio: worst=warn from the real 10.75 GB cache; du grades on its printed total, not its exit code (root-owned partial-download dirs make it exit 1) — pinned in a test. + +*** 2026-07-07 Tue @ 20:49:05 -0500 Built Phase 2 — storage & snapshot collectors (dotfiles 94e8371) +All Phase 2 collectors TDD'd against the fake-tool harness (argv-matched cases added): btrfs unallocated/scrub-age/device-errors (SMART cross-check), per-disk SMART health + last self-test, fstrim posture (discard mount option = continuous trim, ratio's real setup), disk top-consumers on a new slow-local cache (=maint scan --slow=, ~/.local/state/maint), snapper counts split timeline·single·pre-post (split keys on cleanup, not type), full ZFS family. =priv.py= pulled forward from Phase 6 (read-only verbs =smart_json= + =btrfs_scrub_status=, validated args, MAINT_SUDO) so probes stay elevation-free. maint suites 48→118; make test green (51). Live read-only verified on BOTH hosts: ratio worst=warn (10.75 GB cache; scrub 0d, SMART ok), velox over tailscale shows the ZFS family live (ONLINE/autotrim-on/scrub 1d/231 snaps) with a genuine fstrim warn (timer off — Phase 6 Confirm remedy). TOML gained storage keys (btrfs_unalloc_warn_gb, smart_*, fstrim_stale_days, hog_*); installed copies refreshed on ratio + velox. + +*** 2026-07-07 Tue @ 21:30:59 -0500 Built Phase 3 — packages, security, systemd collectors (dotfiles 3eaab6e) +All Phase 3 collectors TDD'd: orphans name+size with [curation.kept_orphans], pacnew safe-delete vs needs-merge, keyring freshness, reboot-required (uname -r vs modules dir), -Qkk on =maint scan --slow= with a noise split (unverifiable-as-user reads, mtime-only, [curation.qkk_known] paths tallied but never counted — an unfiltered live run sat permanently red at 68 "altered" files; classified, ratio shows 10 real findings); =maint scan --net= writes checkupdates/yay/arch-audit/fwupd caches (yay rc-1-silent = AUR-clean zero; failures never clobber good cache; malformed payloads degrade to unprobed), readers grade with age + stale bump; is-system-running with state-aware cause, failed-unit roster since/exit/journalctl hint, maintenance-timers meta-metric (capability-derived expected set, @-prefix instance match), taint letters decoded. Prereq done: arch-audit added to archsetup deps + installed both hosts. Review subagent (live-verified) found 2 Important (yay AUR-clean rc quirk, cache-shape envelope kill) + 2 minor — all fixed red-first; archsetup now enables btrfs-scrub@-.timer on btrfs installs (was ratio-only out-of-band, the meta-metric expects it). maint suites 118→169; full make test green. Live on ratio: 23-metric envelope, worst=warn from real findings (10.75GB cache, 8 orphans, mirrorlist.pacnew, 713 repo updates, 32 CVEs, 1 firmware). + +*** 2026-07-07 Tue @ 21:59:27 -0500 Built Phase 4 — logs + mem·pwr collectors (dotfiles 40c448b) +All Phase 4 collectors TDD'd: journal error digest (identifier groups w/ first/last seen + journalctl hint, known-noise curation tallied but never counted, byte-array MESSAGE decoded), coredumps by binary over a TOML window, kernel/hw event classes (MCE/EDAC/IO/thermal/GPU — generic driver noise excluded), journald disk usage, app-log staleness mirroring the log-cleanup cron's filename-date rules; memory pressure + top-5 RSS, OOM kills (kill-verdict lines only — one event spans several matching lines), swap/zram, temps via sensors -j (k10temp Tctl / coretemp package / amdgpu edge), Intel thermal-throttle count, EPP read, battery capacity/health/charge-cap, unclean-shutdown rate from wtmp (last -x pairing). Throttle/EPP/battery self-gate on sysfs and drop out of the envelope. Review subagent (live-verified) found 1 Blocking — windowed-empty coredumpctl exits 1 (my earlier --since-now pin was wrong), which left the metric permanently unprobed on healthy hosts — plus OOM ~3× overcount and a malformed-curation-entry envelope crash; all fixed red-first, re-review Approve. maint suites 169→227; full make test green. Live read-only both hosts: ratio 34 metrics (telega-server crashing 3× in window — real find), velox 38 (genuine 75% battery-health warn; charge_control_end_threshold ABSENT on velox's cros_ec hardware → Phase 9's SET 80% lever must gate on the file). TOML +journal_disk_warn_gb/coredump_window_days, new [memory], [power] temp/health/unclean keys; installed both hosts. + +*** 2026-07-07 Tue @ 22:35:28 -0500 Built Phase 5 — network posture + services collectors (dotfiles 2e92a9f) +All Phase 5 collectors TDD'd after live-pinning every tool format on both hosts (velox presence sweep over tailscale: full tool set present): nm_state (passive — NM's own connectivity check covers DNS+HTTP, so status never generates traffic), chronyc offset vs [network].ntp_offset_warn_ms, tailscale Health + peer tally, fail2ban per-jail bans, listeners digest via priv ss (expected-entry curation tallied-not-counted, unexpected+wildcard=crit, v4/v6 dedup, multi-holder sockets match any name), firewall (priv ufw status; fallback requires unit-active AND ufw.conf ENABLED=yes; names "ufw down — N public binds exposed" from the digest), rsyncshot freshness (1MB log-tail parse; hourly graded only where the log shows hourly runs — velox is daily-only; daily ≥48h/missing = crit), docker df JSONL + stopped containers w/ [curation.expected_containers], libvirt roster (queried only when libvirtd already active — a read must not socket-activate), cron drift (root rsyncshot expectations gate on backup-source). priv.py +4 read-only verbs; thresholds.curation_entries() tolerant helper. Review subagent (live-verified): Request Changes — 1 Important (malformed fail2ban counts raise ValueError into the envelope) + ufw-disable unit-state false positive, docker unknown-unit sizes reading 0, unfloatable chrony offset; all fixed red-first, re-review Approve. maint suites 227→297; full make test green (54 suites, unpiped exit 0 — an earlier tail-piped run masked make's exit code, rerun properly). Live both hosts: ratio 45 metrics, velox 49. REAL FINDS: listeners crit on both (ratio postgres/8080 wildcard binds, velox syncthing *:8384/*:22000) — curation material for Craig; backups stale on both since 2026-07-06 (truenas down for repairs, Craig confirmed — clears when it returns). TOML +[network]/hourly-backup/cron keys, installed both hosts. + +*** 2026-07-07 Tue @ 23:14:56 -0500 Built Phase 6 — remedy layer, priv verbs, guard, doctor CLI (dotfiles e171c08) +The full remedy layer TDD'd in one pass (no 6a/6b split needed): remedies.py allowlisted table (~30 remedies — exact argv, tiers per the design tables as amended by the dated decisions, re_probe_ids, long flags, RECLAIM SPACE macro resolving steps against the envelope incl. per-config snapper expansion); priv.py +28 write verbs (closed builders, type-validated args, per-verb timeouts; =build()= returns the exact sudo-prefixed argv so dry-run and the GUI arm-press share one string); guard.py pure live-update matcher over new TOML =[updates] guard_patterns= (fnmatch, case-sensitive, pairwise-tested; cold pending-cache is surfaced as a note, never silent); curation.py user-layer writes (mark/unmark/clear + KEEP/MARK KNOWN/EXPECTED wrappers, disable-flags for shipped defaults, self-verifying TOML emitter); doctor.py clean|review + iter_fix streaming the event feed (begin/ok/fail/guard/note; failures red without aborting; re-probed truth after each action); cli =maint fix <id> [--dry-run] [--force]= + =maint doctor clean|review= wall renderer; status envelope now stamps =levers= (always-on for free controls: epp_set, charge_limit, update, topgrade). KILL four guards: pid:name victim, TERM-only verb, session-critical denylist ([curation.session_critical], new TOML table) at preview, comm revalidation + denylist re-check at fire. Live-pinned before coding: coredumpctl HAS NO clean verb (find -mtime +N -delete via priv), log-cleanup at ~/.local/bin/cron/, rsyncshot takes <mode> <keep>, ufw enable needs --force, EPP value enum. Review subagent (live-verified): Request Changes — 1 Blocking (json.dumps astral-char surrogate pairs = invalid TOML: one emoji in a MARK KNOWN example would brick thresholds.load() for the whole console → ensure_ascii=False + write self-verifies by re-parsing before replace) + 1 Important (KeyError from sparse installed TOML escapes the event stream → caught, degrades to a red wall entry) + 2 Minor (cold-cache guard note, comm-vs-denylist re-check); all fixed red-first (6 new tests), re-review Approve. maint suites 297→393 (96 in the new suite); full make test green ×2 (unpiped, exit 0). VERIFIED LIVE on ratio: doctor clean executed the Auto tier for real — paccache -ruk0 freed 223.91 MiB and the re-probe measured the cache drop exactly; the live-update guard trips on ratio's genuinely-pending mesa/vulkan-radeon set. TOML +guard_patterns/+session_critical/+timeline_quarterly installed both hosts. + +*** 2026-07-07 Tue @ 23:52:40 -0500 Built Phase 7 — GUI shell (dotfiles d28435e) +The GTK4 window lands as the fourth panel sibling (net.cjennings.maintpanel, humble-object layering: GTK-free PanelModel presenter + pure viewmodel formatters + thin gui.py view). Read-only shell: faceplate (worst lamp, CVE/ATTN badges, MNT·01, close), inert doctor/update keys, updates strip (pending/CVE/AUR/firmware from the strip ids), eight-band selector (lamp + fixable/watch split from the envelope's levers; updates/security categories fold into PACKAGES), metric-row subpanel per band with per-id value formatting (bytes/percent/days/°C/ms/bool). Hydration off-thread; 3s live tier (build_live: cheap mem·pwr reads) + 30s full rebuild, both visibility-gated; MAINT_PANEL_FIXTURE=good|bad renders shipped envelope fixtures (reshaped from the E5 prototype by tests/maint/gen_fixtures.py, deterministic, real remedy ids) and freezes both tiers so the degraded board stays put on a healthy machine. Review subagent: Request Changes — 1 Blocking (build_live returned bare metrics, so the 3s merge stripped the levers the full envelope established and the band splits oscillated between tiers → attach_levers in build_live) + 2 Minor (synthetic error envelope missing worst read as healthy green → panel.error_envelope with worst=unprobed; smoke leaked the panel process on mid-run assertion errors → try/finally reap); all fixed red-first, re-review Approve (+1 hardening: merge into an error envelope is a no-op). maint suites 393→447 (54 in test_panel.py); full make test green (56 suites, unpiped exit 0). AT-SPI smoke PASS on live Hyprland (faceplate, badges, strip, all 8 tiles, band switch, clean close; window parked on a special workspace, never the active one). Screenshots off-workspace via headless output: bad fixture + live board vs the E5 reference. Live board on ratio honest: 32 CVE / 13 ATTN, NETWORK red from the real listeners crit, SERVICES fixable from the truenas-down backup staleness. GOTCHAS: windowrules need hyprctl reload before first launch (panel opens tiled otherwise); pkill -f "maint panel" self-matches the invoking shell — anchor with "maint panel$". + +*** 2026-07-08 Wed @ 00:39:23 -0500 Built Phase 8 — GUI levers: digests, armed keys, rotary band, live strip (dotfiles 388521e) +The storage/snapshots/packages subpanels grew their evidence digests (disk hogs, per-drive SMART, device errors, snapper by-type, orphans KEEP/REMOVE, pacnew DELETE/MERGE, CVE advisories), packages got the E5 rotary band selector (ORPHANS/PACNEW/ADVISORIES), and the updates strip went live: state-tiered border, UPDATE/TOPGRADE behind the arm-to-override live-update guard, REBOOT offered when required or after an update lands. Every lever is arm-then-fire — first press shows the exact argv on the new act line (identical to =maint fix --dry-run=, the parity criterion), second press fires through =doctor.iter_fix= with the re-probe landing back on the act line; one key armed at a time, band/deck switch disarms; fixture boards always dry-run and never write curation, which is what lets the AT-SPI smoke exercise the full press-press path safely. Engine support: snapper probe emits =stale_singles= (DELETE STALE's candidates, newest kept), both scrub probes parse running-%, =doctor.pending_updates()= public for the arm-press guard read, fixtures regenerated with deterministic evidence. Review subagent (live-verified): Request Changes — 2 Important (refresh tiers raced a firing remedy: a 30s tick could probe the package DB mid-transaction and land stale data after the fire's own re-probe → tiers pause on =firing= + post-fire rebuild retries past an in-flight tick; digest builders KeyError'd on identity-less evidence rows replayed from cache files → skip-and-degrade, 8 new red-first tests) + 4 Minor (all taken: broadened strip-key except, scrub key offers first idle target instead of vanishing while any runs, busy-refused press no longer consumes the arm, fire-time guard refusal names the tripped set in GUI wording). Re-review: Approve. maint suites 447→516; full =make test= green (exit 0, unpiped); AT-SPI smoke PASS end-to-end on the bad fixture; headless-output screenshots vs E5; live board honest on ratio (real orphans w/ KEEP/REMOVE, 10.5 GB cache CLEAN, arm-press names the genuinely pending mesa set). Velox pulled to 388521e (no restow/TOML/windowrule changes). Scroll-position-on-arm check added to Manual testing. + +*** 2026-07-08 Wed @ 01:51:26 -0500 Built Phase 9 — GUI: systemd, logs, mem·pwr, network, services (dotfiles 9a2985f + a3da942) +Shipped in two gated halves. 9a (9a2985f): failed-unit + timer rosters (armed RESTART/RESET per unit; ENABLE only on present-but-inactive timers), logs rotary deck (SIGNAL / KNOWN NOISE / COREDUMPS / KERNEL·HW) with the journal MARK KNOWN lifecycle (arm-then-fire mark bound to identifier + sample, UNMARK, armed CLEAR MARKS, empty-sample withhold), OPEN JOURNAL terminal delegation, row levers VACUUM/CLEAR/RUN. 9b (a3da942): CPU-mode segmented control (current + "default" excluded — priv's closed verb refuses "default"), top-memory guarded KILL (session-critical disabled pre-press, arm refusal behind it, fire-time comm revalidation behind both), battery SET 80% gated on the sysfs knob (velox: unsupported → withheld, verified live), listeners curation (probe emits pid + owning unit from the cgroup v2 line; STOP for system.slice services, KILL otherwise, "?" watch-only), firewall ENABLE / tailscale UP / chrony STEP row levers, services deck (containers MARK EXPECTED + START, docker df, cron roster + backups w/ worst-of chip lamp). RECLAIM SPACE deferred to Phase 10's wall by design — the act line can't honestly render a 7-step stream. Reviews: 9a Request Changes (curation done-callbacks bypassed the busy-retry rebuild → routed through _refresh_after_fire) → Approve; 9b Request Changes (DEFAULT EPP dead control + cgroup hybrid-hierarchy, "?"-curation, cron chip lamp, unprobed ✓ minors — all fixed red-first) → Approve. maint 516→612 tests; make test 58 suites exit 0; AT-SPI smoke PASS across all 8 bands; headless screenshots vs E5; live envelopes verified both hosts. +**** 2026-07-08 Wed @ 01:13:48 -0500 Built 9a — systemd + logs subpanels (dotfiles 9a2985f) +Failed-unit roster (armed RESTART/RESET per unit, since/exit context, journalctl hint) + expected-timer roster (ENABLE only on present-but-inactive timers); logs rotary deck (SIGNAL / KNOWN NOISE / COREDUMPS / KERNEL·HW) with the journal MARK KNOWN lifecycle (arm-then-fire mark bound to identifier + sample, UNMARK, armed CLEAR MARKS, empty-sample withhold), OPEN JOURNAL terminal delegation, and row levers VACUUM / CLEAR / RUN. Review subagent (live-verified): Request Changes — 1 Important (curation done-callbacks called refresh_full, which silently no-ops while a tick refresh holds busy → a mid-tick write left a pre-write envelope on the board up to 30s while the act line claimed success; routed all four incl. Phase 8's keep through _refresh_after_fire) + minors taken (single _mark_token definition, three test gaps closed, docstring truthfulness, stale arm wording cleared on band/deck switch — my own find during screenshot eyeballing). Re-review: Approve. maint 516→562 tests; make test 58 suites exit 0 unpiped; AT-SPI smoke PASS incl. the new rosters/deck/mark path; headless screenshots vs E5. velox pulled (no restow). Accepted gap: KNOWN NOISE subsec lists the live curation table on a fixture board — fixture-sourced entries list is later polish. + +*** 2026-07-08 Wed @ 02:36:54 -0500 Built Phase 10 — results wall + doctor keys (dotfiles cc2fb5d) +Results wall shipped as the session action log: doctor events stream per-event (begin opens a running entry with the exact argv, ok/fail resolves in place, re-probe notes follow), 3.5-visible-row scroll cap with autoscroll, HIDE/COPY (COPY emits CLI-wall text), curation writes logged (fixture-suppressed as dim notes). CLEAN UP streams the auto tier with no arm press; REVIEW & FIX swaps the subpanel to the confirm-tier roster (item-less remedies get armed FIX keys, per-item ones point at their subpanel; review events carry item + worst severity). RECLAIM SPACE macro landed on the disk-usage row (deferred from 9b); macro arm line shows first step + remaining count; zero-step macros refuse by name. Review (live-verified): Request Changes — 1 Important: the roster's FIX for update/topgrade could never override the live-update guard (fire-time re-arm used the bare rid while the FIX key arms on "rid:", and _on_lever never passed force — an infinite arm→guard loop); fixed by threading the pressed token through _fire and sharing the strip's guard arming (_guard_arm_line + panel.guarded). Minors all taken: wall_abort resolves stranded running entries red when a stream dies, empty-macro IndexError → named RemedyError, test predicate tightened. Re-review: Approve, no residuals. maint 612→645; make test 59 suites exit 0 ×2; AT-SPI smoke extended (wall, HIDE/SHOW, CLEAN UP stream, review roster, 14-line RECLAIM stream) PASS; screenshots vs E5; LIVE verify on ratio: real CLEAN UP on the live board streamed "3 done" with real re-probes (coredumps warn 5, disk_usage ok 60). Velox pulled cc2fb5d. + +*** 2026-07-08 Wed @ 03:24:44 -0500 Built Phase 11 — waybar glyph + timers (dotfiles 10033be) +custom/maint replaced custom/sysmon: waybar-maint renders from the state file maint-scan.timer writes every 30 min; color tracks the worst diagnostic (lever-less) metric only, actionable findings never color the bar; missing/stale scan data dims to an honest unprobed state naming the timer. Battery hosts show live battery % + charging icon from sysfs on waybar's interval; low charge forces red. Left-click toggles the console (new maint-panel wrapper); right-click keeps the btop scratchpad. Bare =maint scan= became the glyph scan (envelope → state → RTMIN+12); maint-net-scan.timer drives =--net --slow= hourly. Retired same commit: waybar-sysmon, sysmon-cycle, both suites, #custom-sysmon CSS ×3 — plus unnamed collateral the grep caught: waybar-collapse's collapsed base set (maint glyph stays on battery hosts — it IS the battery display now) and a stale dotfiles todo. Review (live-verified): Approve with 4 Minor, all taken red-first — scope!=Device gate so a wireless mouse's battery can't masquerade as the machine's (shared capability.device_scope), unprobed diagnostics named in the tooltip instead of "all clear" under a dimmed glyph, comment truth, dead state field dropped; re-confirm clean (677 maint tests). Full make test exit 0; smoke PASS (first run's pipe masked a transient AT-SPI race — rerun unpiped clean). LIVE both hosts: ratio timer fired for real (glyph amber from genuine diagnostics: firmware update + journal errors; 11 actionable NOT coloring), velox pulled + one-time stow + battery 100% text w/ warning class. velox's user systemd manager found wedged (pre-existing) — timers enabled via manual symlinks + state seeded; Craig rebooting to clear. + +*** 2026-07-08 Wed @ 04:15:45 -0500 Built Phase 11b — prototype fidelity pass (dotfiles 3ee22a8) +Rebuilt the GUI presentation to the E5 instrument-card idiom. Each band renders a 4-column card grid: big-number readouts (viewmodel.card_spec registry — one builder per bespoke metric id, count-card fallback for the rest), cairo progress bars w/ threshold ticks (cache scaled to 1.5× the warn line), radial gauges (scrub cadence, keyring/topgrade age, temperatures — sweep scaled to the metric's own threshold), status chips (PASSED/FAILING/INACTIVE/YES; firewall chips the state word, exposure detail moves to the caption), and the CPU-mode segmented control promoted from digest section to card (current mode lit, alternatives as joined armed keys — same DIGEST_ONLY epp_set arm-then-fire). Evidence digests stay full-width below the grid. Selector became a two-row 4-col grid of wide tiles w/ count chips (3! · 7✓, crit reddens) + severity left borders; subpanel header carries the attention/ok/fixable/watch summary right-aligned beside a gold band title. The scoped NVMe wear dial was skipped honestly — no standalone wear metric exists (wear rides SMART evidence rows). Presentation only: engine/remedies/doctor/curation and the lever contracts unchanged. New 42-test suite (card kinds, threshold scales, running-scrub override, unprobed degrade incl. tick drop, seg exclusions, chips/subhead/band_counts, both-fixture sweep). Review (live-verified): Approve w/ 1 Important (pre-existing: chrony "not running" string crashed format_value's ms branch — fixed w/ card guard) + 4 Minor all taken red-first (disk None guard, _pct negative clamp, unprobed tick, dead row_detail/split_label/.maint-val removed); re-review Approve. make test 61 suites exit 0 ×2 unpiped; AT-SPI smoke 92 checks PASS (new gauge/chip/subhead assertions — DrawingAreas carry accessible_role=IMG + names so the smoke can see them); all 8 bands + review roster screenshotted on both fixtures + the live board against the settled headless-Chrome prototype render. Velox pulled (no restow). Gotcha pinned: a card body's vexpand propagates up a Gtk.Grid — a one-card band ballooned until grid.set_vexpand(False). + +*** 2026-07-08 Wed @ 05:05:20 -0500 Ran the granted /refactor sweep over the maint package (dotfiles a178470..73e9d94) +Four parallel read-only scans (complexity, duplication, dead-code, simplification) converged on the same hotspots; ten behavior-preserving commits applied and pushed. Headlines: six curation handlers folded into one _curation helper (~150 lines of repeated fixture/done plumbing, act/wall strings byte-identical), _on_lever/_on_strip_key merged into _press_lever, _on_fired split (_rearm_after_guard + _fire_summary), the "tool: not found / exit N" wording single-sited in cmd.why() across 20 probe sites, exact clones deduped (packages curation_entries, _parse_asctime, double VERSION), ten uniform digest sections folded into _evidence_digest, _card_body split per kind, listener key rule named, plus a small-simplifications batch. Deliberately skipped: format_value's flat chain (readable as-is), _journal_lines generalization (rc-semantics risk), test-panel-maint Makefile target (Phase 12 scope). Verification: suite parity exact pre/post (59 suites / 2490 tests, exit 0 unpiped), AT-SPI smoke PASS end-to-end, live ratio envelope 45 metrics zero unprobed, review subagent walked the full diff against 3ee22a8 and approved (no behavior change; accessible names untouched; its one latent-robustness minor pinned as an invariant comment). gui.py 1680→1605; net −103 lines. Velox pulled. + +*** 2026-07-08 Wed @ 05:46:02 -0500 Built Phase 12 — VM remedy scenarios + AT-SPI target (archsetup d6993d3, dotfiles 0636554) +Scenario orchestration landed as =run-maint-scenarios.sh= over the existing vm-utils.sh snapshot primitives: nine break → =maint fix= → assert scenarios in three groups (logs, packages, systemd), batched per Decision 11 (one boot per group, maint-ready snapshot restored between groups, clean-install restored at the end). =run-maint-nspawn.sh= is the fast lane — the packages group against a cached pacstrap rootfs in seconds. Plan layer is pure (=--list=, no KVM) with a 19-test unit suite; =make test-maint= wires it in. Dotfiles side: panel smoke now runs GOOD + BAD fixture passes (healthy render, badge tallies from the fixture, no-REBOOT, leak check) and =make test-panel-maint= is enumerated. Verified: full VM run 9/9 green from pristine snapshot ×2 (test-results/maint-20260708-054029), nspawn 4/4, smoke 100 checks exit 0, unit suite green under both FS_PROFILE values. Review subagent: Request Changes (FS_PROFILE env leak in the unit suite, journal-vacuum assert vacuous both directions) — all six findings fixed red-first, re-review Approve. FS_PROFILE=zfs scenarios: filtered but unexercised — the zfs base image fails to build (see the zfs DKMS task). + +*** 2026-07-08 Wed @ 06:18:58 -0500 Built Phase 13 — install wiring, workflow move, docs (archsetup bef7053 + 18c081f, dotfiles 9a3f0c7) +Installer: install_maintenance_config in user_customizations installs the shipped TOML to ~/.config/archsetup/ (re-run refreshes it, ~/.config/maint/ user layer never touched) and enables maint-scan/maint-net-scan timers via wants-symlinks (hyprland-only — the units live in that stow tier; no session bus during install, syncthing idiom); orchestrator + dispatch-branch tests pin the wiring. Deps swept from the probes' argv: expac, lm_sensors, fwupd added (arch-audit was already in from Phase 3; the rest ride base/archinstall or existing sections). maint/README.md in dotfiles covers user (CLI, panel, glyph, config layers, guard, privilege) + developer (module map, env seams, four test layers, add-a-metric recipe). system-health-check.org moved home → archsetup docs/workflows/ and rewired: TOML-authoritative severity section, threshold lines cite TOML keys (TOML wins on disagreement), inventory paths → docs/homelab-inventory/, maint-CLI fast-path note; only the four #+HOSTNAME: host inventories moved (+ the truenas specs asset they link) — the personal gear records stay in home per the domain split; discoverability via .ai/project-workflows symlink + notes.org entry; handoff note in home's inbox (2026-07-08-0607) lists home-side removals and flags strix-soak-watch as a candidate to move later. Verified: fresh-shell =maint status= green from installed paths on ratio; make test-unit exit 0; dotfiles make test exit 0; shellcheck count unchanged; review subagent Approve (2 Minor doc nits, both taken). + +*** 2026-07-08 Wed @ 06:18:58 -0500 Flipped the spec to IMPLEMENTED +Spec keyword DOING → IMPLEMENTED with a dated history line naming the closing commits (phases 1-13, dotfiles 43a39ac..9a3f0c7, archsetup d6993d3/bef7053/18c081f) and the Status mirror updated. Residuals tracked as their own tasks: manual-testing checklist, zfs base-image DKMS bug [#C], vNext [#D]. |
