From 32bebc2b078e93f9b830dc1db7a0ce3d44990c26 Mon Sep 17 00:00:00 2001 From: Gabriel Brown Date: Fri, 21 Aug 2026 20:02:25 -0400 Subject: [PATCH] One door per name on the IPC bus A second IpcHandler with an already-used target does not error -- it silently shadows the first, and for the polkit target that means the agent's authentication requests stop reaching the prompt: every password dialog on the desktop, gone without a message. That duplicate nearly shipped once, because the handlers live scattered through a long shell.qml. The contract pairs every IpcHandler with its quoted target across the shell's QML (harnesses excluded -- each is its own root), fails on any name declared twice, and refuses to pass on an empty scan so a declaration-format change cannot quietly blind it. --- README.md | 2 +- tests/quickshell/ipc-targets-contract | 57 +++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100755 tests/quickshell/ipc-targets-contract diff --git a/README.md b/README.md index c2b13ca..edf9dde 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ docs/ Settings reference, and the design specs behind the work ## Tests -134 of them, under `tests/`. Run the lot, or a subset by pattern: +135 of them, under `tests/`. Run the lot, or a subset by pattern: ```sh panama test # everything diff --git a/tests/quickshell/ipc-targets-contract b/tests/quickshell/ipc-targets-contract new file mode 100755 index 0000000..0689325 --- /dev/null +++ b/tests/quickshell/ipc-targets-contract @@ -0,0 +1,57 @@ +#!/usr/bin/env bash + +# One IpcHandler per target name. +# +# `qs ipc call ...` resolves by name, and the handlers are spread +# across a shell.qml several hundred lines long -- a second IpcHandler with an +# already-used target is exactly the mistake a reader makes there, and it does +# not error: one handler silently shadows or conflicts with the other. For the +# polkit target that is not cosmetic -- the agent delivers authentication +# requests through it, so a shadowed `begin` breaks every password prompt on +# the desktop. This nearly shipped once; now it cannot. +# +# Harness files are excluded: each is its own root, never loaded beside the +# shell, so sharing a target with it is not a conflict. + +set -uo pipefail + +repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +shell_dir="$repo_dir/config/dot/quickshell" + +findings=() +note() { findings+=("$1"); } + +# target name file, one line per IpcHandler declaration. The awk pairs +# each `IpcHandler {` with the next quoted `target:` line in the same file, +# which is how every handler in this codebase is written. +declarations="$(find "$shell_dir" -name '*.qml' -not -name '*-harness.qml' -exec awk ' + /IpcHandler *{/ { pending = 1 } + pending && /target: "/ { + match($0, /target: "[^"]+"/) + printf "%s\t%s\n", substr($0, RSTART + 9, RLENGTH - 10), FILENAME + pending = 0 + } +' {} +)" + +# The scan finding almost nothing means the declaration format changed, not +# that the handlers went away -- fail loudly rather than pass on an empty read. +total="$(grep -c . <<<"$declarations" || true)" +(( total >= 5 )) \ + || note "only $total IpcHandler targets were found; the scan no longer matches how handlers are declared" + +while read -r count target; do + [[ -n "$target" ]] || continue + if (( count > 1 )); then + files="$(awk -F'\t' -v t="$target" '$1 == t { print $2 }' <<<"$declarations" \ + | sed "s|$repo_dir/||" | paste -sd ', ')" + note "the \"$target\" IPC target is declared $count times ($files); one silently shadows the other" + fi +done < <(cut -f1 <<<"$declarations" | sort | uniq -c) + +if (( ${#findings[@]} > 0 )); then + printf 'ipc targets contract: %d finding(s)\n' "${#findings[@]}" >&2 + printf ' - %s\n' "${findings[@]}" >&2 + exit 1 +fi + +printf 'ipc targets contract: PASS (%d targets, each declared once)\n' "$total"