# ADR-006: Module Lifecycle Protocol — Validation, Boot-Count Guard, and Recovery **Status:** Accepted **Date:** 2026-07-31 **Deciders:** Bongbetic (solo developer) **Source:** [Task #5](https://github.com/soubarnak/Mjolnir/issues/5) ## Context `mjolnir-km` is an out-of-tree kernel module on the sole reference machine (MSI Thin A15 B7UCX), co-resident with `msi-ec`. It implements a custom cpuidle governor and telemetry sampler (FR-070, FR-071). Three safety FRs govern its lifecycle: FR-077 (no autoload until validated), FR-078 (boot-count guard — 2 unclean boots disable autoload), and FR-079 (known-good kernel entry preserved as recovery path). The daemon owns the module lifecycle (ADR-001 step 3), and DKMS handles rebuild (ADR-004 `AUTOINSTALL=yes`). ## Decision ### Persistence: SQLite Validation records and boot-attempt counters live in the daemon's SQLite store (ADR-001 step 6 opens before step 3 loads the module — no phase mismatch). Two tables: - `module_validation` — `(kernel_version TEXT, module_version TEXT, validated_at INTEGER, validation_method TEXT)`. One row per validated kernel. - `boot_attempt` — `(boot_id TEXT PK, started_at INTEGER, clean INTEGER, autoload_enabled INTEGER)`. `boot_id` from `/proc/sys/kernel/random/boot_id`. Written on daemon startup before module load. DAU purge retains last 10 boots. ### Clean-boot threshold: 120 seconds Boot is clean when the daemon shuts down gracefully with the module loaded for ≥120 seconds. A kernel module governor fault typically manifests within 30–60s of selection — 120s provides 4× polling cycles of observed clean operation. Aligns with systemd `TimeoutStartSec=90s`. Covers the suspend/resume validation cycle in FR-077. ### Autoload gate: modprobe.d blacklist `/etc/modprobe.d/mjolnir-km-autoload.conf` containing `blacklist mjolnir-km`. Managed by the daemon: - File exists → auto-load blocked (post-install default; after boot-count guard trips). - File removed → auto-load permitted (after validation run or admin re-enables). Explicit `modprobe mjolnir-km` by the daemon bypasses the blacklist (it's alias-resolution-only). No DKMS `POST_INSTALL` script — the daemon owns the policy. DKMS only builds; on first boot of a new kernel, the daemon detects no validation record and writes the blacklist before attempting load. ### Known-good kernel entry: GRUB snapshot `just install-km` creates a `/etc/grub.d/40_mjolnir_recovery` script that copies the current default GRUB entry (pre-Mjolnir kernel command line) as a new entry named "Mjolnir-Free (recovery)" with `modprobe.blacklist=mjolnir-km` appended. Re-runs on every `update-grub`, re-deriving the latest non-Mjolnir entry. Recovery notes at `/usr/share/doc/mjolnir/RECOVERY.md` reference it by name. ### Validation run: interactive admin flow FR-077 validation steps (load, governor register/deregister, suspend/resume, clean unload) are sequenced by the daemon via a new `StartValidationRun` D-Bus method (PolKit-guarded). The suspend/resume step requires admin acknowledgment — not automatable without user consent. The daemon emits `ValidationStage` signals: 1. Admin invokes `mjolnirctl validate` or GTK "Validate Module". 2. Daemon loads module, sends `GOV_SELECT` → verifies, sends `TELEM_SAMPLE` → verifies, sends `GOV_DESELECT` → verifies. 3. Daemon emits `ValidationStage("suspend_required")`. Admin suspends manually. 4. Post-resume: daemon verifies boot_id unchanged, re-runs GOV_SELECT → TELEM_SAMPLE → GOV_DESELECT. 5. Daemon unloads module, persists validation record, removes modprobe.d blacklist. 6. Emits `ValidationStage("complete")`. On failure at any step: daemon unloads module, keeps blacklist, emits `ValidationStage("failed", reason)`. ### User notification Two channels for boot-count guard trip (FR-078): - D-Bus `AutoloadDisabled("boot_count_guard")` signal → GTK desktop notification (ADR-003). - CLI: `mjolnirctl status` shows `autoload: disabled (reason: 2 consecutive unclean boots)`. Both direct admin to run `mjolnirctl validate` to re-validate. ## Consequences - **Positive:** Daemon owns the full lifecycle — no split-authority between DKMS hooks, systemd, and the daemon. - **Positive:** modprobe.d blacklist is a one-file gate, trivial to inspect and to override in recovery. - **Positive:** Validation is interactive — the admin sees each step and owns the suspend decision. - **Negative:** Validation requires the admin to be present and to manually suspend — cannot be fully automated. - **Negative:** The 120s clean-boot threshold means a developer who insmod/rmmods in <120s gets an unclean boot recorded; acceptable since developer boots won't trip the guard (it takes 2 consecutive unclean boots with autoload enabled, and dev boots don't have autoload set). ## Related - [ADR-001: Component Architecture](./001-component-architecture.md) — startup sequencing step 3 - [ADR-002: Netlink Protocol](./002-netlink-protocol.md) — GOV_SELECT/DESELECT/TELEM_SAMPLE commands - [ADR-003: D-Bus API Contract](./003-dbus-api-contract.md) — AutoloadDisabled signal, ValidationStage signal added - [ADR-004: Build Sequencing](./004-build-sequencing.md) — DKMS AUTOINSTALL, just install-km - [Netlink Protocol Spec](../spec/netlink-protocol-spec.md) — §8.3 validation run hooks - SRS FR-070–079, FR-075, FR-076 - SRS RISK-001 (co-resident DKMS modules), RISK-005 (dev machine = test machine)