Repository navigation
docs(qa): re-point identity-auth.api-key-ui-lifecycle's revoke oracle to 403 API_KEY_REVOKED - #24012
Merged
objectstack-fleet[bot] merged 1 commit intoOct 16, 2026
Conversation
… to 403 API_KEY_REVOKED Step 4 and acceptance[2] expected a revoked key's next x-api-key call to answer 401. Since the API-key lifecycle, a recognised revoked key is refused 403 with error.code API_KEY_REVOKED, and only an unknown key gets the generic 401, so the item scored a correct runtime red. The step and the clause now capture the status, the code and the absence of rows; the refusal's wording is not pinned. A 200, a generic 401 or a 403 with any other code still fails. Revision 1 -> 2 with a history row. Claude-Session: https://claude.ai/code/session_016PVjjuVPte5XEJvaQx1E4t Co-authored-by: Claude <noreply@anthropic.com>
Contributor
Author
席位核对 · ACCEPT · PR #24012 (draft, head
|
objectstack-fleet
Bot
deleted the
claude/issue-23992-api-key-ui-revoke-oracle
branch
October 16, 2026 18:57
This was referenced Oct 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #23992
Clause-②: no
The platform-checklist item
identity-auth.api-key-ui-lifecycleexpected a revoked API key's nextx-api-keycall to answer401. Since the API-key lifecycle (#23367 ruling A, executed by #23541), a recognised but revoked key is refused403witherror.codeAPI_KEY_REVOKED. Only an unknown key still gets the generic401. So the item scored a correct runtime red. This PR re-points step 4 and acceptance clause 2 to the shape that shipped, bumpsrevision1 → 2, and appends ahistoryrow naming #23992, #23367 and #23541. The diff isdocs/qa/platform-checklist/areas/identity-auth.jsononly (+10 / −8, one entry). There is no product code change.The clause is not loosened. A revoked key that answers
200or serves any row still fails it. So does a post-revoke refusal that is the generic401, or a403carrying any other code. Step 5 (restore →200) is unchanged. The oracle scores the status, the code and the absence of rows. It pins none of the refusal message's wording, because the lifecycle owner rewords that text (#23944, PR #23991). This matches what PR #24000 did for the MCP revocation clauses.Old and new text
Step 4 (
steps[4])revoke the key via the row action revoke_api_key (PATCH /api/v1/data/sys_api_key/{id} bodyExtra {revoked:true}); immediately re-call the data route with x-api-key and capture the 401revoke the key via the row action revoke_api_key (PATCH /api/v1/data/sys_api_key/{id} bodyExtra {revoked:true}); immediately re-call the data route with x-api-key and capture the 403 whose body's error.code is API_KEY_REVOKED and which carries no rows (a recognised key its own lifecycle refuses — only an UNKNOWN key gets the generic 401)Acceptance clause 2 (
acceptance[2])revoke is immediate and enforced server-side: after revoke_api_key the same x-api-key call 401s on the very next requestthe post-revoke x-api-key request returns 401the revoke PATCH response + the 401revoke is immediate and enforced server-side: after revoke_api_key the same x-api-key call is refused on the very next request with the key's own lifecycle refusal — 403, error.code API_KEY_REVOKED, and no rows. Since #23367 (executed by #23541) a recognised but revoked key is refused 403 API_KEY_REVOKED; the generic 401 answers only a key the server does not recognise at all. The refusal's message is the key's own lifecycle message and its wording is NOT pinned here: the code is what a client branches on, and the lifecycle owner rewords that text (#23944)the post-revoke x-api-key request returns HTTP 403 and its body's error.code is API_KEY_REVOKED, with no record rows in the body (packages/core/src/security/api-key.ts#lifecycleRefusalOf decides it; the data routes answer it through packages/rest/src/rest-server.ts#enforceAuth). Score the status, the code and the absence of rows, never the message's wording. A 200 or any served row, a generic 401, or a 403 carrying any other code is a FAIL — the last means the key was accepted and something later refusedthe revoke PATCH response + the 403 API_KEY_REVOKED refusal bodyAlso in the same entry
negative[1]keeps its text and adds the other failure direction: a post-revoke refusal that is the generic401, or carries any code other thanAPI_KEY_REVOKED, is a FAIL.sourcegains one row:packages/core/src/security/api-key.ts#lifecycleRefusalOf+packages/rest/src/rest-server.ts#enforceAuth. Both anchors resolve undercheck-platform-checklist.historygains therevision: 2row (ref: #23992).The source lines (at
origin/maine18b23fac)packages/core/src/security/api-key.ts:217:const API_KEY_REVOKED_REFUSAL = { code: 'API_KEY_REVOKED', status: 403 } as const;packages/core/src/security/api-key.ts:299startslifecycleRefusalOf. Its first arm (lines 306-321),if (row.revoked === true || row.revoked === 1), returnscode: API_KEY_REVOKED_REFUSAL.code(line 308) with the located message.packages/core/src/security/resolve-authz-context.ts:501raises it as the brandedApiKeyLifecycleRefusalError, whose status isAPI_KEY_LIFECYCLE_REFUSAL_STATUS= 403 (api-key.ts:228).packages/rest/src/rest-server.ts:3493-3495: the context resolver remembers the branded refusal beside the request. Lines 2478-2481:enforceAuthanswersres.status(lifecycle.status).json({ error: { code: lifecycle.code, message: lifecycle.message } }). A request with no remembered refusal falls through to the generic anonymous401.packages/rest/src/rest-api-key-lifecycle.pin.test.ts, "revoked, expired and inactive answer 403 with three distinct codes".Re-run record: #24011
QA run · identity-auth.api-key-ui-lifecycle (1/1) · e1e711ae · 2026-10-16 · 1 PASS / 0 PARTIAL / 0 FAIL / 0 BLOCKED / 0 NOT-RUN.node scripts/qa/qa-rollup.mjsparses the title (row#24011,1/1, shae1e711ae).The run used a real showcase boot built from this branch's tree (
e1e711ae2; product treeorigin/maine18b23fac).pnpm buildran 75/75 tasks. The console was built at the.objectui-shapinf8267df93abawithpnpm objectui:build, andcheck:console-shawas green. The server ran on its own port (41873) and its own file DB, and was stopped by PID afterwards. Personas were the seeded admin and members minted by invite + sign-up. The browser legs were driven through Playwright (Chromium 141).key; get-by-id (owner and admin) and the mine list carry nokeyfield, onlyprefix; the dialog showed the raw value, and after Done it is gone from the pageshowcase_taskviax-api-key200, 10 rows, the same id set as the owner's session. Onshowcase_inquirythe admin sees 3 rows, the owner's session 0 and the owner's key 0API_KEY_REVOKED{"revoked": true}200. The very next read answered 403 with body{"error": {"code": "API_KEY_REVOKED", "message": …}}and no rows, and a repeat answered the same. Through the console's Revoke API Key row action, the request body was{"revoked":true}and the next two reads answered 403API_KEY_REVOKED. Control: an unknown key answers 401UNAUTHENTICATED{"revoked": false}200, then a read answered 200 with 10 rows. The console's Restore API Key row action gave the same resultGates (at
e1e711ae2, the final commit)node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderives 13 commands. Each was run with its exit code captured before any pipe, and all 13 exited 0:check-ci-filter-parity,check-closing-keyword-parity(+--self-test),check-comment-mask-corpus,@objectstack/lint check:doc-formula-expressions,check:cross-package-test-inputs,check:doc-authoring,check:driver-memory-census,check:gitlink-declared,check:nul-bytes,check:platform-checklist("OK — 16 areas, 327 items …; symbol anchors: 834/844 resolved …"),check:refd-timer-probe,check:watch-hint-literal.--ranreconciliation: "13 derived famil(ies) accounted for — 13 run, 0 NOT-MEASURED (a DERIVED zero — all 13 recorded an exit code and none of them is 3)".node scripts/pm/check-governed-merges.mjs --branch claude/issue-23992-api-key-ui-revoke-oracle: "0 of 1 path(s) hit the register … NOT governed"; 18 changed lines.Acceptance notes
api-key-revoke-lifecycleandapi-key-owner-revoke, accept a generic401as well as403 API_KEY_REVOKEDfor a revoked key. The strict403+ code relay is pinned only at unit level (packages/rest/src/rest-api-key-lifecycle.pin.test.ts), and this item has noautomated.ref. Carrier: none.sourcerow forsys-api-key.object.tsstill saysapiMethods ['get','list'], but the object declares['get', 'list', 'update']. This is a stale annotation that scores nothing, so it is outside this card's class and was left untouched. Carrier: none.negative[1]andsource[]are inside the claimed entry but outside the claim's parenthetical field list. Both carry the ruling's "do not loosen" rule and the citation it asks for.Generated by Claude Code