Server-authored actions

Every single-issue read, project overview, and transition response carries two additive keys:

  • actions — the operations that are legal right now, for you, with every input pre-filled or described;
  • blocked — the operations that are not, each with a stable reason_code and a human-readable message.

Clients render what they receive. No client holds a copy of the lifecycle, so when the rules change on the server, every client follows without an update.

Execute an action

Each action names its HTTP method and resolved same-origin href, and declares its fields:

Field kindWhat you do
constantEcho the server’s value verbatim (for example expected_status)
textAuthor content within the stated bounds
selectChoose one of the enumerated options
client_idGenerate a stable retry token and keep it for retries

That table is the entire out-of-band contract. Send method to href with a JSON body containing every field, and the request is exactly what the server expects — the compare-and-set token and any linkage IDs ride along as constants, so you never compose one.

$fp2 issue show --id <issue-reference> --json | jq '.actions[].name, .blocked'

Blocked reasons

reason_codeMeaning
illegal_edgeThe status graph has no edge from the current status
terminal_statusThe issue is done or cancelled
open_childrenClosing requires every child done or cancelled first
open_questionsClosing requires every question answered first

Treat the codes as stable and the messages as presentation.

Errors carry the fix

Rendering is a hint; the handler is the enforcement. A transition the representation never offered — or one built from a stale read — is rejected with a typed conflict whose body includes the current legal actions: recover from the error body directly. Other mutation conflicts (a rejected comment kind, an invalid resolves, a child under a terminal parent, a reused client_id) return a stable error code, a message, and executable repair commands; re-read the issue to obtain a fresh action set.

Rules of engagement

  • Act only on advertised actions; never construct a mutation from memory.
  • Use the returned action set (or re-read the issue) after every write.
  • Choose and persist your client_id before the first send, and reuse it verbatim to retry — the server replays the original result instead of duplicating the write.

Action sets are principal-aware: an agent and a human reading the same issue receive different comment kinds and only humans receive answer actions. If an option is not offered to you, the server will also refuse it.