Reboot protocol: host proof and remaining board gate
Historical reboot-v1 record. The 2026-10-10 correction and acceptance supersede its missing-source default gate and additional physical collection requests. Reboot-v2 retains a valid cached selection; v1 checkpoints can be recovered but require a new trial to resume. Results below describe v1 only.
2026-10-02. Base c5254075e531487af82841b3ae76582e5535f0fb; source and
narrow tests are committed with this record. host-check.txt
records timestamps, exact commands and results: python3 tests/test_handheld_theme_trial.py (22 passed), openspec validate the-shell-loads-omarchy-themes --strict (passed), and git diff --check
(passed). These are host Python protocol tests with injected command, capture,
boot-ID and ACK inputs. Pointer publication, preference parsing/publication,
state directory moves, report fingerprint validation and restoration execute
the real implementation. No board, serial, cross-build, installed system,
physical reboot, QEMU, panel or full fresh-home evidence was obtained.
--workload reboot now has --reboot-phase begin and resume. Each invocation
finishes without rebooting. The sole operator owns the board and console across
all steps and performs the actual reboot. Begin and every resume verify the
installed system against the pinned candidate; resume requires a new Linux boot
ID and the identical manifest and workload bytes. Public results contain only
fixed status, timestamps, store/source/workload identities, opaque theme/media
IDs and capture hashes. Private source names, preferences, raw output and native
capture pixels stay outside public evidence.
The protocol backs up the entire normal theme-state directory by a durable
same-filesystem rename into a persistent mode-0700 directory before installing
isolated trial state at its normal path. This preserves additional consumers’
private state, including keyboard state. It only hides its own staged source
fixture; it does not rename a production clone. The installed helper daemon
ignores per-call catalog roots, so trial calls force the installed CLI fallback
with an absent private helper socket, honoring the trial --user-themes root.
All successful public gates are labeled state-check-passed with
evidence_class: filesystem-and-cli-state-only. A report fingerprint describes
the desired staged asset, not decoded output or photons. Capture hashes are
unreviewed native evidence until the operator checks the pixels and panel.
Startup source was read: nix/shell.nix exports the same themeDefault/
themeDefaultId to Rust (K230_THEME_DEFAULT_GENERATION) and card-shell
(SWAY_K230_CARD_THEME_DEFAULT) alongside the normal state-root paths.
AppearanceReceiver::bind_with_roots in
nix/rust-shell-client/src/appearance.rs uses
selected_snapshot(...).or(default_snapshot);
card_appearance_start in nix/card-shell/appearance.c initially assigns the
fallback and replaces it only if the active pointer loads successfully. The
theme-helper service in nix/shell.nix only serves the catalog CLI with the
normal root; it does not certify the rendered default at boot. These source
observations motivate the no-pointer check and remain distinct from runtime
proof. The candidate manifest must match those installed default identities.
The three boot arms are:
- Remembered theme/wallpaper: require unchanged generation, selected-background
fingerprint and preference bytes; a preview without
--backgroundmust pick the remembered opaque background ID. - Empty theme state: preserve the trial’s selected state separately, reboot
with no theme pointer/preferences/app appearance and capture the result.
The state check expects the packaged default; it does not prove it rendered. This empties the theme subtree only.
fresh_home: UNVERIFIEDis retained: a full fresh HOME with a usable chooser and recovery route remains a separate physical requirement. - Unavailable source: restore selected trial state, hide only the private source fixture, then reboot. The gate requires default recovery, captures the observed appearance, and fails if the cached user generation persists.
There is an existing requirement/design conflict: the spec’s “Boot with no
saved preferences” scenario requires the declared default when a user source
is unavailable, while design decision 1 retains the last working generation
independently of a removed clone. theme_catalog.selected() currently reports
id: null with the retained generation in this case; the receivers load that
cached generation. The protocol reports failed-default-required and restores
normal state. It does not change consumer policy or accept this as default
success. The coordinator must reconcile that conflict before task 5.5 can pass.
On completion or a protocol gate error, the complete normal state directory and
source fixture return to their original locations, and both receivers use the
existing acknowledged restoration protocol. Failed ACKs are public
restoration: FAILED; private recovery permits an explicit retry. Interrupted
steps retain the durable original directory and recovery record. After any
interruption, use recovery rather than assuming the last checkpoint describes
all intervening filesystem moves. Directory backups are single-use, retained
privately, and never overwritten. No process watchdog runs while the board is
off. Normal-session restoration on glass, including supervised consumers after
startup, still requires the final operator reboot/observation.
The tests exercise same-boot and candidate/workload refusal before mutation, wallpaper/report/preference mismatch, incomplete catalog responses, actual state preservation, interruption recovery at each prepared stage, missing-source failure, and a failed restoration ACK followed by manual retry. One test models the required missing-source default policy solely to exercise the successful protocol branch; it is not evidence that the installed loader implements it. No generated host result is offered as a physical result.
Reserved-operator commands (not run here)
Use the existing manifest/preflight procedure, with the same installed system, pinned theme/capture commands, default generation, normal state root and both ACK sockets. Keep the manifest private and add:
"reboot_trial": {
"theme_name": "trial-fixture",
"background_id": "24 lowercase hex digits from the installed preview"
}
Stage an attributed, exact-revision unchanged community fixture at
/home/shell/.local/state/k230-reboot-trial-UNIQUE/sources/trial-fixture.
This directory belongs only to this trial. Keep its license/revision/hash in the
operator’s reviewed source record. Stage the candidate, pinned workload artifact,
trial script and its imported modules in this persistent private directory.
The necessary modules are theme_preferences.py, theme_transaction.py,
runtime_trace.py, theme_timing.py, theme_background_metrics.py,
theme_background_status.py, app_appearance.py and theme_gtk.py.
Resolve the fixture’s theme/background IDs via the same pinned theme_command,
using --user-themes for the private fixture root and the absent
--helper-socket override. No .git metadata or private capture directory is
exported. Use a separate persistent output directory. The private directory
must be canonical, owned by the shell user, mode 0700, outside /run, /tmp,
/var/tmp and store/device pseudo-filesystems, outside normal theme state, and
on the same filesystem as the theme-state parent.
Replace UNIQUE and every candidate store identity with concrete paths in the
committed operator record. Run as shell inside its actual Wayland session;
retain exclusive theme activation and the board/serial reservation across all
reboots. No concurrent activation, chooser Apply, normal-helper request or
refresh may run during preparation, directory moves or restoration. Keep the
normal helper quiescent while the script prepares each arm; it retains the
normal root and could otherwise write trial state while directories move. These are operator commands, not a host invocation or a board result:
python3 /home/shell/.local/state/k230-reboot-trial-UNIQUE/tools/handheld-theme-trial.py \
--candidate-manifest /home/shell/.local/state/k230-reboot-trial-UNIQUE/candidate.json \
--workload reboot --reboot-phase begin \
--reboot-private-dir /home/shell/.local/state/k230-reboot-trial-UNIQUE \
--output /home/shell/.local/state/k230-reboot-evidence-UNIQUE
After each awaiting-operator-reboot result, the sole console operator runs:
flock -w 120 /tmp/k230-board.lock python3 tools/console.py /dev/ttyACM0 --wait=3 'systemctl reboot'
Once the unchanged system and shell session return, record the boot console identity and actual panel appearance before resuming, since resume prepares the next arm. Then run in the shell user’s active session:
python3 /home/shell/.local/state/k230-reboot-trial-UNIQUE/tools/handheld-theme-trial.py \
--candidate-manifest /home/shell/.local/state/k230-reboot-trial-UNIQUE/candidate.json \
--workload reboot --reboot-phase resume \
--reboot-private-dir /home/shell/.local/state/k230-reboot-trial-UNIQUE \
--output /home/shell/.local/state/k230-reboot-evidence-UNIQUE
Repeat this reboot/observation/resume sequence for the three arms. A gate failure restores normal state and exits nonzero; stop and retain the fixed failure result and reviewed log. For an interruption or failed restoration, run under the same installed candidate and active shell session:
python3 /home/shell/.local/state/k230-reboot-trial-UNIQUE/tools/handheld-theme-trial.py \
--candidate-manifest /home/shell/.local/state/k230-reboot-trial-UNIQUE/candidate.json \
--workload reboot \
--restore-private-dir /home/shell/.local/state/k230-reboot-trial-UNIQUE
The fixed failure result remains a failure even after manual recovery; record the separate retry observation. Reboot once more after restoration and record the normal shell/theme/wallpaper and supervised consumers on the panel. Review private captures before publishing selected nonsensitive images. Commit a phase-by-phase console/panel record with actual boot IDs, installed store path, source/workload identities, observation timestamps, chooser/recovery checks, reviewed photograph/native-capture paths and normal-session restoration. Separately obtain a true fresh-home physical boot with the pinned default; the protocol’s theme-state reset cannot substitute for it. Task 5.5 stays unchecked, and this change stays open.