Repository navigation
Commit 0822122
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
0 commit comments