From 780eedc1a933675f177b77cfcd4042e6daa80d64 Mon Sep 17 00:00:00 2001 From: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com> Date: Tue, 23 Jun 2026 09:36:34 +0200 Subject: [PATCH 1/6] docs(readme): add startup flow diagram Adds a Mermaid flowchart covering all startup phases from PID 1 to switch_root, with inline release/debug branching at each error point. Includes a terminal-states legend and a note on the apply_boot_env_decision core_result capture invariant. Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com> --- README.md | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/README.md b/README.md index 5a0984d..554ee07 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,96 @@ Not yet implemented (planned): - Factory reset (backup, wipe, restore) - Flash modes (disk clone, network, HTTP/HTTPS) +## Startup Flow + +The diagram traces every phase from PID 1 start to `switch_root`. The `release-image` +feature flag determines the error-handling branch at each fatal failure point. + +```mermaid +flowchart TD + START([PID 1 starts]) --> MOUNT_ESS["mount_essential_filesystems\n/dev Β· /proc Β· /sys Β· /run"] + + MOUNT_ESS -->|OK| LOGGER["KmsgLogger::init()"] + MOUNT_ESS -->|Fail| EARLY_ERR{Image type?} + EARLY_ERR -->|release| HALT1(["πŸ”΄ eprintln loop β€” halt"]) + EARLY_ERR -->|debug| ESHELL(["🐚 emergency sh β€” respawn"]) + + LOGGER -->|OK| CONFIG["Config::load()\n/proc/cmdline Β· os-release"] + LOGGER -->|Fail| FEB + + CONFIG -->|OK| RDEV["detect_root_device()"] + CONFIG -->|Fail| FEB + + RDEV -->|OK| LAYOUT["PartitionLayout::new()\ncreate_omnect_symlinks()"] + RDEV -->|Fail| FEB + + LAYOUT -->|OK| CORE["mount_core_partitions()\nrootfs + boot + fsck"] + LAYOUT -->|Fail| FEB + + CORE --> BENV["open_boot_env()"] + + BENV --> CLASSIFY{"classify_boot_env"} + CLASSIFY -->|"OK β†’ Available"| APPLY["apply_boot_env_decision()\ncore_result Γ— env decision"] + CLASSIFY -->|"Fail + release β†’ Degraded"| APPLY + CLASSIFY -->|"Fail + debug β†’ Abort"| FEB + + APPLY -->|"FsckRequiresReboot\nfsck result persisted first"| REBOOT(["πŸ” Reboot"]) + APPLY -->|Fatal| FEB + APPLY -->|"OK\nDegraded: ods.degraded_boot=true"| FBDETECT["compute_first_boot()\nset_update_pending()"] + + FBDETECT --> ISETUP["init_setup::run()\nresize-data preflight\nif feature = resize-data"] + ISETUP -->|FsckRequiresReboot| REBOOT + ISETUP -->|"Other error\nContinueDegraded β€” warn"| BMODE["BootMode::detect() β†’ Normal"] + ISETUP -->|OK| BMODE + + BMODE --> MREM["mount_remaining_partitions()\ndata Β· factory Β· cert + fsck"] + MREM -->|"FsckRequiresReboot\nfsck result persisted first"| REBOOT + MREM -->|Fatal| FEB + MREM -->|OK| OVL["setup_raw_rootfs_mount()\nsetup_etc_overlay()\nsetup_data_overlay()"] + + OVL -->|OK| LINKS["create_fs_links()\ncreate_ods_runtime_files()"] + OVL -->|Fail| FEB + + LINKS -->|OK| FBM["write_first_boot_marker()\nif first_boot ∧ resize_ok ∧ env_available\nbest-effort β€” warn on fail"] + LINKS -->|Fail| FEB + + FBM --> SR["switch_root β†’ systemd"] + SR -->|OK| SUCCESS(["βœ… systemd running"]) + SR -->|Fail| FEB + + FEB{"Fatal error handler\nupdate_pending?"} + FEB -->|yes| REBOOT + FEB -->|"no + release"| HALT2(["πŸ”΄ kmsg loop β€” halt forever"]) + FEB -->|"no + debug"| DSHELL(["🐚 debug bash/sh β€” respawn"]) + + classDef success fill:#2d6a2d,color:#fff,stroke:#1a3d1a + classDef reboot fill:#1a4d7a,color:#fff,stroke:#0d2d4d + classDef halt fill:#7a1a1a,color:#fff,stroke:#4d0d0d + classDef shell fill:#7a4a1a,color:#fff,stroke:#4d2d0d + + class SUCCESS success + class REBOOT reboot + class HALT1,HALT2 halt + class ESHELL,DSHELL shell +``` + +**Terminal states** + +| Symbol | Outcome | Trigger | +|--------|---------|---------| +| βœ… | `switch_root` β€” systemd takes over | Normal completion | +| πŸ” | Reboot | `FsckRequiresReboot` (unconditional); or any fatal error while `omnect_validate_update` is set β€” triggers bootloader OTA rollback | +| πŸ”΄ | Halt (kmsg loop, infinite) | Fatal error Β· release image Β· no OTA in flight | +| 🐚 | Debug shell (bash β†’ sh fallback, respawning) | Fatal error Β· debug image Β· no OTA in flight | + +**Notes on `apply_boot_env_decision`** + +`mount_core_partitions` result is captured rather than propagated immediately so that +fsck diagnostics can be persisted to the bootloader environment before any reboot. +`apply_boot_env_decision` enforces the invariant that `FsckRequiresReboot` always wins +over a concurrent `DegradedBoot` β€” the two failure modes can co-occur when GRUB's +boot partition is unmountable. + ## Building ```bash From 70891d9947d0d81ac156447958ab10780915e360 Mon Sep 17 00:00:00 2001 From: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com> Date: Wed, 24 Jun 2026 06:28:06 +0200 Subject: [PATCH 2/6] docs(readme): fix diagram per review findings 1,3,4,5 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Finding 1: ISETUP edge label was wrong β€” 'Other error β†’ ContinueDegraded' implied all non-reboot errors are absorbed. Only ResizeData errors are absorbed by handle_result; non-ResizeData errors propagate as Fatal. Relabelled edge and added missing ISETUP -->|Fatal (non-resize)| FEB edge. Finding 3: FEB node understated its role. Renamed to 'Error handler / RecoveryClass?' and expanded edges to show RebootToApply as a distinct branch, making clear FsckRequiresReboot flows through this handler. Added 'Notes on error handling' prose section. Finding 4: persist_fsck_results was annotated only on FsckRequiresReboot edges, implying it only runs on the reboot branch. Moved annotation to the APPLY and MREM nodes where it actually runs (every mount path, including degraded boot). Finding 5: BMODE had no failure edge. Added BMODE -->|Fatal| FEB. Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com> --- README.md | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 554ee07..45aa520 100644 --- a/README.md +++ b/README.md @@ -53,21 +53,24 @@ flowchart TD CORE --> BENV["open_boot_env()"] BENV --> CLASSIFY{"classify_boot_env"} - CLASSIFY -->|"OK β†’ Available"| APPLY["apply_boot_env_decision()\ncore_result Γ— env decision"] + CLASSIFY -->|"OK β†’ Available"| APPLY["apply_boot_env_decision()\ncore_result Γ— env decision\npersist_fsck_results β€” always"] CLASSIFY -->|"Fail + release β†’ Degraded"| APPLY CLASSIFY -->|"Fail + debug β†’ Abort"| FEB - APPLY -->|"FsckRequiresReboot\nfsck result persisted first"| REBOOT(["πŸ” Reboot"]) + APPLY -->|"FsckRequiresReboot"| REBOOT(["πŸ” Reboot"]) APPLY -->|Fatal| FEB APPLY -->|"OK\nDegraded: ods.degraded_boot=true"| FBDETECT["compute_first_boot()\nset_update_pending()"] FBDETECT --> ISETUP["init_setup::run()\nresize-data preflight\nif feature = resize-data"] ISETUP -->|FsckRequiresReboot| REBOOT - ISETUP -->|"Other error\nContinueDegraded β€” warn"| BMODE["BootMode::detect() β†’ Normal"] + ISETUP -->|"ResizeData error\nContinueDegraded β€” warn"| BMODE["BootMode::detect() β†’ Normal"] + ISETUP -->|"Fatal (non-resize)"| FEB ISETUP -->|OK| BMODE - BMODE --> MREM["mount_remaining_partitions()\ndata Β· factory Β· cert + fsck"] - MREM -->|"FsckRequiresReboot\nfsck result persisted first"| REBOOT + BMODE -->|Fatal| FEB + + BMODE --> MREM["mount_remaining_partitions()\ndata Β· factory Β· cert + fsck\npersist_fsck_results β€” always"] + MREM -->|"FsckRequiresReboot"| REBOOT MREM -->|Fatal| FEB MREM -->|OK| OVL["setup_raw_rootfs_mount()\nsetup_etc_overlay()\nsetup_data_overlay()"] @@ -81,10 +84,11 @@ flowchart TD SR -->|OK| SUCCESS(["βœ… systemd running"]) SR -->|Fail| FEB - FEB{"Fatal error handler\nupdate_pending?"} - FEB -->|yes| REBOOT - FEB -->|"no + release"| HALT2(["πŸ”΄ kmsg loop β€” halt forever"]) - FEB -->|"no + debug"| DSHELL(["🐚 debug bash/sh β€” respawn"]) + FEB{"Error handler\nRecoveryClass?"} + FEB -->|"RebootToApply"| REBOOT + FEB -->|"Fatal + update_pending"| REBOOT + FEB -->|"Fatal + no update + release"| HALT2(["πŸ”΄ kmsg loop β€” halt forever"]) + FEB -->|"Fatal + no update + debug"| DSHELL(["🐚 debug bash/sh β€” respawn"]) classDef success fill:#2d6a2d,color:#fff,stroke:#1a3d1a classDef reboot fill:#1a4d7a,color:#fff,stroke:#0d2d4d @@ -106,13 +110,26 @@ flowchart TD | πŸ”΄ | Halt (kmsg loop, infinite) | Fatal error Β· release image Β· no OTA in flight | | 🐚 | Debug shell (bash β†’ sh fallback, respawning) | Fatal error Β· debug image Β· no OTA in flight | +**Notes on error handling** + +All errors from `run_init()` reach `handle_fatal_error` in `main.rs`, which dispatches on +`RecoveryClass`: +- `RebootToApply` (e.g. `FsckRequiresReboot`) β†’ always Reboot, regardless of image type +- `Fatal` + `omnect_validate_update` set β†’ Reboot (bootloader OTA rollback) +- `Fatal` + no OTA in flight + release β†’ Halt (kmsg loop) +- `Fatal` + no OTA in flight + debug β†’ debug shell + +`FsckRequiresReboot` edges in the diagram flow through this handler β€” not via a separate +mechanism. + **Notes on `apply_boot_env_decision`** `mount_core_partitions` result is captured rather than propagated immediately so that fsck diagnostics can be persisted to the bootloader environment before any reboot. `apply_boot_env_decision` enforces the invariant that `FsckRequiresReboot` always wins over a concurrent `DegradedBoot` β€” the two failure modes can co-occur when GRUB's -boot partition is unmountable. +boot partition is unmountable. `persist_fsck_results` runs on every mount path, +including degraded boot. ## Building From a703fbf0b9bc97ef606896af7d6d06de98d353f8 Mon Sep 17 00:00:00 2001 From: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com> Date: Wed, 24 Jun 2026 06:34:15 +0200 Subject: [PATCH 3/6] docs(readme): annotate OVL/LINKS spec gap (finding 2) The overlay, fs-link, and ODS setup steps have no dedicated design doc. Added a prose note explaining this is a faithful port from the legacy bash initramfs (fatal-on-fail matches the on_exit hook behaviour), with the OTA rollback path added by the Rust rewrite. Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com> --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 45aa520..655ddeb 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,15 @@ All errors from `run_init()` reach `handle_fatal_error` in `main.rs`, which disp `FsckRequiresReboot` edges in the diagram flow through this handler β€” not via a separate mechanism. +**Notes on overlay, fs-link, and ODS setup (`OVL` / `LINKS` blocks)** + +These steps (`setup_raw_rootfs_mount`, `setup_etc_overlay`, `setup_data_overlay`, +`create_fs_links`, `create_ods_runtime_files`) are ported from the legacy bash initramfs +without a dedicated design document. The fatal-on-fail contract matches the legacy +behaviour: every operation propagated a non-zero return code through the module exit hook, +which halted the device (release) or dropped to a shell (debug). The Rust port preserves +this behaviour and adds the OTA rollback path (`update_pending β†’ Reboot`) via `FEB`. + **Notes on `apply_boot_env_decision`** `mount_core_partitions` result is captured rather than propagated immediately so that From 0fd10f169bb75ea49d54b762a4510eee20a2b3a7 Mon Sep 17 00:00:00 2001 From: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com> Date: Wed, 24 Jun 2026 13:05:35 +0200 Subject: [PATCH 4/6] docs(specs): mark superseded specs per review findings B and C Finding B: 2026-05-12-first-boot-mode-design.md designed BootMode::FirstBoot which was superseded before implementation. Added 'Status: Superseded by 2026-05-13-preflight-design.md' header and prose note. Finding C: 2026-05-04-resize-data-design.md and 2026-05-13-preflight-design.md both stated resize failure is fatal, contradicting the shipped ContinueDegraded reclassification in 2026-05-27-fsck-and-resize-design.md. Added 'Status: Partially superseded' headers referencing the correcting documents, and an inline annotation on the specific table row in preflight-design.md. Specs retained for design history. Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com> --- docs/superpowers/specs/2026-05-04-resize-data-design.md | 5 +++++ .../superpowers/specs/2026-05-12-first-boot-mode-design.md | 6 +++++- docs/superpowers/specs/2026-05-13-preflight-design.md | 7 +++++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/superpowers/specs/2026-05-04-resize-data-design.md b/docs/superpowers/specs/2026-05-04-resize-data-design.md index a06378c..32fd83c 100644 --- a/docs/superpowers/specs/2026-05-04-resize-data-design.md +++ b/docs/superpowers/specs/2026-05-04-resize-data-design.md @@ -1,5 +1,10 @@ # resize-data: Data Partition Auto-Resize +**Status:** Partially superseded β€” the "resize failure is fatal" claim (Β§ Error Handling, +line ~130) was reclassified to `ContinueDegraded` (non-fatal) in +`2026-05-27-fsck-and-resize-design.md` and `2026-06-22-first-boot-retry-on-resize-failure.md`. +The resize mechanism itself remains valid. Retained for design history. + ## Problem When an omnect OS image is flashed to a disk larger than the image itself, the diff --git a/docs/superpowers/specs/2026-05-12-first-boot-mode-design.md b/docs/superpowers/specs/2026-05-12-first-boot-mode-design.md index e5d5006..87e937b 100644 --- a/docs/superpowers/specs/2026-05-12-first-boot-mode-design.md +++ b/docs/superpowers/specs/2026-05-12-first-boot-mode-design.md @@ -1,9 +1,13 @@ # Design: BootMode::FirstBoot and resize-data relocation **Date:** 2026-05-12 -**Status:** Draft +**Status:** Superseded by `2026-05-13-preflight-design.md` **Branch:** TBD (follows feat/resize-data) +> **Note:** This design was superseded before implementation. `BootMode::FirstBoot` and +> `mode::first_boot::run` were never shipped. Resize-data was implemented as a preflight +> step instead β€” see `2026-05-13-preflight-design.md`. Retained for design history. + --- ## 1. Motivation diff --git a/docs/superpowers/specs/2026-05-13-preflight-design.md b/docs/superpowers/specs/2026-05-13-preflight-design.md index 79b9148..e62443a 100644 --- a/docs/superpowers/specs/2026-05-13-preflight-design.md +++ b/docs/superpowers/specs/2026-05-13-preflight-design.md @@ -1,7 +1,10 @@ # Preflight: Separate One-Time Prep from Mode Dispatch **Date:** 2026-05-13 -**Status:** Draft +**Status:** Partially superseded β€” the "resize failure is fatal" claim (verification table, +line ~191) was reclassified to `ContinueDegraded` (non-fatal) in +`2026-05-27-fsck-and-resize-design.md` and `2026-06-22-first-boot-retry-on-resize-failure.md`. +The preflight separation design itself remains valid. **Supersedes:** `2026-05-12-first-boot-mode-design.md` --- @@ -188,7 +191,7 @@ branch in `BootMode::detect()`. | Resize skipped when data partition missing | βœ“ (filesystem layer) | βœ“ (filesystem layer unchanged) | | Resize sets guard after success | βœ“ | βœ“ | | Resize runs after core mount, before data mount | βœ“ | βœ“ | -| Resize failure is fatal | βœ“ | βœ“ | +| Resize failure is fatal | βœ“ | βœ“ | ← **superseded**: reclassified to `ContinueDegraded` in `2026-05-27-fsck-and-resize-design.md` | | `normal::run` mounts remaining + overlays + ODS + switch_root | βœ“ | βœ“ | | Degraded boot when bootloader unavailable | βœ“ | βœ“ | From 455e66aa24e85d6b5208bc5b9e20b6b99e70f3e6 Mon Sep 17 00:00:00 2001 From: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com> Date: Wed, 24 Jun 2026 09:18:11 +0200 Subject: [PATCH 5/6] docs(readme): route FsckRequiresReboot through error handler (optional finding) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The FsckRequiresReboot edges from APPLY/ISETUP/MREM pointed straight at REBOOT, bypassing FEB, while FEB carried an orphan RebootToApply branch with no incoming edge. Redirect all three through FEB so the diagram matches the code (every error reaches handle_fatal_error) and the recovery-policy Β§2.6 invariant that the handler is the only reboot source. REBOOT is now reached exclusively via FEB. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com> --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 655ddeb..f7f85e8 100644 --- a/README.md +++ b/README.md @@ -57,12 +57,12 @@ flowchart TD CLASSIFY -->|"Fail + release β†’ Degraded"| APPLY CLASSIFY -->|"Fail + debug β†’ Abort"| FEB - APPLY -->|"FsckRequiresReboot"| REBOOT(["πŸ” Reboot"]) + APPLY -->|FsckRequiresReboot| FEB APPLY -->|Fatal| FEB APPLY -->|"OK\nDegraded: ods.degraded_boot=true"| FBDETECT["compute_first_boot()\nset_update_pending()"] FBDETECT --> ISETUP["init_setup::run()\nresize-data preflight\nif feature = resize-data"] - ISETUP -->|FsckRequiresReboot| REBOOT + ISETUP -->|FsckRequiresReboot| FEB ISETUP -->|"ResizeData error\nContinueDegraded β€” warn"| BMODE["BootMode::detect() β†’ Normal"] ISETUP -->|"Fatal (non-resize)"| FEB ISETUP -->|OK| BMODE @@ -70,7 +70,7 @@ flowchart TD BMODE -->|Fatal| FEB BMODE --> MREM["mount_remaining_partitions()\ndata Β· factory Β· cert + fsck\npersist_fsck_results β€” always"] - MREM -->|"FsckRequiresReboot"| REBOOT + MREM -->|FsckRequiresReboot| FEB MREM -->|Fatal| FEB MREM -->|OK| OVL["setup_raw_rootfs_mount()\nsetup_etc_overlay()\nsetup_data_overlay()"] @@ -85,7 +85,7 @@ flowchart TD SR -->|Fail| FEB FEB{"Error handler\nRecoveryClass?"} - FEB -->|"RebootToApply"| REBOOT + FEB -->|"RebootToApply (e.g. FsckRequiresReboot)"| REBOOT(["πŸ” Reboot"]) FEB -->|"Fatal + update_pending"| REBOOT FEB -->|"Fatal + no update + release"| HALT2(["πŸ”΄ kmsg loop β€” halt forever"]) FEB -->|"Fatal + no update + debug"| DSHELL(["🐚 debug bash/sh β€” respawn"]) From 42cb4b5b5790800d7fd665b62b68db56459e6b6e Mon Sep 17 00:00:00 2001 From: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com> Date: Thu, 25 Jun 2026 14:00:02 +0200 Subject: [PATCH 6/6] docs(readme): fix non-evergreen prose per findings D, E, F MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Finding D: 'FEB' (diagram node label) appeared in prose at line 132; replaced with 'the error handler'. Finding E: OVL/LINKS note narrated bashβ†’Rust migration history which goes stale. Replaced with current-state contract: these steps abort the boot on any failure, reaching handle_fatal_error. Finding F: trimmed 'not via a separate mechanism' clause from the FsckRequiresReboot note β€” it answered a past review comment rather than describing current state. Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com> --- README.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f7f85e8..f41d938 100644 --- a/README.md +++ b/README.md @@ -119,17 +119,15 @@ All errors from `run_init()` reach `handle_fatal_error` in `main.rs`, which disp - `Fatal` + no OTA in flight + release β†’ Halt (kmsg loop) - `Fatal` + no OTA in flight + debug β†’ debug shell -`FsckRequiresReboot` edges in the diagram flow through this handler β€” not via a separate -mechanism. +`FsckRequiresReboot` edges in the diagram flow through this handler. **Notes on overlay, fs-link, and ODS setup (`OVL` / `LINKS` blocks)** These steps (`setup_raw_rootfs_mount`, `setup_etc_overlay`, `setup_data_overlay`, -`create_fs_links`, `create_ods_runtime_files`) are ported from the legacy bash initramfs -without a dedicated design document. The fatal-on-fail contract matches the legacy -behaviour: every operation propagated a non-zero return code through the module exit hook, -which halted the device (release) or dropped to a shell (debug). The Rust port preserves -this behaviour and adds the OTA rollback path (`update_pending β†’ Reboot`) via `FEB`. +`create_fs_links`, `create_ods_runtime_files`) abort the boot on any failure: the error +reaches `handle_fatal_error`, which halts the device on a release image, drops to a debug +shell on a debug image, or reboots when an OTA update is in flight (`update_pending`). +No dedicated design spec covers this region. **Notes on `apply_boot_env_decision`**