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.
This commit is contained in:
Gabriel Brown
2026-08-21 20:02:25 -04:00
parent 12d538c4af
commit 32bebc2b07
2 changed files with 58 additions and 1 deletions
+1 -1
View File
@@ -113,7 +113,7 @@ docs/ Settings reference, and the design specs behind the work
## Tests ## 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 ```sh
panama test # everything panama test # everything
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
# One IpcHandler per target name.
#
# `qs ipc call <target> ...` 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 <TAB> 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"