diff --git a/build/dependencies.lock b/build/dependencies.lock index c8083fb66..c1080f726 100644 --- a/build/dependencies.lock +++ b/build/dependencies.lock @@ -209,7 +209,7 @@ manifest = "build/legacy-manifest.json" schema = 1 component_count = 326 file_count = 1967 -byte_count = 260662316 +byte_count = 260663152 source_build_components = 232 unresolved_components = 94 inherited_baseline = "07505ea58c7bba698d6b9220ff43946a43cac76b" diff --git a/build/legacy-manifest.json b/build/legacy-manifest.json index 82b37a9fd..600805f90 100644 --- a/build/legacy-manifest.json +++ b/build/legacy-manifest.json @@ -85,8 +85,8 @@ "kind": "runtime", "path": "static/build/.tmp_update/runtime.sh", "file_count": 1, - "byte_count": 44632, - "tree_sha256": "042b512c1c696aacf5999b9da0b9f1fdfcb365aecc78cd0be8cdb0f570ad1e85", + "byte_count": 45468, + "tree_sha256": "0fab74cc4b9556cdd40e969d54264a99b41ab41ad99c5926142e2dbec6311279", "source": "https://github.com/boburning/BloomOS", "source_revision": "release-commit", "license": "GPL-3.0-only", diff --git a/docs/DEVICE_SUPPORT.md b/docs/DEVICE_SUPPORT.md index 591c6810f..67701adca 100644 --- a/docs/DEVICE_SUPPORT.md +++ b/docs/DEVICE_SUPPORT.md @@ -63,6 +63,7 @@ Exact device revisions, firmware versions, build commits, test dates, contributo | 2026-08-24 | Miyoo Mini Plus (model ID 354) | installed known-good `v0.1.0-dev-02ddb762`; exact `bloom-session` SHA-256 `ca22fe4e...66d268f` from merged `275eb389` | Read-only session-status contention during structured Super Metroid launch | The candidate and exact merged-content repeat each issued 250 status reads at 20 ms cadence during launch. Both completed 250/250 reads with zero errors, reached canonical `RUNNING` with Recent recorded, and retained green aggregate health. The merged repeat produced RetroArch in 3.10 seconds. Lifecycle writers remain exclusively serialized; the bounded status snapshot no longer takes their mutation lock. Host lifecycle/concurrency passed 15/15, focused session/provenance contracts passed 42/42, and shell CI passed in 2m04s. Machine-readable evidence: [`validation/shell/plus-session-status-contention-20260824.json`](validation/shell/plus-session-status-contention-20260824.json). | This is deliberately abusive remote concurrency/process evidence, not a recommendation to poll at 20 ms and not physical gameplay, tactile, or LCD validation. Mini V2, Flip, and external revisions remain pending. | | 2026-08-26 | Miyoo Mini Plus (model ID 354) | exact NET-01 Warm candidate `4beb681a`; installed known-good `v0.1.0-dev-b073b423` retained | Network service schema 2, canonical non-secret settings migration, idempotent SSH request, independent confirmation, explicit component rollback, listener/key preservation, and aggregate health | The complete PLAT-01/NET-01/REC-04 Warm bundle reported Wi-Fi available/associated, SSH on/running, SFTP off/stopped, and Samba off/stopped. Legacy desired state migrated once to canonical generation 113; repeated status and idempotent SSH enable preserved settings SHA-256, the sole key hash, and tracked listener PID. No-auth SSH was refused as publickey-only. Independent confirmation passed. A device-found redundant rollback replay was corrected, and the final explicit rollback restored the installed files without restarting PID 1197; exact settings, key, harness, service matrix, clean listener audit, known-good update, and green health were retained. Machine-readable evidence: [`validation/network/plus-net01-network-service-v2-20260826.json`](validation/network/plus-net01-network-service-v2-20260826.json). | Remote exact-hash SSH/process/listener/settings/transaction/framebuffer evidence is not physical controls, LCD, audio, gameplay, save, or state certification. The automatic screenshot remains local because it contains private library titles. Mini V1-V4 and Flip remain untested. | | 2026-08-29 | Miyoo Mini Plus (model ID 354) | signed and confirmed `v0.1.0-dev-e69a36de`; workflow run 33231170143 | UI-12 built-in boot, charging, update, recovery, Safe Mode, saving, and shutdown resource adoption | The exact 199,809,395-byte signed archive matched SHA-256 `874188cf...c6386` locally and on-device, created verified snapshot `20260829T034848Z-pre-update-1466`, installed in one update cycle, and was confirmed known-good after two successful boots. The maintainer physically observed the new boot and charging artwork; the controlled poweroff completed. Exact installed binary and lifecycle-resource hashes matched the payload, remote boot/update/recovery/Safe Mode frames had three equal framebuffer pages, and Bloom Shell returned byte-for-byte after the display probes. Wi-Fi association, the sole key, WPA projection, network-helper hashes, listener policy, idle transaction state, five verified snapshots, and aggregate health remained intact. Machine-readable evidence: [`validation/ui/plus-ui12-lifecycle-surfaces-20260829.json`](validation/ui/plus-ui12-lifecycle-surfaces-20260829.json). | The maintainer was not present for the brief Saving/Shutdown artwork, so its physical appearance remains pending even though the actual poweroff completed. Remote frames do not certify tactile controls, physical LCD quality, audio, gameplay, or save semantics. Mini V1-V4 and Flip/my285 remain pending. | +| 2026-08-31 | Miyoo Mini Plus (model ID 354) | signed and confirmed `v0.1.0-dev-c1fc6624`; workflow run 33455349733 | UI-12 physical Saving and Shutdown lifecycle correction | The exact 185,669,001-byte signed archive matched SHA-256 `d06dbe0f...01f11`, installed over a verified pre-update snapshot, and retained exact installed `bootScreen` and runtime hashes across two boots. The maintainer physically observed both the Saving and Shutdown screens, the device powered off, and it returned on the same exact version. Post-confirmation aggregate health, five verified save snapshots, the idle network transaction, inactive recovery lease, clean key-only SSH listener policy, and inactive development overlay were all green. Machine-readable evidence: [`validation/ui/plus-ui12-shutdown-c1fc6624-20260831.json`](validation/ui/plus-ui12-shutdown-c1fc6624-20260831.json). | This is a maintainer observation without video capture and covers only Mini Plus. It does not certify Mini V1-V4, Flip/my285, audio, gameplay, save semantics, or long-duration soak behavior. | | 2026-08-16 | Miyoo Mini Flip (model ID 285) | `v0.1.0-dev-bc086aa6`, `v0.1.0-dev-95de9362`, `v0.1.0-dev-fbf1a03e`, `v0.1.0-dev-3ed5ba7d` | Upgrade install, identity, display/input/battery/lid inventory, Wi-Fi capability, clean-entry boot, instrumented shutdown, Windows FAT check, Play Activity WAL compatibility, key-only SSH | Clean install and boot; native 752×560 panel; two input devices; hall sensor present with open-lid raw value 1; live AXP battery 64%; Wi-Fi capability true. A controlled shutdown cycle entered and returned clean. Play Activity converted a seeded rollback-journal database to WAL, preserved its rows, passed integrity and quick checks, and left no WAL/SHM sidecars after three UI opens. Bloom-built static Dropbear 2025.88 accepted repeated Ed25519 logins, refused password-only authentication, reported model 285 over the live harness, and retained about 74 MB available memory. | Earlier cycles entered or returned dirty, although CHKDSK found no filesystem damage. The first Activity Tracker open/exit briefly glitched the display, then two repeats were normal with no logged crash. Longer soak testing, reboot-path isolation, reboot-time SSH activation, and lid behavior remain required. | The V2 result is a maintainer validation baseline, not certification of V2 or evidence for V1, V3, or V4. The first Plus shutdown retest was invalid because the card entered Linux dirty after a failed Windows command-line eject. After repairing and using Windows Safely Remove Hardware, the card entered Linux without the not-properly-unmounted warning and returned to Windows clean after BloomOS shutdown. diff --git a/docs/program/BloomOS_Work_Program_v3.json b/docs/program/BloomOS_Work_Program_v3.json index 5d3f04b5c..8bb55b32b 100644 --- a/docs/program/BloomOS_Work_Program_v3.json +++ b/docs/program/BloomOS_Work_Program_v3.json @@ -1,9 +1,9 @@ { "schema": 1, "program_version": "3.0", - "generated_at": "2026-08-30T20:17:34Z", + "generated_at": "2026-08-31T13:42:24Z", "repository": "boburning/BloomOS", - "audited_main": "d484da394753f4e77189164c3e005423ebe23f3d", + "audited_main": "a79d953416cd177d1927a2dd1172f3b184f5b425", "product_scope": { "hardware_family": "miyoo-ssd202d", "targets": [ @@ -366,7 +366,8 @@ "without a development overlay, a real RetroArch session reached canonical RUNNING, opened GameSwitcher with MENU, opened Quick Settings with START, resumed the same PID, completed a 180-second smoke, quit gracefully, flushed saves, returned to Bloom Shell, and cleaned the command", "a standalone:legacy null-core session reached canonical RUNNING, naturally exited after 20 seconds, finished STOPPED, and retained the exact request hash", "the task catalog snapshot was restored with integrity ok, all uniquely named fixtures were removed, the permanent RetroArch config hash remained 336ee748ca06bba32582087efc867f811f465558df158056b9a25689bf36672e, aggregate health is green, and no emulator process or active session remains", - "structured evidence and privacy-safe screenshots are recorded in docs/validation/shell/plus-f1b-launch-session-20260825.json; this remote checkpoint certifies Mini Plus only and is not physical controls, LCD, audio, or gameplay certification" + "structured evidence and privacy-safe screenshots are recorded in docs/validation/shell/plus-f1b-launch-session-20260825.json; this remote checkpoint certifies Mini Plus only and is not physical controls, LCD, audio, or gameplay certification", + "after PR #642 merged as a79d953416cd177d1927a2dd1172f3b184f5b425, signed installed version 0.1.0-dev-0b3b5d96 with the identical merged tree passed a settled root-shell smoke and a controlled 90-second RetroArch, GameSwitcher, Quick Settings, same-PID resume, stop, save-flush, frontend-return, configuration, health, and listener regression gate; evidence is docs/validation/shell/plus-post-642-f1-20260831.json" ] }, { @@ -1704,18 +1705,19 @@ "id": "UI-12", "track": "UI", "title": "Critical boot/update/shutdown/recovery surfaces", - "status": "complete", + "status": "published", "dependencies": [ "UI-05" ], "github_prs": [ 512, - 513 + 513, + 644 ], "risk_tier": 3, "deployment_tier": "full", "device_gate_required": true, - "last_audited_main": "eece309a75f548217fcef1e76f2ae8e6411d3bc1", + "last_audited_main": "a79d953416cd177d1927a2dd1172f3b184f5b425", "evidence": [ "issue #511 tracks UI-12 after UI-11 merged; current boot/update/shutdown/recovery/Safe Mode services and signed rollback evidence are substantial, but chargingState still resolved animation frames from theme overrides or the active user theme before falling back to built-in resources", "the same audit found that BLOOM_FIXED_THEME previously bypassed user image paths but theme_load still applied the CurrentProfile theme config override, so boot, shutdown, low-battery, Safe Mode, and other fixed-mode renderers were not fully immutable against user presentation state", @@ -1724,7 +1726,11 @@ "PR #512 exact head 32d9c8427e97d4ecfe5c611956f13535eccb51b6 passed signed workflow 33222933815 and installed on Mini Plus as 0.1.0-dev-32d9c842 in one boot attempt; it was confirmed known-good only after exact-version, aggregate-health, global-IPv4, listener, network-binary, idle-session, and coherent three-page framebuffer checks passed, without claiming tactile, physical-LCD, charging, audio, or non-Plus certification", "merged PR #513 publishes the lifecycle-adoption slice: it projects the audited PR #505 art byte-for-byte into bootScreen, chargingState, and installUI at both supported geometries, removes the inherited charging and installer carousels, rejects symlinked projection paths, selects charging art from the actual bounded battery level, and retains built-in fallback rendering", "PR #513 exact implementation head e69a36de76277485357e7f8a372e20071a8b4461 passed signed workflow 33231170143; the 199809395-byte archive matched SHA-256 874188cfdbdbf33efcd9fe6d7b00f08aa14e7b8f755a4917d112935efc3c6386 locally and on the Mini Plus, created verified snapshot 20260829T034848Z-pre-update-1466, installed in one update cycle, and was confirmed known-good after two successful boots", - "the signed Mini Plus checkpoint retained exact key-only SSH, Wi-Fi association, WPA projection, network-helper hashes, idle transaction state, five verified snapshots, and green aggregate health; the maintainer physically observed the new charging and boot artwork, the controlled poweroff completed, and remote boot, update, recovery, and Safe Mode frames were coherent on all three framebuffer pages, while the brief physical Saving and Shutdown artwork remains explicitly unobserved and Mini V1-V4 plus Flip/my285 remain separate certification targets" + "the signed Mini Plus checkpoint retained exact key-only SSH, Wi-Fi association, WPA projection, network-helper hashes, idle transaction state, five verified snapshots, and green aggregate health; the maintainer physically observed the new charging and boot artwork, the controlled poweroff completed, and remote boot, update, recovery, and Safe Mode frames were coherent on all three framebuffer pages, while the brief physical Saving and Shutdown artwork remained explicitly unobserved and Mini V1-V4 plus Flip/my285 remain separate certification targets", + "physical follow-up on signed version 0.1.0-dev-0b3b5d96 proved the missing surface is a real lifecycle defect: Bloom Shell remained visible for about 30 seconds after a three-second power hold, then the watchdog powered off without showing Saving or Shutdown; issue #643 owns the correction and UI-12 is reopened until focused host gates and a physical Mini Plus retest pass", + "GitHub PR #644 publishes the independent current-main UI-12 correction: keymon now exits MODE_BLOOM_SHELL through a bounded graceful signal, Bloom Shell converts that signal to normal SDL cleanup, and runtime treats the authenticated off-order return as expected before rendering the built-in lifecycle surface; 595 native, 595 AddressSanitizer, 819 exhaustive Shell, 73 focused lifecycle and ledger tests, exact manifest/provenance/dependency gates, formatting, JSON, and scoped static analysis passed before publication", + "the first signed PR #644 candidate v0.1.0-dev-e9ce0c30 proved that the graceful path removed the approximately 30-second watchdog delay, retained green health and clean-storage shutdown evidence, but still failed the physical UI gate because neither Saving nor Shutdown was visible; the synchronous follow-up v0.1.0-dev-8aa37619 also failed twice and automatically rolled back to the prior known-good release", + "signed PR #644 candidate v0.1.0-dev-c1fc6624 makes the fixed Saving and Shutdown art self-contained by excluding the legacy battery-header renderer and retaining framebuffer ownership for respective 1500 ms and 2500 ms dwells; workflow 33455349733 and all required hosted checks passed, exact installed hashes persisted across two boots, the maintainer physically observed both screens and completed poweroff, and the candidate was confirmed known-good only after aggregate health, five verified snapshots, idle network transaction, inactive recovery lease, clean key-only SSH listener policy, and inactive development overlay were verified" ] }, { diff --git a/docs/validation/shell/plus-post-642-f1-20260831.json b/docs/validation/shell/plus-post-642-f1-20260831.json new file mode 100644 index 000000000..8a3d8821f --- /dev/null +++ b/docs/validation/shell/plus-post-642-f1-20260831.json @@ -0,0 +1,76 @@ +{ + "schema": 1, + "date": "2026-08-31", + "checkpoint": "post-PR-642 signed F1 regression check", + "operator": "Codex over maintainer-authorized key-only SSH", + "device": "miyoo-mini-plus", + "installed_release": { + "version": "0.1.0-dev-0b3b5d96", + "signed_source_commit": "0b3b5d96609723724c3d5449e4f65628aa8d3246", + "merged_main_commit": "a79d953416cd177d1927a2dd1172f3b184f5b425", + "identical_tree": "ad298ed31769fbc8fdf1eefcec5fda4e15f78c0b", + "workflow_run": "https://github.com/boburning/BloomOS/actions/runs/33378466169", + "archive_size": 185667660, + "archive_sha256": "316b63bb8bc3bdc093aef360fad24c7af54f122862498fa6b358a312a71c4506", + "payload_sha256": "b7f4a5183be8e968da5abd20f5cbf5a99849fbbdfed8aaa51f810e5861d9ab1c", + "update_phase": "known_good" + }, + "session": { + "system": "gba", + "request_sha256": "42561fb0939e438a3eaa5507abc748fc7e500c616d2a85ef8216eecd7c7225f9", + "emulator_id": "libretro:gpsp", + "core": "gpsp_libretro.so", + "core_sha256": "519e19c015a27ac82ca32f026c9f0c58b407ce31e4512495de19bd1828452587", + "canonical_running": true, + "menu_gameswitcher": true, + "start_quick_settings": true, + "same_pid_resume": 704, + "duration_seconds": 90, + "session_duration_seconds": 93, + "start_rss_kib": 29492, + "peak_rss_kib": 31084, + "shutdown_method": "retroarch_quit", + "final_state": "STOPPED", + "recent_status": "recorded", + "graceful_exit": true, + "save_flush": true, + "frontend_return": true, + "command_cleanup": true + }, + "orchestration_observation": { + "first_overlay_run": "frontend_return_timeout after the test duration expired while an overlay handoff was closing", + "immediate_retry": "retroarch_start_timeout because the process-name-only smoke preflight accepted bloom-shell --settings while the runtime still owned that executable handoff", + "classification": "test orchestration defect; the settled root-shell retry and controlled overlay-resume run both passed", + "product_crash_observed": false + }, + "postflight": { + "aggregate_health": "healthy", + "active_sessions": 0, + "shell_guard": "ready with zero consecutive failures", + "network_transaction": "idle", + "listener_audit": "clean with only reviewed key-only SSH", + "global_retroarch_config_sha256_before": "336ee748ca06bba32582087efc867f811f465558df158056b9a25689bf36672e", + "global_retroarch_config_sha256_after": "336ee748ca06bba32582087efc867f811f465558df158056b9a25689bf36672e", + "task_launch_files_remaining": 0 + }, + "physical_observation": { + "power_hold_seconds": 3, + "saving_surface_seen": false, + "shutdown_surface_seen": false, + "shell_remained_visible_seconds": 30, + "disposition": "failed and tracked by issue 643" + }, + "privacy": { + "fixture": "maintainer-owned local game selected only by file type", + "rom_path_recorded": false, + "rom_title_recorded": false, + "rom_hash_recorded": false, + "private_framebuffer_published": false + }, + "limitations": [ + "Session, overlay, process, listener, and framebuffer inspection is remote evidence and does not certify tactile controls, physical LCD quality, audio, or gameplay.", + "This checkpoint certifies only the Miyoo Mini Plus, not Mini V1-V4 or Mini Flip/my285.", + "Natural standalone exit and the RetroAchievements degradation matrix remain covered by the earlier signed F1b evidence because PR 642 changed only networking and update-recovery owners.", + "The missing Saving and Shutdown surfaces are a physical failure, not a pass; issue 643 owns the correction." + ] +} diff --git a/docs/validation/ui/plus-ui12-shutdown-c1fc6624-20260831.json b/docs/validation/ui/plus-ui12-shutdown-c1fc6624-20260831.json new file mode 100644 index 000000000..a32c25563 --- /dev/null +++ b/docs/validation/ui/plus-ui12-shutdown-c1fc6624-20260831.json @@ -0,0 +1,86 @@ +{ + "schema": 1, + "date": "2026-08-31", + "checkpoint": "PR 644 Mini Plus Saving and Shutdown physical gate", + "device": "miyoo-mini-plus", + "signed_release": { + "version": "0.1.0-dev-c1fc6624", + "commit": "c1fc66240475a0763d132da84342b8fb3cdeb66c", + "workflow_run": 33455349733, + "workflow_url": "https://github.com/boburning/BloomOS/actions/runs/33455349733", + "archive_size": 185669001, + "archive_sha256": "d06dbe0ffdcfd90d38d107d4ccec1be3ed71f999aa4fa3130cfc41c89e901f11", + "manifest_valid": true, + "sha256sums_valid": true, + "ed25519_signature_valid": true, + "snapshot_id": "20260901T004904Z-pre-update-2114", + "boot_attempts_before_confirmation": 2, + "final_update_phase": "known_good" + }, + "installed_payload": { + "boot_screen_sha256": "71fae8c2d25292327d7015c29ef9ab314b641d828da8f34d09b202269e641c57", + "runtime_sha256": "5028eed08f4d1fee9904a22c71b4164d3ac26f9636f753da0fb176285e7e9a22", + "exact_version_after_power_cycle": "v0.1.0-dev-c1fc6624" + }, + "physical_observation": { + "saving_surface_seen": true, + "shutdown_surface_seen": true, + "device_powered_off": true, + "device_powered_back_on": true, + "disposition": "pass" + }, + "implementation": { + "fixed_lifecycle_art_self_contained": true, + "legacy_battery_header_disabled_for_saving_and_shutdown": true, + "saving_renderer_dwell_ms": 1500, + "shutdown_renderer_dwell_ms": 2500, + "renderer_owns_framebuffer_for_complete_dwell": true + }, + "post_confirmation": { + "aggregate_health": "healthy", + "save_snapshots": { + "healthy": true, + "total": 5, + "unverified": 0 + }, + "network_transaction": "idle", + "network_recovery_lease": "inactive", + "listener_audit": "clean with reviewed key-only SSH", + "development_overlay_active": false + }, + "validation": { + "focused_shutdown_and_critical_surface_tests": "14 passed", + "pinned_miyoo_arm_compile": "passed", + "required_hosted_checks": { + "format": "passed", + "check_build": "passed", + "test_build": "passed", + "address_sanitizer": "passed", + "shell_test": "passed", + "static_analysis": "passed" + } + }, + "predecessor_rejections": [ + { + "version": "0.1.0-dev-e9ce0c30", + "reason": "Saving and Shutdown were not physically visible", + "confirmed": false + }, + { + "version": "0.1.0-dev-8aa37619", + "reason": "Saving and Shutdown were not physically visible on two attempts; automatic rollback restored the prior known-good release", + "confirmed": false + } + ], + "privacy": { + "device_serial_recorded": false, + "credentials_recorded": false, + "private_paths_recorded": false, + "framebuffer_published": false + }, + "limitations": [ + "The physical result is a maintainer observation; no video capture was recorded.", + "This checkpoint covers only Miyoo Mini Plus, not Mini V1-V4 or Mini Flip/my285.", + "This shutdown check does not certify physical controls, audio, gameplay, save semantics, or long-duration soak behavior." + ] +} diff --git a/docs/validation/ui/plus-ui12-shutdown-e9ce0c30-20260831.json b/docs/validation/ui/plus-ui12-shutdown-e9ce0c30-20260831.json new file mode 100644 index 000000000..534cced31 --- /dev/null +++ b/docs/validation/ui/plus-ui12-shutdown-e9ce0c30-20260831.json @@ -0,0 +1,53 @@ +{ + "schema": 1, + "date": "2026-08-31", + "checkpoint": "PR 644 first signed shutdown candidate physical gate", + "device": "miyoo-mini-plus", + "candidate": { + "version": "0.1.0-dev-e9ce0c30", + "source_commit": "e9ce0c30269b083942f3da3d03a7079a083024cf", + "workflow_run": "https://github.com/boburning/BloomOS/actions/runs/33398942755", + "update_phase": "testing", + "boot_attempts": 2, + "confirmed": false, + "rollback_version": "0.1.0-dev-0b3b5d96" + }, + "physical_observation": { + "graceful_poweroff_faster_than_prior_watchdog": true, + "saving_surface_seen": false, + "shutdown_surface_seen": false, + "device_powered_off": true, + "device_powered_back_on": true, + "disposition": "failed; candidate remains unconfirmed" + }, + "post_boot": { + "aggregate_health": "healthy", + "network_transaction": "idle", + "listener_audit": "clean with reviewed key-only SSH", + "development_overlay_active": false, + "shutdown_log": { + "mode": "poweroff", + "swapoff_complete": true, + "remount_read_only_result": 0, + "recursive_unmount_result": 0, + "final_unmount_result_after_recursive_unmount": 1 + } + }, + "diagnosis": { + "graceful_frontend_exit_effective": true, + "renderer_completion_boundary_present": false, + "runtime_behavior": "bootScreen was started asynchronously and clean power began after a fixed one-second delay", + "correction": "render Saving synchronously while shutdown hooks run, render Shutdown synchronously afterward, and preserve a bounded physical dwell before clean power" + }, + "privacy": { + "device_serial_recorded": false, + "credentials_recorded": false, + "private_paths_recorded": false, + "framebuffer_published": false + }, + "limitations": [ + "The failed physical observation certifies neither lifecycle surface as visible.", + "Remote health and shutdown telemetry cannot replace the next physical LCD and power-hold check.", + "This checkpoint covers only Miyoo Mini Plus, not Mini V1-V4 or Mini Flip/my285." + ] +} diff --git a/src/bloomShell/main.c b/src/bloomShell/main.c index b48ea8844..804875239 100644 --- a/src/bloomShell/main.c +++ b/src/bloomShell/main.c @@ -90,6 +90,7 @@ _Static_assert(WIFI_EVENT_TYPE < SDL_NUMEVENTS, #define MIXED_LABEL_MAX_BYTES 640 static volatile sig_atomic_t game_switcher_requested = 0; +static volatile sig_atomic_t shutdown_requested = 0; extern char **environ; static void request_game_switcher(int signal_number) @@ -98,9 +99,22 @@ static void request_game_switcher(int signal_number) game_switcher_requested = 1; } +static void request_shutdown(int signal_number) +{ + (void)signal_number; + shutdown_requested = 1; +} + static Uint32 game_switcher_wake_tick(Uint32 interval, void *context) { (void)context; + if (shutdown_requested) { + SDL_Event event; + memset(&event, 0, sizeof(event)); + event.type = SDL_QUIT; + if (SDL_PushEvent(&event) == 0) + shutdown_requested = 0; + } if (game_switcher_requested) { SDL_Event event; memset(&event, 0, sizeof(event)); @@ -3636,6 +3650,14 @@ int main(int argc, char **argv) free(games); return 1; } + struct sigaction shutdown_signal = {0}; + shutdown_signal.sa_handler = request_shutdown; + sigemptyset(&shutdown_signal.sa_mask); + if (sigaction(SIGTERM, &shutdown_signal, NULL) != 0) { + bloom_shell_achievements_destroy(&achievement_index); + free(games); + return 1; + } const SDL_VideoInfo *info = SDL_GetVideoInfo(); int width = info != NULL && info->current_w >= 640 ? info->current_w : 640; int height = info != NULL && info->current_h >= 480 ? info->current_h : 480; diff --git a/src/bootScreen/bootScreen.c b/src/bootScreen/bootScreen.c index 945761592..4d8cbe2ab 100644 --- a/src/bootScreen/bootScreen.c +++ b/src/bootScreen/bootScreen.c @@ -65,16 +65,17 @@ int main(int argc, char *argv[]) const char *lifecycle_kind = "boot"; bool show_battery = false; bool show_version = true; + Uint32 lifecycle_dwell_ms = 0; if (argc > 1 && strcmp(argv[1], "End_Save") == 0) { fallback = SCREEN_OFF_SAVE; lifecycle_kind = "saving"; - show_battery = true; + lifecycle_dwell_ms = 1500; } else if (argc > 1 && strcmp(argv[1], "End") == 0) { fallback = SCREEN_OFF; lifecycle_kind = "shutdown"; - show_battery = true; + lifecycle_dwell_ms = 2500; } else if (argc > 1 && strcmp(argv[1], "lowBat") == 0) { fallback = LOW_BAT; @@ -124,6 +125,12 @@ int main(int argc, char *argv[]) render(); render(); + // Keep framebuffer ownership for the complete physical dwell. The fixed + // Saving and Shutdown artwork is self-contained, so it must not enter the + // legacy battery-header renderer used by interactive surfaces. + if (lifecycle_dwell_ms > 0) + SDL_Delay(lifecycle_dwell_ms); + if (lifecycle_loaded) SDL_FreeSurface(background); resources_free(); diff --git a/src/keymon/keymon.c b/src/keymon/keymon.c index a98cd63a0..b81588b16 100644 --- a/src/keymon/keymon.c +++ b/src/keymon/keymon.c @@ -227,6 +227,9 @@ void deepsleep(void) if (system_state == MODE_MAIN_UI) { kill_mainUI(); } + else if (system_state == MODE_BLOOM_SHELL) { + kill(system_state_pid, SIGTERM); + } else if (system_state == MODE_SWITCHER) { kill(system_state_pid, SIGTERM); } diff --git a/static/build/.tmp_update/runtime.sh b/static/build/.tmp_update/runtime.sh index 15335e66a..6e2e6ee6f 100644 --- a/static/build/.tmp_update/runtime.sh +++ b/static/build/.tmp_update/runtime.sh @@ -253,6 +253,7 @@ clear_logs() { } check_main_ui() { + shutdown_surface="End" if [ ! -f $sysdir/cmd_to_run.sh ]; then if mainui_development_fallback_enabled && [ -f /tmp/run_advmenu ]; then rm /tmp/run_advmenu @@ -261,9 +262,10 @@ check_main_ui() { launch_main_ui else launch_bloom_shell + shutdown_surface="End_Save" fi - check_off_order "End" + check_off_order "$shutdown_surface" fi } @@ -428,6 +430,13 @@ launch_bloom_shell() { fi rm -f "$ready_marker_sleep_path" bloom_launch_trace_mark runtime_ready_marker_stopped + if [ -f /tmp/.offOrder ] && [ ! -L /tmp/.offOrder ]; then + [ -z "$launch_id" ] || "$shell_guard" complete "$launch_id" > /dev/null 2>&1 || + log "Bloom Shell shutdown state could not be completed" + log "Bloom Shell exited for the requested poweroff" + set_prev_state "bloom-shell" + return 0 + fi if bloom_dev_shell_restart_pending; then [ -z "$launch_id" ] || "$shell_guard" complete "$launch_id" > /dev/null 2>&1 || log "Bloom development restart state could not be completed" @@ -974,6 +983,15 @@ check_off_order() { if [ -f /tmp/.offOrder ]; then touch /tmp/shutting_down + # Bloom Shell persists frontend state during its graceful exit. Present + # the built-in Saving surface synchronously while the remaining + # shutdown hooks finish, then hand display ownership to the built-in + # Shutdown surface. Each renderer retains framebuffer ownership for its + # own bounded physical dwell and returns only after all pages are stable. + if [ "$1" = "End_Save" ]; then + bootScreen End_Save + fi + #EmuDeck - CheckOff scripts check_off_scripts=$(find "$sysdir/checkoff" -type f -name "*.sh") @@ -981,8 +999,7 @@ check_off_order() { sh "$check_off_script" done - bootScreen "$1" & - sleep 1 # Allow the bootScreen to be displayed + bootScreen End bloom-power request poweroff fi } diff --git a/test/shell/clean_shutdown.bats b/test/shell/clean_shutdown.bats index e700b3b9f..80c997667 100644 --- a/test/shell/clean_shutdown.bats +++ b/test/shell/clean_shutdown.bats @@ -62,3 +62,38 @@ teardown() { teardown_bloom_fixture; } grep -F 'system("/mnt/SDCARD/.tmp_update/bin/bloom-power request poweroff")' /workspace/src/chargingState/chargingState.c grep -F ' bloom-power request poweroff' /workspace/static/build/.tmp_update/runtime.sh } + +@test "Bloom Shell power hold exits through the guarded lifecycle surface" { + keymon=/workspace/src/keymon/keymon.c + shell=/workspace/src/bloomShell/main.c + runtime=/workspace/static/build/.tmp_update/runtime.sh + + grep -A5 -F 'else if (system_state == MODE_BLOOM_SHELL)' "$keymon" | + grep -F 'kill(system_state_pid, SIGTERM);' + grep -F 'shutdown_signal.sa_handler = request_shutdown;' "$shell" + grep -F 'sigaction(SIGTERM, &shutdown_signal, NULL)' "$shell" + grep -A7 -F 'if (shutdown_requested)' "$shell" | grep -F 'event.type = SDL_QUIT;' + grep -A7 -F 'if (shutdown_requested)' "$shell" | grep -F 'if (SDL_PushEvent(&event) == 0)' + grep -A8 -F 'if [ -f /tmp/.offOrder ] && [ ! -L /tmp/.offOrder ]; then' "$runtime" | + grep -F '"$shell_guard" complete "$launch_id"' + grep -A10 -F 'if [ -f /tmp/.offOrder ] && [ ! -L /tmp/.offOrder ]; then' "$runtime" | + grep -F 'set_prev_state "bloom-shell"' + grep -A8 -F 'launch_bloom_shell' "$runtime" | grep -F 'shutdown_surface="End_Save"' + grep -F 'check_off_order "$shutdown_surface"' "$runtime" +} + +@test "Bloom Shell shutdown renders Saving then Shutdown synchronously before poweroff" { + runtime=/workspace/static/build/.tmp_update/runtime.sh + + saving_line="$(grep -n 'bootScreen End_Save' "$runtime" | cut -d: -f1)" + hooks_line="$(grep -n 'check_off_scripts=$(find' "$runtime" | cut -d: -f1)" + shutdown_line="$(grep -n 'bootScreen End$' "$runtime" | cut -d: -f1)" + power_line="$(grep -n 'bloom-power request poweroff' "$runtime" | tail -n 1 | cut -d: -f1)" + + [ "$saving_line" -lt "$hooks_line" ] + [ "$hooks_line" -lt "$shutdown_line" ] + [ "$shutdown_line" -lt "$power_line" ] + ! grep -F 'bootScreen "$1" &' "$runtime" + ! grep -F 'bootScreen End_Save && sleep' "$runtime" + ! grep -F 'bootScreen End && sleep' "$runtime" +} diff --git a/test/shell/critical_surfaces.bats b/test/shell/critical_surfaces.bats index 2d07829c3..f29393fc9 100644 --- a/test/shell/critical_surfaces.bats +++ b/test/shell/critical_surfaces.bats @@ -9,6 +9,12 @@ grep -F 'SYSTEM_RESOURCES "lifecycle/boot/%dx%d/frame-%02d.png"' "$source" grep -F 'lifecycle_kind = "shutdown"' "$source" grep -F 'lifecycle_kind = "saving"' "$source" + grep -A4 -F 'strcmp(argv[1], "End_Save") == 0' "$source" | grep -F 'lifecycle_dwell_ms = 1500;' + grep -A4 -F 'strcmp(argv[1], "End") == 0' "$source" | grep -F 'lifecycle_dwell_ms = 2500;' + grep -F 'SDL_Delay(lifecycle_dwell_ms);' "$source" + ! grep -A4 -F 'strcmp(argv[1], "End_Save") == 0' "$source" | grep -F 'show_battery = true;' + ! grep -A4 -F 'strcmp(argv[1], "End") == 0' "$source" | grep -F 'show_battery = true;' + grep -A6 -F 'strcmp(argv[1], "lowBat") == 0' "$source" | grep -F 'show_battery = true;' } @test "charging state uses exact geometry and actual battery-level art" { @@ -81,6 +87,12 @@ import json root = "/workspace" with open(root + "/docs/validation/ui/plus-ui12-lifecycle-surfaces-20260829.json", encoding="utf-8") as stream: evidence = json.load(stream) +with open(root + "/docs/validation/shell/plus-post-642-f1-20260831.json", encoding="utf-8") as stream: + followup = json.load(stream) +with open(root + "/docs/validation/ui/plus-ui12-shutdown-e9ce0c30-20260831.json", encoding="utf-8") as stream: + first_candidate = json.load(stream) +with open(root + "/docs/validation/ui/plus-ui12-shutdown-c1fc6624-20260831.json", encoding="utf-8") as stream: + final_candidate = json.load(stream) with open(root + "/docs/program/BloomOS_Work_Program_v3.json", encoding="utf-8") as stream: program = json.load(stream) @@ -106,10 +118,47 @@ assert preserved["listener_audit_clean"] is True assert preserved["authorized_key_count"] == 1 assert preserved["aggregate_health_after_confirmation"] == "healthy" assert any("not present" in item for item in evidence["limitations"]) -assert package["status"] == "complete" -assert package["github_prs"] == [512, 513] +assert followup["installed_release"]["merged_main_commit"] == "a79d953416cd177d1927a2dd1172f3b184f5b425" +assert followup["physical_observation"]["saving_surface_seen"] is False +assert followup["physical_observation"]["shutdown_surface_seen"] is False +assert followup["physical_observation"]["disposition"] == "failed and tracked by issue 643" +assert first_candidate["candidate"]["source_commit"] == "e9ce0c30269b083942f3da3d03a7079a083024cf" +assert first_candidate["candidate"]["update_phase"] == "testing" +assert first_candidate["candidate"]["confirmed"] is False +assert first_candidate["physical_observation"]["graceful_poweroff_faster_than_prior_watchdog"] is True +assert first_candidate["physical_observation"]["saving_surface_seen"] is False +assert first_candidate["physical_observation"]["shutdown_surface_seen"] is False +assert first_candidate["post_boot"]["aggregate_health"] == "healthy" +assert first_candidate["post_boot"]["shutdown_log"]["remount_read_only_result"] == 0 +assert first_candidate["diagnosis"]["renderer_completion_boundary_present"] is False +release = final_candidate["signed_release"] +physical = final_candidate["physical_observation"] +post_confirmation = final_candidate["post_confirmation"] +assert release["commit"] == "c1fc66240475a0763d132da84342b8fb3cdeb66c" +assert release["workflow_run"] == 33455349733 +assert release["archive_size"] == 185669001 +assert release["archive_sha256"] == "d06dbe0ffdcfd90d38d107d4ccec1be3ed71f999aa4fa3130cfc41c89e901f11" +assert release["ed25519_signature_valid"] is True +assert release["boot_attempts_before_confirmation"] == 2 +assert release["final_update_phase"] == "known_good" +assert physical["saving_surface_seen"] is True +assert physical["shutdown_surface_seen"] is True +assert physical["device_powered_off"] is True +assert physical["device_powered_back_on"] is True +assert physical["disposition"] == "pass" +assert final_candidate["implementation"]["legacy_battery_header_disabled_for_saving_and_shutdown"] is True +assert final_candidate["implementation"]["saving_renderer_dwell_ms"] == 1500 +assert final_candidate["implementation"]["shutdown_renderer_dwell_ms"] == 2500 +assert post_confirmation["aggregate_health"] == "healthy" +assert post_confirmation["save_snapshots"]["unverified"] == 0 +assert post_confirmation["network_transaction"] == "idle" +assert post_confirmation["listener_audit"] == "clean with reviewed key-only SSH" +assert post_confirmation["development_overlay_active"] is False +assert package["status"] == "published" +assert package["github_prs"] == [512, 513, 644] PY [ "$status" -eq 0 ] grep -F 'validation/ui/plus-ui12-lifecycle-surfaces-20260829.json' /workspace/docs/DEVICE_SUPPORT.md + grep -F 'validation/ui/plus-ui12-shutdown-c1fc6624-20260831.json' /workspace/docs/DEVICE_SUPPORT.md }