Protect live state during restore and reset

This commit is contained in:
Gabriel Brown
2026-08-18 03:10:31 -04:00
parent c2cd79b547
commit 6d3f888784
10 changed files with 212 additions and 17 deletions
+5 -3
View File
@@ -48,19 +48,21 @@ run save >/dev/null 2>&1 && fail 'backing up a missing settings file reported su
[[ "$(run list)" == "[]" ]] || fail 'an empty backup directory did not list as empty'
# ── A snapshot round-trips ───────────────────────────────────────────────────
printf '{"gapsOut":24,"windowRounding":6}' >"$settings"
printf '{"gapsOut":24,"windowRounding":6,"displays":{"DP-2":{"mode":"3840x2160@60","scale":2,"transform":0}}}' >"$settings"
mkdir -p "$(dirname "$home")"
printf '{"initialized":true,"favorites":[{"id":"light.desk","alias":"Desk"}]}' >"$home"
run save >/dev/null || fail 'save failed on a valid settings file'
name="$(run list | jq -r '.[0].name')"
[[ "$name" =~ ^settings-[0-9]{8}-[0-9]{9}\.json$ ]] || fail "unexpected snapshot name: $name"
[[ "$(run list | jq -r '.[0].keys')" == "2" ]] || fail 'snapshot key count is wrong'
[[ "$(run list | jq -r '.[0].keys')" == "3" ]] || fail 'snapshot key count is wrong'
printf '{"gapsOut":99}' >"$settings"
printf '{"gapsOut":99,"displays":{"DP-2":{"mode":"4500x3000@60","scale":1.5,"transform":0}}}' >"$settings"
printf '{"initialized":false,"favorites":[]}' >"$home"
restore_result="$(run restore "$name")" || fail 'restore failed'
[[ "$(jq -r .gapsOut "$settings")" == "24" ]] || fail 'restore did not bring back the snapshot contents'
[[ "$(jq -r .windowRounding "$settings")" == "6" ]] || fail 'restore lost a key'
[[ "$(jq -r '.displays["DP-2"].scale' "$settings")" == "1.5" ]] \
|| fail 'restore bypassed display confirmation by applying snapshot geometry'
[[ "$(jq -r '.favorites[0].id' "$home")" == "light.desk" ]] || fail 'restore did not bring back Home favourites'
[[ "$(jq -r '.favorites[0].alias' "$home")" == "Desk" ]] || fail 'restore lost a Home alias'
jq -e '.home.present == true and .home.data.favorites[0].id == "light.desk"' <<<"$restore_result" >/dev/null \
@@ -43,6 +43,8 @@ for mapping in \
'HomePreferences.initialize(ids);' \
'HomePreferences.setAlias(id, alias);' \
'DesktopPreferences.reload();' \
'DesktopPreferences.set("displays", value);' \
'Displays.externalChangeBlocked = blocked;' \
'SystemSettings.applyPersistedDisplayPolicy();' \
'Keybinds.applyReload();' \
'Wallpaper.set(path);' \
@@ -82,9 +84,11 @@ jq -e '
"home.alias:light.desk=Desk",
"home.alias:light.office=Office",
"desktop.reload",
"display.protect:{\"DP-2\":{\"mode\":\"4500x3000@60\",\"scale\":1.5,\"transform\":0}}",
"system.apply",
"keybinds.reload",
"wallpaper.set:/tmp/restored-wallpaper.jpg",
"display.block:false",
"shell.reload"
]
and .initialized == true
@@ -118,15 +122,26 @@ jq -e '
.calls == [
"home.reset",
"desktop.reload",
"display.protect:{\"DP-2\":{\"mode\":\"4500x3000@60\",\"scale\":1.5,\"transform\":0}}",
"system.apply",
"keybinds.reload",
"wallpaper.set:/tmp/restored-wallpaper.jpg",
"display.block:false",
"shell.reload"
]
and .initialized == false
and .favorites == []
' <<<"$status" >/dev/null || fail "absent Home handoff was wrong: $status"
# Restore refuses before launching the helper while a display apply/recovery is
# active, so no snapshot can race the confirmation boundary.
qs_test ipc call settings-backup-behavior reset >/dev/null
[[ "$(qs_test ipc call settings-backup-behavior restoreWhileDisplayBusy)" == "false" ]] \
|| fail 'snapshot restore started during an active display operation'
status="$(qs_test ipc call settings-backup-behavior status)"
jq -e '.calls == [] and (.lastError | contains("display change"))' <<<"$status" >/dev/null \
|| fail "display-busy restore refusal was not clean: $status"
trap - EXIT
cleanup
printf 'settings backup live contract: PASS\n'
@@ -20,6 +20,7 @@ set -euo pipefail
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
harness="$repo_dir/config/dot/quickshell/settings-system-harness.qml"
system_settings="$repo_dir/config/dot/quickshell/services/SystemSettings.qml"
wallpaper_service="$repo_dir/config/dot/quickshell/services/Wallpaper.qml"
# Preferences are committed to $XDG_CONFIG_HOME, and the Home store lives under
# $XDG_STATE_HOME. Both are isolated so this contract cannot touch the real
@@ -37,6 +38,16 @@ rg -Fq 'HomePreferences.resetHomeDefaults();' "$system_settings" \
if rg -q 'HomePreferences\.(favorites|initialized)\s*=' "$system_settings"; then
fail 'restoreDefaults mutates Home aliases instead of using resetHomeDefaults'
fi
rg -Fq 'root.protectDisplays(protectedDisplays)' "$system_settings" \
|| fail 'restoreDefaults can apply unconfirmed display geometry during reload'
rg -Fq 'Keybinds.applyReload();' "$system_settings" \
|| fail 'restoreDefaults does not replay shipped keybindings'
rg -Fq 'root.applyWallpaper(String(DesktopPreferences.get("wallpaperPath") ?? ""));' "$system_settings" \
|| fail 'restoreDefaults does not visibly reapply the shipped wallpaper'
rg -Fq 'const effectivePath = path === "" ? root.shippedPath : path;' "$wallpaper_service" \
|| fail 'clearing wallpaper preference leaves the old image visible'
rg -Fq 'property string storedValue:' "$wallpaper_service" \
|| fail 'the shipped wallpaper cannot remain represented by the default empty preference'
qs_for_harness() {
XDG_CONFIG_HOME="$config_home" XDG_STATE_HOME="$state_home" qs -p "$harness" "$@"
@@ -92,6 +103,9 @@ before="$(qs_for_harness ipc call settings-system-test stored windowRounding)"
# ── Reset spans every store, not just the schema one ─────────────────────────
qs_for_harness ipc call settings-system-test seedHome >/dev/null
qs_for_harness ipc call settings-system-test commit dockHideDelayMs 900 >/dev/null
display_fixture='{"DP-2":{"mode":"4500x3000@60","scale":1.5,"transform":0}}'
[[ "$(qs_for_harness ipc call settings-system-test commit displays "$display_fixture")" == "true" ]] \
|| fail 'the protected display fixture did not apply'
sleep 0.4
home_before="$(qs_for_harness ipc call settings-system-test homeState)"
@@ -100,11 +114,24 @@ jq -e '.count == 1 and .initialized == true' <<<"$home_before" >/dev/null \
[[ "$(qs_for_harness ipc call settings-system-test stored dockHideDelayMs)" == "900" ]] \
|| fail 'the dock fixture did not apply'
qs_for_harness ipc call settings-system-test restoreDefaults >/dev/null
[[ "$(qs_for_harness ipc call settings-system-test restoreDefaults)" == "true" ]] \
|| fail 'restoreDefaults refused a safe reset'
sleep 0.6
reset_state="$(qs_for_harness ipc call settings-system-test resetState)"
jq -e '.calls == [
"display.block:true",
"display.protect",
"keybinds.reload",
"wallpaper.set:",
"display.block:false"
] and .displayBlocked == false' <<<"$reset_state" >/dev/null \
|| fail "reset did not safely replay non-reactive state: $reset_state"
[[ "$(qs_for_harness ipc call settings-system-test stored dockHideDelayMs)" == "250" ]] \
|| fail 'reset did not restore a schema default'
[[ "$(qs_for_harness ipc call settings-system-test stored displays | jq -cS .)" == "$(jq -cS . <<<"$display_fixture")" ]] \
|| fail 'reset replaced confirmed display geometry without confirmation'
home_after="$(qs_for_harness ipc call settings-system-test homeState)"
jq -e '.count == 0 and .initialized == false' <<<"$home_after" >/dev/null \