@@ -0,0 +1,319 @@
|
|||||||
|
# Mjolnir CLI v1 Contract
|
||||||
|
|
||||||
|
`mjolnirctl` — user-accepted command-line contract for Mjolnir v1.
|
||||||
|
|
||||||
|
## 1. Context and constraints
|
||||||
|
|
||||||
|
- `mjolnirctl` is an unprivileged client.
|
||||||
|
- All communication uses the `org.bongbetic.Mjolnir` name 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 of `mjolnirctl 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).
|
||||||
|
- No watch in v1.
|
||||||
|
|
||||||
|
### 3.4 `battery`
|
||||||
|
|
||||||
|
- `battery health` — current health record.
|
||||||
|
- `battery history` — time-series; same time flags as `telemetry 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 `ChargeStartThreshold` and `ChargeEndThreshold` as 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 list`
|
||||||
|
- `rule add`
|
||||||
|
- `rule 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-run` validates without mutation.
|
||||||
|
|
||||||
|
### 3.7 `never-touch`
|
||||||
|
|
||||||
|
- `never-touch list`
|
||||||
|
- `never-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 start`
|
||||||
|
- `calibration status`
|
||||||
|
- `start` remains 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_id`
|
||||||
|
- `timestamp`
|
||||||
|
- `knob_id`
|
||||||
|
- `trigger_event_id`
|
||||||
|
- `policy_rule`
|
||||||
|
- `principal`
|
||||||
|
- `prior_value`
|
||||||
|
- `new_value`
|
||||||
|
- `mode`
|
||||||
|
- `external_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:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"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>` with `s|m|h` suffix, 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_denied` plus action ID. This is the stable CLI code mapped from the D-Bus error `org.bongbetic.Mjolnir.Error.NotAuthorized`.
|
||||||
|
|
||||||
|
### 4.8 Battery health record
|
||||||
|
|
||||||
|
`battery health` output must include the complete record fields:
|
||||||
|
|
||||||
|
- `timestamp`
|
||||||
|
- `cycle_count`
|
||||||
|
- `design_capacity`
|
||||||
|
- `full_charge_capacity`
|
||||||
|
- `wear_percent`
|
||||||
|
- `charge_temperature`
|
||||||
|
- `validity_status`
|
||||||
|
- `staleness_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.
|
||||||
Reference in New Issue
Block a user