diff options
| author | Craig Jennings <c@cjennings.net> | 2026-08-04 00:22:34 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-08-04 00:22:34 -0500 |
| commit | e833b2dabadc733e59630a194eb003f367ecde29 (patch) | |
| tree | 2c42e2c16b7b56e9f67dd07e1479ae48c2b82aad | |
| parent | f487561cfa340b87b6595180a4acb8872fb538a3 (diff) | |
| download | archsetup-e833b2dabadc733e59630a194eb003f367ecde29.tar.gz archsetup-e833b2dabadc733e59630a194eb003f367ecde29.zip | |
The idle-commit lock-chain bug was fixed on 2026-07-29 and stayed open at [#A]
six days past deadline. Verified against the tree rather than the commit
message: the wrapper is in the rendered chain, the live config matches, and the
test that used to pin the bug now pins the fix. The evidence the task itself
named is that the relaunch log is receiving entries, so the instrument is
present rather than silently removed.
Recorded a finding that changes the night-watch task's premise. Two relaunches
on 2026-07-30 postdate the mitigation, and the watchdog logs the exit code but
not the sender, so "the night watch is the only thing killing hyprlock" is now
unproven. Instrument before designing the handshake.
The world face lost half its premise when the 07-31 rebuild dropped the layout
flag, so it re-scopes to the hour-format toggle alone and drops to [#C]. Filed
the decaying spine tests and answered the drift audit's open delivery question,
which is what let it take :solo:.
| -rw-r--r-- | todo.org | 143 |
1 files changed, 99 insertions, 44 deletions
@@ -99,44 +99,6 @@ Grading: Minor severity (no wrong behavior today; it's a latent drift that produ :solo: — the design was settled when the fix was chosen, the surface is enumerable (=session.restore=, =programs.py=, =panel.apply_toggle=), and it is verifiable locally: the existing restore suite plus a test that a stored active program is reconstructed field by field after a simulated session start. -** TODO [#A] Idle commits silently drop the screen-lock wrapper :bug:hyprland:dotfiles:security: -DEADLINE: <2026-07-29 Wed> -:PROPERTIES: -:CREATED: [2026-07-29 Wed] -:LAST_REVIEWED: 2026-07-29 -:END: -Caught live 2026-07-29 05:30, seconds after it happened, while verifying that Craig's watch-stage change had landed. - -=idle.py= renders the *whole* hypridle.conf, including a hardcoded =GENERAL= block. That block said =lock_cmd = pidof hyprlock || hyprlock=. The live config said =|| screen-lock=. So every idle-stage commit through the panel rewrote =lock_cmd= and dropped the wrapper out of the chain. - -The wrapper is not incidental. It carries the flock duplicate guard (the fix for the 2026-07-27 unrecoverable wedge), the crash-relaunch watchdog, and the relaunch log. Parking one stage removed all three in a single write, and nothing said so. - -=tests/settings/test_settings.py:562= asserted the bare =|| hyprlock= form, so the suite *enforced* the regression. That is why 3845 tests stayed green through a day of work on exactly this subsystem. A test can pin the bug as readily as the fix. - -The false-negative this sets up is worth naming: with the wrapper gone the relaunch log stops receiving entries, and an empty log reads as "the problem is fixed" when it means "the instrument was removed". The =screen-lock= header already warns that an empty file is not proof; this is the mechanism that would have produced one. - -FIXED here, TDD, in the working tree pending commit: -- =idle.py= =GENERAL= now names =screen-lock=, with a comment saying why the line is load-bearing. -- The test now pins the wrapper form. Red first against the old template. -- Live config rewritten through the panel's own path and hypridle restarted; =lock_cmd= confirmed back to =screen-lock=, one hypridle running. - -Grading: Critical severity (=write_conf= truncates, so any hypridle key the renderer does not model is silently deleted rather than preserved — that is configuration data loss, and the =lock_cmd= case proved it happens in the field) x some users sometimes (only when an idle stage is committed, which is rare) = P2 = [#B]. - -An earlier draft graded this [#A] on a "security carve-out". That was wrong: disarming the guard is an availability problem, not a leak, and the carve-out is for privacy, security, compliance and safety. The severity band is what carries the weight here, and silent deletion of configuration is the =Critical= band's data-loss case. - -Two further fixes came out of an independent review of the first one: - -- *Fail-open restored.* =pidof hyprlock || screen-lock= made the wrapper the end of the chain, and =screen-lock= is a stow symlink in =~/.local/bin=, not a system binary. An unstowed tree, or a hypridle started without =~/.local/bin= on PATH, resolves it to 127 — so the screen would never lock *at all*. That is worse than the duplicate client the wrapper prevents. The chain now ends =|| hyprlock=, matching the wrapper's own fail-open discipline. -- *The file now says it is generated.* Three comment lines at the top of the rendered output name the renderer and warn that edits are overwritten. The absence of that header is how the divergence survived unnoticed. - -Still open, and why this stays a task rather than closing with the fixes: the header warns, but nothing *prevents* the next divergence, and the exposure is wider than =lock_cmd= alone. The review enumerated it: - -- =before_sleep_cmd= and =after_sleep_cmd= sit in the same hardcoded block, at identical risk. -- Every stage command is hardcoded in =_stage_commands= (brightness level, lock, watch, dpms, suspend), same one-way overwrite. -- =write_conf= *truncates* rather than merges, so any hypridle key the renderer does not know about (=ignore_dbus_inhibit=, =ignore_systemd_inhibit=, =inhibit_sleep=, =on-lock=, =on-unlock=) is deleted rather than preserved. That is the largest hole: a key nobody has added yet would vanish the first time a stage is parked. - -Options: have the renderer preserve the existing general block and unknown keys instead of emitting its own, or accept the template as the single source and move every hypridle setting into the panel. A design call for Craig, and the truncation half is the part that will bite next. - ** TODO [#B] "SCREEN OFF" reads as a state, not a stage :bug:dotfiles:design: :PROPERTIES: :CREATED: [2026-07-29 Wed] @@ -171,6 +133,12 @@ ROOT CAUSE of the lockdead screens, found 2026-07-29 00:50 within minutes of the The collision, on hypridle's own timings: - t=450s: =loginctl lock-session= → screen-lock → hyprlock up, watchdog waiting on it. - t=480s: =settings-watch start= maps the kiosk and kills hyprlock. Exit 143. + +*Two relaunches after the mitigation, unexplained (found 2026-08-04).* The watch stage was parked in =db5ac60= at 2026-07-29 05:59, and nine of the eleven entries in =screen-lock.log= fall before that (07-28 19:00 through 07-29 05:08, all rc=143, the collision as diagnosed). But two more landed on *2026-07-30 10:58:11 and 10:58:13* — rc=143 then rc=137, SIGTERM then SIGKILL, two seconds apart. Craig unlocked the keyring 30 seconds later. + +So parking the stage did not stop every relaunch. Either something else kills hyprlock, or that pair was Craig at the keyboard. The log cannot distinguish those, and the journal for that window shows only an unrelated settings tick. + +What would settle it: the watchdog does not record *who* sent the signal, only the exit code. Logging the killer (or at least distinguishing a session-initiated unlock from an external kill) would turn this from a guess into a reading. Worth doing before designing the handshake, since the handshake assumes the night watch is the only thing killing hyprlock and that assumption is now unproven. - t=480s + =LOCK_RELAUNCH_DELAY= (0.5s): the watchdog relaunches hyprlock, which comes back *on top of* the night watch. - In that half-second gap the session is locked with no client, so Hyprland draws lockdead. The replacement hyprlock then blurs it. @@ -295,9 +263,21 @@ What's off is the *grouping* for multi-line messages. Thirteen =systemd-coredump Consequence is modest: the digest's top-N rows get eaten by near-duplicates, so genuinely distinct errors fall off the evidence list sooner. It doesn't affect the metric's value or severity. The question is what you'd want: group coredumps by the binary named in the first line (a special case for =systemd-coredump=), signature only the *first line* of any multi-line message (a general rule, and arguably the right one — the first line is the error, the rest is context), or leave it alone. The middle option is the smallest general change and I'd lean that way, but it changes grouping for every multi-line error, so it's yours to call. -** TODO [#B] World face: orientation + hour-format pickers in the settings panel :feature:dotfiles: -The world face (=settings/faces/world.html=, shipped 6afbe09) already supports both orientations and 12/24-hour via =?layout= and =?hour12= query params, defaulting vertical/12h. What's missing is letting Craig CHOOSE them from the desktop-settings panel. Build: (1) two new fields in the wallpaper state (world_layout, world_hour12) with the vertical/12h defaults; (2) =project.py build_uri("world")= appends =&layout=&hour12== read from state; (3) panel controls in the world channel's config section (=gui.py _conf_projected=, currently just a preview) — an orientation toggle and a 12/24 toggle; (4) TDD the URI-building and state round-trip. Solo — buildable, agent-verifiable (URI + state tests, headless render), no open design question (the two faces already exist and are approved). Requested by Craig 2026-07-23; deferred so the vertical face could ship first. +** TODO [#C] World face: hour-format picker in the settings panel :feature:dotfiles:solo: +:PROPERTIES: +:LAST_REVIEWED: 2026-08-02 +:END: +Put a 12/24-hour toggle for the world face in the desktop-settings panel. The knob already exists — =settings/faces/world.html= reads =hour12= from =SETTINGS= with a =?hour12== URI override, defaulting to 12 — but the only way to change it is to hand-edit a launch URI, which is not a way to change anything. + +Build: (1) a =world_hour12= field in the wallpaper state, default true; (2) =project.py build_uri("world")= appends =&hour12== read from state; (3) a 12/24 toggle in the world channel's config section (=gui.py _conf_projected=, currently just a preview); (4) TDD the URI-building and the state round-trip. Solo — buildable, agent-verifiable through the URI and state tests plus a headless render, and nothing left to decide. + +Re-scoped 2026-08-02, down from [#B]. As written on 2026-07-23 this also wanted an orientation picker, because the face then supported vertical and horizontal through =?layout==. The 2026-07-31 rebuild replaced that with the single vertical spine and its horizontal day axis, and no =layout= flag survives in =world.html= — so the orientation half has nothing left to pick between and is dropped rather than deferred. One toggle is parking-lot work, not this cycle's, hence [#C]. + +Not =:quick:= despite being small: four pieces with tests is a sitting rather than a spare moment. I said "probably quick" when recommending the re-scope and that was optimistic — the piece count didn't drop, only the field count within each. ** TODO [#C] Wallpaper channel: timed transitions as an alternative to sunrise/sunset :feature:dotfiles: +:PROPERTIES: +:LAST_REVIEWED: 2026-08-02 +:END: From the roam inbox (Craig, claimed 2026-07-23): the wallpaper channel switches on sunrise/sunset today (the sun-pair mode, =settings/src/settings/wallpaper.py=, location read live via whereami with a state.json cache). Add a timed-schedule mode as an alternative: fixed clock times drive the transitions rather than the solar calc. Not :solo: — the capture itself flags the missing inputs ("we'll need to know the transition times, and how many of them there are"). The count and the times are a design decision Craig owes: is it a two-image day/night flip at fixed hours, an N-way ring across the day, per-image dwell vs shared interval? The =set= channel already does fixed-interval cycling through a set, so the new part is specifically clock-anchored transition points, not just "a timer". Ask for the schedule shape at pickup, then build against the existing wallpaper.apply presenter vocabulary. @@ -320,12 +300,38 @@ From the roam inbox (Craig, 2026-07-25). If auto-dim is toggled off and the layo :END: From the roam inbox (Craig, 2026-07-25). The maintenance queue button shows the status wall but has no copy button. Add one, following the global COPY key pattern already on the maint doctor wall (dotfiles =8bc79ba=). Grade: cosmetic/feature = [#C]. ** TODO [#B] Panel family: unify the look across net/bt/maint/audio and desktop-settings :feature:design:dotfiles: +:PROPERTIES: +:LAST_REVIEWED: 2026-08-02 +:END: From the roam inbox (Craig, claimed 2026-07-24): the network, bt, maint, and audio waybar panels look alike, but the desktop-settings panel looks quite different. He wants them to read as one family. Deliverable: enumerate every difference (chrome, header layout, typography, spacing, control styling, color roles, close-button placement, section dividers) between the two groups and a plan to converge them on one look. Not :solo: — it needs a design pass and Craig's taste calls on which direction each group moves. When picked up, catalogue the deltas from live captures of all five, propose the shared design language (likely the Dupre instrument-console the settings panel uses, since that's the newest and most deliberate), then bring Craig the change list before touching code. -** TODO [#C] Panel text cut off — needs a few px more space :bug:dotfiles:solo: +** TODO [#C] Panel text cut off — needs a few px more space :bug:dotfiles:quick:solo: +:PROPERTIES: +:LAST_REVIEWED: 2026-08-02 +:END: From the roam inbox (Craig, claimed 2026-07-24): panel labels look cut off; a few more pixels of space fixes it. He named the audio and bt panels, but his "before" capture is the networking panel (=~/pictures/screenshots/2026-07-23_202419.png=; "after" resizing =~/pictures/screenshots/2026-07-23_202458.png=), so the whole panel family likely shares the tight spacing. Confirm which panels clip at pickup, then add the padding/width. Grade: cosmetic × every glance at the affected panels = P3 = [#C]. Solo — buildable (CSS/size tweak) and screenshot-verifiable, no design call once the clipping panels are identified. +** TODO [#C] Spine face tests decay against the wall clock :bug:test:dotfiles:solo: +:PROPERTIES: +:LAST_REVIEWED: 2026-08-02 +:END: +=settings/faces/timeline-face-spine.test.mjs= has thirteen =SP.spineRows(g, h)= calls that omit the third argument, so =ref= falls back to its =new Date()= default while the file's events fixture is pinned to =JUL= (2026-07-31 18:30 UTC). Any assertion that depends on how much room the day needs is then measured against today's clock, and rots as the fixture recedes. + +One of them, "spacing is uniform everywhere except the gap home opens", had already rotted: green on 07-31 because that was the fixture's own date, red by 08-02. Fixed in place on 2026-08-02 by pinning =JUL=; the remaining thirteen pass today by luck. The measurement, for whoever picks this up — with =ref=now= the even step is 85.21 and home's gaps are 129.10 / 65.40 (the lower one collapses below a plain gap); with =ref=JUL= the step is 78.54 and the gaps are 129.10 / 145.46. Only the lower gap moves, because =up= does not depend on events and =down= does. + +Six other calls in the same file already pass =JUL= explicitly, so the convention exists and this is a miss, not a gap in the design. Fix: pass =JUL= at every call whose assertion reads geometry. Leave the call around line 747 alone — it sweeps =new Date(t0)= deliberately. + +Grade: minor severity (dev-facing only; no product behavior is wrong, the face itself is fine) x some users, sometimes (each call rots independently, whenever the fixture drifts far enough) = P3 = [#C]. Not merely cosmetic though: a suite that goes red for no real reason is how a genuine regression gets waved through. + +Solo — mechanical, an existing convention to copy, and verifiable by running the suite plus re-running it under a faked clock to prove the determinism actually holds. + ** TODO [#C] Night-watch live telemetry :feature:maint: +:PROPERTIES: +:LAST_REVIEWED: 2026-08-02 +:END: Craig, 2026-07-21 ("mind. blown."): drive the Dupre Night Watch screensaver (docs/prototypes/2026-07-21-night-watch-screensaver-prototype-1.html, the idle-pipeline eye-candy stage in the desktop-settings spec) with real system data instead of synthetic signals — a passive status wall while the machine idles. Candidate mappings: scope = CPU load trace, drift chart = memory pressure history, spectrum = per-core utilization, VU pair = net throughput up/down, blinkenlights = disk I/O, tape counter = uptime, systems lamps / annunciator = maint status verdicts, engine-order telegraph = current power profile, VFD wire = maint status one-liner (temps, battery, pending updates). Browser prototype can poll a small local JSON endpoint; the production shape belongs to the idle-stage build. Depends on the spec's idle-pipeline implementation landing first. ** TODO [#C] Wlogout screen review :bug:hyprland:dotfiles: +:PROPERTIES: +:LAST_REVIEWED: 2026-08-02 +:END: Craig, 2026-07-21: the wlogout window (Super+Shift+Q — lock/reboot/shutdown/logout/suspend/hibernate) "isn't great and has bugs." Review it end to end: catalogue the specific bugs, then assess the design against the Dupre instrument-console family (it predates the panel aesthetic). Config lives in dotfiles; the bind is hyprland.conf:428 (=pgrep -x wlogout || wlogout-menu=). Context: the desktop-settings panel spec withdrew lock/suspend in favor of this screen (2026-07-21 amendment), so it's now the sole owner of session-exit actions — worth being good. Grade each bug found via the severity×frequency matrix; this parent stays a [#C] review until specifics emerge. ** TODO [#B] Audit cgit-published repos for secrets and privacy :bug:security: :PROPERTIES: @@ -1032,13 +1038,21 @@ Some entries are libraries likely pulled in as dependencies (blas-openblas, open - [ ] webkit2gtk - [ ] whisper.cpp -** TODO [#B] Installed-package drift audit :chore:packages: +** TODO [#B] Installed-package drift audit :chore:packages:solo: +:PROPERTIES: +:LAST_REVIEWED: 2026-08-02 +:END: Compare the packages explicitly declared by =pacman_install= / =aur_install= against the installed system, separately from the existing inventory's unexpected-package review. Report missing declared packages and substitutions where a provider is installed instead of the named package (the ratio -=emacs= versus =emacs-wayland= case). Decide whether the audit is on-demand, -timer-driven, or surfaced at startup; it must not make package changes. +=emacs= versus =emacs-wayland= case). It must not make package changes. + +*On demand only* (Craig, 2026-08-02) — a command I run when I want it, not a +timer and not a startup line. It is the only mode that cannot nag me, and the +other two stay cheap to add later once the report has proven itself. This was +the open decision that kept the task off =:solo:=; with it answered the rest is +mechanical, so the tag goes on. From the rulesets Emacs/package-audit handoffs, 2026-07-16. The existing =scripts/audit-packages.sh= validates repository availability, not installed @@ -3194,3 +3208,44 @@ Solo for the instrumentation half only: adding the relaunch logging is buildable Next step when picked up: land the relaunch logging first so the next occurrence produces evidence, then try to reproduce by locking with =wf-recorder= running. Superseded 2026-07-29 by =[#A] Night watch and the lock watchdog fight each other=. The logging landed (dotfiles =5bbe2c3=) and answered it within hours: three =rc=143= entries, SIGTERM, from =settings-watch= killing hyprlock by design. Nothing was crashing, so both the AMD-iGPU and the screen-recorder hypotheses in this task are wrong. Kept closed rather than deleted because the reasoning that led here is worth the record. +** DONE [#A] Idle commits silently drop the screen-lock wrapper :bug:hyprland:dotfiles:security: +CLOSED: [2026-08-04 Tue] DEADLINE: <2026-07-29 Wed> +:PROPERTIES: +:CREATED: [2026-07-29 Wed] +:LAST_REVIEWED: 2026-07-29 +:END: +Caught live 2026-07-29 05:30, seconds after it happened, while verifying that Craig's watch-stage change had landed. + +=idle.py= renders the *whole* hypridle.conf, including a hardcoded =GENERAL= block. That block said =lock_cmd = pidof hyprlock || hyprlock=. The live config said =|| screen-lock=. So every idle-stage commit through the panel rewrote =lock_cmd= and dropped the wrapper out of the chain. + +The wrapper is not incidental. It carries the flock duplicate guard (the fix for the 2026-07-27 unrecoverable wedge), the crash-relaunch watchdog, and the relaunch log. Parking one stage removed all three in a single write, and nothing said so. + +=tests/settings/test_settings.py:562= asserted the bare =|| hyprlock= form, so the suite *enforced* the regression. That is why 3845 tests stayed green through a day of work on exactly this subsystem. A test can pin the bug as readily as the fix. + +The false-negative this sets up is worth naming: with the wrapper gone the relaunch log stops receiving entries, and an empty log reads as "the problem is fixed" when it means "the instrument was removed". The =screen-lock= header already warns that an empty file is not proof; this is the mechanism that would have produced one. + +Fixed by dotfiles =ab059fb= (2026-07-29 05:59). Verified 2026-08-04 against the tree rather than the commit message: =idle.py:47= renders =lock_cmd = pidof hyprlock || screen-lock || hyprlock=, the live =hypridle.conf= matches, and =test_settings.py= now asserts the wrapper is in the chain plus a second test for the bare-hyprlock fallback. The test that used to pin the bug now pins the fix. + +The evidence that matters is the one this task named: =~/.local/var/log/screen-lock.log= is *receiving entries*, so the instrument is present. An empty log was the false negative to fear, and it did not happen. + +FIXED here, TDD, in the working tree pending commit: +- =idle.py= =GENERAL= now names =screen-lock=, with a comment saying why the line is load-bearing. +- The test now pins the wrapper form. Red first against the old template. +- Live config rewritten through the panel's own path and hypridle restarted; =lock_cmd= confirmed back to =screen-lock=, one hypridle running. + +Grading: Critical severity (=write_conf= truncates, so any hypridle key the renderer does not model is silently deleted rather than preserved — that is configuration data loss, and the =lock_cmd= case proved it happens in the field) x some users sometimes (only when an idle stage is committed, which is rare) = P2 = [#B]. + +An earlier draft graded this [#A] on a "security carve-out". That was wrong: disarming the guard is an availability problem, not a leak, and the carve-out is for privacy, security, compliance and safety. The severity band is what carries the weight here, and silent deletion of configuration is the =Critical= band's data-loss case. + +Two further fixes came out of an independent review of the first one: + +- *Fail-open restored.* =pidof hyprlock || screen-lock= made the wrapper the end of the chain, and =screen-lock= is a stow symlink in =~/.local/bin=, not a system binary. An unstowed tree, or a hypridle started without =~/.local/bin= on PATH, resolves it to 127 — so the screen would never lock *at all*. That is worse than the duplicate client the wrapper prevents. The chain now ends =|| hyprlock=, matching the wrapper's own fail-open discipline. +- *The file now says it is generated.* Three comment lines at the top of the rendered output name the renderer and warn that edits are overwritten. The absence of that header is how the divergence survived unnoticed. + +Still open, and why this stays a task rather than closing with the fixes: the header warns, but nothing *prevents* the next divergence, and the exposure is wider than =lock_cmd= alone. The review enumerated it: + +- =before_sleep_cmd= and =after_sleep_cmd= sit in the same hardcoded block, at identical risk. +- Every stage command is hardcoded in =_stage_commands= (brightness level, lock, watch, dpms, suspend), same one-way overwrite. +- =write_conf= *truncates* rather than merges, so any hypridle key the renderer does not know about (=ignore_dbus_inhibit=, =ignore_systemd_inhibit=, =inhibit_sleep=, =on-lock=, =on-unlock=) is deleted rather than preserved. That is the largest hole: a key nobody has added yet would vanish the first time a stage is parked. + +Options: have the renderer preserve the existing general block and unknown keys instead of emitting its own, or accept the template as the single source and move every hypridle setting into the panel. A design call for Craig, and the truncation half is the part that will bite next. |
