docs(adr): add ADR-006 — module lifecycle protocol

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.
This commit is contained in:
soubarnak
2026-07-31 10:28:49 +05:30
parent 6fce73149f
commit f3938174cc
+76
View File
@@ -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 3060s 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-070079, FR-075, FR-076
- SRS RISK-001 (co-resident DKMS modules), RISK-005 (dev machine = test machine)