caravan: drop the target field that armed nothing, and quote a power argv
A spool target carried a device-key, decoded and handed to the backend, where
only admit_provision reads it -- and that runs only inside Ground.report, which
this CLI never calls. Admission happens when a report is written; the CLI
observes reports it did not write. So the field named a gate that could never
run, and an operator setting it had every reason to think claims were being
checked. The gate itself is real and tested; it belongs to the fleet service,
which uses the library and holds its own keys, as the keys for a drained window
already do. Saying so in the config interface is the fix; carrying a dead field
is not.
Landed 50e2e0e claimed a target "now pins the device's public half", which was
wrong in exactly this way.
Separately, a power command was split on bare spaces, so `command pdu --label
"rack 4" cycle %s` reached the tool as five arguments where it meant four. It
now splits on whitespace with double-quoted runs held together, and prints back
in a form that parses -- an unterminated quote is an error rather than a guess
at where it ended. Still an argv and still no shell, so the quoting decides word
boundaries and nothing else.
caravan: drop the target field that armed nothing, and quote a power argv
A spool target carried a device-key, decoded and handed to the backend, where
only admit_provision reads it -- and that runs only inside Ground.report, which
this CLI never calls. Admission happens when a report is written; the CLI
observes reports it did not write. So the field named a gate that could never
run, and an operator setting it had every reason to think claims were being
checked. The gate itself is real and tested; it belongs to the fleet service,
which uses the library and holds its own keys, as the keys for a drained window
already do. Saying so in the config interface is the fix; carrying a dead field
is not.
Landed 50e2e0e claimed a target "now pins the device's public half", which was
wrong in exactly this way.
Separately, a power command was split on bare spaces, so `command pdu --label
"rack 4" cycle %s` reached the tool as five arguments where it meant four. It
now splits on whitespace with double-quoted runs held together, and prints back
in a form that parses -- an unterminated quote is an error rather than a guess
at where it ended. Still an argv and still no shell, so the quoting decides word
boundaries and nothing else.
caravan: drop the target field that armed nothing, and quote a power argv
A spool target carried a device-key, decoded and handed to the backend, where
only admit_provision reads it -- and that runs only inside Ground.report, which
this CLI never calls. Admission happens when a report is written; the CLI
observes reports it did not write. So the field named a gate that could never
run, and an operator setting it had every reason to think claims were being
checked. The gate itself is real and tested; it belongs to the fleet service,
which uses the library and holds its own keys, as the keys for a drained window
already do. Saying so in the config interface is the fix; carrying a dead field
is not.
Landed 50e2e0e claimed a target "now pins the device's public half", which was
wrong in exactly this way.
Separately, a power command was split on bare spaces, so `command pdu --label
"rack 4" cycle %s` reached the tool as five arguments where it meant four. It
now splits on whitespace with double-quoted runs held together, and prints back
in a form that parses -- an unterminated quote is an error rather than a guess
at where it ended. Still an argv and still no shell, so the quoting decides word
boundaries and nothing else.
caravan: one bench-board provider, with power as the axis that varies
Almost nothing in the cm5 backend was about a Compute Module. A board on a
bench is a block device exposed to this host, a boot partition holding the
provider database, an identity to bind against, a flash that preserves the
state partition, and a power step. Only the last of those differs between
boards, and the backend hardcoded one answer to it: a hand on a USB-C plug,
with rpiboot spelled into the errors.
So caravan.cm5 becomes caravan.board, and how a board is powered becomes
something a target says: manual, always-on, or a command to run. No
per-board presets -- nothing about a board's behaviour is implied by its
name, so a wrong combination is visible in the target file instead of hidden
behind one. Power.of_string is the only place the legal strategies are named,
so the YAML decoder and the backend cannot drift apart, and a command is run
as an argv rather than through a shell, so a serial cannot be re-read as
syntax.
always-on is the one that earns its keep immediately: it takes no step and
needs no terminal, so the flash path converges unattended and is exercised by
the suite rather than only by a hand on a bench.
Flashing is deliberately not an axis. Uniboot.Flash implements exactly one
layout -- rewrite the bytes before the last MBR entry, boundary read from the
device's own table -- so a second flash or layout value would have one legal
setting and say nothing. It becomes an axis when a board needs GPT or a fixed
offset, not before.
caravan: let apply resolve destructive drift behind --destroy-data
The operator holds the credentials to destroy the data volume with the
provider's own tools, so refusing in-tool destruction only added a
detour through the console -- and the console is where the wrong
volume gets deleted by hand. resolve_destructive is resolve's
counterpart for the drift only destruction can close: a volume too
small, of the wrong class, or foreign is detached from every holder
and deleted, and the next pass recreates it empty.
The invariant moves rather than falls: plan itself still never emits
Delete_volume (the fuzz property now checks that), only the explicit
resolution path does, and it is fenced twice. The CLI runs it only
under --destroy-data, an interactive run must retype the volume's name
(--yes skips confirmations, not destructions), and the provider call
carries the same name as a witness the way Block.Volume.delete
demands. When one conflict has both a lossless and a destructive fix,
--force wins: adoption before deletion.
The CLI is split into per-command modules while it grew its third
resolution tier.