Skip to content

Commit b098b0e

Browse files
os-zhuangclaude
andauthored
docs(ai): stop tool.requiresConfirmation promising a gate it does not provide (#3715) (#3740)
The flag is read by no execution path (LLM tool set, ToolRegistry.execute, the REST execute route, the MCP bridge — verified in #3711), while the authoring surface actively taught reliance on it: a form section titled "Access & safety" with helpText "Ask user to approve before executing (for destructive actions)", plus SKILL.md / MCP_GUIDE / README all recommending it for destructive work. Owner call: DEFER the prune-or-wire decision (#3715), stop the false promise now. The shape is likely needed once side-effect tools exist — ToolCategory already anticipates `action` (send email / create record), `integration` (external API) and `flow`, none of which the ADR-0033 draft/publish gate covers; that ADR's "the draft is the approval gate" reasoning held when the only tools were metadata mutators. - spec describe: [EXPERIMENTAL — not enforced] + pointer to the real gate - form: section renamed "Declarative metadata (not enforced)"; both fields (this + the already-dead `permissions`) name the enforced alternative - SKILL.md / MCP_GUIDE.md / README.md: point at action-level ai.requiresConfirmation + the approval queue, and note that AI metadata edits are already gated by draft/publish - ledger note records the deferral - regenerated docs + the four i18n bundles (diff verified: only the tool form's label/description/helpText keys moved) No behaviour change. 6710 spec tests; liveness/docs/api-surface/skill-docs/ skill-examples/i18n/role-word all green. Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 094fa34 commit b098b0e

12 files changed

Lines changed: 84 additions & 25 deletions

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
docs(ai): stop `tool.requiresConfirmation` promising a gate it does not provide (#3715)
6+
7+
The flag is read by **no execution path** — not the LLM tool set, not
8+
`ToolRegistry.execute`, not `POST /ai/tools/:name/execute`, not the MCP bridge.
9+
Yet the authoring surface actively taught reliance on it: the Studio form
10+
section was titled *"Access & safety"* with helpText *"Ask user to approve
11+
before executing (for destructive actions)"*, and the AI skill doc, MCP guide
12+
and spec README all recommended it for destructive operations.
13+
14+
The prune-or-wire decision is deliberately **deferred** (#3715 — the field's
15+
shape is likely needed once side-effect tools exist, which `ToolCategory`
16+
already anticipates with `action` / `integration` / `flow`). What changes now
17+
is only the promise:
18+
19+
- spec `.describe()` carries `[EXPERIMENTAL — not enforced]` + a pointer to the
20+
real gate;
21+
- the form section is renamed *"Declarative metadata (not enforced)"* and both
22+
its fields (this and the already-dead `permissions`) say so, with the enforced
23+
alternative spelled out;
24+
- `skills/objectstack-ai/SKILL.md`, `MCP_GUIDE.md` and `README.md` now point at
25+
the action-level `ai.requiresConfirmation` + approval queue (and note that AI
26+
metadata edits are already gated by draft/publish, ADR-0033).
27+
28+
No behaviour change: nothing read the flag before and nothing reads it now.

content/docs/references/ai/tool.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ AI tool definition. [READ-ONLY PROJECTION — not an execution entry point] Auth
4040
| **parameters** | `Record<string, any>` || JSON Schema for tool parameters |
4141
| **outputSchema** | `Record<string, any>` | optional | [EXPERIMENTAL — not enforced] JSON Schema for tool output. Keys are folded into the tool description only; outputs are not validated (liveness #1878/#1893). |
4242
| **objectName** | `string` | optional | Target object name (snake_case) |
43-
| **requiresConfirmation** | `boolean` || Require user confirmation before execution |
43+
| **requiresConfirmation** | `boolean` || [EXPERIMENTAL — not enforced] Require user confirmation before execution. NOTHING pauses on this flag (#3715) — use the action-level `ai.requiresConfirmation` + approval queue for a real gate. |
4444
| **permissions** | `string[]` | optional | Required permission-set capabilities |
4545
| **active** | `boolean` || Whether the tool is enabled |
4646
| **builtIn** | `boolean` || Platform built-in tool flag |

packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1651,9 +1651,9 @@ export const enMetadataForms: NonNullable<TranslationData['metadataForms']> = {
16511651
label: "Schemas",
16521652
description: "Inputs the tool accepts and the shape of its output."
16531653
},
1654-
access_and_safety: {
1655-
label: "Access & safety",
1656-
description: "Permissions and confirmation requirements."
1654+
declarative_metadata_not_enforced: {
1655+
label: "Declarative metadata (not enforced)",
1656+
description: "Recorded on the tool definition but read by no execution path — see the per-field notes for where the real gates live."
16571657
}
16581658
},
16591659
fields: {

packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1651,9 +1651,9 @@ export const esESMetadataForms: NonNullable<TranslationData['metadataForms']> =
16511651
label: "Esquemas",
16521652
description: "Entradas que acepta la herramienta y forma de su salida."
16531653
},
1654-
access_and_safety: {
1655-
label: "Acceso y seguridad",
1656-
description: "Permisos y requisitos de confirmación."
1654+
declarative_metadata_not_enforced: {
1655+
label: "Declarative metadata (not enforced)",
1656+
description: "Recorded on the tool definition but read by no execution path — see the per-field notes for where the real gates live."
16571657
}
16581658
},
16591659
fields: {

packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1651,9 +1651,9 @@ export const jaJPMetadataForms: NonNullable<TranslationData['metadataForms']> =
16511651
label: "スキーマ",
16521652
description: "ツールが受け取る入力と出力の形状。"
16531653
},
1654-
access_and_safety: {
1655-
label: "アクセスと安全性",
1656-
description: "権限と確認要件。"
1654+
declarative_metadata_not_enforced: {
1655+
label: "Declarative metadata (not enforced)",
1656+
description: "Recorded on the tool definition but read by no execution path — see the per-field notes for where the real gates live."
16571657
}
16581658
},
16591659
fields: {

packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1651,9 +1651,9 @@ export const zhCNMetadataForms: NonNullable<TranslationData['metadataForms']> =
16511651
label: "参数 / 返回结构",
16521652
description: "工具接收的输入与输出形态"
16531653
},
1654-
access_and_safety: {
1655-
label: "访问与安全",
1656-
description: "权限与确认要求"
1654+
declarative_metadata_not_enforced: {
1655+
label: "Declarative metadata (not enforced)",
1656+
description: "Recorded on the tool definition but read by no execution path — see the per-field notes for where the real gates live."
16571657
}
16581658
},
16591659
fields: {

packages/spec/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ export const objectStackMCP = MCPServerConfigSchema.parse({
133133
],
134134
handler: 'flows.create_record',
135135
sideEffects: 'write',
136+
// NOTE: declarative only — nothing pauses on this flag (#3715). A real
137+
// human-in-the-loop gate is `ai.requiresConfirmation` on the underlying
138+
// action (+ the approval queue), or `approval: 'always'` on an MCP binding.
136139
requiresConfirmation: true,
137140
},
138141
{

packages/spec/docs/MCP_GUIDE.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,12 @@ export const salesAgent = AgentSchema.parse({
355355
- `write`: Modifies data
356356
- `delete`: Deletes data
357357

358-
For operations with side effects, consider setting `requiresConfirmation: true`
358+
For operations with side effects, gate them with something that is enforced:
359+
`approval: 'always'` on the MCP tool binding, or `ai.requiresConfirmation` on
360+
the underlying **action** (+ the HITL approval queue).
361+
362+
> ⚠️ `requiresConfirmation` on the **tool definition** is declared but read by
363+
> no execution path — it produces no pause (#3715).
359364
360365
### 4. Security
361366

packages/spec/liveness/tool.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
"evidence": "zero reads off any TOOL definition in any repo; every requiresConfirmation read is action.ai.requiresConfirmation (a different, live property). Ignored by the LLM tool set (vercel-adapter.ts:263-272), ToolRegistry.execute (tool-registry.ts:183-210), POST /ai/tools/:name/execute (tool-routes.ts:91,105) and the MCP bridge (framework packages/mcp/src/mcp-server-runtime.ts:176, which uses a hardcoded destructive-name list)",
4444
"authorWarn": true,
4545
"authorHint": "NOT ENFORCED — nothing pauses for confirmation on this flag, on any path. To gate a destructive operation, put it behind an action with `ai.requiresConfirmation` + the HITL approval queue, which is the only path that actually stops execution.",
46-
"note": "RE-VERIFIED 2026-07 (#3686 preview-claim sweep): the prior `live` verdict cited only a metadata-admin PREVIEW panel, which echoes what the author typed. SAFETY-SHAPED: tool.form.ts:36-42 places it in a section titled 'Access & safety' with helpText 'Ask user to approve before executing (for destructive actions)', and skills/objectstack-ai/SKILL.md:547 tells authors to rely on it — so the authoring surface actively teaches a guarantee nothing delivers. ADR-0033 (:31, :60) already recorded it as never-enforced and resolved to DELETE the placeholder (the draft/publish workspace is the real approval gate); that deletion was never executed. Prune tracked separately."
46+
"note": "RE-VERIFIED 2026-07 (#3686 preview-claim sweep): the prior `live` verdict cited only a metadata-admin PREVIEW panel, which echoes what the author typed. SAFETY-SHAPED: tool.form.ts:36-42 places it in a section titled 'Access & safety' with helpText 'Ask user to approve before executing (for destructive actions)', and skills/objectstack-ai/SKILL.md:547 tells authors to rely on it — so the authoring surface actively teaches a guarantee nothing delivers. ADR-0033 (:31, :60) already recorded it as never-enforced and resolved to DELETE the placeholder (the draft/publish workspace is the real approval gate); that deletion was never executed. OWNER CALL 2026-07: the prune-or-wire decision is DEFERRED (#3715), but the false promise stops now — the spec `.describe()`, the Studio form section (formerly titled \"Access & safety\", whose copy told authors to use it for destructive operations) and the SKILL.md / MCP_GUIDE / README guidance all now say NOT ENFORCED and point at the action-level gate."
4747
},
4848
"permissions": {
4949
"status": "dead",

packages/spec/src/ai/tool.form.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,17 @@ export const toolForm = defineForm({
3434
],
3535
},
3636
{
37-
label: 'Access & safety',
38-
description: 'Permissions and confirmation requirements.',
37+
// NOT an enforcement section — both fields are declared-but-dead (#3715,
38+
// liveness/tool.json). The label used to read "Access & safety" with
39+
// copy that told authors to rely on them for destructive operations,
40+
// which is exactly the false promise this section must not make.
41+
label: 'Declarative metadata (not enforced)',
42+
description: 'Recorded on the tool definition but read by no execution path — see the per-field notes for where the real gates live.',
3943
collapsible: true,
4044
collapsed: true,
4145
fields: [
42-
{ field: 'requiresConfirmation', helpText: 'Ask user to approve before executing (for destructive actions)' },
43-
{ field: 'permissions', widget: 'string-tags', helpText: 'Required permissions to use this tool' },
46+
{ field: 'requiresConfirmation', helpText: 'NOT ENFORCED (#3715) — nothing pauses for confirmation on this flag. For a real gate use the action-level `ai.requiresConfirmation` + approval queue; AI metadata edits are already gated by draft/publish.' },
47+
{ field: 'permissions', widget: 'string-tags', helpText: 'NOT ENFORCED — tool invocation is not permission-gated by this list. Gate the underlying action via permission sets (ADR-0066), or restrict the agent that exposes the tool.' },
4448
],
4549
},
4650
],

0 commit comments

Comments
 (0)