15 KiB
Mjolnir CLI v1 Contract
mjolnirctl — user-accepted command-line contract for Mjolnir v1.
1. Context and constraints
mjolnirctlis an unprivileged client.- All communication uses the
org.bongbetic.Mjolnirname on the D-Bus system bus. - No direct hardware or store access.
- No daemon auto-start; a missing daemon is reported immediately.
2. Command hierarchy
Canonical hierarchy:
mode status telemetry battery rule config calibration
validate diagnostics capabilities gpu never-touch why
mjolnirctl <command> <subcommand> [args]- Sole alias:
mjolnirctl --why <write-id>is the required equivalent ofmjolnirctl why <write-id>. - No other aliases, short options, or inferred subcommands.
3. Command contract
3.1 mode
mode get— report the active mode plus thermal-override and external-profile annotations.mode set <auto|powersaver|normal>— wait for daemon confirmation and report the resulting mode.- Invalid enum values are rejected locally before any daemon call.
- Bare
mode(no subcommand) does nothing.
3.2 status
Reports: daemon version, active mode, probe completion, thermal override, external profile, module/autoload state, degradation reason.
- Capability detail stays in
capabilities. - Current sample stays in
telemetry.
3.3 telemetry
telemetry get— current sample.telemetry history— time-series history.- Default window:
--since 1h. - Alternative:
--from <RFC3339> --to <RFC3339>. - Output ascending by timestamp.
- Empty result is success (exit 0).
- Default window:
- No watch in v1.
3.4 battery
battery health— current health record.battery history— time-series; same time flags astelemetry history.battery thresholds get— current charge thresholds.battery thresholds set --start <percent> --end <percent>.- Both threshold values required; applied atomically.
- Local validation:
0 <= start < end <= 100, integers. - Daemon validates against hardware.
- Preset aliases deferred to the charge-preset decision.
- Dependency note: ADR-003 currently exposes
ChargeStartThresholdandChargeEndThresholdas independent readwrite properties, so the open D-Bus contract must provide or define an atomic two-value update mechanism before implementation. This CLI document does not choose that wire shape; see section 5 and section 6.
3.5 rule
rule listrule addrule update <rule-id>rule delete <rule-id>- Canonical rule fields deferred to the per-app rule decision.
- No arbitrary D-Bus dictionary input.
- Delete has no confirmation prompt.
3.6 config
config export <path>config import <path>- Export refuses to overwrite an existing file unless
--force. - Import validates the complete document before atomic apply.
- Unreadable path fails.
- No prompts.
--dry-runvalidates without mutation.
3.7 never-touch
never-touch listnever-touch set <process>...— atomically replaces the full list.never-touch set --clear— clears the list.- No add/remove variants; avoid them in v1.
3.8 calibration
calibration startcalibration statusstartremains attached through terminal daemon state and relays progress.- No cancel, stop, or background mode.
3.9 validate
- Sole validation command.
- Starts interactive validation, relays progress, stays attached through terminal state.
- No status or cancel subcommands.
3.10 diagnostics
diagnostics generate- No destination path argument.
- Daemon creates a redacted bundle and returns its path.
- Human output prints path and size.
- JSON output:
{"path": ..., "size_bytes": ...}. - CLI does not copy, rename, or inspect the bundle.
3.11 capabilities
capabilities get— normalized stable fields:- knob ID
- support state
- reason
- range
- topology
3.12 gpu
gpu get— dGPU power and display topology.- Unsupported hardware is data, not an error: exit 0.
3.13 why / --why
why <write-id>and--why <write-id>; write ID is an opaque daemon-issued identifier.- Output full attribution:
write_idtimestampknob_idtrigger_event_idpolicy_ruleprincipalprior_valuenew_valuemodeexternal_profile_state
- Daemon resolves
policy_rule; non-policy writes use"none". - Unknown write ID: exit 2.
4. Cross-cutting decisions
4.1 Global options
Only:
--help--version--output human|json--timeout <duration>
No CSV, watch, shell completion, color/verbose/quiet flags, cancellation, background workflows, daemon start, or retries.
4.2 Output
- Human is the default regardless of TTY.
- Singleton resources use key/value blocks; lists and history use aligned tables.
- Explicit units on all values.
- Lowercase enums.
- Status annotations included.
- No required color or Unicode.
JSON success is one object:
{"schema_version": 1, "command": "...", "data": {...}}
- snake_case keys, lowercase enums, UTC RFC3339 timestamps, explicit units.
- Long workflows use JSON Lines with the same versioned event shape.
- JSON errors: one structured object on stderr; stdout carries only successful data; no partial-success output.
4.3 Exit codes
| Code | Meaning |
|---|---|
| 0 | Success (incl. degraded/unsupported, exit 0) |
| 2 | Usage / validation / not-found |
| 3 | Daemon / module unavailable |
| 4 | Authorization denied |
| 5 | Unsupported capability |
| 6 | Policy conflict / rate limit |
| 7 | Internal operation failure |
| 124 | Timeout |
| 130 | Ctrl-C detach |
Successful degraded/unsupported-capability responses remain exit 0.
D-Bus errors from org.bongbetic.Mjolnir.Error.* map to exit codes as follows; these are CLI mappings of settled D-Bus errors:
| D-Bus error | Exit |
|---|---|
UnknownKnob |
2 |
InvalidConfig |
2 |
ModuleAbsent |
3 |
NotAuthorized |
4 |
CapabilityLocked |
5 |
UnsupportedOperation |
5 |
RateLimited |
6 |
ThermalOverride |
6 |
CalibrationInProgress |
6 |
4.4 Timeouts
- D-Bus default deadline 5 seconds, overridable by global
--timeout. - Daemon absence reported immediately; no retries.
- Long workflows (calibration/validate) default 30 minutes and have a separate timeout override.
- Ctrl-C on calibration/validate detaches, returns 130, daemon safely continues.
4.5 Time arguments
--since <duration>withs|m|hsuffix, or--from <RFC3339> --to <RFC3339>.- No mixing of forms.
- Output always UTC RFC3339.
4.6 Numeric and ID rules
- Percentages: integer 0..100, no suffix.
- Reject locale-dependent numbers and implicit conversions.
- IDs opaque; CLI never interprets them.
- Human labels units according to D-Bus fields.
4.7 Authorization
- PolKit delegated to the active agent; CLI never prompts or embeds credentials.
- No agent or denied authorization gives actionable stderr and exit 4.
- JSON error stable:
authorization_deniedplus action ID. This is the stable CLI code mapped from the D-Bus errororg.bongbetic.Mjolnir.Error.NotAuthorized.
4.8 Battery health record
battery health output must include the complete record fields:
timestampcycle_countdesign_capacityfull_charge_capacitywear_percentcharge_temperaturevalidity_statusstaleness_status
The exact types/enums and source contract for charge_temperature, validity_status, and staleness_status must be settled by the data-model/D-Bus work; no enum values or wire types are invented here. No derived fade prediction.
5. Required and Pending D-Bus Dependencies
This section records CLI prerequisites for the open D-Bus contract ticket and separates them from the surface already settled in ADR-003 and the XML. EditNeverTouchList(as) is already settled in ADR-003 and the current XML and is therefore not labelled required or [REQUIRED] here.
Required additions — absent from ADR-003/XML
These four additions are absent from ADR-003 and the current XML. They are required CLI prerequisites for the open D-Bus contract ticket; they are not settled ADR-003 interfaces.
| Addition | Type / shape | Purpose |
|---|---|---|
NeverTouchList |
read-only property as |
read the current never-touch list |
StartValidationRun |
method | start validation |
ValidationStage |
progress signal | validate progress reporting |
ExplainWrite |
method (s write_id) -> a{sv} |
why full attribution; read/audit-gated |
NeverTouchList, StartValidationRun, ValidationStage, and ExplainWrite are absent from ADR-003 and the current XML. ValidationStage's signature is pending: ADR-006 shows both a single-stage string form and a failed-with-reason form, so this document does not invent a wire signature.
Pending read surfaces for status
The accepted status command reports module/autoload state and degradation reason. Both are required read surfaces, but their final property names and wire types are pending; descriptive labels only are used here:
- current module/autoload state — wire shape pending
- overall degradation reason — wire shape pending
The existing AutoloadDisabled signal is transition-only: it fires when autoload is disabled and cannot replace a current-state read. status needs read surfaces for the present module/autoload state and degradation reason; no final property names are invented here.
Pending atomic threshold mechanism
battery thresholds set --start <percent> --end <percent> applies both values atomically. The current ChargeStartThreshold/ChargeEndThreshold independent readwrite properties are insufficient for atomicity. The open D-Bus contract ticket must settle a two-value atomic update mechanism; its exact wire shape is pending. This is separate from the four absent additions and the two pending read surfaces above: it concerns the existing settled threshold properties rather than a new named surface.
6. Command-to-D-Bus mapping
Where known. [REQUIRED] marks one of the four absent additions from section 5 — a required CLI prerequisite for the open D-Bus contract, not a settled ADR-003 interface. [PENDING] marks a surface whose wire shape is unresolved: the two status read surfaces, the ValidationStage signature, and the atomic threshold mechanism. EditNeverTouchList(as) is current settled ADR-003/XML surface and carries no tag.
| Command | D-Bus surface |
|---|---|
mode get / mode set |
mode property / setter; thermal-override, external-profile properties |
status |
daemon version, mode, probe, override, profile properties; module/autoload state read [PENDING]; degradation reason read [PENDING] |
telemetry get / history |
telemetry property; history method |
battery health / history |
battery health property; history method |
battery thresholds get / set |
ChargeStartThreshold/ChargeEndThreshold properties; atomic two-value update mechanism [PENDING] (see 3.4) |
rule list/add/update/delete |
rule methods (fields deferred to per-app rule decision) |
config export/import |
ExportConfig(s path) / ImportConfig(s path) with validation and atomic semantics as CLI requirements |
calibration start/status |
calibration start/status; progress signal |
validate |
StartValidationRun() [REQUIRED]; ValidationStage signal [REQUIRED] (signature [PENDING]) |
diagnostics generate |
diagnostics method returning bundle path |
capabilities get |
capabilities introspection |
gpu get |
dGPU power/topology properties |
never-touch list |
NeverTouchList read-only property as [REQUIRED] |
never-touch set |
EditNeverTouchList(as) — current settled ADR-003/XML surface |
why / --why |
ExplainWrite(s write_id) -> a{sv} [REQUIRED], read/audit-gated |
7. Scope exclusions and deferred work
Excluded in v1:
- CSV, watch, shell completion, color/verbose/quiet flags
- Cancellation, background workflows
- Daemon start, retries
Deferred:
- Rule field syntax — waits for Per-app rule contract — identity, precedence, and restoration.
- Charge preset aliases/wiring — waits for Charge preset mapping — start hysteresis for 60, 80, and 100 percent.
8. Rationale
- Single-command, explicit-hierarchy CLI keeps v1 small and auditable.
- Unprivileged D-Bus-only client keeps the surface safe; no direct hardware paths.
- Degraded/unsupported hardware is data (exit 0) so scripts can distinguish capability from failure.
- Atomicity (thresholds, never-touch replace, config import) avoids partial states.
- Human default regardless of TTY keeps output deterministic for users and tools alike.
- JSON Lines for long workflows keeps progress streamable without breaking the single-object success shape.