Decisions › D07

Every action dry-runs first, carries a danger class, and only a person runs the real one

Decision
D07
Date
2026-10-22
Status
Accepted
Area
hardware safety

Context

Zach's question was whether the panel is read-only or can also command. If it commands, it is more complicated because he would have to deal with permissions, so that it does not interfere with the thing it controls. The converged design split the problem in two planes: a status plane that only reads, and an action plane where the driver registers utilities and the driver runs them.

The decision

Every real action starts as a dry run: the plugin prints the exact command it would run and the preconditions it would check, and runs nothing. Each action has a danger class: query reads only, interrupts stops or resets what runs, changes writes stored state, moves makes hardware move. Everything except a query needs an explicit confirmation for that device.

Only a person runs the real thing. With stdin not a terminal (an agent, a pipe, a cron job) the host refuses everything except a dry run. From the page a real run needs the single-use token the dry run issued (good for 5 minutes, bound to that device, action and node), and a moves action needs a ticked physical precondition such as “the joint is free to spin”.

Added on 24 October: when a moves run ends any way except finished (stopped, timed out, failed, a typed Ctrl-C), the host itself runs the plugin's own stop verb. For a motor that is mode 0. It needs no confirmation, because stopping is the safe direction.

Why

“If commanding its more complicated beacuse now you need to deal with like permissions... so you don't intefer with the thing...”

Zach Yamaoka, notes, 22 Oct 2026

“but then it now needs to like pipe into the actual system and confirm etc may may more complicated.”

Zach Yamaoka, notes, 22 Oct 2026

“I think we can have place holders for buttons”

Zach Yamaoka, notes, 22 Oct 2026

No line of Zach's says “dry run first”; it is the design that answers his permissions worry, and it is recorded in the vocabulary and the API docs. The decision page says so rather than inventing a quote.

Options considered

OptionVerdictReason
A read-only dashboardRejectedZach wanted buttons: “we can have place holders for buttons”, and the printer model has them.
Every action confirmed in a modal (that morning's default in the converged design)ReplacedRound 2 made every click a dry run first, so the confirmation shows the exact command and the preconditions.
Dry run, then a single-use token, a danger class and a physical preconditionChosenA dry run is how an agent proves a write button without writing.
Agents may press real buttonsRejectedWritten into the API doc: “only a person runs an action; agents can't click”.

Consequences

  • The Utilities panel's one write endpoint is gated three ways (the page's own header, same origin, a JSON body) and by the token, and the doc says an agent must not replay the flow.
  • During the v1 build a test mutation removed the passive flag and ran a driver's checks against the real adapter for about 3 seconds. They were query-only and went through the plugin's own gate: no motion, and the boards were unpowered. A guard now sits in front of every live-passive test and refuses any diagnose without the passive flag and any act. See port ownership.

Evidence

Commits in the app's repository (private):

  • 95ee374 2026-10-22 Every click a dry run first; moves needs a ticked precondition
  • 42e71f7 2026-10-22 Host: a non-TTY may only dry-run
  • 693cb80 2026-10-24 Runs, host side: the plugin's own stop for a moves run that did not finish

← All decisions