@objectstack/plugin-approvals 17.0.0-rc.2 → 17.0.0-rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,287 @@
1
1
  # @objectstack/plugin-approvals
2
2
 
3
+ ## 17.0.0-rc.4
4
+
5
+ ### Minor Changes
6
+
7
+ - ddc2527: fix(approvals): the ADR-0044 revise window is a service-owned node type, not a bare `wait` (#3823)
8
+
9
+ #3801 gated `POST /api/v1/automation/:name/runs/:runId/resume` on the **node type**
10
+ that produced the suspension: an `approval` pause declares
11
+ `resumeAuthority: 'service'`, so it continues only through `ApprovalService`.
12
+ ADR-0044's **revise window** was the same trust boundary in a shape that key
13
+ could not see. Send-back parked the run on an ordinary `wait` node the flow
14
+ author placed — correctly `resumeAuthority: 'any'`, because a signal wait is
15
+ _meant_ to be resumable by an external producer — and `ApprovalService.resubmit`
16
+ was the only thing that checked anything about continuing it.
17
+
18
+ Demonstrated (not reasoned) against the real engine: a raw `resume(runId)` with
19
+ an **empty body**, from any caller, walked the `resubmit` back-edge into the
20
+ approval node and opened round N+1 with **no submitter check and no `resubmit`
21
+ audit row** (`['submit','revise']` — no third row, ever). Worse, when another
22
+ request was already pending on the record — the exact case `resubmit` refuses
23
+ with `DUPLICATE_REQUEST` _specifically to keep the run alive_ — the raw resume
24
+ went around that guard: the approval node's re-entry failed **after** the engine
25
+ consumed the suspension, and the run was **permanently destroyed** with its
26
+ round-N request stuck `returned` and no resubmit able to reach it.
27
+
28
+ The revise pause is therefore its own node type:
29
+
30
+ - **`approval_revise`** (`APPROVAL_REVISE_NODE_TYPE`), registered by
31
+ `@objectstack/plugin-approvals` alongside the `approval` node, declaring
32
+ `resumeAuthority: 'service'`. It stays a first-class box on the canvas, in the
33
+ run log and in the suspended-run store — only the _reuse_ of `wait` was wrong.
34
+ It takes **no config**: the window ends on the submitter's explicit resubmit,
35
+ never on a signal or timer. The `resumeAuthority` gate itself is unchanged.
36
+ - `sendBack` refuses a `revise` edge whose target is not an `approval_revise`
37
+ node, **before any mutation** (like the existing missing-`revise`-edge check),
38
+ so no run can be parked in a window something else can advance.
39
+ - New gating lint `flow-approval-revise-target-not-service-owned`
40
+ (severity `error`, on `os build` / `os validate` / `os lint` and the runtime
41
+ metadata publish gate) rejects the old shape at authoring time.
42
+
43
+ **Upgrading a flow authored against the original ADR-0044 D3.** One token:
44
+
45
+ - **FROM:** `{ id: 'wait_revision', type: 'wait', waitEventConfig: { eventType: 'signal', … } }`
46
+ - **TO:** `{ id: 'wait_revision', type: 'approval_revise' }` — drop
47
+ `waitEventConfig` / any `config`; the window has no event to wait on.
48
+
49
+ Until you do, such a flow keeps registering and running and its approvals stay
50
+ decidable (`approve` / `reject` / `recall` / `reassign` are untouched), but
51
+ **send-back is refused** with a message naming the node and this fix, and
52
+ re-publishing it reports the lint error. A run _already parked_ in a legacy
53
+ revise window keeps its recorded node type (a republish never re-types a live
54
+ pause) and is drained by `resubmit` or `recall` as usual.
55
+
56
+ ADR-0044's 2026-07-28 amendment records the reversal of its D3 and of its
57
+ `Alternatives` rejection of a service-owned revise pause, with the evidence
58
+ above; the implementation section there records what shipped, why the approval
59
+ node does not re-suspend itself instead, and why no ADR-0087 conversion was
60
+ added for the old shape.
61
+
62
+ ### Patch Changes
63
+
64
+ - 7e5ac28: fix(approvals): 删除两处读 `session.roles` 的 admin 豁免 —— 记录锁与委托守卫回到单一权限词汇 (#4839)
65
+
66
+ `plugin-approvals` 的 `lifecycle-hooks.ts` 里有两处 admin 豁免,都读
67
+ `ctx.session.roles`:审批**记录锁**的 `bindApprovalLockHook`,以及
68
+ `sys_approval_delegation` 的 `bindDelegationWriteGuard`。两处都已删除。
69
+
70
+ **这不是行为变更。** `session.roles` 在整个平台没有生产者 —— ObjectQL 的
71
+ `buildSession()` 逐字段构造 session,从不写 `roles` —— 所以两个分支在任何真实引擎
72
+ 路径上都是死代码,记录锁一直就对 admin 生效,委托一直就只能本人管理。删除让代码
73
+ 说出运行时本来就在做的事(spec 的 `HookContext` 声明了 `roles`,消费方在读,生产方
74
+ 从不写:典型的 declared ≠ enforced)。
75
+
76
+ **为什么不是「改用正确判据」而是删除。** `roles.includes('admin')` 还是第二套权限
77
+ 方言:本仓库的权限一律由 ADR-0095 词汇裁决(能力授予 `permissions`、任职
78
+ `positions`、由其派生的 posture),ADR-0090 D3 更是直接禁掉 `role` 这个拼法。同包的
79
+ `ApprovalService.isOverrideActor` 已经这么做了。维护者裁定两处都取「删除」而非改判据:
80
+
81
+ - **记录锁**:admin 释放锁定记录的正规路径已经存在(#3424 —— `recall` /
82
+ `decideNode` 驳回 / `reassign`,全部由 `isOverrideActor` 把关并留痕
83
+ `via_override`)。让审批终结来释放锁,记录就永远不会在审批在途时被改写 —— 这正是
84
+ 合规场景购买记录锁所要的保证。
85
+ - **委托**:最终语义确定为**仅本人管理**(`delegator_id` 必须等于写入者;只有 system
86
+ 上下文旁路)。审批人临时不可用时,替他处置**在途**审批用的是
87
+ `reassign`(把该审批人的名额交给替代人,连 per_group 分组归属一起带过去)/
88
+ `recall` / 驳回。反过来,「替别人建一条委托」本来也做不到这件事:委托只在请求
89
+ **开启**时(`resolveApproverSpec` 内的 `applyOooDelegation`)被查询,对已经挂在该
90
+ 审批人名下的在途审批毫无作用。
91
+
92
+ 新增 `admin-exemption-retired.test.ts`,把上述证据变成可执行断言,并加了一道源码级
93
+ pin:本包非测试源码中不得再出现 `roles` 标识符或与字符串 `'admin'` 的比较。
94
+
95
+ spec 侧 `session.roles` 的退役(至此零消费方)按 ADR-0049 enforce-or-remove 另立协议
96
+ 单处理,不在本次改动内。
97
+
98
+ - 19e1a8f: fix(approvals): an approval decision can no longer strand a flow run silently when no automation engine is attached (#4420)
99
+
100
+ #4420's fix closed every path by which a decision could be recorded while its
101
+ flow stayed parked — except one, and it is the one where none of the new guards
102
+ could run. Every guard it added (`assertRunResumable`'s pre-flight, the
103
+ `RESUME_TARGET_LOST` refusal, the `RESUME_FAILED` throw) hangs off the
104
+ automation engine. In a process where **no engine is attached**, all of them
105
+ were skipped by the same `typeof this.automation?.resume === 'function'`
106
+ condition that wrapped the resume itself — so the decision was written, the
107
+ mirrored status field advanced, and the call answered HTTP 200 with
108
+ `resumed: false` and **nothing logged at all**. That is #4420's reported
109
+ symptom exactly, reproduced in the one composition its fix could not see.
110
+
111
+ The composition is reachable the same way the original bug was: a flow parks at
112
+ an `approval` node in a process that has the automation service, and the
113
+ decision arrives in one that does not (the plugin failed to init, or the host
114
+ was recomposed between releases). The request row still carries a
115
+ `flow_run_id` — which is the row's own declaration that a run is parked on this
116
+ decision.
117
+
118
+ **What changes.** The decision still stands. Rolling it back is not on the
119
+ table (a human really decided, and the row is durable by then), and refusing
120
+ every such call would break the standalone approvals compositions the
121
+ pre-flight deliberately protects — so `finalized` and `resumed` are unchanged
122
+ for every existing caller. What changes is that the gap is no longer silent:
123
+
124
+ - it is logged at **`error`**, per the durability rule in `AGENTS.md` —
125
+ persisted state and runtime state disagree while nothing looks broken from
126
+ the outside, which is the class that rule exists for;
127
+ - the response carries **`resumeError`**, so `resumed: false` arrives with its
128
+ reason and the stranded run's id instead of leaving the caller to guess
129
+ whether a resume was even attempted.
130
+
131
+ It reuses the already-registered `RESUME_FAILED` code and the existing resume
132
+ message shape rather than introducing a new vocabulary — the fact being
133
+ reported (an outcome recorded whose run did not advance) is the same one.
134
+
135
+ Applied at all five sites that resume a recorded outcome: `decide`, the
136
+ revision-limit auto-rejection, `sendBack`, `resubmit`, and both branches of
137
+ `recall` (whose revise-window path needs `cancelRun` rather than `resume`).
138
+
139
+ A request that names **no** run is unaffected and stays quiet — there is
140
+ nothing parked on it, and reporting one there would be the mirror-image
141
+ failure that trains operators to skim `error`.
142
+
143
+ - Updated dependencies [9fe9c1d]
144
+ - Updated dependencies [d4e0809]
145
+ - Updated dependencies [f724f69]
146
+ - Updated dependencies [28ad90e]
147
+ - Updated dependencies [f8644c7]
148
+ - Updated dependencies [306ca50]
149
+ - Updated dependencies [978fed2]
150
+ - Updated dependencies [cfc293f]
151
+ - Updated dependencies [de70b42]
152
+ - Updated dependencies [64cd010]
153
+ - Updated dependencies [fb3d99b]
154
+ - Updated dependencies [cdfbee2]
155
+ - Updated dependencies [29c6c9d]
156
+ - Updated dependencies [d21c001]
157
+ - Updated dependencies [f1cc3a3]
158
+ - Updated dependencies [ddc2527]
159
+ - Updated dependencies [553a47f]
160
+ - Updated dependencies [a3a884d]
161
+ - Updated dependencies [cfed092]
162
+ - Updated dependencies [2e284b2]
163
+ - Updated dependencies [1b49eaf]
164
+ - Updated dependencies [0161c7f]
165
+ - Updated dependencies [e900015]
166
+ - Updated dependencies [b5bdf48]
167
+ - Updated dependencies [a019e52]
168
+ - Updated dependencies [64fc6d5]
169
+ - Updated dependencies [b746aa0]
170
+ - Updated dependencies [947d4f9]
171
+ - Updated dependencies [eaaf03c]
172
+ - Updated dependencies [d17df80]
173
+ - Updated dependencies [7d0e7b5]
174
+ - Updated dependencies [6513c17]
175
+ - Updated dependencies [c142ced]
176
+ - Updated dependencies [eda599e]
177
+ - Updated dependencies [c001422]
178
+ - Updated dependencies [77022a9]
179
+ - Updated dependencies [52760bf]
180
+ - Updated dependencies [5543020]
181
+ - Updated dependencies [880d343]
182
+ - Updated dependencies [6e82972]
183
+ - Updated dependencies [4615a18]
184
+ - Updated dependencies [7f62706]
185
+ - Updated dependencies [667fa44]
186
+ - Updated dependencies [37e38d1]
187
+ - Updated dependencies [0f17114]
188
+ - Updated dependencies [1eb13a0]
189
+ - Updated dependencies [c52e608]
190
+ - Updated dependencies [db0d53c]
191
+ - Updated dependencies [4dfd002]
192
+ - Updated dependencies [77be690]
193
+ - Updated dependencies [811c30c]
194
+ - Updated dependencies [b49ccfd]
195
+ - Updated dependencies [85d95e7]
196
+ - Updated dependencies [168f60f]
197
+ - Updated dependencies [244ca86]
198
+ - Updated dependencies [546ab3c]
199
+ - Updated dependencies [58f3220]
200
+ - Updated dependencies [07f1822]
201
+ - Updated dependencies [0b51bb6]
202
+ - Updated dependencies [08f93bc]
203
+ - Updated dependencies [d9971d3]
204
+ - Updated dependencies [eb3e650]
205
+ - Updated dependencies [abeb375]
206
+ - Updated dependencies [ef4efa8]
207
+ - Updated dependencies [cbb6a5c]
208
+ - Updated dependencies [02dc076]
209
+ - Updated dependencies [795b6e1]
210
+ - Updated dependencies [175d789]
211
+ - Updated dependencies [55dbbba]
212
+ - Updated dependencies [72c3c86]
213
+ - Updated dependencies [7f1a635]
214
+ - Updated dependencies [e98fb14]
215
+ - Updated dependencies [0f2fdcd]
216
+ - Updated dependencies [8ffa8b9]
217
+ - Updated dependencies [674ac99]
218
+ - Updated dependencies [1b9a53b]
219
+ - Updated dependencies [502564d]
220
+ - Updated dependencies [471839d]
221
+ - Updated dependencies [46365ab]
222
+ - Updated dependencies [b508244]
223
+ - Updated dependencies [594508e]
224
+ - Updated dependencies [1c625ca]
225
+ - Updated dependencies [71f205d]
226
+ - Updated dependencies [414395b]
227
+ - Updated dependencies [c5adfe1]
228
+ - Updated dependencies [26e1029]
229
+ - Updated dependencies [108ba8d]
230
+ - Updated dependencies [b4ad984]
231
+ - Updated dependencies [a9f32df]
232
+ - Updated dependencies [aeb9b27]
233
+ - Updated dependencies [7d27da0]
234
+ - Updated dependencies [089767f]
235
+ - Updated dependencies [e4c8b6c]
236
+ - Updated dependencies [acb10f6]
237
+ - Updated dependencies [1c3da1f]
238
+ - Updated dependencies [a34fd2e]
239
+ - Updated dependencies [889ae47]
240
+ - Updated dependencies [4f4c3fb]
241
+ - Updated dependencies [7adc841]
242
+ - Updated dependencies [4845f85]
243
+ - Updated dependencies [bf1edef]
244
+ - Updated dependencies [7b005b4]
245
+ - Updated dependencies [94f7b6a]
246
+ - Updated dependencies [5c94f83]
247
+ - Updated dependencies [73e576f]
248
+ - Updated dependencies [c5a5996]
249
+ - Updated dependencies [51a587d]
250
+ - Updated dependencies [ae490ef]
251
+ - Updated dependencies [f61c8cf]
252
+ - Updated dependencies [e3ef52b]
253
+ - Updated dependencies [07f1822]
254
+ - Updated dependencies [04fab5e]
255
+ - Updated dependencies [efedd28]
256
+ - Updated dependencies [5278e11]
257
+ - Updated dependencies [23dba62]
258
+ - Updated dependencies [ba98e26]
259
+ - Updated dependencies [f104bab]
260
+ - Updated dependencies [fc5f536]
261
+ - Updated dependencies [f8cfbb4]
262
+ - Updated dependencies [c89d18c]
263
+ - Updated dependencies [aac90a5]
264
+ - Updated dependencies [1e6ab15]
265
+ - Updated dependencies [c87ef70]
266
+ - Updated dependencies [3cb0618]
267
+ - Updated dependencies [32a0874]
268
+ - Updated dependencies [7055c22]
269
+ - Updated dependencies [785a748]
270
+ - Updated dependencies [3af0354]
271
+ - Updated dependencies [866ff16]
272
+ - Updated dependencies [5a85e67]
273
+ - Updated dependencies [946a131]
274
+ - Updated dependencies [c183a12]
275
+ - Updated dependencies [8064b07]
276
+ - Updated dependencies [4a56dbd]
277
+ - Updated dependencies [06df4fa]
278
+ - @objectstack/spec@17.0.0-rc.4
279
+ - @objectstack/types@17.0.0-rc.4
280
+ - @objectstack/core@17.0.0-rc.4
281
+ - @objectstack/platform-objects@17.0.0-rc.4
282
+ - @objectstack/formula@17.0.0-rc.4
283
+ - @objectstack/metadata-core@17.0.0-rc.4
284
+
3
285
  ## 17.0.0-rc.2
4
286
 
5
287
  ### Minor Changes