You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit b13a1ed
Browse filesBrowse the repository at this point in the historyBrowse files
fix(plugin-approvals): a field approver id or an out-of-office delegate that names no account adds no phantom slot (#27852)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} -->
_Requested by **Elon** · [project
thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DMNav4GoJtdKDeq5tRaBC9R)_Fixes#27752
## 维护者速读
-
改了什么:审批节点里,按记录字段取的审批人(`field`)如果对应的账号不存在,不再生成一个没人能处理的空槽位;外出委托指向一个已删除的账号时,审批退回给原审批人本人。
- 为什么改:#27749
已经把直接指定用户(`user`)的这种情况修好了,这两条兄弟路径还是老样子,审批单会卡在一个不存在的人身上,日志里也没有任何提示。
- 风险与回滚:只改 `plugin-approvals`
的审批人展开;目录读取失败时一律保留原槽位,不会因为一次读失败清空审批人。回滚即还原这一个提交。
- 先看:`approval-service.ts` 里 `resolveApproverSpec` 的 `field` 分支和
`applyOooDelegation` 末尾的退回逻辑。
- 两分钟验证:`pnpm --filter @objectstack/plugin-approvals exec vitest run
src/approval-service.test.ts -t 27752`,9 条用例全绿。
- 需要你做的:无。
## What changes
- **`type: 'field'`** (`resolveApproverSpec`): each id the record field
holds now takes #27749's `user` treatment. The id is a slot only when an
account carries it, asked through the same predicate
(`reassignTargetRefusal` with no organization). An id no account carries
logs the shared "expanded to nobody" warning (naming the field and the
id), adds no slot, and a slate it empties reaches the node's
`onEmptyApprovers`. A failed directory read keeps the slot.
- **Out-of-office delegation** (`applyOooDelegation`): a chain that ends
on a delegate no account carries routes the slot back to the original
approver, per the triage ruling (6510482313). It logs an "expanded to
nobody" warning naming delegator and delegate, and the chain's hops are
taken back out of the substitution collector, so no `ooo_substitute`
action or notification is written for a reroute that did not happen. A
failed read keeps the delegate. A removed delegator with a live delegate
still routes to the delegate (#24065). The fallback lives in
`applyOooDelegation`, so it covers every caller: `user`, `field`,
`manager` and `expression` with `resolveAs: 'user'`.
- A small private `namesNoAccount(value)` wraps the predicate plus its
fail-open catch, and the `user` branch now uses it too (same behaviour).
## Why
#27749 (#27397) closed the phantom-slot path for `user` approvers. The
`field` branch and the delegation step still handed an id no account
carries straight to the slate: the request opened on a slot nobody could
take, with no warning and no `onEmptyApprovers`.
## Verification
- Measured on `main` first: of the 9 new cases, 5 were red (the two
`field` cases and the three dead-delegate cases) and the 4 pins (live
ids quiet, failed-read keeps the slot for both paths, live delegate
routes with its substitution) were green.
- Ablation: disabling the `field` check reds the 2 `field` cases.
Disabling the delegate check reds the 3 delegate cases. Keeping the
check but dropping the collector rollback reds the 2 cases that assert
no substitution. Restored: all green.
- `plugin-approvals` full suite: 105 files, 1347 tests pass. Four
existing cases routed to ids their test double's directory did not carry
(`u4`, `u7`, `bob`). Their fixtures now staff those accounts, test-only,
as #27749 did for `user`.
- Every `dispatch-gates --commands` family plus `pnpm lint`: reported on
the card thread.
Clause-②: no
Metadata-protocol: none
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
- [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.
- [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/`.
- [x] 8. Title, body and comments are in English.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_016WV4cwhsfZfJRshUTzrYYT
Co-authored-by: Claude <noreply@anthropic.com>
fix(plugin-approvals): a `field` approver id, or an out-of-office delegate, that names no account no longer opens a slot no one can take
6
+
7
+
Clause-②: no
8
+
Metadata-protocol: none
9
+
10
+
#27397 made a `{ type: 'user' }` approver that names no account expand to nobody. Its two siblings now follow it:
11
+
12
+
-**`type: 'field'`.** Each id the record field holds is a slot only when an account carries it (the same predicate the reassign door asks, with no organization). An id no account carries logs the shared "expanded to nobody" warning, naming the field and the id, and adds no slot, so a slate it leaves empty reaches the node's `onEmptyApprovers`. A failed directory read keeps the slot.
13
+
-**Out-of-office delegation.** A delegation chain that ends on a delegate no account carries routes the slot back to the original approver, with an "expanded to nobody" warning naming both. It never empties the slate, and no `ooo_substitute` action or notification is recorded for it. A failed read keeps the delegate. A removed delegator whose delegate is live still routes to the delegate, as before.
0 commit comments