Control actions table
Control actions table
Section titled “Control actions table”A project’s page in BoB Control offers
bob commands as buttons, in three groups: Start work, Check and Ship. Every
button comes from one declared table,
scripts/dashboard/control-actions.json (#3920). The page runs nothing the
table does not declare, and adding an action changes no page code.
| File | scripts/dashboard/control-actions.json |
| Schema | bob-control-actions/1, validated by schemas/control-actions.schema.json (make check-schemas) |
| Read by | the dashboard server (resolveActions in scripts/dashboard/control-project-page.js) and the run route (scripts/dashboard/control-action-run.js) |
| Guarded by | make check-control-actions (scripts/checks/check-control-actions.js) |
| Tested by | tests/test-control-actions.sh |
The document
Section titled “The document”{ "schema": "bob-control-actions/1", "actions": [ { "id": "plan-remaining", "group": "Check", "label": "What is left in the plan", "command": "bob plan remaining <N>", "mutates": false, "placeholders": { "N": { "label": "Flight plan", "pattern": "^[1-9][0-9]*$", "prefill": "flightplan" } } } ]}| Field | Required | Meaning |
|---|---|---|
schema |
yes | bob-control-actions/1. |
description |
no | A note on the file itself. |
actions[] |
yes | One row per button, at least one. The page lists them in the order written, within their group. |
actions[].id |
yes | The key a run request names: lowercase, digits and -, up to 41 characters. A request never carries the command. Unique in the table. |
actions[].group |
yes | Start work, Check or Ship. |
actions[].label |
yes | What the action does, in plain words, up to 60 characters. |
actions[].command |
yes | The command template: bob <noun> and what follows, on one line. Its noun and verb must be a row of scripts/bob/commands.json. |
actions[].mutates |
yes | true when the command changes something. See Commands that change something. |
actions[].confirm |
no | true makes every run ask first, its dry run too. |
actions[].description |
no | The one line shown beside the action. Without it, the line is the command’s tagline from commands.json. |
actions[].placeholders |
no | One entry per <NAME> in the command. See Placeholders. |
No other field is allowed, at either level.
The actions the page ships with
Section titled “The actions the page ships with”make check-control-actions requires each of these, under its group (#3920):
| Group | Commands |
|---|---|
| Start work | bob plan build, bob warp start, bob do "<question>", bob decompose <N> |
| Check | bob status, bob warp status, bob plan remaining <N>, bob groom --all, bob tour |
| Ship | bob warp finalize, bob deploy (confirm), bob warp stop (confirm) |
How to add an action
Section titled “How to add an action”- Make sure the command is routed: it must be a row of
scripts/bob/commands.json(bob <noun> --helpanswers for it). - Add one row to
actions[]inscripts/dashboard/control-actions.json, in its group and in the position it should appear. Give it anid, agroup, alabeland thecommand. Setmutatesto whether it changes anything. - For each
<NAME>in the command, add aplaceholdersentry. Give it apatternwhenever the value has a known shape. - Set
confirm: truewhen even a dry run should ask first. - Run
make check-schemas check-control-actions. Both must pass. - Run
make test-control-actions. The page shows the new row on its next load; the server reads the table each time.
No page code changes. A row the server cannot resolve (its command is not routed) is left off the page, and the check fails on it first.
Commands that change something
Section titled “Commands that change something”mutates: true decides which button the row gets and when it runs:
| The row | Button | What happens |
|---|---|---|
mutates: false |
Run | Runs at once, in the project’s terminal. |
mutates: true, and the command has --dry-run |
Dry run | Runs with --dry-run. Run for real… then appears, and asks first. |
mutates: true, no --dry-run |
Run… | Shows the full command and runs it only after Run it. |
confirm: true |
Dry run… | Asks before the dry run too. |
| Any row with a placeholder | Fill in… | Opens the form first; the rules above apply to the run it sends. |
Whether a command has --dry-run is read from its flags in
commands.json, not declared here. On the server, a mutating action runs
without confirmed: true only as its dry run. Its real run, and every run of
a confirm row, needs confirmed: true; without it the route answers 409 with
the command it would have run.
Placeholders
Section titled “Placeholders”A placeholder is a <NAME> in the command, or "<NAME>" when the value is
one quoted argument. Each has an entry under placeholders:
| Field | Meaning |
|---|---|
label |
The field’s label in the form. |
pattern |
A regular expression the value must match. Without it, any one line of up to 300 characters. |
prefill |
What the page fills in from the project’s state. flightplan is the open flight plan’s number: the warp run’s plan, else the first open plan. |
The form has one field per placeholder, the command’s own flags from
commands.json with their descriptions, and a live preview of the command that
will run. The server checks each value against its pattern, accepts only the
command’s own flags, and shell-quotes every value. Anything else is 400.
The run route
Section titled “The run route”A button sends POST /control/project/run with { project, action, values?, flags?, dry_run?, confirmed?, machine? }, never the command. machine names the
fleet machine the caller expects the project’s terminal on:
| Rule | Answer |
|---|---|
| A body with any other field | 400 |
An action the table does not declare |
404 |
A value that fails its pattern, or a flag that is not the command’s own |
400 |
A real run of a mutating action, or any run of a confirm row, without confirmed: true |
409, with the command it would have run |
A caller that is neither this machine with its token nor a machine machines.json declares, or a request arriving on an address other than loopback or the tailnet |
403 |
| Run | 200. The command is typed into the project’s terminal, and one line is added to $BOB_HOME/control-runs.jsonl (bob control runs prints it) |
The command bar uses the same route with command_id in place of action
(The command bar).
make check-control-actions
Section titled “make check-control-actions”| Finding | Means |
|---|---|
unknown-command |
An action’s command is not a row of scripts/bob/commands.json. |
duplicate-id |
Two actions share an id. |
missing-action |
One of the actions the page ships with is not in the table, or not under its group. |
unknown-declared |
scripts/dashboard/control-commands.json (the command bar’s reads and machine lists) names a command that is not routed. |
Exit 0 is clean, 1 is a finding, and 2 means a file could not be read.
Other values Control reads
Section titled “Other values Control reads”| Value | Defined in |
|---|---|
| The stuck threshold: a session quiet for more than 60 minutes is stuck on the Now page | Fleet Feed → the stuck threshold (STUCK_AFTER_MINUTES in scripts/dashboard/control-running.js) |
An ask’s unblocks field, and the order of the asks |
bob asks → What an ask unblocks (scripts/asks/unblocks.js) |