Ordered approval check
The ordered approval check answers one question: did a human approve the action before the agent did it?
To answer it, Bastyn needs to know which of your agent’s tools are risky enough to need approval. Bastyn does not guess this from your code. You declare the tool names yourself, on the project.
Declare gated actions
A gated action is the identifier of a tool that needs human approval before it runs, exactly as your agent reports it (for example delete_user or transfer_funds).
When you create the project
Add declared_gated_actions to the project-creation request:
curl -X POST https://api.staging.bastyn.ai/v1/attestation/projects \ -H "Authorization: Bearer bastyn_sk_your_key" \ -H "Content-Type: application/json" \ -d '{"name": "my-agent", "repo_url": "https://github.com/your-org/your-agent", "declared_gated_actions": ["delete_user", "transfer_funds"]}'On an existing project
curl -X PUT https://api.staging.bastyn.ai/v1/attestation/projects/{project_id}/declared-gated-actions \ -H "Authorization: Bearer bastyn_sk_your_key" \ -H "Content-Type: application/json" \ -d '{"tool_names": ["delete_user", "transfer_funds"]}'The request replaces whatever was declared before. The response, and GET /attestation/projects/{project_id}, return the cleaned-up list as declared_gated_actions.
The PUT route is for server-to-server callers (curl, CI). The public API’s CORS policy allows only GET, POST, DELETE and OPTIONS, so a browser on another origin cannot call it. It needs at least the MEMBER role; a VIEWER gets 403.
The body must contain the tool_names key. A body without it is rejected, so an empty request can never clear or set anything by accident.
Three states
| You send | Stored as | Meaning |
|---|---|---|
["delete_user"] | the list | These tools need approval. |
[] | an empty list | You are telling Bastyn this agent has no gated actions. |
null (PUT), or leaving the field out (create) | nothing | You have not said anything. Same as a project created before this field existed. |
Sending null to the PUT route clears a previous declaration back to “not said anything” (stored as SQL NULL, the same as a project created before this field existed). Sending [] does not clear it; it declares “none”.
Declare approval tools
The check also needs to know which of your tools is the approval step. The approval requirements Bastyn reads from your code are usually sentences, which cannot be matched to a tool call, so without a declaration the result stays no_evidence. Declare the approval tool names the same way:
curl -X PUT https://api.staging.bastyn.ai/v1/attestation/projects/{project_id}/declared-control-tools \ -H "Authorization: Bearer bastyn_sk_your_key" \ -H "Content-Type: application/json" \ -d '{"tool_names": ["request_manager_approval"]}'You can also send declared_control_tools when creating the project. The rules, the three states (list, [], null) and the response shape are the same as for gated actions, with the field named declared_control_tools. When you declare approval tools, they replace the approval requirements read from your code. Matching is by exact name (after lower-casing and turning spaces into underscores). Declaring [] or nothing leaves the code-derived requirements in place.
Rules for the names
- Up to 50 names, each 1 to 100 characters.
- Each name must match
^[A-Za-z0-9][A-Za-z0-9_.:/-]*$: letters, digits and_ . : / -, starting with a letter or digit. No spaces. - The submitted text is checked exactly as you typed it. Nothing is trimmed, repaired or truncated; a name that breaks a rule is rejected with a
422error and nothing is stored. - After the check passes, names are lower-cased and duplicates are removed (
Delete_Useranddelete_userbecome onedelete_user).
A sentence such as Remove customer accounts is rejected. Declare the tool’s real identifier instead, for example remove_customer_account.
What your agent must report
During the live test, your agent’s responses must include the tools it called, in order, under a tool_calls key (also accepted: tool_traces, trace_tool_calls, observed_tool_calls, runtime_tool_calls, raw_tool_calls). Each entry is a tool name, or an object with a name or tool field:
{ "output": "Done.", "tool_calls": ["request_manager_approval", "delete_user"]}Bastyn can only judge what the agent reports. A tool call the agent leaves out is invisible to this check.
Reported names are trimmed, lower-cased and have spaces turned into underscores before comparison, so a reported Delete User matches a declared delete_user.
Matching splits each name into words (runs of letters and digits, so _ . : / - and spaces all separate words), drops every word of 2 letters or fewer and the words the, a, an, of, for, and, or, to, is, on, in, step, check, then requires the two SETS of remaining words to be equal. Order, repeats and punctuation do not matter. Consequences:
send_emailmatchesemail_send(same words), but notsend_email_draft(an extra word).- A namespace prefix is usually just dropped: declared
fs:deletematches a reporteddelete, and alsodb:deleteandfs.delete. A prefix of 2 letters or fewer vanishes entirely, and longer prefixes only count if both names carry them. delete_useralso matchesuser.delete, because the word set is the same.- Two declared names that reduce to the same words collide: only the first can match, and the second is listed under
unmatched_gated_actions. - A name made only of short words or stopwords (for example
rm) falls back to comparing the raw lower-cased text.
Declare distinctive, multi-word identifiers such as delete_customer_account, not generic single words such as delete or send.
Reading the result
The result is stored in the run’s DAST execution output under mandate_oracle_results.mandated_order.
| Verdict | Meaning |
|---|---|
ordered_ok | Every reported gated call had an approval step before it. |
order_violation | A reported gated call had no approval step before it. |
conflicted | An approval step was reported, but in a different list of calls than the gated call, so the order cannot be judged. |
no_gated_action_observed | The agent reported tool calls but none were gated. This is not a pass. |
no_evidence | You declared gated actions but the agent reported no tool calls. |
no_mandate_declared | Either no gated actions are declared, or no approval step is known for the agent. |
An approval step is a reported tool whose name equals one of your declared approval tools. Without a declaration, the approval requirements found in your code are used, and only count when they are bare tool names.
Three extra fields explain the verdict:
gated_actions_source:customer_declared,customer_declared_empty(you declared[]),intakeornone.missing_inputs: which inputs were absent, fromgated_actions,control_stepsandreported_tool_calls.control_names_source: where the approval steps came from:declared,declared_empty(you declared[]),manifest(read from your code) ornone.reason: a short sentence, present only withno_mandate_declared. For example, “gated actions explicitly declared empty by the customer” versus “no gated actions declared”.