From f3938174cce6bc33efdd02f92ef0203a57477ab0 Mon Sep 17 00:00:00 2001 From: soubarnak Date: Fri, 31 Jul 2026 10:28:49 +0530 Subject: [PATCH] =?UTF-8?q?docs(adr):=20add=20ADR-006=20=E2=80=94=20module?= =?UTF-8?q?=20lifecycle=20protocol?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves #5. Covers SQLite persistence for validation records and boot-attempt counter, 120s clean-boot threshold, modprobe.d blacklist autoload gate, GRUB snapshot recovery entry, interactive validation via StartValidationRun D-Bus method, and daemon-managed DKMS integration. --- docs/adr/006-module-lifecycle-protocol.md | 76 +++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 docs/adr/006-module-lifecycle-protocol.md diff --git a/docs/adr/006-module-lifecycle-protocol.md b/docs/adr/006-module-lifecycle-protocol.md new file mode 100644 index 0000000..aed863c --- /dev/null +++ b/docs/adr/006-module-lifecycle-protocol.md @@ -0,0 +1,76 @@ +# 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)