Skip to content

Commit 0822122

Browse files
test(dogfood): the approval act doors answer an unreadable request as a missing one at the action door, on a returned request and behind the organization wall (#29688)
<!-- claude-projects-attribution: {"github_login":"os-warren"} --> _Requested by **Warren** · [project thread](https://claude.ai/code/project/chan_01U5RdnAAmkSQmZbqh6FtqZ8?thread=cmsg_01U5RdnAAmkSQmZbqh6FtqZ8CfRsNViWjz94aTvKx1vMXi)_ Fixes #29670 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:只改一个测试文件。审批请求"动手入口看不到即按不存在回答"的 HTTP 钉子补上三种此前没有提交的情形:通用操作入口(`/actions/sys_approval_request/...` 转发)、已退回(returned)状态、隔离(walled)姿态下的跨组织调用。每种都配一个能看到请求的对照调用方。 - 为什么改:#28916 的修复在验收时由一次性探针覆盖过这三种情形,但探针没有提交,这三种情形没有常驻的钉子。 - 风险与回滚:不改任何运行时代码;dogfood 是私有包,不发布,`skip-changeset`。回滚即 revert。 - 先看:`packages/qa/dogfood/test/approval-act-doors-read-visibility.dogfood.test.ts` 的三个新用例(操作入口、退回状态、隔离姿态)和抽出的 `bootScene` 助手;原用例的断言未删减,只是改用该助手。 - 两分钟验证:`pnpm --filter @objectstack/dogfood exec vitest run test/approval-act-doors-read-visibility.dogfood.test.ts`,预期 4/4 通过。 - 需要你做的:无,席位落地。 ## What changes One test file, `packages/qa/dogfood/test/approval-act-doors-read-visibility.dogfood.test.ts`. No production code changes. The file pinned that a non-participant gets `404 REQUEST_NOT_FOUND` on the nine approvals act routes, with the same status and body as an id that names no request, for a pending request and a decided one. It now also pins three cases: 1. **The action door.** `POST /actions/sys_approval_request/:action` forwards the request page's `type: 'api'` approval actions (`approval_approve`, `approval_reject`, `approval_reassign`, `approval_send_back`, `approval_request_info`, `approval_remind`, `approval_recall`, `approval_resubmit`, `approval_comment`) to the routes with the caller's own credentials and relays the route's answer. For a non-participant, each action answers an existing request with the same status and body as an id that names none (`404 REQUEST_NOT_FOUND`), and nothing is written. The existing test in `packages/verify/src/action-door-api-forward-denied-subject.test.ts` pins the door against the direct route and pins the unknown id on its own; this pins the two ids against each other. Control: the submitter is relayed the route's `403 FORBIDDEN`, and the pending approver decides through the door (`200`). 2. **The returned state.** The fixture flow gains an ADR-0044 revise window (`approval_revise` node, `revise` edge, `resubmit` back-edge). The approver sends a request back (`200`, status `returned`), and then every act door answers the non-participant as for no request, and nothing is written. Control: the approver who sent it back still reads it and is told `409 INVALID_STATE` on `approve`, and the submitter resubmits (`200`, round 2 opens a fresh pending request). 3. **The walled posture across organizations.** A second boot uses `multiTenant: 'posture-only'` (the `isolated` posture, checked as `posture: 'isolated'`, `degraded: false`). Organization A has an owner and two plain members, the submitter and the approver. Organization B has an owner. The owner of B gets, on every act door, the same status and body for A's request as for an id that names none, and nothing is written. Control: the submitter (a member of A) is refused `403 FORBIDDEN` on `approve`, the approver decides (`200`), and A's own owner decides a second request through the override (`200`). So B's owner was refused as a stranger, not as an administrator. Structure: one `bootScene()` helper now holds the boot and the shared calls. The single-posture cases share one boot (`beforeAll`), and the walled case has its own boot. The original case keeps every assertion it had. The header's pointer to the service-level half named a file that does not exist (`act-doors-read-visibility.test.ts`); it now names `approval-service.test.ts`, where that block lives (#28916 contract review, should-fix 2). ## Tests <details><summary>The file, on head 7284ed8</summary> ``` pnpm exec vitest run --maxWorkers=1 test/approval-act-doors-read-visibility.dogfood.test.ts (under os-verify-lock) Test Files 1 passed (1) Tests 4 passed (4) os-verify-lock: VERDICT command-exit 0 pnpm --filter @objectstack/dogfood typecheck (NODE_OPTIONS=--max-old-space-size=6144) exit 0 ``` The dogfood `tsconfig.json` includes `test/**/*`, so the typecheck covers this file. </details> <details><summary>Ablation of refuseUnreadableRequest, once, restored</summary> Mutation through `scripts/ablation-replace.mjs` (wrap mode, which holds the restore trap), inside a script with its own `trap` restore on the absolute path, under `os-verify-lock`. In `refuseUnreadableRequest` (`packages/plugins/plugin-approvals/src/approval-service.ts`), the read-rule line `if (await this.requestRowForCaller(requestId, context, true)) return;` is replaced by `if ((context as any)?.ablation !== 'ABLATION-29670') return;`, an unconditional return that carries a marker. This is the same mutation #28916 used. ``` ablation-replace: ok mutation landed: anchor 1 -> 0, blob 6d3a542 -> e3ac266c0feb marker count during: 1 mutated build exit 0 ablation-dist-preflight: ✓ dist/: marker present in 2 built files (dist/index.js, dist/index.mjs) × answers a non-participant 404 REQUEST_NOT_FOUND on every act door, ... (the original case) × the action door answers a non-participant the same status and body for an existing request as for an id that names none, ... × a returned (sent back) request answers a non-participant as no request on every act door, ... × answers the other organization's owner 404 REQUEST_NOT_FOUND on every act door, ... AssertionError: pending · approve {} → {"code":"FORBIDDEN","error":"actor 'ID' is not a pending approver"}: expected 403 to be 404 AssertionError: approval_approve {} → {"success":false,"error":{"code":"FORBIDDEN","message":"actor 'ID' is not a pending approver","httpStatus":403}}: expected 403 to be 404 AssertionError: returned · approve {} → {"code":"INVALID_STATE","error":"request is returned"}: expected 409 to be 404 AssertionError: walled · pending · approve {} → {"code":"FORBIDDEN","error":"actor 'ID' is not a pending approver"}: expected 403 to be 404 Test Files 1 failed (1) Tests 4 failed (4) ablation-replace: ok restored: blob == HEAD (6d3a542) and `git diff HEAD` is empty blob after restore: 6d3a542 (= HEAD blob) git status --porcelain: empty restored build exit 0 ablation-dist-preflight --absent: ✓ marker absent from all 6 built files; ✓ tree clean against HEAD Test Files 1 passed (1) Tests 4 passed (4) ``` All three new cases go red under the mutation, and each fails on the case it pins: the action door relays the route's `403`, the returned request tells its state (`409`), and the wall alone does not hide the request from the act doors (`403`). The first attempt used `return void 'ABLATION-29670';`. The bundler drops that string, so `ablation-dist-preflight` found no marker in `dist/` and that reading does not count. The run above, with a marker that reaches `dist/`, is the one that counts. </details> ## Acceptance notes - The walled case adds a second boot to this file instead of a sibling file. The harness does not need a sibling: `multiTenant: 'posture-only'` is a boot option, and the file needs no environment toggle, since `isolated` is the default posture. - The walled control changed once while the pin was written. The first draft had the submitter create organization A. Then the submitter was A's owner, and the override let them approve (`200`, not the `403` the draft expected). That is the documented override, not a defect. The submitter and the approver are now plain members of A, and A's owner is the override control. - Under the ablation, the walled case shows that the organization wall alone does not hide another organization's request from the act doors. The doors load the row as the system, and the read rule (`refuseUnreadableRequest`) is what hides it. The pin now holds that. - The `Dogfood Regression Gate` runs on main's push and on schedule, not on pull requests, so the local runs above are this PR's only evidence for the file. ## Verification - `node scripts/pm/dispatch-gates.mjs --commands` on head 7284ed8 derived 51 families. Each was run with its exit code recorded before any pipe. `--ran` reports `51 derived, 50 run, 1 NOT-MEASURED, 0 UNRUN`. - 50 exited 0. `check-plugin-teardown-shape --self-test` exited 3 at first (its positive-control commit was missing from the shallow clone). It exited 0 after `git fetch --depth=1 origin 621a487`. - NOT MEASURED: `check:dual-build-cjs-loads`, reason: it needs every package built and exits 3 (PREREQUISITE NOT MET). CI runs it. - `pnpm lint` on head 7284ed8: exit 0. - No changeset: `@objectstack/dogfood` is `"private": true` and publishes nothing, so this PR carries `skip-changeset`. ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (not touched) - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` passes. - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. (publishes nothing: `skip-changeset`) - [x] 6. `packages/spec` touched: its generated artifacts are regenerated and committed. (not touched) - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [ ] 8. Title, body and comments are in English. (The 维护者速读 section is in Chinese, per the dispatch contract.) - [x] The *Before Submitting* list in CONTRIBUTING.md is done. Card thread session `session_01JQwxbYmDKH17yoN6vVuaTF`, dispatched by `domain:server#1 · os-warren · session_01JQwxbXsK9j25sd7eVwW96G`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01JQwxbYmDKH17yoN6vVuaTF --- _Generated by [Claude Code](https://claude.ai/code/session_01JQwxbYmDKH17yoN6vVuaTF)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 33ee9aa commit 0822122

1 file changed

Lines changed: 322 additions & 117 deletions

File tree

0 commit comments

Comments
 (0)