Repository navigation
Description
domain:devx#1 · os-warren · session_01JQwxbWBtYWqaV8NhvUrShB
Filed by the dispatching PM seat session for the W4-C3a runner session_01JQwxbZsvATYMNMArStUo2M, whose own write was refused by its session permissions (RUNNER rule 2: staged text, posted verbatim through the same door).
立卡门类别: ① 产品缺陷 — 具名落点 (apiForwardCredentialRefusal / API_FORWARD_DOOR_ACCEPTS in packages/runtime/src/action-execution.ts) + 复现
reach: 公开入口实测一次错误结果 — MCP /api/v1/mcp list_actions under an osk_ key lists revoke_api_key with no unavailable, and run_action then answers 403 (three fresh boots at 7fa9873)
业务理由: an operator who runs an agent headless on an API key is told the key-revoke action is runnable, is asked by the agent to confirm it (428), and then the action fails. That wastes a human confirmation and breaks the agent's flow at the moment someone is trying to revoke a key. Fixing the marker keeps the list/run promise #28178 introduced. Not doing it: every API-key agent keeps offering an action it can never complete.
动手的读者: the domain:server seat (runtime action execution, owner of the #28178 marker); triage first touch confirms the lane, then os-dev.
查重: revoke_api_key → 16 hits (0 open cards; 2 open are QA run records #27392 / #27438), list_actions unavailable → 13 hits (0 open cards; #28178 closed is the related fix), apiForwardCredentialRefusal → 0. No duplicate.
QA-source: #28942 · ai.mcp-run-action-exposure-gate · negative[1]
What happens. list_actions marks an api action unavailable when its forward target cannot accept the caller's credential (#28178; apiForwardCredentialRefusal in packages/runtime/src/action-execution.ts). The marker reads a per-door table (apiForwardTargetDoor / API_FORWARD_DOOR_ACCEPTS) that classes revoke_api_key's target, PATCH /api/v1/data/sys_api_key/{id}, as the data door, and the data door as accepting api_key. But the API-key management guard (packages/core/src/security/api-key-management-credential.ts) refuses any API-key-authenticated change to sys_api_key with 403 PERMISSION_DENIED.
So an MCP agent connected with an osk_ key is told revoke_api_key is runnable (no unavailable), confirms it with the human (428 first), and is then refused, with nothing changed. That is the list/run disagreement the checklist negative forbids. The ADR-0011 identity-actions addendum already says this target accepts a signed-in session, so the table is the outlier. The refusal itself holds; this is a listing-honesty defect, not an authorization hole.
Reproduction (fresh stock showcase boot at 7fa9873; reproduced on three fresh boots):
- Admin, signed in:
POST /api/v1/keys {"name":"r2-repro"}→ key K;POST /api/v1/keys {"name":"throwaway"}→ id T. - MCP Streamable HTTP on
/api/v1/mcpwithAuthorization: Bearer K(initialize, thennotifications/initialized). - tools/call
list_actions {}: the entryrevoke_api_key(objectNamesys_api_key, requiresConfirmation true) has nounavailablekey, while every identity-route action in the same list carriesunavailable: {code: UNAUTHENTICATED, reason: "…accepts a signed-in session only, and this request was authenticated with an API key…"}. - tools/call
run_action {"actionName":"revoke_api_key","objectName":"sys_api_key","recordId":T,"confirm":true}→ isError{"code":"PERMISSION_DENIED","status":403,"message":"PERMISSION_DENIED: Changing an API key requires a signed-in session. … a request authenticated with an API key cannot revoke, restore or change API keys."}. - Admin
GET /api/v1/data/sys_api_key: T readsrevoked: false.
- Expected: the listing marks
revoke_api_keyunavailablefor an API-key caller (with the guard's own sentence), or the run succeeds. - Actual: listed as runnable, then refused 403.
- Control: the same
run_actionover MCP with the admin's session bearer →ok: true, T readsrevoked: true.
Fix direction: teach the credential table that the sys_api_key write routes refuse api_key, or derive the marker from the same guard. Related: #28178 (closed).
Full evidence: QA run record #28942.
Generated by Claude Code
Activity
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: ai.mcp-run-action-exposure-gate
Triage: priority:p1 · domain:server · pm:queue · 2026-11-02T05:04Z
⛔ Not a claim.
apiForwardCredentialRefusal/API_FORWARD_DOOR_ACCEPTS(packages/runtime/src/action-execution.ts) class thesys_api_keywrite route as acceptingapi_key, solist_actionsoffersrevoke_api_keyto an API-key caller thatrun_actionthen refuses. The card states the refusal holds and this is not an authorization hole, so there is no SECURITY flag. It is graded p1 under 「发版本优先的都p1」 because negative[1] is the only FAIL in the 18.3.0 release-candidate run QA run · release-18.3.0-w4-c3a (44/44) · 7fa9873f · 2026-11-02 · 41 PASS / 2 PARTIAL / 1 FAIL / 0 BLOCKED / 0 NOT-RUN #28942, on a P1 item at the release head. The location and fix direction are named, so it goes topm:queueindomain:server.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC
Claim: PM loop round 1 · card thread · 2026-11-02T05:19Z
Session:
session_01R1q2xdtYgU6FKrZ7LgjPCC(the seat); the card thread issession_01FEkQ5oTRCUNQ4EA3YV84sZ(the #28412 / #28717 / #28771 / #28809 / #28858 / #28874 card thread, continuing; restarted frommainafter PR #28925)Account:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-jqgfyc(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#1Ruling-ref: triage#1 graded the card p1 (05:04Z, the only FAIL of the 18.3.0 RC run #28942) and ruled it a listing-honesty defect, not an authorization hole. Route (the dispatching seat's call): the
list_actionsmarker derives from the API-key management guard's own predicate, one source, with the guard's sentence. Dispatched in the seat-1 split of the server queue under 「队列进了很多任务,抓紧派发」 (Leehom, 2026-11-02T05:15Z) and 「定级你可以自己决定」 (Leehom, 2026-11-01T12:11Z).File surface:
apiForwardCredentialRefusal/apiForwardTargetDoor/API_FORWARD_DOOR_ACCEPTS(packages/runtime/src/action-execution.ts) and, if the predicate must be shared, an export besidepackages/core/src/security/api-key-management-credential.ts; their tests and changesets. ⛔ Nopackages/spec/**, no change to the guard's refusal, no new error code.Container & model:
S,mode:thread, model and effort as the thread was openedClause-②: no (expected: one entry of an existing answer gains its existing
unavailablemember for an API-key caller; the dev measures)Metadata-protocol: per AGENTS.md, measured by the dev (likely none)
Responsibility: the code is the action credential marker of
@objectstack/runtimeand the API-key management guard of@objectstack/core· the platform path is an API-key MCP caller offeredrevoke_api_keyas runnable, then refused 403 · reached by everylist_actionsunder anosk_keyThread-read: none
Premise: holds on
main7fa9873f(the card's three fresh boots)Serial constraints cleared: #28178 closed. No open server card edits
action-execution.ts's credential table; #28932 (seat 1, another thread) editsbuildApiActionRequestin the same file, so mergeorigin/mainonce if it lands first.Resume-from: none — fresh from
origin/mainState:
pm:queue→pm:dispatched, assigned toos-project-manager, recorded by this claim.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC
os-dev-report
{
"issue": 28943,
"status": "done",
"branch": "claude/project-thread-jqgfyc",
"pr": "#29036",
"session": "session_01FEkQ5oTRCUNQ4EA3YV84sZ",
"premise_still_valid": true,
"summary": "Measure on main f7c545a: the guard (core apiKeyManagementCredentialRefusal) refuses every non-read data-door operation on sys_api_key to an api_key admission (create, update PATCH/PUT, delete, bulk, import, clone, any later write verb), asked at refuseUnexposedDataAction, RestServer.enforceApiAccess and the stdio MCP write arms; the /api/v1/keys mint is a separate session-only refusal with its own sentence. Actions landing on one: sys_api_key revoke_api_key and restore_api_key (PATCH /api/v1/data/sys_api_key/{id}) and create_api_key (the /keys mint); none in examples or hotcrm. list_actions offered only revoke_api_key as runnable to an osk_ caller (the only ai.exposed one). Fix, one source: the guard predicate is already exported from @objectstack/core and already imported by runtime action-execution.ts, so no cycle and no new export. apiForwardCredentialRefusal, where the target door accepts the credential, now asks the guard about the data-door request the forward makes (module-private apiForwardDataDoorOperation reads object and operation from method and path; apiForwardDataDoorGuardRefusal returns the guard's own 403, PERMISSION_DENIED and sentence). list_actions marks revoke_api_key unavailable with that code and sentence; forwardApiAction (REST action door, MCP run_action) answers the same refusal before forwarding instead of relaying the data door's after. Guard unchanged, no new code, no spec, no new gate; sys_api_key reads, other objects, and the session caller's listing and run unchanged. Patch changeset on @objectstack/runtime.",
"tests": "New describe [#28943] in packages/runtime/src/domains/api-action-forward.test.ts (boot gains extraObjects: sys_api_key with revoke_api_key as declared, crm_case with the same PATCH shape). Red on base f7c545a, green on head: list_actions under a key marks revoke_api_key with the guard code and sentence; MCP run_action and REST action door under a key answer 403 PERMISSION_DENIED with the listed sentence and forward nothing; request-by-request reading (writes refused, reads and a neighbouring object not, session and no-credential callers never). Controls green on both by design: session listing unmarked and run_action forwards the PATCH with revoked true; crm_case write under a key unmarked and forwarded. Ablation via ablation-replace.mjs (marker read of the guard to return null): landed, 3 pins red, restore blob equals HEAD and git diff HEAD empty.",
"gates": "Head c3b472f after a whole-repo pnpm build under the lock exit 0: full @objectstack/runtime suite under the lock 8272 passed 19 skipped exit 0 (core not touched); pnpm typecheck in full for runtime exit 0 incl check:test-typecheck (ledger unchanged); eslint --no-inline-config src exit 0; dispatch-gates --commands derived 61 (incl check:route-ledger-census), each run with its exit captured before any pipe, all exit 0; --ran: 61 derived, 61 run, 0 NOT-MEASURED. origin/main moved 12 commits, none touching the three runtime files (#28932 not landed); git merge-tree clean. No live MCP probe. CI on PR #29036 not awaited.",
"dogfood_battery": "n/a, no ! in the PR title and no (narrowing) arm (Clause-②: no)",
"mcp_calls": "3 — mcp__github__issue_read x3 (get #28943, get_comments #28943 twice, the second to re-read the newest Claim before editing); no write tool",
"api_writes": "3 — plain git push of claude/project-thread-jqgfyc (new branch, c3b472f); relay fleet-write pr_create #29036 (read-back identical, assignees none, no labels named); this os-dev-report via post-stamped.mjs --comment=28943",
"open_questions": [],
"out_of_scope_findings": []
}
objectstack-fleet commentedon Nov 2, 2026
domain:server#1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC
Release: server seat 1 shift close · 2026-11-02T07:37Z
Session:
session_01R1q2xdtYgU6FKrZ7LgjPCC(the seat that claimed this card); card threadsession_01FEkQ5oTRCUNQ4EA3YV84sZ(paused)Who / what / where: Leehom (
os-project-manager) in the server-lane project chat — 「进展中的开发先提交代码然后暂停,先确保已有的pr都合并」 (2026-11-02T07:11Z), 「自动合并中的也交给下一任跟进」 (2026-11-02T07:23Z), 「我说的是pm-dispatch 中车道下班的流程,都要走完」 (2026-11-02T07:33Z). Shift close returns every claim to the queue (seat-post-protocol.md 交接收尾清单 step 2); no stay-behind.Resume-from:
claude/project-thread-jqgfyc@c3b472f49e897797ecaef905d7b107bec3e873c6— PR #29036, ACCEPTed by the seat on this head. Dequeued on a MERGE_CONFLICT with #29005; auto-merge is OFF. Next: merge origin/main onto the branch, push, re-run the seat check on the new head, re-arm.Successor: read the handoff 6571543819 on seat post #6367 first; land PR #29036 on green, write the landing record, close this card. ⛔ Do not re-dispatch the card from scratch.
State:
pm:dispatched→pm:queue, assigneeos-project-managercleared, recorded by this release.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC
Resume-from amendment · 2026-11-02T07:44Z: the card thread merged
origin/maininto the branch after the release was written (plain push, no rebase). Resume-from is nowclaude/project-thread-jqgfyc@98b8895f05, PR #29036. Conflict was the harness objectDef line inapi-action-forward.test.ts, both sides kept; the thread reports 78/78 green and runtimepnpm typecheckexit 0. The seat's ACCEPT was on the pre-merge head: the successor re-runs the seat check on98b8895f05, then arms auto-merge.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY
Claim: PM loop round 1 · takeover of a released card · 2026-11-02T08:15Z
Session:
session_01ADHkmQjPiXgNoSaEpqeGvYAccount:
os-steve(the card's assignee, set in the same round)Branch:
claude/project-thread-jqgfycWorktree: the predecessor card thread's (paused); no new dev is dispatched
Domain:
domain:serverSeat:
domain:server#1File surface: unchanged from the predecessor's claim 6569706666 (
apiForwardCredentialRefusaland its readers inpackages/runtime/src/action-execution.ts, the forward test); the open PR #29036 is the surface.Container & model:
S,mode:thread, landing only (no new build)Clause-②: no
Metadata-protocol: none
Responsibility: as in the predecessor's claim (unchanged)
Thread-read: 6571831222
Premise: holds (PR #29036 ACCEPTed by the predecessor seat)
Serial constraints cleared: none new; the seven PRs this seat inherited are its only in-flight surface.
Resume-from:
claude/project-thread-jqgfyc@98b8895f05510e7247801a5439fecd6f2950c9faTakeover record: the predecessor (
os-project-manager,session_01R1q2xdtYgU6FKrZ7LgjPCC) released this card in 6571709827 at its shift close (Leehom: 「自动合并中的也交给下一任跟进」). This seat takes it on the maintainer's summons in the Claude Code project "objectstack spec" (「server 1 号座位你也接手开席位」), seat 2 (#20966) having left it to seat 1. Last pushed head:98b8895f05(PR #29036). Dequeued on a MERGE_CONFLICT; the branch mergedorigin/mainat this head after the predecessor's ACCEPT. This seat re-runs the seat check on this head, then re-arms.objectstack-fleet commentedon Nov 2, 2026
domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY
Landed · 2026-11-02T10:40Z
af8448fe9e, through the merge queuecompletedbyFixes #28943pm:dispatchedis removed. Seat record: seat ACCEPT r2 6572410751 on head98b8895f, takeover claim 6572348542.