aboutsummaryrefslogtreecommitdiff
path: root/.ai
diff options
context:
space:
mode:
Diffstat (limited to '.ai')
-rw-r--r--.ai/sessions/2026-07-17-10-55-inbox-fixes-emacs-wayland-sentry-prep.org238
1 files changed, 238 insertions, 0 deletions
diff --git a/.ai/sessions/2026-07-17-10-55-inbox-fixes-emacs-wayland-sentry-prep.org b/.ai/sessions/2026-07-17-10-55-inbox-fixes-emacs-wayland-sentry-prep.org
new file mode 100644
index 0000000..da2ca40
--- /dev/null
+++ b/.ai/sessions/2026-07-17-10-55-inbox-fixes-emacs-wayland-sentry-prep.org
@@ -0,0 +1,238 @@
+#+TITLE: Session Context
+#+AUTHOR: Craig Jennings
+#+DATE: 2026-07-16
+
+* Summary
+
+** Active Goal
+
+Process the project inbox (5 handoffs across the session), then remove a dormant clone, switch ratio's Emacs to the pgtk build, and prep the sentry spec for Craig's READY-gate review. Two of the five handoffs were bug fixes that shipped; two were design questions filed for a Monday scouting pass; one was rejected on evidence.
+
+** Decisions
+
+- *gmail residue-probe fix (home):* took home's one-word epoch fix, but hoisted the rule to cover both anchored queries once and scoped it to the anchor windows so the deliberate day-resolution date-slicing isn't implicated. Dropped home's proposed count-check — it depends on resultSizeEstimate + a 100-cap the same file documents as untrustworthy.
+- *secret-scan fix (takuzu):* accepted their case-sensitivity split (measured: ~6% false-positive on 100KB base64 → 0), rejected the =;base64,= line-skip (guards a ~1-in-1.7M event, costs a real blind spot on minified data-URI lines — reproduced). All three variants (elisp/bash/go).
+- *subproject promotion (home):* deferred, not rejected. N=1 (home is the only project with subprojects across 27 scopes); promoting 282 lines into the always-on rules layer contradicts the proposal's own thin-always-on principle. Scheduled 2026-07-20 for a scouting pass.
+- *polyglot bundle collision (home):* fixed the silent half (install-lang guard refuses a colliding second bundle), deferred the design half. Reframed: the guard is NOT "polyglot unsupported" — non-overlapping pairs (bash+python) compose today. The line is overlap-vs-not, and nobody chose it. Scheduled 2026-07-20, paired with subprojects (same scouting question).
+- *wl-copy proposal (clock-panel):* REJECTED on evidence. Their diagnosis was inverted — plain =wl-copy= persists (while its process lives); =--foreground= is what disables the fork. Failed review Q1 (is it right), not the value gate.
+- *settings.json model:* fable → opus, Craig's call (bd76d98). Reverses the deliberate 07-13 fable pin; the field has flip-flopped 4x. =~/.claude/settings.json= symlinks here, so machine-wide.
+- *gloss:* removed the local clone (Craig's direction), remote deliberately left alive. Verified fully pushed before deleting.
+- *ratio Emacs:* installed =emacs-wayland= (pgtk), restoring archsetup's own intent (line 2605, unconditional since 2026-04-11); ratio had drifted for ~3 months. Craig restarted; verified pgtk/native-Wayland.
+
+** Data Collected / Findings
+
+- *gloss:* 24 commits all pushed, live bare remote at 7073b16; local-only =.ai/=/=todo.org=/=inbox/= all regenerable or moot. Safe to delete.
+- *Emacs on ratio was the X11 build* (=extra/emacs=, =:pgtk nil :x t=), on XWayland via DISPLAY=:0 while WAYLAND_DISPLAY sat unused — why xclip worked despite the pure-Wayland rule. archsetup line 2605 says =emacs-wayland=; ratio drifted. archsetup's package tracking is a before/after run diff (=comm -13=, "Statistics"), never an intent-vs-reality audit — the class of drift that hid this.
+- *=emacs.service= failed since 2026-07-09* (start-limit-hit, 5x exit-1, cause undiagnosed — no Emacs stderr in journal). Live daemon is a bare =emacs --daemon= PPID 1, unsupervised. Unit is dotfiles-owned (stow symlink into =~/.dotfiles=, its own repo/scope), package is archsetup's.
+- *I broke Craig's clipboard* testing clock-panel's claim — killed wl-copy owners repeatedly; each kill emptied the clipboard (owner holds the selection only while alive). Restored via Emacs. This corroborated clock-panel's symptom while disproving their mechanism.
+- *sentry spec bookkeeping was broken:* Decision 10 (suite policy) was misfiled under Review findings; Decisions cookie hand-typed [10/10], findings cookie dead =[/]=. Moved it, let org recompute → [10/10] and [12/12], both live. Zero new lint findings vs HEAD.
+- *pkill -f self-matches the shell running it* (exit 144, twice) — promoted to KB.
+
+** Files Modified
+
+- Commits (all pushed to origin/main): cf3eadc (gmail epoch fix + mirror), 794a8bd (secret-scan case-split, 3 variants + 9 bats), c98fda5 (install-lang collision guard + 9 bats + report preserved), the subproject-deferral docs commit, bd76d98 (opus model), 0cc1256 (sentry spec cookie fix).
+- =todo.org=: 2 tasks filed [#C] :spec: SCHEDULED 2026-07-20 (subproject promotion, polyglot support), both carrying the review findings as decision inputs.
+- =docs/design/=: 3 files preserved (subproject proposal + home instance, polyglot collision report).
+- KB: =agents/20260717105401-pkill-f-self-match.org= written + pushed.
+- Replies sent: home ×3 (probe, subprojects, polyglot), takuzu ×1 (secret-scan), clock-panel ×1 (wl-copy reject), archsetup ×2 (emacs drift + package-audit follow-up). gloss local clone removed.
+
+** Next Steps
+
+- *FIRST (Craig's standing ask):* deep-read the sentry spec (=docs/specs/2026-07-14-sentry-workflow-spec.org=), now structurally clean. Flip DRAFT → READY, then spec-response decomposes the build. The load-bearing decision to weigh: launching sentry hands it the repo until the morning merge.
+- *2026-07-20 scouting pass:* subproject promotion + polyglot support, one conversation — "which projects would actually be polyglot / have subprojects, and why."
+- *Owed, unsent:* home's applicability line for github-prs + personal-calendar (residue probe N/A there — one query, no seam). Wording-only.
+- *Unread inbound:* clock-panel's wl-copy review correction (arrived 14:56) — deferred to next session, non-urgent.
+- *Open elsewhere:* velox unverified for the emacs-wayland drift (offline); =emacs.service= failure undiagnosed (dotfiles' + archsetup's, handed off); archsetup asked whether it wants an intent-vs-reality package audit.
+
+KB: promoted 1 / consulted yes
+
+* Session Log
+
+** 2026-07-16 09:11 CDT — Startup
+
+Ran the startup workflow. Phase A.0: rulesets pull skipped (dirty tree — =.claude/settings.json= carries an uncommitted =fable= → =opus= model flip against a committed =fable= pin Craig deliberately kept on 07-13). =make install= had nothing new to link. Project fetch/reconcile clean.
+
+Phase A: no crash anchor (previous session wrapped cleanly). =.ai/= synced from templates, no churn. Staleness zero, language-bundle silent, spec-sort and host-identity probes silent. Roam inbox: 1 item. KB: 87 =:agent:= nodes, no project matches, best-practices node path not resolved.
+
+Phase B: read the three most recent session summaries. Inbox carries 6 new files = 3 handoffs (home's triage-intake probe-gap fix + edited plugin; takuzu's pre-commit secret-scan false-positive fix + fixed hook; home's subproject-pattern promotion proposal + reference =subprojects.org=). All three are shared-asset proposals, so all three go through the inbox engine's skeptical review rather than acting silently.
+
+Craig picked: work the inbox in order (two bug fixes first, subproject proposal last).
+
+** 2026-07-16 10:44 CDT — Inbox item 1: home's gmail residue-probe gap (landed cf3eadc)
+
+Home's diagnosis verified against the canonical and accepted. The probe cut off at =before:<anchor-YYYY/MM/DD>= (day resolution) while the scan started at =after:<anchor-epoch>= (second resolution), and Gmail's =before:<date>= excludes the named day, so the anchor day belonged to neither query and each sweep advanced past it without revisiting. An evening anchor hid most of a day. Exactly the failure the probe was added 2026-07-08 to prevent.
+
+Skeptical review (cross-project battery) produced three findings beyond home's ask:
+
+1. *No engine change needed* — the plugin declares =ANCHOR: epoch= and the engine already computes + substitutes every format, so =before:<anchor-epoch>= works as-is. Verified in =triage-intake.org= (the ANCHOR contract at line 266).
+2. *Answered home's "do other plugins share this?" — no.* Checked all five: cmail and telegram are =ANCHOR: none= (state-based, no since-window, no second query to gap); github-prs and personal-calendar are =ANCHOR: iso8601= but carry no residue probe, so there's only one query. personal-gmail is the only source pairing an anchored scan with a residue probe.
+3. *Declined home's count-equality verification check.* It depends on =resultSizeEstimate= and a full unread count, and the same file documents at lines 28/31 that the estimate is unreliable (stuck at "201" across queries whose union exceeded 300) and the MCP caps at 100 with no =pageToken=. The check would have pointed the next reader at numbers the plugin already says not to trust.
+
+Applied home's one-word fix, plus the rule consolidation home themselves suggested: the epoch rule now states once that it governs *both* anchored queries, and the probe cross-references rather than restating. Critically, the rule had to be *scoped to the anchor windows* — the date-slice walk at line 30 uses =before:<oldest-full-day-seen>= at day resolution deliberately, safe because slices overlap and dedupe by message id. A blanket "always epoch" rule would have implicated it.
+
+=/review-code --staged= surfaced one Minor I fixed before committing: the two epoch examples were ~2 months apart (=after:1778856990=, =before:1784177122=) in a paragraph whose whole point is that both queries must meet at the same instant. Now both show =1784177122=.
+
+Canonical edited first, mirror synced via =scripts/sync-check.sh --fix=, both sides identical. =make test= green (374 pytest + 67 + 12 + ERT + bats, zero failures). Commit cf3eadc, *not yet pushed*.
+
+Replied to home: unblocked (they'd filed it =[#B] :infra:blocker:=), answered both their questions, named the two declines with reasons, and raised one thing for their eyes — github-prs and personal-calendar have an anchored scan and no residue probe at all, so they're blind to pre-anchor items with nothing to catch it. Different question from home's gap; may be fine for open-PR/calendar state.
+
+Deleted both inbox files for the item.
+
+*Observation, not filed:* =cross-project.md= shows bare =inbox-send <target>= invocations, but it's not on PATH — only =.ai/scripts/inbox-send.py= exists, and =claude-templates/bin/= carries just =agent-page= and =ai=. The prose names the script path correctly; the examples imply a command that doesn't resolve. Worth a decision (link it in =make install=, or fix the examples).
+
+** 2026-07-16 11:20 CDT — Inbox item 2: takuzu's pre-commit secret-scan fix (landed 794a8bd)
+
+takuzu reported the hook false-positiving on an embedded PNG sprite data URI, forcing =--no-verify=. They diagnosed two causes and sent a fixed elisp-variant copy. I accepted cause 1, *measured and rejected cause 2*.
+
+*Cause 1 (accepted, correct).* =grep -iE= applied case-insensitivity to the fixed-case AWS token, so =AKIA[0-9A-Z]{16}= matched any mixed-case 20-char run (the =[0-9A-Z]= class also goes case-insensitive under =-i=). Their fix splits fixed-case tokens (AKIA, sk-, PEM) into a case-sensitive pass, leaving only keyword=value patterns under =-i=.
+
+*Cause 2 (rejected on evidence).* Their claim: "even case-sensitive, a 100KB base64 blob can contain AKIA+16 uppercase by chance," justifying a skip of any line containing =;base64,=. I generated ~10MB of real random base64 (100 sprite-sized blobs) and measured:
+
+- case-insensitive (the shipped hook): *7 matches, 6 of 100 blobs would false-positive* (~6%) — this is the live bug.
+- case-sensitive (their fix): *0 matches.*
+- theoretical rate for the residual: ~1 in 1.7M per 100KB blob ((1/64)^4 x (36/64)^16 x 1e5 positions).
+
+So cause 1 is the entire bug; cause 2's justification is ~1e-6. And the skip *costs* something: it drops whole lines. I verified with a minified-bundle line carrying both a data URI and a live =api_key= — under their fix the line is skipped and the key sails through; without it, the key is caught. Minified JS/CSS is exactly where data URIs live and where a bundle is one enormous line, so the blind spot lands precisely where both can co-occur.
+
+Craig picked option 1: case-sensitivity split only, drop the skip, with tests.
+
+*TDD.* Wrote =scripts/tests/pre-commit-secret-scan.bats= first (9 cases across all three variants). Red confirmed: cases 5 and 6 failed on the unfixed hooks, reproducing the live failure. Case 7 (a real credential sharing a line with a data URI) is the guard against anyone re-adding the skip. Green after the fix. Verified the new bats file is discovered by the Makefile glob rather than assuming it (=verification.md='s "a passing gate can skip your new file").
+
+*Self-review caught one regression.* Splitting one grep into two meant a line matching both passes printed twice, reading as two separate leaks. Verified empirically, wrote a 10th test (red), fixed with =awk '!seen[$0]++]'= (green). Verdict was Request Changes on my own diff until that landed.
+
+Applied to all three canonical variants (=languages/{elisp,bash,go}/githooks/pre-commit=); scan block verified byte-identical across them by md5. =bash -n= clean, shellcheck clean modulo two *pre-existing* findings I confirmed against HEAD and left alone: SC2164 on the unguarded =cd "$REPO_ROOT"= at line 8 of elisp+go (bash has =|| exit 1=), and SC2016 on go's gofmt printf. =make test= exit 0, zero failures.
+
+Replied to takuzu with the measurement, the blind-spot repro, the honest caveat (I don't have their actual sprite — if it holds a genuine uppercase AKIA run, case-sensitivity alone won't clear it), credit that their two-pass plumbing is what landed, and the two pre-existing findings.
+
+*Follow-up candidates (not filed yet):* the SC2164 inconsistency across the three variants, and go's em-dash.
+
+*Two commits unpushed:* cf3eadc, 794a8bd.
+
+*Inbox grew mid-item:* two new handoffs from home arrived (polyglot-coverage note at 10:06, reply-probe at 11:14).
+
+** 2026-07-16 11:35 CDT — Pushed cf3eadc + 794a8bd
+
+Craig picked push-then-continue. Pre-push reconcile clean (0 behind, 2 ahead), one remote (=git@cjennings.net:rulesets.git=). Pushed f14dd87..794a8bd, verified upstream at 794a8bd, 0/0.
+
+** 2026-07-16 11:58 CDT — Inbox item 3: home's subproject-pattern promotion (deferred, filed [#C])
+
+home proposed promoting its subproject pattern into =claude-rules/=. I recommended a thin rule (~50 lines) over their 282-line doc; *Craig chose option 3 — defer entirely and scout first.*
+
+His framing, which is the task's actual content: scout which projects would plausibly get subprojects, and why, before shaping any rule. If he hasn't scouted by the time it comes up, offer to do it together — brainstorm candidates, explore the reasons behind each. That evidence decides drop-vs-adjust. Don't shape the rule before the scouting.
+
+*Review findings* (recorded in the task body so the decision has its inputs):
+
+- *N=1, verified.* Scanned all 27 =.ai= scopes: home is the only project with subprojects, all nine from the 2026-06-11 fold.
+- *The placement contradicts the proposal's own principle.* =claude-rules/*.md= loads into every session of every project — the always-on layer. home's doc argues that layer is "a tax paid whether or not it's relevant today" and depth belongs "one open away". At 282 lines it'd be the 3rd-largest rule and +11% on the layer (20 files, 2491 lines), so every unrelated project's session would carry a one-project convention.
+- *Precedent for the shape if it ever promotes:* =patterns.md= (29 lines, explicit "don't carry the catalog in context") and =docs-lifecycle.md= (75 lines, depth in a spec).
+- *Dangling reference:* the doc cites =claude-rules/git-hosting-privacy-model=; no such file (verified). Real content is protocols.org's gitignore-vs-track + public-reachability decision.
+- *Instance vs rule:* metrics, self-improvement log, kill criteria, rollout dates, adoption table are home's.
+
+Content preserved: both files moved to =docs/design/2026-07-15-subproject-pattern-proposal.org= and =docs/design/2026-07-15-subprojects-convention-home-instance.org=, linked from the task.
+
+*Route-candidate: deliberately NOT stamped.* =route_recommend.py= returned "home strong", but that's a false positive — it matched on "home" appearing throughout as the proposer. The work is rulesets' own (deciding rulesets' rules layer, scheduled for Craig here), so it's a local keeper and stays unstamped per core §3.
+
+Replied to home: deferred not rejected, the N=1 + placement reasoning, the two fix-regardless items (dangling ref, instance-vs-rule split), and an ask — if they have a view on which other projects would plausibly fold, that's exactly the scouting input. Also answered their probe reply: agreed on their github-prs/personal-calendar analysis (a gap needs two queries with a seam; those have one query and no seam), taking their suggested doc line to Craig with today's batch, and owned half the =:blocked:=/=:blocker:= mix-up (my reply took their tag at face value rather than checking it against the convention).
+
+*Still open:* home's polyglot bundle-collision note (the last inbox item), and home's suggested applicability line for github-prs + personal-calendar.
+
+** 2026-07-16 13:43 CDT — Inbox item 4: home's polyglot bundle collision (guard landed c98fda5)
+
+home reported that python + typescript both ship =coverage-makefile.txt=, so the second install prints =[skip]= and silently drops its fragment. *The investigation found two worse collisions home hadn't checked.*
+
+*The full collision map* (5 shared filenames, 3 real):
+
+| file | bundles | behavior |
+|------|---------|----------|
+| =gitignore-add.txt= | 5 | appended + deduped → *composes* ✓ |
+| =CLAUDE.md= | 3 | seed-only; its fallback comment shows multi-bundle was already considered ✓ |
+| =coverage-makefile.txt= | 4 (elisp,go,python,typescript) | visible =[skip]=, fragment dropped ✗ |
+| =claude/settings.json= | 3 (bash,elisp,go) | =cp -rT= *silent overwrite*, prints =[ok]= ✗✗ |
+| =githooks/*= | 3 (bash,elisp,go) | =cp -rT= *silent overwrite*, prints =[ok]= ✗✗ |
+
+*Reproduced* (elisp then bash into one project): settings.json flips validate-el.sh → validate-bash.sh; pre-commit loses check-parens; validate-el.sh left orphaned on disk; output prints =[ok] .claude/= and =[ok] githooks/=. So home's severity read was inverted — the =[skip]= they flagged at least announces itself, while these two claim success while removing quality gates. Same class as the 794a8bd secret-scan bug: a gate you believe you have and don't.
+
+*No live damage:* checked every project with a bundle; the colliding trio isn't doubled anywhere, and clock-panel (python+typescript) could only ever have hit the coverage fragment.
+
+*The fix* (Craig picked option 1: fix the silent half, defer the design half). Guard runs *before any write*, so a refusal can't half-install. Detection reuses =sync-language-bundle.sh='s rule fingerprint (project has bundle X iff one of X's rule files is in =.claude/rules/=) — no marker file, works on pre-guard installs. Refuses when the incoming bundle would overwrite a file another shipped bundle also ships, naming each file. =FORCE=1= still overrides and the message now says out loud that FORCE also re-seeds CLAUDE.md (home's catch).
+
+*TDD:* 9 bats cases, red-first. Notably *test 8 initially false-passed* — it asserted the message contained "FORCE=1" and "CLAUDE.md", both of which already appear in the pre-existing =[skip] CLAUDE.md already exists (use FORCE=1 to overwrite)= line, so it passed against an unguarded install. Tightened to assert the refusal fired first and to exclude =[skip]= lines.
+
+*Self-review caught a coverage gap.* The guard had an untested path: a *detected* bundle sharing *no* overwritten file (bash ships settings+githooks, python ships coverage only → zero overlap), which hits =[ -n "$files" ] && ...= under =set -euo pipefail=. Verified by hand that it works, then added test 7 because "worked when I ran it once" isn't coverage and a refactor to a plain =if= could turn it into a false refusal with nothing to catch it.
+
+*That gap changed the framing.* The guard is NOT "polyglot unsupported" — =bash= + =python= composes cleanly today and yields a real polyglot project with both rule sets. The line is *overlap-vs-not*, not polyglot-vs-not, and nobody chose it; it fell out of which bundle happens to ship what. Corrected the task body before filing so Monday's decision doesn't run on a wrong premise.
+
+Also caught a factual error in my own commit draft pre-voice: it credited takuzu for home's report.
+
+*Filed:* =[#C] Polyglot projects — supported, or refused? :spec:= SCHEDULED 2026-07-20, paired with the subproject task (same scouting question). Report preserved at =docs/design/2026-07-16-polyglot-bundle-collision.txt=.
+
+*Separate finding, unfiled:* gloss has =validate-el.sh= on disk but *no settings.json at all*, so its hook has never fired. Single bundle, so not this bug — a different gap.
+
+Replied to home with the full map, the inverted-severity finding, the overlap-vs-polyglot correction, and the gloss note. *Inbox is empty.* Pushed c98fda5.
+
+** 2026-07-16 14:05 CDT — Removed the gloss clone; committed the opus model switch
+
+*gloss removed* (=rm -rf /home/cjennings/code/gloss=) on Craig's direction. Verified before deleting rather than trusting the description: 24 commits all pushed (0 behind / 0 ahead after fetch), one branch, no stashes, no tags, clean tree, and =git ls-remote git@cjennings.net:gloss.git= confirmed a live bare repo at 7073b16. Code is fully recoverable by re-cloning.
+
+Local-only content assessed before the delete (=.ai/=, =todo.org=, =inbox/= are all gitignored there, so none of it was on the remote):
+- =.ai/= 1.5M but *zero* archived sessions and no live context; contents are template-synced from rulesets, so regenerable. Not a real loss.
+- =todo.org= 3 open tasks — all gloss-scoped (integration tests, gloss-core refactor, shakedown), so moot once gloss is gone.
+- =inbox/= one unprocessed handoff: rulesets' own 2026-06-12 priority-scheme ask. Also moot.
+
+Craig's "not a real work in progress" checked out (last commit 2026-05-07, no sessions). *Remote deliberately left alive* — he confirmed "leave the remote", so the project is still re-clonable; =rm -rf= on the clone is not retirement.
+
+*settings.json: fable → opus committed* (bd76d98, pushed). Craig's call. Investigated provenance rather than assuming: the file was last written *2026-07-14 15:32*, i.e. NOT this session and after the last archived session ended (07-14 02:50) — so my initial "the harness wrote it this session" read was wrong and I corrected it before reporting. The field has now flip-flopped four times (73835a2 opus→, d5bc9b3 →fable, e91073d "pin ... as intended", bd76d98 →opus). =~/.claude/settings.json= symlinks here, so it's the machine-wide default.
+
+*Process slip, owned:* I invoked the trivial-one-liner exception to skip =/voice personal= on bd76d98, then wrote a three-line body containing an em-dash. The body took it out of trivial territory; the pass should have run. Not force-pushing over punctuation, but the exception is for subject-only commits.
+
+*Correction Craig is owed on the earlier gloss finding:* I reported "gloss has validate-el.sh but no settings.json, so its hook never fired" and passed that to home. gloss is now deleted, so the note is moot — but the underlying question (does anything else have an orphaned hook?) stands unanswered for the remaining projects.
+
+*New handoff mid-work:* clock-panel sent a Wayland clipboard finding (=wl-copy= selection owner dies with the command). Its own src block arrived with literal =\n= sequences instead of newlines — the sender's formatting is mangled and worth flagging back.
+
+** 2026-07-16 14:30 CDT — Inbox item 5: clock-panel's wl-copy proposal (REJECTED on evidence)
+
+clock-panel proposed adding a =setsid zsh -c '... | exec wl-copy --foreground' &= form to the shared Wayland rules, claiming a plain =printf | wl-copy= "leaves the desktop clipboard empty once the command exits, because the wl-copy selection owner no longer survives."
+
+*Tested all three forms on Craig's machine* (backed up his clipboard first — it held "jotto" — and restored it after). Read back from a *separate* Bash call each time so the originating shell was gone:
+
+| form | result |
+|------|--------|
+| =printf %s "..." \vert wl-copy= (plain) | *works* — survived a 3s wait + unrelated commands; owner alive throughout |
+| their =setsid ... --foreground &= form | *works* — but only by re-creating the default behavior |
+| =wl-copy --foreground= alone, no setsid/=&= | *FAILS* — blocks until killed, clipboard empty after. Their exact symptom. |
+
+*The diagnosis is inverted.* =wl-copy --help=: "-f, --foreground Stay in the foreground instead of forking." wl-copy forks and persists *by default*; =--foreground= is what disables it. So =--foreground= is the *cause* of their symptom, not the cure. Their fix works only because =setsid= + =&= restore the daemonization plain wl-copy already does for free.
+
+*Failed the review, not the value gate.* Wayland clipboard guidance is in scope (gate Q2 passes). It fails review Q1 (is it right) — adopting it would ship a more complex incantation that lands back at default behavior, plus a false mechanism in the rules layer, which is what the next person reasons from.
+
+*The real kernel:* =--foreground= is a genuine trap; if reached for, it must be detached. Roughly the inverse of the proposal. Craig chose option 1 — hold even that line until we know what clock-panel actually hit.
+
+*My own test bug worth remembering:* =pkill -f 'wl-copy'= self-matched (the Bash tool's own command line contains the string "wl-copy"), killing the shell running it — exit 144. Used =pgrep -x= + =kill <pid>= instead. =pkill -f= on a string that appears in your own command line is a self-kill.
+
+Replied to clock-panel with the three test results, the =--help= quote, the inverted-mechanism read, an explicit "this isn't a value-gate failure" note, three specific things that would change my mind (verbatim failing command — my guess is it already had =--foreground=; how they observed the empty clipboard, since a same-shell read can race the fork; any timeout/trap/sandbox reaping the process group), and the mangled-transport flag with a =--file= heredoc suggestion.
+
+*Inbox back to zero.*
+
+** 2026-07-16 15:18 CDT — Clipboard incident (self-inflicted), then the Emacs XWayland finding
+
+*I broke Craig's clipboard.* Testing clock-panel's claim on his live machine, I killed wl-copy owner processes repeatedly. wl-copy holds the selection only while its process lives, so each kill emptied the clipboard. His original content ("jotto") died with the first one. Symptoms he reported: =C-y= showing "Mark set" with nothing inserted (that's =yank= on an empty string) and Chrome greying out paste (empty clipboard). I had backed up + restored once, then kept testing on the same live clipboard *without re-checking* — the discipline was applied once and abandoned. Restored "jotto" via Emacs (=gui-set-selection=) rather than wl-copy, since Emacs is long-lived and won't evaporate.
+
+*Owed to clock-panel: a partial retraction.* I rejected their proposal partly on "I tested it, plain wl-copy works" — but I read the clipboard back *seconds* after copying, before the owner had any chance to die, and then reproduced their exact failure on Craig's machine. The =--foreground=-is-backwards half of the analysis holds; the confidence didn't. *Not yet sent.*
+
+*The real finding.* Craig's Emacs reported =framep= = =x=. Investigation:
+
+- Installed package was =extra/emacs 30.2-3= (X11 build): =(:pgtk nil :x t)=, =window-system x=, =DISPLAY=:0= with =WAYLAND_DISPLAY=wayland-1= unused. On XWayland, contradicting the documented pure-Wayland setup — and why =xclip= reads his clipboard fine despite protocols.org saying never to use it.
+- =archsetup= line 2605 has run =pacman_install emacs-wayland= *unconditionally since bb9c9bb (2026-04-11, "fix: emacs-wayland package ... velox config sync")*. So *ratio drifted from archsetup's own intent for ~3 months*; the commit named velox, so the fix likely only ever reached velox. This also explains =emacs.md='s "Verified on Emacs 30.2 pgtk" claim — true on velox, false on ratio.
+- Host derived at runtime per the host-identity rule: =uname -n= → ratio. velox offline 2d, unverified.
+
+*Installed =emacs-wayland 30.2-3= on ratio* (Craig's call, option 2). =emacs= had =Required By: None=; =emacs-wayland= =Provides: emacs= so ledger/mu optdeps stay satisfied. =--noconfirm= *failed* (it takes the default N on a conflict prompt) — used =yes | sudo pacman -S emacs-wayland= for one atomic remove+install. Snapper snapshots 7680/7681 taken. New binary verified =pgtk:t x:nil=; running daemon still X11 (holds the deleted inode). *Restart is Craig's to make.*
+
+Blast radius checked before advising: this Claude session is tmux =aiv-rulesets= and *survives* an Emacs restart (as do 6 other agent sessions); his 4 frames and EMMS's mpv don't.
+
+*Bonus finding — =emacs.service= failed since 2026-07-09 06:00:04* (1 week), =start-limit-hit= after 5x exit-1. Cause *undiagnosed* (journal has no Emacs stderr; my untested hypothesis is a daemon-already-running conflict). The live daemon (PID 3004861, bare =emacs --daemon=, started Jul 14) has PPID 1 and nothing in the Hyprland config starts it. So =Restart=on-failure= isn't protecting anything and a reboot wouldn't restore the intended shape.
+
+*Ownership split, flagged to Craig:* the unit is a stow symlink into =~/.dotfiles/= — its own repo (=git@cjennings.net:dotfiles.git=) with its own =.ai/= scope. Package = archsetup's; unit = dotfiles'. Craig routed the handoff to archsetup; I sent there only and named the boundary, leaving the dotfiles side his call.
+
+Handoff sent: =archsetup/inbox/2026-07-16-1518-from-rulesets-handoff-archsetup-emacs.org=. The open question posed to them: nothing appears to detect "archsetup says X, machine has Y" on an *existing* box — intent is expressed for fresh installs only.
+
+*Test-methodology lessons worth keeping:* (1) =pkill -f '<pattern>'= self-matches when the pattern appears in your own command line — it killed the shell running it (exit 144), twice. Use =pgrep -x= + explicit kill. (2) Verifying a persistence claim seconds after the write proves nothing about persistence.