Skip to content

qa(checklist): name the persona and status of three approvals refusals a non-reader gets as 404 - #33327

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-v8xylo
Nov 14, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-v8xylo

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Leehom · project thread

domain:devx#4 · os-project-manager · session_01FEkQ5kHpQrXX5yTHRb5h4F

Fixes #33275

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:审批区测试清单里三条验收文字改了口径,只改 docs/qa/platform-checklist/areas/approvals.json:回复请求页的「组织成员发评论」、法定人数的「不在任何审批岗位上的人再批一次」、决策动作矩阵的「非提交人撤回」。三条现在都点名是哪类人,以及这类人拿到什么回答。
  • 为什么改:产品是对的。看不到这条审批请求的人去操作它,服务端答 404「请求不存在」,不暴露请求是否存在;能看到请求但没资格的人才答 403。清单还写着一律 403,QA 照跑会把正确的产品判成失败。
  • 风险与回滚:只改清单文字,不动产品代码;三个条目各升一版并加一条历史记录。回滚即还原本 PR。
  • 先看:approvals.json 里 approvals.decision-action-matrix 的 acceptance[6] verify(新增点名两类人,并排除管理员);其余两处是同一句口径。
  • 两分钟验证:pnpm check:platform-checklist,预期退出码 0。
  • 需要你做的:无,席位落地。

What changes

docs/qa/platform-checklist/areas/approvals.json, three items, text only:

Item Field Was Now Revision
approvals.request-page-reply-and-reader-visibility acceptance[1] verify + evidence the org member's comment POST → 403 FORBIDDEN → 404 REQUEST_NOT_FOUND, no row lands; 403 named as the answer to a caller who CAN read the request and is neither pending approver nor submitter (a past actor, or a record reader on an object that declares the flag); the service-layer pins it cites are named for what they hold 5 → 6
approvals.quorum-m-of-n steps[4] Ada's repeat approve → 403; "a user holding none of the routed positions is refused the same way" Ada's repeat approve → 403 FORBIDDEN (unchanged; her own decision keeps her a reader); the no-position user → 404 REQUEST_NOT_FOUND, tally unchanged either way 9 → 10
approvals.decision-action-matrix acceptance[6] verify + evidence "recall attempted by a non-submitter is FORBIDDEN" the denied leg names its persona: a reader who is not the submitter (a pending approver, e.g. the reassign target) → 403 FORBIDDEN; a member who cannot read it → 404 REQUEST_NOT_FOUND; ⛔ not the dev admin, whose override recalls a pending request by design (#3424) 10 → 11

Each item gets a history row citing this card. No clause sentence changed. No other item, no other file.

Why

Re-measured from source on origin/main 87330e8 before any edit (the card's readings came from PR #33273's Acceptance notes and were not driven live):

  • packages/plugins/plugin-approvals/src/approval-service.ts refuseUnreadableRequest (L10154): a caller who is not the override actor and for whom requestRowForCaller(…, enforceVisibility = true) finds nothing is thrown REQUEST_NOT_FOUND: ID, byte for byte what a missing id gets. packages/rest/src/approvals-routes.ts handleApprovalError (L199–L200) maps FORBIDDEN → 403 and REQUEST_NOT_FOUND → 404.
  • Who reads a request: visibleRequestIds (L9706): the submitter, a current approver (any acting address), anyone whose actor_id or acted_as is on a sys_approval_action row of it, the override actor (sees everything), plus the record-reader tier only where the object declares enable.approvalsVisibleToReaders. git grep approvalsVisibleToReaders -- examples/ → 0 hits (exit 1; control: the same grep in packages/plugins/plugin-approvals/src → hits in approval-service.ts and approvals-plugin.ts).
  • The three doors ask it before their own check:
    • comment (L7428) → loadPendingRow (L2341) → refuseUnreadableRequest, then the slot check FORBIDDEN: actor '…' is not on this request (L7442). Clause 1 stale: the org member is no participant, so 404.
    • decideNode (L5270) asks it, then FORBIDDEN: actor '…' is not a pending approver (L5281). Clause 2 half stale: Ada acted, so she reads the request and gets the 403; a user on no position who never acted gets 404.
    • recall (L6120) asks it, then the submitter / override check, FORBIDDEN with the approval_recall_not_submitter copy (L6164). Clause 3 under-specified: 403 for a reader, 404 for a non-reader, and the dev admin's override admits a pending recall, so the admin is no persona for the denied leg.
  • Service-layer pins that already hold each answer (none is in an item's automated.ref, so per RUNNER rule 6 they are evidence of that layer only): approval-service.test.ts L2104 "comment: submitter and approver may reply; outsiders may not" (outsider → REQUEST_NOT_FOUND), L2925 "every caller shape…" (stranger → REQUEST_NOT_FOUND, override admin on no slot → FORBIDDEN); recall-refusal-user-copy.test.ts L316 "a plain non-submitter is still refused, and the request is untouched" (both legs: the pending approver u2, a reader who did not submit → FORBIDDEN; stranger u7 → REQUEST_NOT_FOUND; the request stays pending).
  • approvals.decision-action-matrix negative[2] is not stale and is unchanged: it fails any accepted POST from an actor the service should deny, whatever status the refusal carries.

Tests

No test added or changed: the diff is checklist text, and the product behaviour it now describes is already pinned at the service layer (above). Nothing to ablate (os-dev.md: docs are not ablated).

Acceptance notes

Verification

On head fd55e259 (branch claude/project-thread-v8xylo, merge base origin/main 87330e8), the list from node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (16 commands), each exit code captured before any pipe:

16 derived, 16 run, all exit 0; --ran reconciliation exit 0
node scripts/check-adr-links.mjs :: exit 0
node scripts/check-adr-links.mjs --self-test :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:platform-checklist :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:watch-hint-literal :: exit 0

check:doc-formula-expressions first answered exit 3 (PREREQUISITE NOT MET, nothing measured: @objectstack/formula and @objectstack/lint unbuilt in a fresh worktree). Built them under the verify lock (turbo run build --filter=@objectstack/formula --filter=@objectstack/lint, VERDICT command-exit 0), reran: exit 0.

dispatch-gates --ran: "16 derived famil(ies) accounted for — 16 run, 0 NOT-MEASURED", exit 0.

No package is touched, so there is no package build, test, typecheck or scoped pnpm lint owed; every other family is CI's and is read from this PR's check-runs.


Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01FEkQ5kHpQrXX5yTHRb5h4F

…s a non-reader gets as 404

approvals.request-page-reply-and-reader-visibility acceptance[1], approvals.quorum-m-of-n
steps[4] and approvals.decision-action-matrix acceptance[6] expected 403 FORBIDDEN from a
caller who cannot read the request. ApprovalService.refuseUnreadableRequest answers that
caller REQUEST_NOT_FOUND (404) before any slate or submitter check, so the request's
existence never leaks; 403 stays the answer to a reader who holds no slot. Each item gets
a revision bump and a history row.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEkQ5kHpQrXX5yTHRb5h4F
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:devx#4 · os-project-manager · session_01FEkQ5ooRyyS95kdyC2dPEX

席位核对 · ACCEPT · head fd55e259 · 2026-11-14T17:09Z

  • 形态: draft → main, attribution first, Fixes #33275 (all three clauses delivered). One path from the PR's file list: docs/qa/platform-checklist/areas/approvals.json (+26/-8). No governed path, no packages/spec/src/**, Clause-②: no ⇒ no contract-review face; lane seat lands.
  • 前提实测: on origin/main packages/plugins/plugin-approvals/src/approval-service.ts throws REQUEST_NOT_FOUND from refuseUnreadableRequest (about L10154) and recall asks it right after the row read (about L6119–6120), before its submitter check; comment refuses a reader off the request with FORBIDDEN … is not on this request (about L7442). The three re-pointed clauses match: non-reader → 404 REQUEST_NOT_FOUND, reader off the slate → 403 FORBIDDEN.
  • Diff read: quorum-m-of-n steps[4] (rev 9→10), decision-action-matrix acceptance[6] verify/evidence (rev 10→11, names both personas and excludes the dev admin's override), request-page-reply-and-reader-visibility acceptance[1] verify/evidence (rev 5→6); one history row each citing checklist: re-point three approvals clauses that still expect 403 where a non-reader gets 404 REQUEST_NOT_FOUND (sibling half of #33060) #33275, clause sentences unchanged, negative[2] confirmed and untouched.
  • 门禁 check-runs (head fd55e259): 51 runs · 28 success · 23 skipped · 0 failed; Lint & Repo Gates and TypeScript Type Check success. K = 10 merges behind main (< 15). Dev's local gates 16/16 exit 0.
  • Changeset: skip-changeset holds: docs/qa/** ships in no package (checklist data, read by the QA runner from the tree).
  • 接受的偏差: none. 范围外去向: none. Skills-docs: not owed (Metadata-protocol: none).
  • 落地条款: non-governed, all checks green ⇒ the seat readies and arms auto-merge. Ruling-ref: the maintainer's standing authorisation 「按照 pm-dispatch 规则复核过的都授权」 (2026-10-25T11:35Z) and 「你应该使用 pm-dispatch 技能审核和落地pr」 (this account's project). Batch C (checklist: re-point 36 stale items in access-security, api-backend, approvals (v18.4.0 sweep close-out, batch C of #32908) #33332) unblocks when this merges.

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

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

checklist: re-point three approvals clauses that still expect 403 where a non-reader gets 404 REQUEST_NOT_FOUND (sibling half of #33060)

1 participant