Skip to content

feat(fleet): out-of-band power control, a fleet read model, and a terminal interface - #4

Open
wamxx wants to merge 2 commits into
developfrom
feature/fleet-control
Open

feat(fleet): out-of-band power control, a fleet read model, and a terminal interface#4
wamxx wants to merge 2 commits into
developfrom
feature/fleet-control

Conversation

@wamxx

@wamxx wamxx commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Implements plans/fleet-control.md — all fourteen phases. Not for merging yet: opened
so the design can be read before it lands.

What this adds

Out-of-band control power list/status/on/off/pxe, install <id> — Redfish through curl, or a script you supply
A fleet read model status, machines, groups, each with --json; render <id> --format <ext>
A terminal interface rescriptum tui, and --remote URL against a deployment's admin API
One admin endpoint GET /fleet, asserted byte-identical to machines --json

Off unless configured. No RESCRIPTUM_CONTROLLERS_FILE, no credentials, no outbound
connections, no reachable code path. tui is not in default, and CI asserts the default
dependency graph does not reach ratatui.

The parts worth reviewing

install is mostly refusing. Powering on a machine that boots into a broken answer
leaves an installer at a prompt in a rack. It renders every format the machine resolves
for, checks policy — differently in each unclaimed mode, because one case is dangerous
and the other merely useless — re-arms from the installed- archive, arms a one-time
network boot and reads it back, then powers on or restarts depending on the state it
reads.

Two vendor behaviours, verified in source rather than assumed. A Redfish PATCH can
answer 204 No Content and do nothing — PiKVM's does, while still reporting the override
disabled, which is why the read-back is mandatory rather than tidy. And its @odata.id
disagrees with the path it is served from, so no URL is ever followed out of a response
body: the id is taken and the URL composed from base.

The guard moved out of admin. "A write cannot leave the answer set broken" is not an
HTTP property. admin now maps the outcome onto a status code, and the admin suite
passed unchanged
across the split — which is what proves it was a refactor.

The read model is a command before it is a screen. Built as a screen it would be
untestable, unscriptable, and gone if the interface were never finished. The interface's
own state lives in src/tui/mod.rs, depends on no terminal library, and is tested in every
build including --no-default-features. A key press returns an Action; the loop performs
it between frames, so one unreachable BMC cannot freeze a screen.

Cost

armv7 (floor 2.17), with the feature on: +199,408 bytes (+6.9%) and +45 locked
packages
. Zero with it off. Cargo.lock carries the graph regardless, so an advisory
reachable only through it gets an --ignore with that reason written beside it.

Numbers

619 → 741 tests. fmt, clippy --all-targets --all-features -D warnings, and all four
feature combinations green — including tui without boot, which --all-features does
not cover and which now has its own CI step. Documentation in both languages, links
checked.

Known gap

tui --remote shows the three screens the admin API actually has — machines, groups,
problems. The rest say "local only" rather than growing a read API with its own
auth-exposed surface. It powers nothing, and that is refused in the state machine so no
screen can forget it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KQnAk5r4fLKcWuAwUY6Pa6

wamxx and others added 2 commits August 30, 2026 16:12
…minal interface

Implements `plans/fleet-control.md`. Three things exist now that did not: a way
to tell a machine to network-boot and power on, a model of the fleet that is a
command before it is a screen, and a terminal interface that renders it.

**Out-of-band control is off unless `RESCRIPTUM_CONTROLLERS_FILE` names a file.**
Unset, there are no credentials, no outbound connections and no reachable code
path — the rule `RESCRIPTUM_MEDIA_DIR` and `RESCRIPTUM_INSTALLED_TOKEN` already
follow. Redfish only, through `curl`, because there is no TLS in this binary and
a megabyte of rustls to double a 2.8 MB binary for one feature is not a trade
worth making. Anything Redfish cannot reach is reached by `kind = "command"`:
argv only, never a shell, nothing from a request, and a deadline.

**`install` is mostly refusing.** Powering on a machine that boots into a broken
answer leaves an installer at a prompt in a rack, so it renders every format the
machine resolves for first, then checks policy — differently in each unclaimed
mode, because one case is dangerous and the other merely useless — then re-arms
from the `installed-` archive, arms a one-time network boot and reads it back,
and finally powers on *or restarts*, decided by reading the power state.

**Two findings from the hardware, verified rather than assumed.** A Redfish
`PATCH` can answer `204 No Content` and do nothing: PiKVM's does, which is why
the boot override is read back rather than trusted. And `@odata.id` is
inconsistent with the path it is served from on that same device, so no URL is
ever followed out of a response body — the id is taken and the URL composed.

**The guard moved out of `admin`.** "A write cannot leave the answer set broken"
is not an HTTP property; `admin` now maps its outcome onto a status code, and
the admin suite passed unchanged across the split, which is what proves it was a
refactor.

**The read model is a command first.** `status`, `machines`, `groups`, each with
`--json`, plus `render <id> --format <ext>` which had no first-class form.
Building it as a screen would have made it untestable, unscriptable, and gone if
the interface were never finished. `GET /fleet` serves the same bytes, asserted.

**The interface keeps its state in `src/tui/mod.rs`, which depends on no terminal
library** and is tested in every build, `--no-default-features` included. A key
press returns an `Action`; the loop performs it between frames, so one
unreachable BMC cannot freeze a screen. `tui` is not in `default`, and CI
asserts the default dependency graph does not reach ratatui — on the *output* of
`cargo tree`, never its exit code, which exits 0 for an optional dependency that
is off.

Two log changes, both logging rather than instrumentation: `Resolution::how`
reports the extension instead of the family, so a boot script and an answer
document are no longer the same event; and a 404 names the machine that asked,
which a Proxmox POST states only in its body.

Cost on armv7 (floor 2.17): +199,408 bytes (+6.9%) and +45 locked packages, with
the feature on. Zero with it off.

619 -> 741 tests. Documentation in both languages, links checked.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KQnAk5r4fLKcWuAwUY6Pa6
`cargo audit` reads `Cargo.lock`, which carries the whole dependency graph whatever
the feature flags say — so ratatui's tree blocks CI for a binary that links none
of it. The plan anticipated this and set the rule: ignore an advisory reachable
**only** through the `tui` graph, with the reason written beside it.

Verified rather than assumed: `lru` and `paste` are absent from the default
graph, and `cargo tree -i` shows both arriving solely through ratatui.

Two of the three are unsoundness rather than unmaintainedness, so the comment
says to revisit when ratatui moves off `lru 0.12`. They stay ignored only while
nothing shipped links them — which the gates job asserts separately.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KQnAk5r4fLKcWuAwUY6Pa6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant