Skip to content

[Decision] Should an approver be able to see and decide their pending approvals over MCP? #32442

Closed

Description

@os-bob

Filing category: ② a decision only the maintainer can make.
Batch-of: 15 cards filed together from one MCP capability test session (2026-11-11; 6 in cloud, 9 in objectstack).

维护者速读

  • 现象:申请人能通过 MCP 提交请假,审批流程(approval 节点)也能正常生成 sys_approval_request;但审批人那一侧在 MCP 上没有任何入口:list_actions 里没有审批动作,sys_approval_request 不在 list_objects 里,同意/驳回只能回到界面或 REST。
  • 要你裁:审批人的「看待办 + 同意/驳回」是否进入 MCP,以及走哪个形状。
  • 推荐:A:把审批请求自带的决定动作(同意/驳回/转签/退回)按审批人身份开放给 AI,复用现有 list_actions / run_action,不新增工具。

One-sentence problem

An agent can file a leave request for its user but cannot act for the manager who has to approve it, so half of every approval process stays outside MCP.

Evidence (reach)

Hosted tenant (pnpm dev:stack, Team plan; cloud ff373e02, framework d7c3d44e): an MCP-authored flow with an approval node (org_membership_level: owner) published; create_record over MCP opened sys_approval_request pending naming the owner. Over MCP as that owner: list_actions has no approval action; list_objects has no sys_approval_request. The decision was only possible via POST /api/v1/approvals/requests/:id/approve (the console's 通过 button), after which the flow resumed and the record read approved.

Governing text

  • docs/NORTH-STAR.md — "Two kinds of user: today, business staff through the UI …; tomorrow, agents through MCP. Every step of the road must hold for both kinds of user." The road includes "approvals and automation".
  • packages/mcp/README.md — "sys_*-object actions are held back fail-closed" (approval-request actions live on a sys_* object; some sys_* actions such as invite_user are already exposed with aiExposed: true).

Options

Option What it does What the customer sees Effort
A Expose the approval request's own decide actions (approve / reject / reassign / send back) with ai.exposed, gated to the request's current approvers; let an approver read their pending requests through the existing record tools "Approve Zhang San's leave" works from Claude; same gates as the console button approvals + mcp seats · 1–2 PRs · precedent: sys_user actions exposed via aiExposed (no PR number measured)
B Add dedicated MCP tools (list_my_approvals, decide_approval) Same capability, a separate tool family mcp seat · 1–2 PRs · no precedent
C Keep approvals UI-only Agents submit, humans decide in the console none

Business reading: A = the approval inbox is just more actions an agent can take for its user; B = approvals get their own agent API; C = approvals are deliberately a human-only step.

Recommendation

A. Two years out the agent operates the app through the same declared actions a person clicks (Salesforce Agentforce and ServiceNow agents act through declared actions/approvals, not bespoke endpoints); A adds no tool family and keeps one permission gate. C is a legitimate product stance (human-in-the-loop for decisions) — if chosen, the MCP skill should say so explicitly so agents do not try. Confidence gap: whether approver identity over an OAuth delegation should require a stronger confirmation (requiresConfirmation) — recommend yes, untested.
Self-check: only ① picks A; ②③④ do not flip it.

After the ruling

A: approvals seat exposes the decide actions with approver-scoped gates + requiresConfirmation; mcp seat lets list_actions include them for the approver; e2e: submit over MCP as user A, decide over MCP as approver B, record resumes.

os-decision-facets
① 项目长远合理性: A 复用「动作」作为 agent 唯一操作面,不新增工具族,缩小特例;B 扩大契约面。
② 实际业务拉动: 今天任何「请假/报销/采购」审批场景用 MCP 走全程都会在审批人这一步断开(本次实测)。
③ 防 AI 犯错: A 下审批走与界面同一个带审批人校验的动作;无入口时 agent 会去改状态字段,绕过审批。
④ 创业阶段不扩散: A 不新增工具;C 零成本但留下 North Star 缺口。
Prior rulings read: "approval mcp approver decide tool" → 128 hits (top: #30806, #27101, #28657; none rule on MCP decisions); ADR-0011; thread: none

Reader who acts: the approvals seat and the packages/mcp seat, after the ruling.
Dedupe: repo:objectstack-ai/objectstack is:issue approval mcp approver decide tool → 128 hits, none on approver-side MCP access; cloud approver mcp approval tool → 12 hits (cloud#3600 scenario epic, cloud#4091 AI write hold, cloud#3301 lint), none.

Activity

  1. added
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Nov 11, 2026
  2. objectstack-fleet commented on Nov 11, 2026

    @objectstack-fleet
    Contributor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: decision card routed · domain:server · p2 · 2026-11-11T15:01Z

    ⛔ Not a claim.

    • Lane: domain:server: approvals on the MCP face (packages/mcp / list_actions). Once it is ruled, that lane builds the chosen option.
    • Grade: p2. The ruling is the maintainer's, and the card stays needs-user-decision.

    Generated by Claude Code

  3. objectstack-fleet commented on Nov 12, 2026

    @objectstack-fleet
    Contributor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Ruling: letter A · maintainer 「32443 直接做 B,其他同意」 · 2026-11-12T00:05Z

    The maintainer ruled in the triage seat's session on 2026-11-12, verbatim: 「32443 直接做 B,其他同意」. It was given in answer to triage's explanation of #32442, #32443, #32445 and #32446, so 「其他同意」 takes this card's recommendation.

    • Chosen: A. The approval request's own decide actions (approve / reject / reassign / send back) are exposed to AI with ai.exposed, gated to the request's current approvers. An approver reads their pending requests through the existing record tools. No new MCP tool is added.
    • Lane: domain:server (approvals + packages/mcp). p2. It is 1–2 PRs, with a precedent (sys_user actions exposed the same way).
    • needs-user-decision comes off, and the card goes to pm:queue.

    Generated by Claude Code

  4. added
    pm:queueReady for the PM dispatch loop
    and removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Nov 12, 2026
  5. objectstack-fleet commented on Nov 12, 2026

    @objectstack-fleet
    Contributor

    domain:server#5 · os-elon-musk · session_016WV4cwJbS8sZAfru2iyvQq

    Claim: PM loop round 1 (slot freed by #32757 entering the decision box)
    Session: session_016WV4cwJbS8sZAfru2iyvQq
    Account: os-elon-musk
    Branch: the card thread's designated claude/project-thread-* branch (this seat's card thread re-posts this whole claim with the exact branch before its PR opens)
    Worktree: objectstack-issue-32442
    Domain: domain:server
    Seat: domain:server#5
    File surface: packages/plugins/plugin-approvals/src/ (the approval request's decide actions and their approver gate, its translation bundles if labels change), packages/mcp/src/ (only where the sys_* fail-closed hold-back must admit these actions; tests beside them), content/docs/** that teaches approvals over MCP, .changeset/**. A packages/spec/src/** hunk is a breach: stop and report
    Container & model: M, mode:thread, model: default (dispatch-gates --tier: no path-derived mandate; per-card call, default tier), effort: high (xhigh for the gate design and red-line checks only)
    Clause-②: no
    Metadata-protocol: none
    Responsibility: n/a — not a defect card (executes the maintainer's ruling A, 6810312442)
    Thread-read: 6810312442
    Premise: holds
    Serial constraints cleared: #32445 (os-sales, PR #32816) adds a sys_* action in packages/platform-objects and service-messaging, not plugin-approvals; its generated translation files differ from plugin-approvals' own bundles. #32766/#32767 (seat 6) edit packages/mcp/src/mcp-server-runtime.ts, not the hold-back in mcp-http-tools.ts. Ordinary concurrency; merge origin/main once, before landing

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

    @objectstack-fleet
    Contributor

    domain:server#5 · os-elon-musk · session_016WV4cxnQXys268N88ne5Ao

    Claim: card thread for the seat claim 6836969826 (the decide half of ruling A; the read half waits for a ruling, seat 5)
    Session: session_016WV4cxnQXys268N88ne5Ao
    Account: os-elon-musk
    Branch: claude/project-thread-vlti6b
    Worktree: objectstack-issue-32442
    Domain: domain:server
    Seat: domain:server#5
    File surface: packages/plugins/plugin-approvals/src/ (the approval request's decide actions and their approver gate, its translation bundles if labels change), packages/mcp/src/ (only where the sys_* fail-closed hold-back must admit these actions; tests beside them), content/docs/** that teaches approvals over MCP, packages/qa/dogfood/test/approval-decide-over-mcp.dogfood.test.ts (test infra for the negative pins, seat-approved), .changeset/**. A packages/spec/src/** hunk is a breach: stop and report
    Container & model: M, mode:thread, model: default (dispatch-gates --tier: no path-derived mandate; per-card call, default tier), effort: high (xhigh for the gate design and red-line checks only)
    Clause-②: no
    Metadata-protocol: none
    Responsibility: n/a — not a defect card (executes the maintainer's ruling A, 6810312442)
    Thread-read: 6810312442
    Premise: holds
    Serial constraints cleared: #32445 (os-sales, PR #32816) adds a sys_* action in packages/platform-objects and service-messaging, not plugin-approvals; its generated translation files differ from plugin-approvals' own bundles. #32766/#32767 (seat 6) edit packages/mcp/src/mcp-server-runtime.ts, not the hold-back in mcp-http-tools.ts. Ordinary concurrency; merge origin/main once, before landing
    Parent-claim: 6836969826

  8. objectstack-fleet commented on Nov 12, 2026

    @objectstack-fleet
    Contributor

    domain:server#5 · os-elon-musk · session_016WV4cwJbS8sZAfru2iyvQq

    Read half · decision to the maintainer · 2026-11-12T22:06Z

    The decide half (approve / reject / reassign / send back exposed to AI with confirmation, ApprovalService the only gate) ships now in a Refs #32442 (decide half) PR from this seat's card thread. The read half ("an approver reads their pending requests through the existing record tools") cannot be built under the ruling without a new declared key, so it goes back to the maintainer. A fable review verified the dev's facts and found two more against B. When the decide half lands, this card is released in one stroke to needs-user-decision for the read half (it is in flight until then, so the six-state label moves at that moment).

    Governing text: packages/mcp/src/mcp-http-tools.ts object-tool guard / list_objects filter (every sys_* object refused by name; only the host-wide allowSystemObjects); packages/spec/src/data/object.zod.ts ObjectCapabilities (no per-object AI key); ADR-0066 :78 and #27681 (exposure is declared by the author, no deployment switch); .claude/skills/pm-dispatch/references/lanes/spec.md (a metadata-protocol addition is a spec sub-card, the product card Blocked-by: it).

    Option What Cost
    A (dev + fable recommend) Spec sub-card: a per-object, read-only AI exposure key, meaningful only on sys_* (lint refuses it elsewhere; stored-metadata families refused before the key is read), named like actions' ai.exposed. Server card: the bridge attests the object, the three mcp guards honour the attestation, sys_approval_request declares it; rows stay under request-read-gate.ts (the console's My Pending rows). spec + mcp + runtime + approvals, ~2 PRs, Metadata-protocol: added, contract review
    B An exposed "my pending approvals" read action Not cheaper: ui/action.zod.ts method enum has no GET; run_action is destructiveHint: true and needs actions:execute, so a read-only agent cannot see it and every list prompts a confirmation
    C Hard-code sys_approval_request as readable in mcp Undeclared exception, contrary to #27681; repeats for the next system table
    D Decide only The agent needs the request id from the user

    Flag (no ruling asked unless the maintainer reads "current approvers" literally): the service gate also admits the override holder (platform/tenant admin rescue, #3424), so an administrator's agent can override-decide like the administrator's button, behind confirmation. Narrowing it would be a check in the approval service, never in mcp.

    Analysis: the card thread's read-half-needs-decision.md; review: fable-review.md (seat 5 project files).

  9. objectstack-fleet commented on Nov 12, 2026

    @objectstack-fleet
    Contributor

    domain:server#5 · os-elon-musk · session_016WV4cwJbS8sZAfru2iyvQq

    Ruled: the maintainer, in this seat's thread (project "Objectstack server"), verbatim 「同意」, answering the read-half decision card whose recommendation was A (comment 6837228055, after an independent fable review).

    • Answer given: 2026-11-12T22:13Z; recorded 2026-11-12T22:15Z.
    • Chosen: A. A per-object, read-only AI exposure declaration in spec, declared by sys_approval_request; an approver reads their pending requests with the existing MCP record tools, rows under request-read-gate.ts (the console's My Pending rows). The spec half is spec: a per-object, read-only AI exposure declaration so an agent can read a declared system object through the MCP record tools (#32442 read half) #32865 (filed now; triage sets lane and grade).
    • Override holder: stays as the console button has it (the administrator's agent can override-decide behind confirmation); the ruling did not narrow it.
    • Card shape from here: the decide half lands with Refs #32442 (decide half). At that landing this card is released in one stroke to pm:blocked with Blocked-by: #32865 for the read half, never needs-user-decision (the decision is made).
  10. objectstack-fleet commented on Nov 12, 2026

    @objectstack-fleet
    Contributor

    domain:server#5 · os-elon-musk · session_016WV4cxnQXys268N88ne5Ao

    os-dev report — #32442 (decide half) · PR #32886 · head 62de438

    {
      "issue": 32442,
      "status": "done",
      "branch": "claude/project-thread-vlti6b",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/32886",
      "session": "session_016WV4cxnQXys268N88ne5Ao",
      "premise_still_valid": true,
      "summary": "Decide half of ruling A, per seat 5's split (PR says Refs #32442 (decide half); the read half is ruled A (6837430865) and blocked by spec card #32865). approval_approve, approval_reject, approval_reassign and approval_send_back on sys_approval_request declare ai.exposed with requiresConfirmation: true (ADR-0011 bucket C, precedent sys_user.ban_user); as same-origin type 'api' actions they forward to their approvals routes as the caller, so ApprovalService is the only gate on who decides. No mcp, runtime or spec code changes.",
      "tests": "plugin-approvals sys-approval-request.object.test.ts: Tests 17 passed (17). New dogfood approval-decide-over-mcp.dogfood.test.ts on head 62de438433: Tests 5 passed (5) (approver lists the four decisions attested + requiresConfirmation; stranger REQUEST_NOT_FOUND; submitter FORBIDDEN 'is not a pending approver'; AI service account own key REQUEST_NOT_FOUND; approver 428 ACTION_CONFIRMATION_REQUIRED then approved with one approve row by the approver). Ablations with EXIT-trap restore: A1 decideNode gates removed: pins 2,3,4 red, 1,5 green; A2 approve without requiresConfirmation: pins 1,5 red; A3 approve exposed:false: 5/5 red.",
      "gates": "dispatch-gates --commands: 95 derived; all 95 exit 0 after whole-tree build (pass started on d4fe7020c7); 37 text-affected commands re-run on final head 62de438433, all exit 0; pnpm lint exit 0; plugin-approvals typecheck exit 0; dogfood typecheck exit 134 = local V8 OOM, re-run with 8 GB heap exit 0, 0 error TS. dispatch-gates --ran: 95 derived, 95 run, 0 NOT-MEASURED. CI check-runs: in_progress at report time.",
      "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
      "mcp_calls": "0",
      "api_writes": "2 — post-stamped comment (claim re-post 6837196480), fleet-write pr_create #32886; this report is the 3rd",
      "open_questions": [],
      "security_findings": "withheld",
      "out_of_scope_findings": [
        "class: c · reach: an AI service account named on a slate (or reassigned onto one) can confirm its own run_action today · evidence: actionConfirmationRefusal reads no principalKind; closed by open PR #32691 (ADR-0151 D3 rule 3) · carrier: PR #32886 acceptance notes · noted, not filed"
      ]
    }
  11. 13 remaining items

  12. removed
    priority:p0Queue-jump: outranks batch and breaks the round — never exempts claiming or same-file serial
    on Nov 13, 2026
  13. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    Contributor

    domain:server#5 · os-elon-musk · session_016WV4cwJbS8sZAfru2iyvQq

    Claim: the read half, p1 (triage#1 07:58Z: p1, not an 18.4.0 blocker). Ruled A (6837430865); its blocker #32865 closed completed (PR #32971, 04c35f0952). The decide half already landed as PR #32886.
    Session: session_016WV4cwJbS8sZAfru2iyvQq
    Account: os-elon-musk
    Branch: the card thread's designated claude/project-thread-* branch (the card thread re-posts this whole claim with the exact branch before its PR opens)
    Worktree: objectstack-issue-32442
    Domain: domain:server
    Seat: domain:server#5
    File surface: packages/mcp/src/mcp-http-tools.ts (the record tools' sys_ refusal asks isAiReadExposed from @objectstack/spec for the read tools only: query, get one, aggregate, describe, list; writes unchanged), plus the stdio bridge's matching guard if it has its own; packages/plugins/plugin-approvals/src/sys-approval-request.object.ts (declares enable.aiReadable: true); the read tools' description text naming the "pending for me" filter shape; tests beside each; a dogfood pin for the five acceptance points in comment 6846046224 (only the console's My Pending rows for an approver, nothing for the submitter, enough display fields, the tool text, and end to end submit, list, approve, resume)
    Container & model: M, mode:thread, model: default, effort: high (M, p1; the contract review goes to triage#1 per the maintainer's 05:39Z word, not fable)
    Clause-②: yes (widening)
    Metadata-protocol: none
    Responsibility: objectstack: the MCP record tools refuse every sys_ object by name, so an approver's agent cannot list what waits on them and the decide half (PR #32886) needs a pasted request id | sys_approval_request declares enable.aiReadable; the read tools honour isAiReadExposed with every later gate unchanged (permissions, the approvals request-read gate, field masking); a "pending for me" read returns exactly the console's my_pending rows | every approver using an MCP agent
    Thread-read: 6847462671
    Premise: holds (read from code on main: isAiReadExposed has no consumer outside packages/spec, and the MCP sys_ guard is in mcp-http-tools.ts)
    Serial constraints cleared: draft PR #33057 (os-tesla, #32936) edits tool description text in mcp-http-tools.ts, a different region; whichever lands second merges main. #32993 (submitter recall over MCP) is a sibling card, not stacked

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

    @objectstack-fleet
    Contributor

    domain:server#5 · os-elon-musk · session_016WV4cwJx2CfBxrD3ojeDPr
    os-dev-report

    os-dev report — #32442 (read half) · needs_decision · branch head 77b7a8a · 2026-11-13T08:52Z

    Step 0 on main is red as the order expects: over MCP every read of sys_approval_request is refused by the sys_* name guard (dogfood approval-read-over-mcp.dogfood.test.ts, 6/6 red). The admission is built and pushed (no PR yet). After admission the dogfood measured two forks the ruling does not settle, so the card goes back to the seat with options.

    {
      "issue": 32442,
      "status": "needs_decision",
      "branch": "claude/project-thread-bb7kzf",
      "pr": null,
      "session": "session_016WV4cwJx2CfBxrD3ojeDPr",
      "premise_still_valid": true,
      "summary": "Built and pushed at 77b7a8a1: an optional McpDataBridge member aiReadExposed(name), answered by both bridges (runtime domains/mcp.ts, stdio-data-bridge.ts) with isAiReadExposed over the whole definition; the five read tools ask it for a sys_ name before refusing (fail-closed on absence or throw), every write tool and validate_expression and the file tools keep refusing; sys_approval_request declares enable.aiReadable: true (sys_approval_action declares nothing); query_records text names the waiting-on-you filter shape. Two forks measured after admission: F1, a member holds no data-door read on sys_approval_request (no default set grants it, by design per action-execution.ts), so the approver's query answers 403 PERMISSION_DENIED and list_objects hides the object; F2, the filter pending_approvers contains {current_user_id} misses slates addressed by email or position/team literals (pending_approvers stores the address; measured: an email-addressed request returned 0 rows for its approver under a test grant), a second fold approver-address.ts forbids.",
      "tests": "mcp: vitest run src/mcp-system-object-ai-read.test.ts src/mcp-action-system-object-exposure.test.ts src/mcp-http-tools.list-objects-outage.test.ts -> Test Files 3 passed, Tests 33 passed. dogfood approval-read-over-mcp.dogfood.test.ts: step 0 (mcp dist from 4462b4d9, runtime and approvals source at main) 6 failed of 6, each 'is a system object and is not exposed via MCP'; after admission, no grant: 5 failed / 1 passed (403 PERMISSION_DENIED); after admission plus a test-only read grant: 3 passed (submitter reads own 2, stranger reads 0 by query and by id, tool text) / 3 failed (pending-for-me 0 rows on an email-addressed slate). Ablation not run: behaviour still undecided.",
      "gates": "NOT MEASURED: dispatch-gates families, reason: stopped at needs_decision before the PR; to run on the final head.",
      "dogfood_battery": "n/a — no ! in the title and no (narrowing) arm",
      "mcp_calls": "2 — issue_read get, issue_read get_comments (reads only)",
      "api_writes": "1 — this post-stamped comment",
      "open_questions": [
        {
          "question": "F1: who may read sys_approval_request rows on the data door, which the MCP read tools go through?",
          "options": [
            "1B plugin-approvals grants every signed-in member read on sys_approval_request; request-read-gate.ts still narrows rows to the approvals door's own answer, so a member sees on REST /data and MCP exactly the inbox rows",
            "1A leave it to apps (grant per profile); out of the box no approver's agent sees the object",
            "1C enable.aiReadable grants the read on the MCP door only; contradicts the spec key's text and splits the doors"
          ],
          "recommendation": "1B: the user value (every approver's agent) needs it, and the rows stay exactly what the approvals door already serves the same member"
        },
        {
          "question": "F2: how does an agent ask for the requests waiting on the caller?",
          "options": [
            "2B the approvals request read gate answers a read pinned pending_approvers $contains the caller's own id with the service's acting-address fold (email, position, team slots included); fixes the console my_pending list view too",
            "2A ship the user-id filter and state its limit in the tool text",
            "2C a new filter token for the caller's approver addresses (spec key, contract review)"
          ],
          "recommendation": "2B: one fold, the approvals service's own, no spec key"
        }
      ],
      "security_findings": "none",
      "out_of_scope_findings": [
        "class: a · reach: the console sys_approval_request list view my_pending (filter pending_approvers contains {current_user_id}) omits a pending request whose slate addresses the viewer by email or position/team literal; measured through the same filter over MCP: 0 rows for the email-addressed approver · dedupe words: my_pending list view approver email position slate · carrier: this card's F2 if 2B is chosen"
      ]
    }

    Analysis with costs and the four facets: seat 5 project files 32442-read/needs-decision.md. Claim note: the HTTP bridge (packages/runtime/src/domains/mcp.ts) answers the attestation and is outside the claim's file surface; the claim re-post before the PR adds it.


    Generated by Claude Code

  16. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    Contributor

    domain:server#5 · os-elon-musk · session_016WV4cwJx2CfBxrD3ojeDPr

    Claim: card thread for the seat claim 6848104060 (the read half). This PR lands the read admission only, as Part of #32442; F1/F2 of report 6848413320 wait on the maintainer, and the dogfood end-to-end pin rides the approvals half. Re-posted to add the HTTP bridge to the file surface (seat-approved).
    Session: session_016WV4cwJx2CfBxrD3ojeDPr
    Account: os-elon-musk
    Branch: claude/project-thread-bb7kzf
    Worktree: objectstack-issue-32442
    Domain: domain:server
    Seat: domain:server#5
    File surface: packages/mcp/src/mcp-http-tools.ts (the record tools' sys_ refusal asks the bridge's aiReadExposed attestation for the read tools only: query, get one, aggregate, describe, list; writes unchanged), packages/mcp/src/stdio-data-bridge.ts and packages/runtime/src/domains/mcp.ts (both bridges answer the attestation with isAiReadExposed), packages/plugins/plugin-approvals/src/sys-approval-request.object.ts (declares enable.aiReadable: true); the read tool's description text naming the "pending for me" filter shape; tests beside each; .changeset/**
    Container & model: M, mode:thread, model: default, effort: high (M, p1; the contract review goes to triage#1 per the maintainer's 05:39Z word, not fable)
    Clause-②: yes (widening)
    Metadata-protocol: none
    Responsibility: objectstack: the MCP record tools refuse every sys_ object by name, so an approver's agent cannot list what waits on them and the decide half (PR #32886) needs a pasted request id | sys_approval_request declares enable.aiReadable; the read tools honour isAiReadExposed with every later gate unchanged (permissions, the approvals request-read gate, field masking) | every approver using an MCP agent
    Thread-read: 6848413320
    Premise: holds (step 0 on main: every MCP read of sys_approval_request refused by the sys_ name guard)
    Serial constraints cleared: draft PR #33057 (os-tesla, #32936) edits tool description text in mcp-http-tools.ts, a different region; whichever lands second merges main. #32993 (submitter recall over MCP) is a sibling card, not stacked
    Parent-claim: 6848104060

  17. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    Contributor

    domain:server#5 · os-elon-musk · session_016WV4cwJbS8sZAfru2iyvQq

    Ruled: read half forks F1 and F2 → 1B + 2B (maintainer, 2026-11-13T09:51Z, in the seat thread)

    • F1 → 1B. plugin-approvals grants every signed-in member read on sys_approval_request. request-read-gate.ts still narrows the rows to the approvals door's own answer (submitter, current approver, past actor; an override holder is unrestricted). A member sees on the data door and over MCP exactly the rows the inbox already shows them, and nothing more.
    • F2 → 2B. The request read gate answers a read pinned pending_approvers $contains <the caller's own id> with the service's acting-address fold, so slates addressed by email, position or team count as "waiting on me". That is the one fold approver-address.ts allows, and the console's my_pending list view inherits the fix.
    • Not chosen: 1A (each app grants the read; out of the box no approver's agent sees the object) and 2A (user-id-only match; misses non-id slates). 1C (the AI key granting the read) contradicts the spec key's own text.
    • Lands on this card, with the end-to-end dogfood pin. The plumbing (admission of an attested sys_ object to the read tools) may land first as Part of #32442. The 18.4.0 merge freeze (triage#1, [sweep] v18.4.0 release test sweep #32908 6848983225) applies: no enqueue of a changeset PR until chore: version packages #29963 merges.
  18. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    Contributor

    domain:server#5 · os-elon-musk · session_016WV4cwJx2CfBxrD3ojeDPr

    Claim: card thread for the seat claim 6848104060 (the read half). The maintainer ruled 1B + 2B (6849636735), so this card now closes the read half in ONE PR, Fixes #32442: the MCP read admission, the member baseline read on sys_approval_request, the request read gate answering "waiting on me" with the approvals fold, and the end-to-end dogfood pin. Re-posted to add the approvals files and the permission-set path to the file surface (seat-approved).
    Session: session_016WV4cwJx2CfBxrD3ojeDPr
    Account: os-elon-musk
    Branch: claude/project-thread-bb7kzf
    Worktree: objectstack-issue-32442
    Domain: domain:server
    Seat: domain:server#5
    File surface: packages/mcp/src/mcp-http-tools.ts (the read tools ask the bridge's aiReadExposed attestation; writes unchanged), packages/mcp/src/stdio-data-bridge.ts and packages/runtime/src/domains/mcp.ts (both bridges answer with isAiReadExposed), packages/plugins/plugin-approvals/src/sys-approval-request.object.ts (declares enable.aiReadable: true), packages/plugins/plugin-security/src/objects/default-permission-sets.ts (1B: member_default names sys_approval_request read-only), packages/plugins/plugin-approvals/src/request-read-gate.ts and packages/plugins/plugin-approvals/src/approval-service.ts (2B: a conjunctive pending_approvers $contains <own id> answered with the service's acting-address fold), packages/qa/dogfood/test/approval-read-over-mcp.dogfood.test.ts (end to end over MCP); tests beside each; content/docs/ai/connect-mcp.mdx, packages/mcp/README.md; .changeset/**
    Container & model: M, mode:thread, model: default, effort: high (M, p1; the contract review goes to triage#1 per the maintainer's 05:39Z word, not fable)
    Clause-②: yes (widening)
    Metadata-protocol: none
    Responsibility: objectstack: the MCP record tools refuse every sys_ object, members hold no data-door read on approval requests, and pending_approvers contains {current_user_id} misses email and position slates, so an approver's agent cannot list what waits on them | read admission by enable.aiReadable, member baseline read with the participant gate deciding rows, the gate answering waiting-on-me with the approvals fold | every approver using an MCP agent, and the console's My Pending view
    Thread-read: 6849636735
    Premise: holds (step 0 on main: every MCP read of sys_approval_request refused; after admission alone, 403 for members and 0 rows for an email-addressed approver, report 6848413320)
    Serial constraints cleared: draft PR #33057 (os-tesla, #32936) edits other description text in mcp-http-tools.ts; main merged once (a596c03c), no conflict with main now. #32993 (submitter recall over MCP) is a sibling card. Lands after the 18.4.0 freeze lifts (#29963)
    Parent-claim: 6848104060

  19. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    Contributor

    triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH

    Path: none

    Triage: H14 · pm:blocking derived · 2026-11-13T12:17Z

    ⛔ Not a claim; grade and state unchanged. Open card(s) #32641 name this card on a line-leading Blocked-by:, so the derived cache is added. It comes off when they close or drop the line.

  20. added
    pm:blockingDerived cache from the Blocked-by reverse index: open card with open dependents — never hand-set
    on Nov 13, 2026
  21. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    Contributor

    domain:server#5 · os-elon-musk · session_016WV4cwJx2CfBxrD3ojeDPr
    os-dev-report

    os-dev report — #32442 (read half) · ready_for_review · PR #33117 · head 7fd55e6 · 2026-11-13T14:55Z

    The maintainer's 1B + 2B (6849636735) is built on this card, with the read admission, in ONE draft PR #33117 carrying Fixes #32442. Step 0 was red on main as the order expects; every red line of the ruling is pinned over the live MCP transport with no test grant, and each half has an ablation that turns its pins red.

    {
      "issue": 32442,
      "status": "ready_for_review",
      "branch": "claude/project-thread-bb7kzf",
      "pr": 33117,
      "head": "7fd55e60c1",
      "session": "session_016WV4cwJx2CfBxrD3ojeDPr",
      "premise_still_valid": true,
      "summary": "Read admission: the five MCP read tools admit a sys_ object the bridge attests with isAiReadExposed (enable.aiReadable: true), fail-closed; writes keep refusing every sys_ object; sys_approval_request declares it. 1B: member_default names sys_approval_request read-only; request-read-gate.ts still decides rows. 2B: the gate answers a conjunctive pending_approvers $contains <own id> with waitingOnCallerIds = approverRequestIds(actingAddresses(caller)), the inbox's own fold; other ids, operators and $or are read as written. Console my_pending inherits it.",
      "tests": "dogfood approval-read-over-mcp 8 passed (email + team slates, submitter and stranger nothing, stranger nothing by id and aggregate n:0, masked snapshot field withheld from approver and served to submitter, only sys_ object listed, writes and sys_approval_action refused, e2e approve with confirmation resumes the flow and stamps the record); plugin-approvals request-read-waiting-on-caller 7 passed; mcp 1113 passed; plugin-security, plugin-approvals full suites and typechecks exit 0; dogfood, mcp, runtime typecheck exit 0.",
      "ablations": "A1 readGuard admits nothing -> 4 red; A2 runtime bridge false -> 1 red; A3 declaration removed -> 1 red; 1B member_default line removed (dist rebuilt, preflight --absent) -> dogfood 7 red, 403; 2B rewrite disabled -> integration position pin red, dogfood 4 red (email slate, masked read, aggregate n:2, e2e). Team pin stays green under 2B: team slots expand to user ids at open.",
      "gates": "dispatch-gates --ran on 7fd55e60c1: 103 derived, 103 run, 0 NOT-MEASURED, 0 UNRUN, all exit 0 (query-options-erasure first 1, fixed by typing the rig's options).",
      "dogfood_battery": "n/a — no ! in the title and no (narrowing) arm",
      "mcp_calls": "0 this round",
      "api_writes": "3 — claim re-post 6849988930, pr_create #33117 via the relay, this comment",
      "contract_review": "owed: Clause-② yes (widening); the seat asks triage#1 (#32588)",
      "open_questions": []
    }
  22. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    Contributor

    domain:server#5 · os-elon-musk · session_016WV4cwJx2CfBxrD3ojeDPr

    Claim: card thread for the seat claim 6848104060 (the read half). The maintainer ruled 1B + 2B (6849636735), so this card now closes the read half in ONE PR, #33117, Fixes #32442: the MCP read admission, the member baseline read on sys_approval_request, the request read gate answering "waiting on me" with the approvals fold, and the end-to-end dogfood pin. Re-posted for contract review round 1 (6857191533, seat-relayed): adds packages/plugins/plugin-audit/src/activity-field-redaction.ts (GHSA-2985-5f64-8wgh), the suites whose premise 1B moved, and the statements it left false.
    Session: session_016WV4cwJx2CfBxrD3ojeDPr
    Account: os-elon-musk
    Branch: claude/project-thread-bb7kzf
    Worktree: objectstack-issue-32442
    Domain: domain:server
    Seat: domain:server#5
    File surface: packages/mcp/src/mcp-http-tools.ts (the read tools ask the bridge's aiReadExposed attestation; writes unchanged), packages/mcp/src/stdio-data-bridge.ts and packages/runtime/src/domains/mcp.ts (both bridges answer with isAiReadExposed), packages/plugins/plugin-approvals/src/sys-approval-request.object.ts (declares enable.aiReadable: true), packages/plugins/plugin-security/src/objects/default-permission-sets.ts (1B: member_default names sys_approval_request read-only), packages/plugins/plugin-approvals/src/request-read-gate.ts and packages/plugins/plugin-approvals/src/approval-service.ts (2B: a conjunctive pending_approvers $contains <own id> answered with the service's acting-address fold), packages/qa/dogfood/test/approval-read-over-mcp.dogfood.test.ts (end to end over MCP); packages/plugins/plugin-audit/src/activity-field-redaction.ts (GHSA-2985-5f64-8wgh); packages/plugins/plugin-approvals/src/approvals-plugin.ts (warning text), packages/runtime/src/action-execution.ts (prose); the suites 1B moved: packages/verify/src/action-door-api-forward-denied-subject.test.ts, packages/qa/dogfood/test/approval-request-schema-party-read.dogfood.test.ts; sys_approval_request declares enable.feeds: false; tests beside each; content/docs/ai/connect-mcp.mdx, content/docs/automation/approvals.mdx, packages/mcp/README.md; .changeset/**
    Container & model: M, mode:thread, model: default, effort: high (M, p1; the contract review goes to triage#1 per the maintainer's 05:39Z word, not fable)
    Clause-②: yes (widening)
    Metadata-protocol: none
    Responsibility: objectstack: the MCP record tools refuse every sys_ object, members hold no data-door read on approval requests, and pending_approvers contains {current_user_id} misses email and position slates, so an approver's agent cannot list what waits on them | read admission by enable.aiReadable, member baseline read with the participant gate deciding rows, the gate answering waiting-on-me with the approvals fold | every approver using an MCP agent, and the console's My Pending view
    Thread-read: 6856567007
    Premise: holds (step 0 on main: every MCP read of sys_approval_request refused; after admission alone, 403 for members and 0 rows for an email-addressed approver, report 6848413320)
    Serial constraints cleared: draft PR #33057 (os-tesla, #32936) edits other description text in mcp-http-tools.ts; main merged once (a596c03c). #32993 (submitter recall over MCP) is a sibling card. The 18.4.0 freeze has lifted
    Parent-claim: 6848104060

  23. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    Contributor

    domain:server#5 · os-elon-musk · session_016WV4cwJx2CfBxrD3ojeDPr

    Claim: card thread for the seat claim 6848104060 (the read half). The maintainer ruled 1B + 2B (6849636735), so this card now closes the read half in ONE PR, #33117, Fixes #32442: the MCP read admission, the member baseline read on sys_approval_request, the request read gate answering "waiting on me" with the approvals fold, and the end-to-end dogfood pin. Re-posted after contract review round 3 (PASS, 6862530251) for the final push: the plugin-audit surface now covers every file the GHSA-2985-5f64-8wgh hardening moves, and the request no longer declares enable.feeds: false (reverted in round 2).
    Session: session_016WV4cwJx2CfBxrD3ojeDPr
    Account: os-elon-musk
    Branch: claude/project-thread-bb7kzf
    Worktree: objectstack-issue-32442
    Domain: domain:server
    Seat: domain:server#5
    File surface: packages/mcp/src/mcp-http-tools.ts (the read tools ask the bridge's aiReadExposed attestation; writes unchanged), packages/mcp/src/stdio-data-bridge.ts and packages/runtime/src/domains/mcp.ts (both bridges answer with isAiReadExposed), packages/plugins/plugin-approvals/src/sys-approval-request.object.ts (declares enable.aiReadable: true), packages/plugins/plugin-security/src/objects/default-permission-sets.ts (1B: member_default names sys_approval_request read-only), packages/plugins/plugin-approvals/src/request-read-gate.ts and packages/plugins/plugin-approvals/src/approval-service.ts (2B: a conjunctive pending_approvers $contains <own id> answered with the service's acting-address fold), packages/qa/dogfood/test/approval-read-over-mcp.dogfood.test.ts (end to end over MCP); plugin-audit, GHSA-2985-5f64-8wgh: packages/plugins/plugin-audit/src/activity-field-redaction.ts, audit-log-field-redaction.ts, audit-writers.ts, parent-field-query-guard.ts, index.ts, and new side-copy-columns.ts and side-copy-migration.ts, with tests beside them; packages/plugins/plugin-approvals/src/approvals-plugin.ts (warning text), packages/runtime/src/action-execution.ts (prose); the suites 1B moved: packages/verify/src/action-door-api-forward-denied-subject.test.ts, packages/qa/dogfood/test/approval-request-schema-party-read.dogfood.test.ts; tests beside each; content/docs/ai/connect-mcp.mdx, content/docs/automation/approvals.mdx, packages/mcp/README.md; .changeset/**
    Container & model: M, mode:thread, model: default, effort: high (M, p1; the contract review goes to triage#1 per the maintainer's 05:39Z word, not fable)
    Clause-②: yes (widening)
    Metadata-protocol: none
    Responsibility: objectstack: the MCP record tools refuse every sys_ object, members hold no data-door read on approval requests, and pending_approvers contains {current_user_id} misses email and position slates, so an approver's agent cannot list what waits on them | read admission by enable.aiReadable, member baseline read with the participant gate deciding rows, the gate answering waiting-on-me with the approvals fold | every approver using an MCP agent, and the console's My Pending view
    Thread-read: 6857527959
    Premise: holds (step 0 on main: every MCP read of sys_approval_request refused; after admission alone, 403 for members and 0 rows for an email-addressed approver, report 6848413320)
    Serial constraints cleared: draft PR #33057 (os-tesla, #32936) edits other description text in mcp-http-tools.ts; main merged once more for this push (b6acbd9a9b). #32993 (submitter recall over MCP) is a sibling card. The 18.4.0 freeze has lifted
    Parent-claim: 6848104060

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:seatpm:blockingDerived cache from the Blocked-by reverse index: open card with open dependents — never hand-setpm:dispatchedDispatched to a dev agent by /pm-dispatchpriority: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