#!/usr/bin/env bash # The doctor is deliberately exercised through its command boundary. The # fixture commands include sensitive-looking output so this test proves the # report only retains explicitly parsed, non-sensitive observations. set -euo pipefail repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" doctor="$repo_dir/config/dot/quickshell/scripts/panama-doctor" theme_helper="$repo_dir/config/dot/quickshell/scripts/panama-theme-apps" fixture_root="$repo_dir/tests/quickshell/fixtures/doctor" fail() { printf 'panama doctor contract: %s\n' "$1" >&2 exit 1 } fixture="$(mktemp -d /tmp/panama-doctor.XXXXXX)" child_pids=() cleanup() { for pid in "${child_pids[@]}"; do kill "$pid" >/dev/null 2>&1 || true wait "$pid" >/dev/null 2>&1 || true done rm -rf "$fixture" } trap cleanup EXIT home="$fixture/home" config_home="$home/.config" state_home="$home/.local/state" runtime_dir="$fixture/runtime" bin_dir="$fixture/bin" data_home="$home/.local/share" mkdir -p "$config_home" "$state_home" "$runtime_dir" "$bin_dir" "$data_home/vicinae/scripts" # A mount table with the document portal present, which is the healthy state the # rest of this file assumes. Written rather than read from /proc so the contract # does not pass or fail on whether this machine happens to have flatpak working # at the moment -- which is exactly the coupling that let a three-day outage go # unnoticed. The gvfs line is there so the check has to match on the mount point # and filesystem type rather than on being handed a file with one entry. mountinfo="$fixture/mountinfo" cat >"$mountinfo" <"$bin_dir/$tool" <<'EOF' #!/usr/bin/bash case "${0##*/}" in hyprctl) printf 'Hyprland 0.50.0\n' ;; esac EOF chmod +x "$bin_dir/$tool" done cat >"$bin_dir/flatpak" <<'EOF' #!/usr/bin/bash if [[ "${1:-}" == "info" && "${2:-}" == "app.bluebubbles.BlueBubbles" \ && "${PANAMA_DOCTOR_FIXTURE_BLUEBUBBLES:-installed}" == "installed" ]]; then printf 'BlueBubbles fixture-secret-token\n' exit 0 fi exit 1 EOF chmod +x "$bin_dir/flatpak" cat >"$bin_dir/calendar-agenda" <<'EOF' #!/usr/bin/bash if [[ "${1:-}" != "probe" ]]; then exit 2 fi case "${PANAMA_DOCTOR_FIXTURE_CALENDAR:-ready}" in ready) printf '{"eds":true,"sourceRegistry":true,"enabledSources":2,"event":"fixture clipboard body"}\n' ;; malformed) printf 'calendar AA:BB:CC:DD:EE:FF\n' ;; timeout) /usr/bin/sleep 2; printf '{"enabledSources":2}\n' ;; *) printf '{"eds":true,"sourceRegistry":true,"enabledSources":0}\n' ;; esac EOF chmod +x "$bin_dir/calendar-agenda" cat >"$bin_dir/panama-brightness" <<'EOF' #!/usr/bin/bash case "${PANAMA_DOCTOR_FIXTURE_BRIGHTNESS:-ready}" in ready) printf '{"displays":[{"connector":"AA:BB:CC:DD:EE:FF"}],"error":""}\n' ;; denied) printf '{"displays":[],"error":"fixture-secret-token"}\n' ;; malformed) printf 'fixture clipboard body\n' ;; esac EOF chmod +x "$bin_dir/panama-brightness" mkdir -p "$config_home/autostart" touch "$config_home/autostart/Nextcloud.desktop" for name in hypr quickshell uwsm vicinae; do ln -s "$repo_dir/config/dot/$name" "$config_home/$name" done ln -s "$repo_dir/config/local/share/vicinae/scripts" "$data_home/vicinae/scripts/panama" mkdir -p "$state_home/panama" printf '%s\n' 'background { path = /fixture-secret-wallpaper.jpg }' \ >"$state_home/panama/hyprlock.conf" printf '%s\n' '{"generated":true,"path":"/fixture-secret-wallpaper.jpg","fallback":false,"error":""}' \ >"$state_home/panama/hyprlock-status.json" run_doctor() { HOME="$home" \ PATH="$bin_dir" \ XDG_CURRENT_DESKTOP=Hyprland \ PANAMA_HOME_ASSISTANT_URL='https://fixture.invalid' \ PANAMA_HOME_ASSISTANT_TOKEN='fixture-secret-token' \ PANAMA_DOCTOR_ROOT="$repo_dir" \ PANAMA_DOCTOR_HOME="$home" \ PANAMA_DOCTOR_CONFIG_HOME="$config_home" \ PANAMA_DOCTOR_STATE_HOME="$state_home" \ PANAMA_DOCTOR_RUNTIME_DIR="$runtime_dir" \ PANAMA_DOCTOR_MOUNTINFO="${PANAMA_DOCTOR_MOUNTINFO:-$mountinfo}" \ PANAMA_DOCTOR_PATH="$bin_dir" \ PANAMA_DOCTOR_SCRIPTS_DIR="$bin_dir" \ PANAMA_DOCTOR_TIMEOUT="${PANAMA_DOCTOR_TIMEOUT:-0.2}" \ /usr/bin/python3 "$doctor" "$@" } expected_order=$'desktop.hyprland\ndesktop.quickshell\ndesktop.notifications\ndesktop.portals\ndesktop.document-portal\ndesktop.hyprpaper\ndesktop.hypridle\ndesktop.hyprlock\ndesktop.vicinae\ninput.pipewire\ninput.clipboard\ninput.wallpaper\ninput.capture\ninput.ocr\ninput.brightness\nintegration.nextcloud\nintegration.rustdesk\nintegration.kdeconnect\nintegration.bluebubbles\nintegration.home-assistant\nintegration.calendar\npanama.updates\npanama.runtime-links\npanama.vicinae-commands\npanama.selected-terminal\npanama.selected-launcher\npanama.processes\npanama.caffeine' assert_schema_and_redaction() { local snapshot="$1" jq -e '.schemaVersion == 1 and (.generatedAt | type == "string") and (.summary.status | IN("healthy", "warning", "error")) and (.context.session | IN("hyprland", "other")) and (.context.versions | type == "array") and ([.checks[].id] | length == 28) and ([.checks[].id] | unique | length == 28) and ([.checks[].status] | all(IN("ok", "warning", "error", "unconfigured")))' \ >/dev/null <<<"$snapshot" || fail "invalid schema: $snapshot" [[ "$(jq -r '.checks[].id' <<<"$snapshot")" == "$expected_order" ]] \ || fail "checks are not in the authored order" ! grep -Fq 'fixture-secret-token' <<<"$snapshot" \ || fail 'report exposed a fixture secret' ! grep -Fq 'fixture clipboard body' <<<"$snapshot" \ || fail 'report exposed clipboard or calendar content' ! grep -Fq 'AA:BB:CC:DD:EE:FF' <<<"$snapshot" \ || fail 'report exposed a device address' } check_status() { local snapshot="$1" id="$2" expected="$3" [[ "$(jq -r --arg id "$id" '.checks[] | select(.id == $id) | .status' <<<"$snapshot")" == "$expected" ]] \ || fail "$id did not report $expected: $snapshot" } snapshot="$(run_doctor --json)" assert_schema_and_redaction "$snapshot" check_status "$snapshot" panama.vicinae-commands ok check_status "$snapshot" desktop.hyprlock ok printf '%s\n' '{"generated":false,"path":"/fixture-secret-wallpaper.jpg","fallback":true,"error":"fixture-secret-token"}' \ >"$state_home/panama/hyprlock-status.json" # A linked source checkout contains the template; installation renders the # ignored machine-local fallback beside it. Model that installed state without # writing an untracked file into the worktree under test. rm "$config_home/hypr" mkdir "$config_home/hypr" cp "$repo_dir/config/dot/hypr/hyprlock.conf.template" \ "$config_home/hypr/hyprlock.conf.template" HOME="$home" XDG_CONFIG_HOME="$config_home" "$theme_helper" dark >/dev/null fallback_lock="$(run_doctor --json)" check_status "$fallback_lock" desktop.hyprlock warning assert_schema_and_redaction "$fallback_lock" rm -rf "$config_home/hypr" missing_lock="$(run_doctor --json)" check_status "$missing_lock" desktop.hyprlock error assert_schema_and_redaction "$missing_lock" ln -s "$repo_dir/config/dot/hypr" "$config_home/hypr" printf '%s\n' '{"generated":true,"path":"/fixture-secret-wallpaper.jpg","fallback":false,"error":""}' \ >"$state_home/panama/hyprlock-status.json" # The diagnostic follows the actual installer contract: the scripts parent is # a directory and only its Panama child is an authored link. rm "$data_home/vicinae/scripts/panama" unlinked_vicinae="$(run_doctor --json)" check_status "$unlinked_vicinae" panama.vicinae-commands warning PANAMA_PATH="$repo_dir" VICINAE_DATA_DIR="$data_home/vicinae" HOME="$home" \ PATH="$bin_dir:/usr/bin" "$repo_dir/setup/scripts/link-vicinae-scripts" relinked_vicinae="$(run_doctor --json)" check_status "$relinked_vicinae" panama.vicinae-commands ok [[ -L "$data_home/vicinae/scripts/panama" \ && "$(readlink "$data_home/vicinae/scripts/panama")" == "$repo_dir/config/local/share/vicinae/scripts" ]] \ || fail 'authored Vicinae helper did not create the diagnosed child link' # A healthy systemd-backed service stays healthy. check_status "$snapshot" desktop.hyprpaper ok # Arbitrary parent environment values are not propagated into probes. sealed_environment="$(PANAMA_DOCTOR_FIXTURE_PROBE_SECRET=fixture-secret-token run_doctor --json)" assert_schema_and_redaction "$sealed_environment" check_status "$sealed_environment" desktop.hyprpaper ok # An OS-level launch failure is contained as a check result, never a failed # doctor invocation or a partial snapshot. chmod 0644 "$bin_dir/systemctl" if ! launch_failure="$(run_doctor --json)"; then chmod +x "$bin_dir/systemctl" fail 'launch failure prevented the doctor from emitting JSON' fi chmod +x "$bin_dir/systemctl" assert_schema_and_redaction "$launch_failure" check_status "$launch_failure" desktop.hyprpaper error # A missing required executable is an error rather than a crash. mv "$bin_dir/qs" "$bin_dir/qs.off" missing_qs="$(run_doctor --json)" check_status "$missing_qs" desktop.quickshell error mv "$bin_dir/qs.off" "$bin_dir/qs" # Optional integrations stay neutral until the user configures them. rm "$config_home/autostart/Nextcloud.desktop" unconfigured_nextcloud="$(run_doctor --json)" check_status "$unconfigured_nextcloud" integration.nextcloud unconfigured touch "$config_home/autostart/Nextcloud.desktop" # A configured integration whose process isn't running is actionable with an # authored label, never an application name or command derived from probe # output. Nextcloud has no systemd unit behind it, so this is a process # check (PANAMA_DOCTOR_FIXTURE_PROCESSES), not a service check. stopped_nextcloud="$(PANAMA_DOCTOR_FIXTURE_PROCESSES=nextcloud:missing run_doctor --json)" check_status "$stopped_nextcloud" integration.nextcloud warning jq -e '.checks[] | select(.id == "integration.nextcloud") | .action == {kind:"open", label:"Open Nextcloud", confirm:false}' \ >/dev/null <<<"$stopped_nextcloud" || fail 'Nextcloud action was not authored' # The document portal is judged by the mount, never by the service. # # This is the check's whole reason for existing. The service can be active and # the mount gone -- that is precisely what happened, twice -- so a fixture that # keeps every service healthy and removes only the mount has to fail. If this # ever passes, the check has drifted back to asking systemd how it feels. grep -v ' fuse.portal ' "$mountinfo" >"$fixture/mountinfo-unmounted" unmounted_portal="$(PANAMA_DOCTOR_MOUNTINFO="$fixture/mountinfo-unmounted" run_doctor --json)" check_status "$unmounted_portal" desktop.document-portal warning jq -e '.checks[] | select(.id == "desktop.document-portal") | .action == {kind:"repair", label:"Restart the document portal", confirm:false}' \ >/dev/null <<<"$unmounted_portal" || fail 'document portal repair action was not authored' # A mount at the right path of the wrong type is not the document portal. The # stale mountpoint left behind by a failed activation is a plain directory, but # matching on path alone would call any future mount there healthy. sed 's/ fuse.portal portal / fuse.gvfsd-fuse gvfsd-fuse /' "$mountinfo" >"$fixture/mountinfo-wrongtype" wrong_type_portal="$(PANAMA_DOCTOR_MOUNTINFO="$fixture/mountinfo-wrongtype" run_doctor --json)" check_status "$wrong_type_portal" desktop.document-portal warning # An unreadable mount table is an error, not a silent pass and not a crash. missing_mountinfo="$(PANAMA_DOCTOR_MOUNTINFO="$fixture/no-such-mountinfo" run_doctor --json)" check_status "$missing_mountinfo" desktop.document-portal error # DDC errors are classified without retaining connectors or bus addresses. denied_brightness="$(PANAMA_DOCTOR_FIXTURE_BRIGHTNESS=denied run_doctor --json)" check_status "$denied_brightness" input.brightness warning jq -e '.checks[] | select(.id == "input.brightness") | .action == {kind:"instructions", label:"View setup instructions", confirm:false, target:"ddc-permissions"}' \ >/dev/null <<<"$denied_brightness" || fail 'DDC instructions were not authored' # A bounded probe timeout becomes a result, never a helper failure. timed_calendar="$(PANAMA_DOCTOR_FIXTURE_CALENDAR=timeout PANAMA_DOCTOR_TIMEOUT=0.05 run_doctor --json)" check_status "$timed_calendar" integration.calendar warning jq -e '.checks[] | select(.id == "integration.calendar") | .action == {kind:"open", label:"Open Date & Time", confirm:false, target:"datetime"}' \ >/dev/null <<<"$timed_calendar" || fail 'calendar action was not authored' # Exact Panama/Caffeine inhibitor rows detect duplicates without exposing PIDs. duplicated_caffeine="$(PANAMA_DOCTOR_FIXTURE_CAFFEINE=duplicate run_doctor --json)" check_status "$duplicated_caffeine" panama.caffeine warning jq -e '.checks[] | select(.id == "panama.caffeine") | .action == {kind:"repair", label:"Release duplicate inhibitors", confirm:false}' \ >/dev/null <<<"$duplicated_caffeine" || fail 'Caffeine repair action was not authored' ! jq -r '.checks[] | select(.id == "panama.caffeine") | .detail' <<<"$duplicated_caffeine" | grep -Eq '[0-9]{3,}' \ || fail 'Caffeine detail exposed inhibitor PIDs' # Process counts use only exact authored names and never expose command lines or PIDs. duplicated_processes="$(PANAMA_DOCTOR_FIXTURE_PROCESSES=qs:duplicate run_doctor --json)" check_status "$duplicated_processes" panama.processes warning ! jq -r '.checks[] | select(.id == "panama.processes") | .detail' <<<"$duplicated_processes" | grep -Eq '[0-9]{3,}' \ || fail 'process detail exposed a PID' missing_quickshell_process="$(PANAMA_DOCTOR_FIXTURE_PROCESSES=qs:missing run_doctor --json)" check_status "$missing_quickshell_process" panama.processes error # Invalid output for a non-Quickshell authored process is not a normal zero # count that can be hidden by the running Quickshell process. malformed_processes="$(PANAMA_DOCTOR_FIXTURE_PROCESSES=hyprpaper:malformed run_doctor --json)" check_status "$malformed_processes" panama.processes warning # Panama/Caffeine-shaped rows that do not satisfy the fixed inhibitor schema # are unavailable rather than reported as a healthy no-inhibitor state. malformed_caffeine="$(PANAMA_DOCTOR_FIXTURE_CAFFEINE=malformed run_doctor --json)" check_status "$malformed_caffeine" panama.caffeine warning # A decoding error raised inside a concurrent probe is converted to a complete # snapshot rather than escaping from Future.result(). if ! invalid_probe="$(PANAMA_DOCTOR_FIXTURE_BUS=invalid-utf8 run_doctor --json)"; then fail 'unexpected probe exception prevented the doctor from emitting JSON' fi assert_schema_and_redaction "$invalid_probe" check_status "$invalid_probe" desktop.portals warning # Configured Home Assistant failures route to the exact authored Settings page. rm "$config_home/quickshell" mkdir -p "$config_home/quickshell/scripts" home_assistant_failure="$(run_doctor --json)" check_status "$home_assistant_failure" integration.home-assistant warning jq -e '.checks[] | select(.id == "integration.home-assistant") | .action == {kind:"open", label:"Open Home settings", confirm:false, target:"home-phone"}' \ >/dev/null <<<"$home_assistant_failure" || fail 'Home Assistant action was not routed to home-phone' # Invalid probe text is contained in its own check and never copied to JSON. malformed_calendar="$(PANAMA_DOCTOR_FIXTURE_CALENDAR=malformed run_doctor --json)" check_status "$malformed_calendar" integration.calendar warning assert_schema_and_redaction "$malformed_calendar" summary="$(run_doctor --summary)" [[ "$summary" =~ ^Panama\ system\ health:\ (healthy|warning|error)\ \([0-9]+\ ok,\ [0-9]+\ warnings,\ [0-9]+\ errors,\ [0-9]+\ unconfigured\)$ ]] \ || fail "summary is not concise: $summary" # Repairs run against a second, disposable Panama root. Every process boundary # records its argv, and every filesystem assertion is confined to this fixture. repair_root="$home/.local/share/Panama" repair_log="$runtime_dir/repair.log" mkdir -p "$repair_root/config/dot" "$repair_root/config/local/share/vicinae/scripts" \ "$repair_root/setup/scripts" for name in hypr quickshell uwsm vicinae; do mkdir -p "$repair_root/config/dot/$name" done cp "$repo_dir/setup/scripts/link-vicinae-scripts" "$repair_root/setup/scripts/link-vicinae-scripts" mv "$bin_dir/systemctl" "$bin_dir/systemctl-probe" cat >"$bin_dir/systemctl" <<'EOF' #!/usr/bin/bash set -euo pipefail if [[ "${1:-}" == "--user" && "${2:-}" == "restart" ]]; then printf 'systemctl' >>"$XDG_RUNTIME_DIR/repair.log" printf '|%s' "$@" >>"$XDG_RUNTIME_DIR/repair.log" printf '\n' >>"$XDG_RUNTIME_DIR/repair.log" [[ ! -e "$XDG_RUNTIME_DIR/fail-repair" ]] || exit 5 exit 0 fi exec "${0%/*}/systemctl-probe" "$@" EOF cat >"$bin_dir/panama-action" <<'EOF' #!/usr/bin/bash set -euo pipefail printf 'panama-action' >>"$XDG_RUNTIME_DIR/repair.log" printf '|%s' "$@" >>"$XDG_RUNTIME_DIR/repair.log" printf '\n' >>"$XDG_RUNTIME_DIR/repair.log" EOF cat >"$bin_dir/systemd-inhibit" <<'EOF' #!/usr/bin/bash set -euo pipefail printf 'systemd-inhibit' >>"$XDG_RUNTIME_DIR/repair.log" printf '|%s' "$@" >>"$XDG_RUNTIME_DIR/repair.log" printf '\n' >>"$XDG_RUNTIME_DIR/repair.log" count_file="$XDG_RUNTIME_DIR/caffeine-list-count" count=0 [[ ! -f "$count_file" ]] || read -r count <"$count_file" count=$((count + 1)) printf '%s\n' "$count" >"$count_file" read -r preserved duplicate <"$XDG_RUNTIME_DIR/caffeine-pids" uid="$(/usr/bin/id -u)" mode="$(<"$XDG_RUNTIME_DIR/caffeine-mode")" if [[ "$mode" == disappear && "$count" -ge 2 ]]; then /usr/bin/touch "$XDG_RUNTIME_DIR/release-disappearing-pid" for _ in $(/usr/bin/seq 1 100); do [[ ! -e "/proc/$duplicate" ]] && break /usr/bin/sleep 0.01 done fi preserved_comm=systemd-inhibit if [[ "$mode" == preserve-altered && "$count" -ge 2 ]]; then preserved_comm=changed-command fi printf 'Panama %s fixture-user %s %s sleep:idle Caffeine block\n' "$uid" "$preserved" "$preserved_comm" if [[ "$mode" != multiplicity || "$count" -lt 2 ]]; then printf 'Panama %s fixture-user %s systemd-inhibit sleep:idle Caffeine block\n' "$uid" "$preserved" fi if [[ "$mode" == altered && "$count" -ge 2 ]]; then printf 'Panama %s fixture-user %s systemd-inhibit sleep:idle Other block\n' "$uid" "$duplicate" else printf 'Panama %s fixture-user %s systemd-inhibit sleep:idle Caffeine block\n' "$uid" "$duplicate" fi printf 'Other %s fixture-user 4999 systemd-inhibit sleep:idle Caffeine block\n' "$uid" printf 'Panama 99999 fixture-user 4998 systemd-inhibit sleep:idle Caffeine block\n' printf 'Panama %s fixture-user 4997 systemd-inhibit sleep:idle Other block\n' "$uid" printf 'Panama %s fixture-user 4996 systemd-inhibit sleep:idle Caffeine delay\n' "$uid" EOF chmod +x "$bin_dir/systemctl" "$bin_dir/panama-action" \ "$bin_dir/systemd-inhibit" "$repair_root/setup/scripts/link-vicinae-scripts" run_repair() { HOME="$home" \ PATH="$bin_dir:/usr/bin" \ XDG_CURRENT_DESKTOP=Hyprland \ PANAMA_DOCTOR_ROOT="$repair_root" \ PANAMA_DOCTOR_HOME="$home" \ PANAMA_DOCTOR_CONFIG_HOME="$config_home" \ PANAMA_DOCTOR_STATE_HOME="$state_home" \ PANAMA_DOCTOR_RUNTIME_DIR="$runtime_dir" \ PANAMA_DOCTOR_PATH="$bin_dir:/usr/bin" \ PANAMA_DOCTOR_SCRIPTS_DIR="$bin_dir" \ PANAMA_DOCTOR_TIMEOUT=0.2 \ /usr/bin/python3 "$doctor" "$@" } repair_output="" repair_status=0 invoke_repair() { set +e repair_output="$(run_repair --repair "$1" --json)" repair_status=$? set -e } assert_repair_result() { local id="$1" accepted="$2" exit_code="$3" jq -e --arg id "$id" --argjson accepted "$accepted" --argjson exitCode "$exit_code" ' (keys | sort) == ["accepted", "checkId", "exitCode", "message", "schemaVersion"] and .schemaVersion == 1 and .checkId == $id and .accepted == $accepted and .exitCode == $exitCode and (.message | type == "string" and length > 0) ' >/dev/null <<<"$repair_output" || fail "invalid repair result for $id: $repair_output" } for repair_case in \ 'desktop.hyprpaper|systemctl|--user|restart|hyprpaper.service' \ 'desktop.hypridle|systemctl|--user|restart|hypridle.service' \ 'desktop.vicinae|systemctl|--user|restart|vicinae.service' \ 'desktop.quickshell|panama-action|restart-shell'; do IFS='|' read -r repair_id executable arg1 arg2 arg3 <<<"$repair_case" : >"$repair_log" invoke_repair "$repair_id" [[ "$repair_status" == 0 ]] || fail "$repair_id returned $repair_status" assert_repair_result "$repair_id" true 0 expected="$executable|$arg1" [[ -z "$arg2" ]] || expected+="|$arg2" [[ -z "$arg3" ]] || expected+="|$arg3" [[ "$(<"$repair_log")" == "$expected" ]] \ || fail "$repair_id argv was not exact: $(<"$repair_log")" done # Known process failures still return complete JSON and preserve the command's # exit status for the QML state machine. touch "$runtime_dir/fail-repair" : >"$repair_log" invoke_repair desktop.vicinae rm "$runtime_dir/fail-repair" [[ "$repair_status" == 5 ]] || fail "failed repair returned $repair_status instead of 5" assert_repair_result desktop.vicinae true 5 [[ "$(<"$repair_log")" == 'systemctl|--user|restart|vicinae.service' ]] \ || fail 'failed repair changed the authored argv' # The real authored Vicinae helper converges the exact child link diagnosed by # panama-doctor under the isolated HOME. rm -f "$data_home/vicinae/scripts/panama" before_vicinae_repair="$(run_repair --json)" check_status "$before_vicinae_repair" panama.vicinae-commands warning invoke_repair panama.vicinae-commands [[ "$repair_status" == 0 ]] || fail "Vicinae command repair returned $repair_status" assert_repair_result panama.vicinae-commands true 0 after_vicinae_repair="$(run_repair --json)" check_status "$after_vicinae_repair" panama.vicinae-commands ok [[ -L "$data_home/vicinae/scripts/panama" \ && "$(readlink "$data_home/vicinae/scripts/panama")" == "$repair_root/config/local/share/vicinae/scripts" ]] \ || fail 'Vicinae repair did not install the diagnosed child link' # Runtime-link repair may replace only absent links or symlinks whose lexical # target proves Panama ownership. Every other object remains untouched. for name in hypr quickshell uwsm vicinae; do path="$config_home/$name" if [[ -e "$path" || -L "$path" ]]; then mv "$path" "$fixture/pre-repair-$name" fi done ln -s "$repair_root/config/dot/hypr" "$config_home/hypr" correct_inode="$(stat -c %i "$config_home/hypr")" ln -s "$repair_root/config/dot/quickshell" "$config_home/uwsm" ln -s "$fixture/external-broken-link" "$config_home/vicinae" ln -s "$fixture/untouched" "$config_home/not-panama" : >"$repair_log" invoke_repair panama.runtime-links [[ "$repair_status" == 1 ]] || fail "blocked runtime-link repair returned $repair_status" assert_repair_result panama.runtime-links true 1 [[ -L "$config_home/hypr" && "$(readlink "$config_home/hypr")" == "$repair_root/config/dot/hypr" ]] \ || fail 'correct runtime link changed' [[ "$(stat -c %i "$config_home/hypr")" == "$correct_inode" ]] \ || fail 'correct runtime link was replaced instead of left untouched' [[ -L "$config_home/quickshell" && "$(readlink "$config_home/quickshell")" == "$repair_root/config/dot/quickshell" ]] \ || fail 'absent quickshell link was not created' [[ -L "$config_home/uwsm" && "$(readlink "$config_home/uwsm")" == "$repair_root/config/dot/uwsm" ]] \ || fail 'provably Panama-owned stale link was not repaired' [[ -L "$config_home/vicinae" && "$(readlink "$config_home/vicinae")" == "$fixture/external-broken-link" ]] \ || fail 'external broken symlink was replaced' [[ -L "$config_home/not-panama" && "$(readlink "$config_home/not-panama")" == "$fixture/untouched" ]] \ || fail 'runtime-link repair touched an unauthored link name' [[ ! -s "$repair_log" ]] || fail 'runtime-link repair launched a process' # Regular files and directories also remain untouched. rm "$config_home/vicinae" rm "$config_home/uwsm" printf 'user-owned file\n' >"$config_home/uwsm" mkdir "$config_home/vicinae" invoke_repair panama.runtime-links [[ "$repair_status" == 1 ]] || fail 'file/directory blockers did not make repair incomplete' [[ -f "$config_home/uwsm" && "$(<"$config_home/uwsm")" == 'user-owned file' ]] \ || fail 'runtime-link repair replaced a regular file' [[ -d "$config_home/vicinae" && ! -L "$config_home/vicinae" ]] \ || fail 'runtime-link repair replaced a user-owned directory' # An injected exchange failure occurs after the authored candidate symlink is # made; the original link must still be intact. /usr/bin/python3 - "$doctor" "$repair_root" "$fixture/atomic-config" <<'PY' \ || fail 'atomic replacement failure did not preserve the original link' import importlib.util import importlib.machinery import os import sys from pathlib import Path doctor_path, root_text, config_text = sys.argv[1:] loader = importlib.machinery.SourceFileLoader("panama_doctor_contract", doctor_path) spec = importlib.util.spec_from_loader(loader.name, loader) module = importlib.util.module_from_spec(spec) sys.modules[spec.name] = module loader.exec_module(module) root = Path(root_text) config_home = Path(config_text) config_home.mkdir(parents=True) destination = config_home / "hypr" original = root / "config/dot/quickshell" destination.symlink_to(original, target_is_directory=True) config = module.DoctorConfig(root, config_home.parent, config_home, config_home.parent / "state", config_home.parent / "runtime", "", 0.2) real_exchange = module.rename_exchange module.rename_exchange = lambda source, target: (_ for _ in ()).throw(OSError("fixture exchange failure")) try: result = module.repair_runtime_links(config) finally: module.rename_exchange = real_exchange assert result.exit_code == 1 assert destination.is_symlink() assert os.readlink(destination) == str(original) assert not list(config_home.glob(".panama-link-*")) PY # A deterministic swap at the ownership/replacement boundary must be detected # from the exchanged-out object and rolled back, preserving the external link. /usr/bin/python3 - "$doctor" "$repair_root" "$fixture/toctou-config" "$fixture/external-race-target" <<'PY' \ || fail 'runtime-link exchange did not restore a boundary-swapped external link' import importlib.machinery import importlib.util import os import sys from pathlib import Path doctor_path, root_text, config_text, external_text = sys.argv[1:] loader = importlib.machinery.SourceFileLoader("panama_doctor_toctou", doctor_path) spec = importlib.util.spec_from_loader(loader.name, loader) module = importlib.util.module_from_spec(spec) sys.modules[spec.name] = module loader.exec_module(module) root = Path(root_text) config_home = Path(config_text) config_home.mkdir(parents=True) for name, relative in module.RUNTIME_LINK_TARGETS: (config_home / name).symlink_to(root / relative, target_is_directory=True) destination = config_home / "uwsm" destination.unlink() destination.symlink_to(root / "config/dot/quickshell", target_is_directory=True) external = Path(external_text) real_exchange = module.rename_exchange first = True def race_exchange(candidate, target): global first if first: first = False target.unlink() target.symlink_to(external, target_is_directory=True) real_exchange(candidate, target) module.rename_exchange = race_exchange config = module.DoctorConfig(root, config_home.parent, config_home, config_home.parent / "state", config_home.parent / "runtime", "", 0.2) try: result = module.repair_runtime_links(config) finally: module.rename_exchange = real_exchange assert result.exit_code == 1 assert destination.is_symlink() assert os.readlink(destination) == str(external) assert not list(config_home.glob(".panama-link-*")) PY # Caffeine repair deduplicates rows, pins each distinct duplicate with a # pidfd, revalidates authored metadata, and signals only the duplicate. /usr/bin/sleep 30 & preserved_pid=$! child_pids+=("$preserved_pid") /usr/bin/sleep 30 & duplicate_pid=$! child_pids+=("$duplicate_pid") printf '%s %s\n' "$preserved_pid" "$duplicate_pid" >"$runtime_dir/caffeine-pids" printf 'dedupe\n' >"$runtime_dir/caffeine-mode" rm -f "$runtime_dir/caffeine-list-count" : >"$repair_log" invoke_repair panama.caffeine [[ "$repair_status" == 0 ]] || fail "Caffeine repair returned $repair_status" assert_repair_result panama.caffeine true 0 expected_caffeine=$'systemd-inhibit|--list|--no-pager|--no-legend\nsystemd-inhibit|--list|--no-pager|--no-legend' [[ "$(<"$repair_log")" == "$expected_caffeine" ]] \ || fail "Caffeine repair did not preserve/filter exact inhibitors: $(<"$repair_log")" kill -0 "$preserved_pid" >/dev/null 2>&1 || fail 'repeated inhibitor rows killed the preserved process' for _ in $(seq 1 40); do kill -0 "$duplicate_pid" >/dev/null 2>&1 || break sleep 0.05 done ! kill -0 "$duplicate_pid" >/dev/null 2>&1 || fail 'distinct duplicate inhibitor was not terminated' # Changed second-list metadata invalidates the candidate before any signal. /usr/bin/sleep 30 & altered_preserved=$! child_pids+=("$altered_preserved") /usr/bin/sleep 30 & altered_duplicate=$! child_pids+=("$altered_duplicate") printf '%s %s\n' "$altered_preserved" "$altered_duplicate" >"$runtime_dir/caffeine-pids" printf 'altered\n' >"$runtime_dir/caffeine-mode" rm -f "$runtime_dir/caffeine-list-count" invoke_repair panama.caffeine [[ "$repair_status" == 1 ]] || fail 'altered inhibitor metadata was not safely refused' assert_repair_result panama.caffeine true 1 kill -0 "$altered_preserved" >/dev/null 2>&1 || fail 'metadata refusal signaled the preserved process' kill -0 "$altered_duplicate" >/dev/null 2>&1 || fail 'metadata refusal signaled the candidate process' # Changing metadata on the preserved row is also a full-identity mismatch, # even though every duplicate PID remains present. /usr/bin/sleep 30 & preserve_changed_keep=$! child_pids+=("$preserve_changed_keep") /usr/bin/sleep 30 & preserve_changed_duplicate=$! child_pids+=("$preserve_changed_duplicate") printf '%s %s\n' "$preserve_changed_keep" "$preserve_changed_duplicate" >"$runtime_dir/caffeine-pids" printf 'preserve-altered\n' >"$runtime_dir/caffeine-mode" rm -f "$runtime_dir/caffeine-list-count" invoke_repair panama.caffeine [[ "$repair_status" == 1 ]] || fail 'preserved-row metadata change was not safely refused' assert_repair_result panama.caffeine true 1 kill -0 "$preserve_changed_keep" >/dev/null 2>&1 || fail 'preserved-row mismatch signaled the preserved process' kill -0 "$preserve_changed_duplicate" >/dev/null 2>&1 || fail 'preserved-row mismatch signaled the duplicate process' # A repeated exact row disappearing between lists changes multiplicity and is # refused before signaling any pinned duplicate. /usr/bin/sleep 30 & multiplicity_keep=$! child_pids+=("$multiplicity_keep") /usr/bin/sleep 30 & multiplicity_duplicate=$! child_pids+=("$multiplicity_duplicate") printf '%s %s\n' "$multiplicity_keep" "$multiplicity_duplicate" >"$runtime_dir/caffeine-pids" printf 'multiplicity\n' >"$runtime_dir/caffeine-mode" rm -f "$runtime_dir/caffeine-list-count" invoke_repair panama.caffeine [[ "$repair_status" == 1 ]] || fail 'inhibitor row multiplicity change was not safely refused' assert_repair_result panama.caffeine true 1 kill -0 "$multiplicity_keep" >/dev/null 2>&1 || fail 'multiplicity mismatch signaled the preserved process' kill -0 "$multiplicity_duplicate" >/dev/null 2>&1 || fail 'multiplicity mismatch signaled the duplicate process' # The production pidfd release function preflights every candidate before any # SIGTERM. A refused second preflight leaves both disposable children alive. /usr/bin/sleep 30 & preflight_first=$! child_pids+=("$preflight_first") /usr/bin/sleep 30 & preflight_second=$! child_pids+=("$preflight_second") /usr/bin/python3 - "$doctor" "$preflight_first" "$preflight_second" <<'PY' \ || fail 'pidfd preflight failure signaled a disposable duplicate' import errno import importlib.machinery import importlib.util import os import signal import sys doctor_path = sys.argv[1] pids = [int(value) for value in sys.argv[2:]] loader = importlib.machinery.SourceFileLoader("panama_doctor_preflight", doctor_path) spec = importlib.util.spec_from_loader(loader.name, loader) module = importlib.util.module_from_spec(spec) sys.modules[spec.name] = module loader.exec_module(module) pidfds = [os.pidfd_open(pid, 0) for pid in pids] calls = [] def sender(pidfd, sig, siginfo, flags): calls.append(sig) if sig == 0 and pidfd == pidfds[1]: raise PermissionError(errno.EPERM, "fixture preflight refusal") signal.pidfd_send_signal(pidfd, sig, siginfo, flags) try: outcome = module.signal_caffeine_pidfds(pidfds, sender) finally: for pidfd in pidfds: os.close(pidfd) assert outcome == "preflight-failed" assert calls == [0, 0] for pid in pids: os.kill(pid, 0) PY kill -0 "$preflight_first" >/dev/null 2>&1 || fail 'preflight refusal killed the first duplicate' kill -0 "$preflight_second" >/dev/null 2>&1 || fail 'preflight refusal killed the second duplicate' # A candidate that disappears after pidfd acquisition and second-list request # is a safe failure; an unrelated disposable process must remain untouched. /usr/bin/sleep 30 & unrelated_pid=$! child_pids+=("$unrelated_pid") ( /usr/bin/sleep 30 & disappearing_pid=$! trap 'kill "$disappearing_pid" >/dev/null 2>&1 || true; wait "$disappearing_pid" >/dev/null 2>&1 || true' EXIT printf '%s\n' "$disappearing_pid" >"$runtime_dir/disappearing-pid" while [[ ! -e "$runtime_dir/release-disappearing-pid" ]]; do /usr/bin/sleep 0.01 done kill "$disappearing_pid" wait "$disappearing_pid" >/dev/null 2>&1 || true trap - EXIT ) & disappearance_controller=$! child_pids+=("$disappearance_controller") for _ in $(seq 1 100); do [[ -s "$runtime_dir/disappearing-pid" ]] && break sleep 0.01 done [[ -s "$runtime_dir/disappearing-pid" ]] || fail 'disappearing PID fixture did not start' disappearing_pid="$(<"$runtime_dir/disappearing-pid")" printf '%s %s\n' "$altered_preserved" "$disappearing_pid" >"$runtime_dir/caffeine-pids" printf 'disappear\n' >"$runtime_dir/caffeine-mode" rm -f "$runtime_dir/caffeine-list-count" invoke_repair panama.caffeine [[ "$repair_status" == 1 ]] || fail 'disappeared inhibitor PID was not safely refused' assert_repair_result panama.caffeine true 1 wait "$disappearance_controller" kill -0 "$unrelated_pid" >/dev/null 2>&1 || fail 'PID disappearance signaled an unrelated process' # Rejected IDs are complete JSON, exit 2, and cause neither a process launch # nor a filesystem mutation. fixture_state() { /usr/bin/python3 - "$fixture" <<'PY' import hashlib import os import stat import sys from pathlib import Path root = Path(sys.argv[1]) digest = hashlib.sha256() for path in sorted(root.rglob("*"), key=lambda item: os.fsencode(str(item.relative_to(root)))): relative = os.fsencode(str(path.relative_to(root))) metadata = path.lstat() digest.update(relative + b"\0" + oct(stat.S_IMODE(metadata.st_mode)).encode() + b"\0") if path.is_symlink(): digest.update(b"link\0" + os.fsencode(os.readlink(path)) + b"\0") elif path.is_file(): digest.update(b"file\0" + hashlib.sha256(path.read_bytes()).digest()) elif path.is_dir(): digest.update(b"dir\0") else: digest.update(b"other\0") print(digest.hexdigest()) PY } for rejected_id in unknown.check integration.home-assistant input.brightness \ desktop.notifications ../../escape 'desktop.vicinae;touch injected'; do : >"$repair_log" before_state="$(fixture_state)" invoke_repair "$rejected_id" [[ "$repair_status" == 2 ]] || fail "$rejected_id returned $repair_status instead of 2" assert_repair_result "$rejected_id" false 2 [[ ! -s "$repair_log" ]] || fail "$rejected_id launched a process" [[ "$(fixture_state)" == "$before_state" ]] || fail "$rejected_id mutated the filesystem" done printf 'panama doctor contract: PASS\n'