Drop the extension, and give the test suite a front door
Phase 6, the last of the fresh-install spec. 159 scripts lose their .sh: 110 contracts, 47 Vicinae commands, 2 compositor contracts. A shebang and the executable bit already select the interpreter. The extension only ever added something that had to stay in sync, and the rename proved the point twice over in the space of an hour. The spec's stated risk was Vicinae's script discovery. One script was renamed and reloaded on its own before the other 46 followed; it came back as scripts:panama.capture and all 47 resolve. What the probe turned up instead is that the extension was never only a filename: Vicinae's command IDs embed it, so every ID changed. Nothing in this repository refers to them, so nothing breaks. The only trace is Vicinae's metadata.json, whose visited map had two Panama entries that are now orphaned -- two commands lost their usage ranking and will earn it back. Worth knowing before anyone renames these again on a machine that has a keybind pointing at one. Rewriting the references by exact filename missed two things it structurally could not see: a name built from a variable, settings-$page.sh, and a glob, -name '*.sh'. Both were in the contract that counts the generated commands, which promptly reported 47 expected and 0 found. The mechanical part of a rename is the part that looks finished. The three subcommands. panama doctor fronts a health check that already existed and already ran at the end of every install but could not be reached from a terminal. panama upgrade re-runs the installer from anywhere. panama test runs the suite, which had no entry point at all -- 121 files that were the main safety net in this repository and were invisible in it. Writing that runner found three tests nothing was running. calendar_agenda_bridge_test, home_assistant_bridge_test and kdeconnect_bridge_test are unittest suites without the executable bit, so no contract invoked them and the first draft of the runner skipped them silently. All three pass, and have passed unobserved for weeks. The runner collects *_test.py as well now, because a runner with a blind spot is worse than no runner for the same reason a dependency checker with one is: it reports PASS. Six worktrees pruned. Each was re-checked rather than trusted to the spec's list, and two needed it: panama-commands is not on feat/panama-commands but on feat/gnome-tweaks-parity, and fix/panama-displays-review reads [ahead 3] -- ahead of its remote, not of main, with every commit patch-equivalent to landed work. roadmap-completion stays; it has five commits that are genuinely unlanded. The branches are left alone: pruning a worktree costs nothing, deleting a branch is a decision. 121 contracts pass. Claude-Session: https://claude.ai/code/session_01NvgBuSWB5sE43yWmg21ozj
This commit is contained in:
Executable
+114
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Application permissions, as far as the desktop can actually enforce them.
|
||||
#
|
||||
# The rules:
|
||||
#
|
||||
# 1. The page never claims more than the portal can do. A native binary opens
|
||||
# /dev/video0 directly, and a settings page implying otherwise is worse
|
||||
# than one that says nothing -- so the limit is stated on the page, not
|
||||
# buried in a comment.
|
||||
# 2. A device nothing has asked for is reported empty, not omitted. "No
|
||||
# application uses your microphone" and a page that quietly leaves the
|
||||
# microphone out look identical and mean very different things.
|
||||
# 3. Absence is not failure. The store answers "No entry for microphone" for a
|
||||
# device nobody has requested; treating that as an error would make the
|
||||
# whole page fail because one device is unused.
|
||||
# 4. Anything that is not an explicit "yes" is withheld. Guessing generously
|
||||
# about a camera is the wrong way to be wrong.
|
||||
# 5. A refusal states its reason.
|
||||
#
|
||||
# The write path is exercised against an application id that does not exist, so
|
||||
# no real application's camera access is changed. What is on this machine --
|
||||
# OBS Studio and GNOME Snapshot -- is read, never written.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
helper="$repo_dir/config/dot/quickshell/scripts/panama-permissions"
|
||||
service="$repo_dir/config/dot/quickshell/services/Permissions.qml"
|
||||
page="$repo_dir/config/dot/quickshell/modules/settings/PrivacyPage.qml"
|
||||
|
||||
probe="org.panama.ContractProbe"
|
||||
|
||||
fail() {
|
||||
printf 'permissions contract: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
for path in "$helper" "$service" "$page"; do
|
||||
[[ -r "$path" ]] || fail "missing $path"
|
||||
done
|
||||
[[ -x "$helper" ]] || fail 'panama-permissions is not executable'
|
||||
|
||||
field() { python3 -c "import json,sys; print(json.load(sys.stdin)$1)"; }
|
||||
|
||||
state="$("$helper" snapshot)" || fail 'snapshot failed'
|
||||
|
||||
if [[ "$(printf '%s' "$state" | field "['available']")" != "True" ]]; then
|
||||
printf 'permissions contract: skipped (the portal permission store is not running)\n'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── 1. The page states the limit ────────────────────────────────────────────
|
||||
|
||||
grep -q 'directly' "$page" \
|
||||
|| fail 'the page does not say that programs outside the portal reach these devices anyway'
|
||||
|
||||
# ── 2 & 3. Unused devices are present and empty, not an error ───────────────
|
||||
|
||||
printf '%s' "$state" | python3 -c "
|
||||
import json, sys
|
||||
state = json.load(sys.stdin)
|
||||
if state['error']:
|
||||
raise SystemExit(f\"snapshot reported an error: {state['error']}\")
|
||||
names = [d['id'] for d in state['devices']]
|
||||
for required in ('camera', 'microphone', 'speakers'):
|
||||
if required not in names:
|
||||
raise SystemExit(f'{required} is missing from the snapshot entirely')
|
||||
" || fail 'a device with no recorded application was dropped or reported as an error'
|
||||
|
||||
# ── 4 & 5. The write path, on an application that does not exist ────────────
|
||||
|
||||
before="$(printf '%s' "$state" | field "['devices']")"
|
||||
|
||||
denied="$("$helper" set camera "$probe" deny)" || fail 'set deny failed'
|
||||
reason="$(printf '%s' "$denied" | field "['error']")"
|
||||
[[ -z "$reason" ]] || fail "denying refused a valid write: $reason"
|
||||
printf '%s' "$denied" | python3 -c "
|
||||
import json, sys
|
||||
for device in json.load(sys.stdin)['devices']:
|
||||
for app in device['applications']:
|
||||
if app['app'] == '$probe':
|
||||
if app['allowed']:
|
||||
raise SystemExit('a denied application was reported as allowed')
|
||||
raise SystemExit(0)
|
||||
raise SystemExit('the denied application was not written at all')
|
||||
" || fail 'deny did not take effect -- the write path is not doing anything'
|
||||
|
||||
allowed="$("$helper" set camera "$probe" allow)" || fail 'set allow failed'
|
||||
printf '%s' "$allowed" | python3 -c "
|
||||
import json, sys
|
||||
for device in json.load(sys.stdin)['devices']:
|
||||
for app in device['applications']:
|
||||
if app['app'] == '$probe' and app['allowed']:
|
||||
raise SystemExit(0)
|
||||
raise SystemExit('allow did not take effect')
|
||||
" || fail 'allow did not take effect'
|
||||
|
||||
# Refusals name their reason rather than merely failing.
|
||||
reason="$(printf '%s' "$("$helper" set camera "$probe" maybe)" | field "['error']")"
|
||||
[[ "$reason" == *"allow or deny"* ]] \
|
||||
|| fail "an invalid decision was not refused with a reason (got: $reason)"
|
||||
|
||||
reason="$(printf '%s' "$("$helper" set nonsense "$probe" allow)" | field "['error']")"
|
||||
[[ -n "$reason" ]] || fail 'an unknown device was accepted'
|
||||
|
||||
# ── Put it back ────────────────────────────────────────────────────────────
|
||||
|
||||
"$helper" forget camera "$probe" >/dev/null || fail 'forget failed'
|
||||
after="$("$helper" snapshot | field "['devices']")"
|
||||
[[ "$before" == "$after" ]] \
|
||||
|| fail 'the contract changed recorded permissions and did not restore them'
|
||||
|
||||
printf 'permissions contract: ok\n'
|
||||
Reference in New Issue
Block a user