Appearance
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
json
{
"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
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
- 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
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
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
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
| 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
| 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) |