Skip to content

MCP: a submitter's agent cannot recall or resubmit its own approval request — approval_recall / approval_resubmit are not ai.exposed, while the approver's four decide actions now are (ruling A on #32442) #32993

Closed

Description

@objectstack-fleet

eval#1 · os-bob · session_012dn2AcTB69pEwZ9ugr1g1w

Filing gate: ① product gap with a public-entry reproduction (an MCP client acting for the submitter), the submitter-side branch of ruling A on #32442 — per the meta-criterion "a new branch reuses the parent card's ruling and enters the queue directly". ⛔ Not a claim; triage decides lane and grade.

What happened

An end user's agent (a third-party MCP client, model GLM-5.3-Flash) operates an OA app on the os-dev-test environment for its user. It submitted an expense claim through the app's own submit_expense_claim action; the approval request opened (approval_status: pending). The user then updated the claim's line items and asked the agent to fill them in. The app locks the lines while the request is pending, so the agent needs to recall the request first. It cannot:

现在还撤不了 —— 这条单子的 approval_status 仍是 pending … 我这边没有撤回审批的可用动作(审批撤回属于审批中心的操作,MCP 只暴露了 submit_expense_claim 提交动作)。需要你操作一步:在 OA 里打开这张报销单 …,点「撤回」。

So the agent hands the step back to the person, who has to switch to the console for one click.

Why

On main (packages/plugins/plugin-approvals/src/sys-approval-request.object.ts), PR #32886 (decide half of ruling A on #32442) declared ai.exposed + requiresConfirmation: true on approval_approve, approval_reject, approval_reassign, approval_send_back. The submitter's own actions on the same object — approval_recall (:787) and approval_resubmit (:829) — stay unexposed. Ruling A covered the approver's side only ("the approval request's own decide actions"), so the submitter's half of the same process is still UI-only.

Expected

Same shape as ruling A, on the submitter's side: approval_recall and approval_resubmit declare ai.exposed with requiresConfirmation: true, forwarded to their approvals routes as the caller, so ApprovalService stays the only gate (its existing approval_recall_not_submitter refusal already keeps a non-submitter out). An agent can then "recall my claim, fix the lines, resubmit" end to end, with the user confirming each step.

Steps

  1. Any record-triggered or action-triggered flow with an approval node; submit a record as user U (the request is pending).
  2. Over MCP as U: list_actions — no recall / resubmit action is listed (only the approver's decide actions, after feat(plugin-approvals): an approver's agent decides a pending approval request over MCP #32886).
  3. The only way to recall is the console's 撤回 button (POST /api/v1/approvals/requests/:id/recall).

Filing notes


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH

    Path: none

    Triage: first touch → domain:server · p1 · pm:queue · 2026-11-13T02:44Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH

    Path: none

    Triage: regrade p1 → p2 under the adopted grading rules (#32997) · 2026-11-13T03:07Z

    ⛔ Not a claim; the card's claim and state are unchanged.


    Generated by Claude Code
    triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH

    Path: none

    Triage: regrade p2 → A submitter's agent cannot recall or resubmit, and it says so plainly and tells the user to recall in the console. That is a loud gap with a remedy, so the MCP focus raise does not apply (rule 8). A core-flow gap with a workaround is p2. under the adopted grading rules (#32997) · 2026-11-13T03:07Z

    ⛔ Not a claim; the card's claim and state are unchanged.


    Generated by Claude Code

  3. added and removed
    priority:p1High: required for production / M2
    on Nov 13, 2026
  4. added
    priority:p0Queue-jump: outranks batch and breaks the round — never exempts claiming or same-file serial
    and removed on Nov 13, 2026
  5. os-bob commented on Nov 13, 2026

    @os-bob
    Contributor

    Re-grade on the maintainer's instruction: priority:p2 → priority:p0. State unchanged (pm:queue, unassigned). Written by a test session, not a seat.


    Generated by Claude Code

  6. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#8 · os-elon-musk · session_016WV4cwqTK71kjQzHae8T5J

    Claim: PM loop round 3 (p0, pm:queue; card ≡ 2 (mod 3); sub-issue of the tracking epic #32897, which has no seat of its own; the submitter-side branch of ruling A on #32442) · 2026-11-13T07:39Z
    Session: session_016WV4cwqTK71kjQzHae8T5J
    Account: os-elon-musk
    Branch: the card thread's designated claude/project-thread-* branch, written in its whole re-posted claim before the first push
    Worktree: the card thread's own checkout
    Domain: domain:server
    Seat: domain:server#8
    File surface: packages/plugins/plugin-approvals/src/sys-approval-request.object.ts (the approval_recall and approval_resubmit declarations only), the approvals action forwarding those two actions to their routes as the caller where #32886 forwarded the decide actions, tests beside them, packages/qa/dogfood/test/approval-decide-over-mcp.dogfood.test.ts (extended), content/docs/** where the decide actions' AI exposure is documented, .changeset/** (stop on breach; explain in the report)
    Container & model: S, mode:thread, model: opus (TIER_DEFAULT unless dispatch-gates.mjs --tier derives more), effort: high
    Clause-②: yes (widening)
    Metadata-protocol: none
    Declaration note: two existing actions become visible and runnable to an AI caller (ai.exposed, requiresConfirmation: true), the same shape #32886 gave the decide actions; ApprovalService stays the only gate
    Responsibility: n/a — gap, ruling A on #32442 extended to the submitter's side (the card's own filing gate)
    Thread-read: 6846917997
    Premise: holds (measured on origin/main 157051c: approval_recall at sys-approval-request.object.ts:796 and approval_resubmit at :838 declare no ai block; approval_recall_not_submitter is enforced in approval-service.ts)
    Serial constraints cleared: no in-flight claim in this lane names plugin-approvals; #32442's read half is pm:blocked on spec #32865 and touches the read door, not these declarations

  7. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 13, 2026
  8. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#8 · os-elon-musk · session_016WV4cwwUDMxb29FgtH4D53

    Claim: PM loop round 3 (p0, pm:queue; card ≡ 2 (mod 3); sub-issue of the tracking epic #32897, which has no seat of its own; the submitter-side branch of ruling A on #32442) · 2026-11-13T07:43Z — card-thread re-post of claim 6847131641 with its branch
    Session: session_016WV4cwwUDMxb29FgtH4D53
    Account: os-elon-musk
    Branch: claude/project-thread-49wiah
    Worktree: the card thread's own checkout
    Domain: domain:server
    Seat: domain:server#8
    File surface: packages/plugins/plugin-approvals/src/sys-approval-request.object.ts (the approval_recall and approval_resubmit declarations only), the approvals action forwarding those two actions to their routes as the caller where #32886 forwarded the decide actions, tests beside them, packages/qa/dogfood/test/approval-decide-over-mcp.dogfood.test.ts (extended), content/docs/** where the decide actions' AI exposure is documented, .changeset/** (stop on breach; explain in the report)
    Container & model: S, mode:thread, model: opus (TIER_DEFAULT unless dispatch-gates.mjs --tier derives more), effort: high
    Clause-②: yes (widening)
    Metadata-protocol: none
    Declaration note: two existing actions become visible and runnable to an AI caller (ai.exposed, requiresConfirmation: true), the same shape #32886 gave the decide actions; ApprovalService stays the only gate
    Responsibility: n/a — gap, ruling A on #32442 extended to the submitter's side (the card's own filing gate)
    Thread-read: 6847131641
    Premise: holds (measured on origin/main 157051c: approval_recall at sys-approval-request.object.ts:796 and approval_resubmit at :838 declare no ai block; approval_recall_not_submitter is enforced in approval-service.ts)
    Serial constraints cleared: no in-flight claim in this lane names plugin-approvals; #32442's read half is pm:blocked on spec #32865 and touches the read door, not these declarations

  9. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH

    Path: none

    Triage: p0 → p1 · not an 18.4.0 blocker · 2026-11-13T08:00Z

    ⛔ Not a claim; the claim and state are unchanged, and the work continues.


    Generated by Claude Code

  10. added
    priority:p1High: required for production / M2
    and removed
    priority:p0Queue-jump: outranks batch and breaks the round — never exempts claiming or same-file serial
    on Nov 13, 2026
  11. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#8 · os-elon-musk · session_016WV4cwwUDMxb29FgtH4D53
    os-dev-report

    {
     "issue": 32993,
     "status": "done",
     "branch": "claude/project-thread-49wiah",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/33076",
     "session": "session_016WV4cwwUDMxb29FgtH4D53",
     "premise_still_valid": true,
     "summary": "approval_recall and approval_resubmit on sys_approval_request declare ai: { exposed: true, description, requiresConfirmation: true }, the shape #32886 gave the four decisions. Both were already same-origin type 'api' actions targeting POST /api/v1/approvals/requests/{id}/recall and /resubmit, so run_action forwards them as the caller and ApprovalService stays the only gate (no check added in the MCP or action door). Tool descriptions are their own text (the recall dialog question stays in the top-level description): recall's says a recalled request is closed and re-sending goes through the app's own submit action; resubmit's says it applies only to a returned request. connect-mcp.mdx gains the submitter's bullet. Changeset plugin-approvals patch, Clause-②: yes (widening), Metadata-protocol: none. Head f5587158; draft PR, Fixes #32993; assignee and review request os-elon-musk.",
     "tests": "sys-approval-request.object.test.ts Tests 18 passed (18) (exposed set = 4 decisions + recall + resubmit; both confirm, name the submitter, target their own route; the recall question never armed as ai.description). approval-decide-over-mcp.dogfood.test.ts 5 -> 8, Tests 8 passed (8): list_actions for approver and submitter lists the six, each aiExposed + requiresConfirmation; submitter recall refused ACTION_CONFIRMATION_REQUIRED unconfirmed (still pending, nothing recorded), confirmed -> recalled, recorded once as recall by the submitter; the approver's recall refused FORBIDDEN carrying the approval_recall_not_submitter sentence (BUILTIN_OPERATION_MESSAGES.en), still pending; sent back over MCP, record edited, unconfirmed resubmit refused, approver's resubmit refused FORBIDDEN, no round 2, then the submitter's confirmed resubmit opens round 2. plugin-approvals test: Test Files 118 passed (118), Tests 1624 passed (1624); plugin-approvals typecheck exit 0; dogfood typecheck exit 0. Ablation (dogfood aliases plugin-approvals to source, no rebuild; committed head f5587158; scripts/ablation-replace.mjs --delete, each restored blob == HEAD, git diff HEAD empty): A recall ai block x1->x0: 3 failed | 5 passed (8) (listing + both recall pins); B resubmit ai block x1->x0: 2 failed | 6 passed (8) (listing + resubmit pin).",
     "gates": "Head f5587158 (origin/main 12b14462 merged before the PR; main had not moved). dispatch-gates --commands derived 96; 92 exit 0 on the first pass; check-plugin-teardown-shape --self-test exit 3 until fixture commit 621a4876 was fetched, then 0; check:skill-examples / check:dts-closure / check:dual-build-cjs-loads PREREQUISITE NOT MET until a whole-tree turbo build (120/120, exit 0), then all 0. dispatch-gates --ran: 96 run, 0 NOT-MEASURED. pnpm lint exit 0. The PR body's Verification names those three as NOT MEASURED at PR open; they are measured green here (seat may amend the body). CI check-runs on #33076 at report time: 9 success, 14 skipped, 7 in_progress (not re-run, not waited on).",
     "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
     "mcp_calls": "0",
     "api_writes": "4 relay writes as objectstack-fleet[bot]: comment on #32993 (claim re-post 6847193267), pr_create (#33076), pr_request_reviewers (#33076, os-elon-musk), this report comment",
     "open_questions": [],
     "security_findings": "none",
     "out_of_scope_findings": [
      "carrier: #32865 · noted, not touched: the approver's read door (#32442 read half) is unchanged; MCP object tools still refuse sys_* objects, so an agent finds a request id through the app.",
      "carrier: none · noted: the card's 'recall, fix the lines, resubmit' story ends with the app's own submit action, because a recalled request is closed and resubmit applies only to a returned request; the recall tool description says so.",
      "carrier: none · noted: the #3424 override holder's agent can recall a pending request it did not submit, the same admission the console's Recall button already has (existing service rule, not a new reach)."
     ]
    }

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:serverDomain lane — seat card indexed by label:pm:seatpriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions