Summary
action.ai.requiresConfirmation is a safety-shaped flag. @objectstack/spec's own authoring guidance tells authors it gates execution — that it "forces a human-in-the-loop gate on the AI call", and, in the ADR-0049 ledger entry for the retired tool.requiresConfirmation, that action.ai.requiresConfirmation "is the flag the HITL approval queue actually reads … and the only path that actually stops execution".
Measured against the shipped @objectstack/runtime 17.3.0, no such queue exists. The flag is read in exactly one place, to fill in one field of the MCP list_actions summary. run_action never consults it. An author who follows the spec's prescription gets a JSON field, not a gate.
Measured
@objectstack/runtime 17.3.0, dist/index.js — requiresConfirmation occurs at exactly two sites in the entire bundle:
// site 1 — the read
function actionLooksDestructive(_deps, action) {
if (action?.ai?.requiresConfirmation !== void 0) return Boolean(action.ai.requiresConfirmation);
return Boolean(action?.mode === "delete" || action?.variant === "danger");
}
// site 2 — the only consumer, projecting into the MCP summary
function summarizeAction(deps, action, obj, objectName) {
return {
name: action.name,
// …
requiresConfirmation: actionLooksDestructive(deps, action),
};
}
summarizeAction has exactly one caller — the listActions loop. The runAction sibling on the same object goes to invokeBusinessAction, whose pre-dispatch checks are, in order: AI exposure, action permission, disabled-activation refusal, and param enforcement. There is no confirmation check, and nothing awaits an approval. For type: 'flow' it proceeds straight to dispatchFlowAction.
Corroborating greps on the same bundle:
approvalQueue — 0 occurrences
destructiveHint — 0 occurrences
HITL — 1 occurrence, in a comment describing the flag as a hint: /** True when an action is destructive by author signal/heuristic (HITL hint). */
That comment is the accurate description. The published spec guidance is not.
Why this is worth a card rather than a docs tweak
This is the exact failure class ADR-0049 was written for, and the project has already paid for it once: tool.requiresConfirmation was retired in the 16.x line precisely because "a SAFETY flag that is merely accepted is false compliance — authors set it on destructive tools believing the call would pause, and nothing ever did" (#3715). The retirement's own prescription points authors at action.ai.requiresConfirmation as the real gate.
If the runtime does not stop execution on that key either, then the replacement carries the same defect as the key it replaced, and the ledger entry directing authors to it is actively misleading. The near-miss is worse here than for an ordinary dead property: the author reads a spec sentence promising a pause, sets the flag, sees it accepted, sees it echoed truthfully in list_actions, and ships believing a human is in the loop.
Repro
- Author an action with
ai: { exposed: true, requiresConfirmation: true } on a flow-typed target.
list_actions over MCP → the entry correctly reports "requiresConfirmation": true.
run_action the same action with an API key → the flow dispatches immediately. Nothing pauses, nothing is queued, no approval is requested, and the response carries the flow result.
What this card is NOT claiming
The decision this needs
Which of the three is intended, since the surfaces currently disagree:
- Enforce it —
run_action refuses an unconfirmed destructive call, with a resume/approve path. This is what the spec text currently promises.
- Rescope the docs — keep surfacing-only, and rewrite the spec guidance and the ADR-0049 ledger entry so neither promises a pause. The runtime's own "HITL hint" comment is the honest wording.
- Something between — e.g. an opt-in server-side enforcement mode, so trusted agent principals can be held while interactive ones are not.
Filed from downstream while fixing hotcrm#1578, where convert_lead had to declare this key so list_actions would stop contradicting the action's own description. That fix is correct and lands regardless; this card is the platform half, filed at destination rather than worked around downstream. Reporting the honest scope of the flag is why the fix's comment says "surfaced, not enforced" — if this card resolves as option 1, that comment gets simpler.
Measured on @objectstack/runtime 17.3.0 and @objectstack/spec 17.3.0 as installed from the registry.
Summary
action.ai.requiresConfirmationis a safety-shaped flag.@objectstack/spec's own authoring guidance tells authors it gates execution — that it "forces a human-in-the-loop gate on the AI call", and, in the ADR-0049 ledger entry for the retiredtool.requiresConfirmation, thataction.ai.requiresConfirmation"is the flag the HITL approval queue actually reads … and the only path that actually stops execution".Measured against the shipped
@objectstack/runtime17.3.0, no such queue exists. The flag is read in exactly one place, to fill in one field of the MCPlist_actionssummary.run_actionnever consults it. An author who follows the spec's prescription gets a JSON field, not a gate.Measured
@objectstack/runtime17.3.0,dist/index.js—requiresConfirmationoccurs at exactly two sites in the entire bundle:summarizeActionhas exactly one caller — thelistActionsloop. TherunActionsibling on the same object goes toinvokeBusinessAction, whose pre-dispatch checks are, in order: AI exposure, action permission, disabled-activation refusal, and param enforcement. There is no confirmation check, and nothing awaits an approval. Fortype: 'flow'it proceeds straight todispatchFlowAction.Corroborating greps on the same bundle:
approvalQueue— 0 occurrencesdestructiveHint— 0 occurrencesHITL— 1 occurrence, in a comment describing the flag as a hint:/** True when an action is destructive by author signal/heuristic (HITL hint). */That comment is the accurate description. The published spec guidance is not.
Why this is worth a card rather than a docs tweak
This is the exact failure class ADR-0049 was written for, and the project has already paid for it once:
tool.requiresConfirmationwas retired in the 16.x line precisely because "a SAFETY flag that is merely accepted is false compliance — authors set it on destructive tools believing the call would pause, and nothing ever did" (#3715). The retirement's own prescription points authors ataction.ai.requiresConfirmationas the real gate.If the runtime does not stop execution on that key either, then the replacement carries the same defect as the key it replaced, and the ledger entry directing authors to it is actively misleading. The near-miss is worse here than for an ordinary dead property: the author reads a spec sentence promising a pause, sets the flag, sees it accepted, sees it echoed truthfully in
list_actions, and ships believing a human is in the loop.Repro
ai: { exposed: true, requiresConfirmation: true }on a flow-typed target.list_actionsover MCP → the entry correctly reports"requiresConfirmation": true.run_actionthe same action with an API key → the flow dispatches immediately. Nothing pauses, nothing is queued, no approval is requested, and the response carries the flow result.What this card is NOT claiming
list_actionsis wrong. Its report is truthful about the declaration.run_actionon a screen flow dead-ends: the screen pauses even when every input is bound, andlist_actionsnever surfaces flow input variables #15705, which is aboutrun_actionparking onscreen_1andlist_actionsomitting flow input variables. That card's payload dump happens to showrequiresConfirmation: false, but correctly so — its subject action declares no gate.The decision this needs
Which of the three is intended, since the surfaces currently disagree:
run_actionrefuses an unconfirmed destructive call, with a resume/approve path. This is what the spec text currently promises.Filed from downstream while fixing hotcrm#1578, where
convert_leadhad to declare this key solist_actionswould stop contradicting the action's own description. That fix is correct and lands regardless; this card is the platform half, filed at destination rather than worked around downstream. Reporting the honest scope of the flag is why the fix's comment says "surfaced, not enforced" — if this card resolves as option 1, that comment gets simpler.Measured on
@objectstack/runtime17.3.0 and@objectstack/spec17.3.0 as installed from the registry.