@objectstack/plugin-approvals 17.0.0-rc.0 → 17.0.0-rc.2
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 +863 -0
- package/dist/index.d.mts +2236 -2688
- package/dist/index.d.ts +2236 -2688
- package/dist/index.js +591 -128
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +590 -127
- package/dist/index.mjs.map +1 -1
- package/package.json +17 -10
- package/.turbo/turbo-build.log +0 -22
- package/scripts/i18n-extract.config.ts +0 -38
- package/src/action-link-pages.ts +0 -102
- package/src/approval-actor-impersonation.test.ts +0 -330
- package/src/approval-node.test.ts +0 -356
- package/src/approval-node.ts +0 -196
- package/src/approval-revise.test.ts +0 -418
- package/src/approval-service.test.ts +0 -2858
- package/src/approval-service.ts +0 -3617
- package/src/approvals-plugin.ts +0 -294
- package/src/approver-cross-org.integration.test.ts +0 -206
- package/src/approver-org-scope.test.ts +0 -201
- package/src/approver-org-scope.ts +0 -261
- package/src/index.ts +0 -42
- package/src/lifecycle-hooks.ts +0 -201
- package/src/nav-contribution.test.ts +0 -50
- package/src/record-lock-schedule-run.integration.test.ts +0 -206
- package/src/status-mirror-cascade.integration.test.ts +0 -224
- package/src/sys-approval-action.object.ts +0 -149
- package/src/sys-approval-approver.object.ts +0 -85
- package/src/sys-approval-delegation.object.test.ts +0 -42
- package/src/sys-approval-delegation.object.ts +0 -142
- package/src/sys-approval-request.object.test.ts +0 -116
- package/src/sys-approval-request.object.ts +0 -413
- package/src/sys-approval-token.object.ts +0 -101
- package/src/translations/bundle-ownership.test.ts +0 -48
- package/src/translations/en.objects.generated.ts +0 -311
- package/src/translations/es-ES.objects.generated.ts +0 -311
- package/src/translations/index.ts +0 -23
- package/src/translations/ja-JP.objects.generated.ts +0 -311
- package/src/translations/zh-CN.objects.generated.ts +0 -311
- package/tsconfig.json +0 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,868 @@
|
|
|
1
1
|
# @objectstack/plugin-approvals
|
|
2
2
|
|
|
3
|
+
## 17.0.0-rc.2
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 2826d1e: fix(automation,approvals): an approval decision can no longer succeed while its flow stays parked (#4420)
|
|
8
|
+
|
|
9
|
+
A flow paused at an `approval` node, a deploy, then an approver clicking
|
|
10
|
+
Approve: the request row flipped to `approved`, the UI toasted success — and
|
|
11
|
+
the flow never moved. No next-stage request, no error, the record's mirrored
|
|
12
|
+
status frozen mid-workflow. Approval flows pause for days by design, so a
|
|
13
|
+
restart mid-flight is the normal case: every release could quietly zombify
|
|
14
|
+
every in-flight approval, with the approvers none the wiser.
|
|
15
|
+
|
|
16
|
+
Durable suspended runs (#1518) had shipped and were not the missing piece. Two
|
|
17
|
+
other things were.
|
|
18
|
+
|
|
19
|
+
**The wiring could enable a store over a table nobody had created.** Object
|
|
20
|
+
registration and store activation resolve different services in different
|
|
21
|
+
phases — `manifest` at `init()`, `objectql` at `start()` — and the plugin
|
|
22
|
+
declared no ordering. Composed ahead of ObjectQL, `init()` found no `manifest`,
|
|
23
|
+
warned, and continued; `start()` then attached the DB-backed store anyway. Every
|
|
24
|
+
suspend failed with `no such table: sys_automation_run` into a log line nobody
|
|
25
|
+
read, pauses silently stayed in memory, and the next restart lost them all.
|
|
26
|
+
Now: `AutomationServicePlugin` declares `optionalDependencies:
|
|
27
|
+
['com.objectstack.engine.objectql']` (order-if-present, per ADR-0116 — an
|
|
28
|
+
engine-less kernel must still boot); a registration missed at `init()` is
|
|
29
|
+
retried at `start()`, which still lands before ObjectQL's schema sync; the
|
|
30
|
+
store is never attached when registration did not happen, and says so at
|
|
31
|
+
**error** level instead of warning; the table is probed once at boot so a
|
|
32
|
+
broken setup surfaces there rather than one failed write at a time; and a
|
|
33
|
+
failed durable write of a paused run is logged at error — it is data loss in
|
|
34
|
+
waiting, not a warning.
|
|
35
|
+
|
|
36
|
+
**A reported resume failure read as success.** `AutomationEngine.resume()`
|
|
37
|
+
answers a lost run by _returning_ `{ success: false }`, never by throwing.
|
|
38
|
+
`ApprovalService` discarded that return value, and `decide()` counted only a
|
|
39
|
+
thrown error as failure — so a decision against a dead run came back
|
|
40
|
+
`resumed: true`, HTTP 200. Resume failures are now classified
|
|
41
|
+
(`RUN_NOT_FOUND`, `STORE_UNAVAILABLE`, `RESUME_IN_PROGRESS`, joining
|
|
42
|
+
`PERMISSION_DENIED` / `INVALID_SIGNAL`), so a run that is gone for good is
|
|
43
|
+
distinguishable from a store that is merely unreachable, and the raw resume
|
|
44
|
+
route maps them to 404 / 503 / 409.
|
|
45
|
+
|
|
46
|
+
Approvals acts on them. A new `AutomationEngine.hasSuspendedRun(runId)` — which
|
|
47
|
+
reads the suspension store, unlike `getRun()`, and throws rather than answering
|
|
48
|
+
`false` when the store is unreadable — pre-flights every flow-advancing
|
|
49
|
+
operation (`decide`, `sendBack`, `resubmit`) **before its first write**, so the
|
|
50
|
+
zombie half-state is never created rather than merely reported: the decision
|
|
51
|
+
fails with `RESUME_TARGET_LOST` (HTTP 409) and the request stays actionable. A
|
|
52
|
+
resume that fails after the decision is durable can no longer be undone, but it
|
|
53
|
+
now throws `RESUME_FAILED` (HTTP 500) naming the stranded run instead of
|
|
54
|
+
reporting success. A concurrent duplicate resume stays benign — the engine's
|
|
55
|
+
idempotency guard is doing its job — and reports through the new optional
|
|
56
|
+
`resumeError` field. Recall and revise-window cancellation stay non-fatal by
|
|
57
|
+
design (they abandon the request), but log at error with the reason instead of
|
|
58
|
+
swallowing it. Compositions with no automation engine attached are unaffected.
|
|
59
|
+
|
|
60
|
+
Existing zombie requests from affected deployments (already `approved`, run
|
|
61
|
+
stranded) are not repaired by this change — `releaseDeadRunRequests` only
|
|
62
|
+
sweeps requests that are still `pending`.
|
|
63
|
+
|
|
64
|
+
- 0848bea: feat(spec)!: retire the overloaded `managedBy: 'system'` bucket — the residue becomes `system-data` (#3355)
|
|
65
|
+
|
|
66
|
+
**FROM → TO: `managedBy: 'system'` → `managedBy: 'system-data'`.** One-line fix:
|
|
67
|
+
rename the value. Nothing else about the object changes. `os migrate meta --from 16`
|
|
68
|
+
rewrites it for you; stored metadata is CONVERTED by the ADR-0087 entry
|
|
69
|
+
`object-managed-by-system-to-system-data`, never silently reinterpreted.
|
|
70
|
+
|
|
71
|
+
ADR-0103 split the overloaded `system` bucket in v16, and it split it
|
|
72
|
+
**additively**: the 20 engine-owned objects moved to the new explicit
|
|
73
|
+
`engine-owned`, while the 8 admin/user-writable ones — the RBAC link tables
|
|
74
|
+
(`sys_user_position`, `sys_user_permission_set`, `sys_position_permission_set`),
|
|
75
|
+
`sys_user_preference`, `sys_approval_delegation`, and the three messaging config
|
|
76
|
+
grids — stayed behind on `system`. That was the right move for a v16 that could
|
|
77
|
+
not break authors, but it left the enum in a state where the surviving value
|
|
78
|
+
names the half that had already moved out: `system` sitting on precisely the
|
|
79
|
+
objects a user writes.
|
|
80
|
+
|
|
81
|
+
That is not a cosmetic complaint. An author choosing between `system` and
|
|
82
|
+
`engine-owned` had nothing in the vocabulary to choose _on_, so the bucket was
|
|
83
|
+
re-overloadable by anyone reading the name in good faith — a model author most
|
|
84
|
+
of all, since "system table" reads as "the engine owns this" in every other
|
|
85
|
+
codebase. `system-data` states both boundaries explicitly: the **schema** is the
|
|
86
|
+
platform's (versus `platform`, which is tenant-modelled), the **data** is the
|
|
87
|
+
admin's or the user's (versus `engine-owned`, where the engine owns both).
|
|
88
|
+
|
|
89
|
+
Because v16 already drained the engine side, the conversion is a **one-to-one
|
|
90
|
+
mechanical value rename** with no judgement call — by construction every
|
|
91
|
+
remaining `system` declaration is writable platform data.
|
|
92
|
+
|
|
93
|
+
**One deliberate consequence — the affordance default flips.** `system` defaulted
|
|
94
|
+
LOCKED and each of the 8 objects re-opened its writes with a
|
|
95
|
+
`userActions: { create: true, edit: true, delete: true }` block. `system-data`
|
|
96
|
+
defaults **WRITABLE** (full CRUD), because a bucket that exists to say "the data
|
|
97
|
+
is yours" should not make every member ask for it back. Those blocks are now
|
|
98
|
+
redundant and have been deleted from the 8 platform objects; keep `userActions`
|
|
99
|
+
only to **NARROW**. If you converted an object that carried no `userActions`, it
|
|
100
|
+
gains the generic affordances — the honest reading of the bucket it moved into.
|
|
101
|
+
|
|
102
|
+
**No enforcement moves.** The engine write guard, the `DelegatedAdminGate`, RLS
|
|
103
|
+
and permission sets all adjudicate off resolved affordances and the principal,
|
|
104
|
+
never off the bucket name. `system-data` simply joins `platform` / `config` as a
|
|
105
|
+
bucket the fail-closed guard does not cover, because a writable default has
|
|
106
|
+
nothing to close on. The 8 objects passed that guard before (via `userActions`)
|
|
107
|
+
and pass it now (via the bucket default), for the same resolved-affordance
|
|
108
|
+
reason.
|
|
109
|
+
|
|
110
|
+
`'system'` is **retired from the load path**: the enum rejects it with a
|
|
111
|
+
prescription naming `system-data` and the one-line fix. Absorbing it silently at
|
|
112
|
+
load would leave every author still writing the name this rename exists to
|
|
113
|
+
unteach.
|
|
114
|
+
|
|
115
|
+
### Patch Changes
|
|
116
|
+
|
|
117
|
+
- 5a84d41: fix(approvals): record an admin override of a staffed approver slate AS an override (#4466)
|
|
118
|
+
|
|
119
|
+
An admin who is not in a request's `pending_approvers` may still act on it — the
|
|
120
|
+
`#3424` privileged-override path exists so a request routed to an unstaffed
|
|
121
|
+
position, or to approvers who have all left, is not undecidable forever. The
|
|
122
|
+
override is defensible; what was not is what the audit trail recorded.
|
|
123
|
+
|
|
124
|
+
`sys_approval_action` had no override column at all. So an admin overriding a
|
|
125
|
+
properly-staffed slate wrote a row **byte-for-byte identical** to the designated
|
|
126
|
+
approver approving normally: a reader of the timeline saw `approve` by the admin
|
|
127
|
+
and could not tell whether the admin _was_ an approver or _overrode_ the ones who
|
|
128
|
+
were, and the bypassed approver's later `409 INVALID_STATE` was the only trace —
|
|
129
|
+
existing only if they happened to try. The platform knows at decision time (it
|
|
130
|
+
took the `isOverrideActor` branch to admit the call at all), so this was dropped
|
|
131
|
+
information, not unavailable information. The whole point of an approval record
|
|
132
|
+
is to answer "who authorized this, and were they entitled to?".
|
|
133
|
+
|
|
134
|
+
`sys_approval_action` now carries **`via_override`** (boolean, optional), set on
|
|
135
|
+
exactly the actions admitted by that branch — `decideNode`'s approve/reject and
|
|
136
|
+
`reassign`'s admin rescue. It is surfaced on `ApprovalActionRow.via_override`
|
|
137
|
+
(`@objectstack/spec/contracts`), returned by `listActions`, and added to the
|
|
138
|
+
object's `highlightFields` and two grid list views so a timeline can say
|
|
139
|
+
"overrode the approver slate" instead of rendering it as an ordinary approval.
|
|
140
|
+
|
|
141
|
+
Three distinctions the column keeps apart deliberately:
|
|
142
|
+
|
|
143
|
+
- **`true`** — the actor held no slot in the slate and was admitted only by the
|
|
144
|
+
override branch.
|
|
145
|
+
- **`false`** — checked, and it was not an override. An admin who _is_ a
|
|
146
|
+
designated approver is approving normally and records `false`: the marker is
|
|
147
|
+
about which branch admitted the call, not about whether the actor holds admin
|
|
148
|
+
rights.
|
|
149
|
+
- **absent** — a row written before this column existed. "Not recorded" is not
|
|
150
|
+
the same claim as "not an override", so `rowFromAction` maps `null` to
|
|
151
|
+
`undefined` rather than to `false`.
|
|
152
|
+
|
|
153
|
+
Additive and nullable, so this needs no data migration: existing rows keep
|
|
154
|
+
working and simply read as unrecorded. Levelled `patch` rather than `minor`
|
|
155
|
+
because nothing an author writes changes — but note it _is_ an observable
|
|
156
|
+
behaviour change on a read surface: `listActions` responses and the
|
|
157
|
+
`sys_approval_action` grid views now carry a field consumers did not see before,
|
|
158
|
+
and `sys_approval_action` gains a column on next schema sync.
|
|
159
|
+
|
|
160
|
+
- 0b795da: fix(approvals): the record lock now holds for predicate (`multi`) updates (#4778)
|
|
161
|
+
|
|
162
|
+
The ADR-0019 record lock — "while a record has a pending `sys_approval_request`,
|
|
163
|
+
block edits to it" — was enforced only for updates that reach the hook with an
|
|
164
|
+
`input.id`. The engine extracts that id from a **scalar** `where.id` alone; an
|
|
165
|
+
operator object (`{ $in: [...] }`) or any other predicate is a multi-row write
|
|
166
|
+
that routes to `updateMany` and arrives with no id. The hook opened with
|
|
167
|
+
`if (!id) return`, so it read _"no row was resolved"_ as _"there is nothing to
|
|
168
|
+
authorize"_ when the truth was _"nothing was ever queried"_.
|
|
169
|
+
|
|
170
|
+
Rewriting the very same edit as `multi: true` therefore walked straight past the
|
|
171
|
+
lock:
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
// rec_1 carries a pending approval, lockRecord is not disabled
|
|
175
|
+
await ql.update(
|
|
176
|
+
"crm_opportunity",
|
|
177
|
+
{ amount: 999 },
|
|
178
|
+
{ where: { id: "rec_1" } }
|
|
179
|
+
); // RECORD_LOCKED
|
|
180
|
+
await ql.update(
|
|
181
|
+
"crm_opportunity",
|
|
182
|
+
{ amount: 999 },
|
|
183
|
+
{ where: { id: { $in: ["rec_1"] } }, multi: true }
|
|
184
|
+
); // went through
|
|
185
|
+
await ql.update(
|
|
186
|
+
"crm_opportunity",
|
|
187
|
+
{ amount: 999 },
|
|
188
|
+
{ where: { name: "x" }, multi: true }
|
|
189
|
+
); // went through
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
No privilege was needed for that bypass — not an `admin` role, not `isSystem`,
|
|
193
|
+
not `lockRecord: false`, not a whitelisted `approvalStatusField`. Every caller
|
|
194
|
+
shape that can spell a predicate (SDK, ObjectQL, a flow's `update_record`) could
|
|
195
|
+
produce it. It is the same fail-open reasoning fixed for `sys_attachment`
|
|
196
|
+
(#4757) and `sys_comment` (#4630), in the one place where it needed no
|
|
197
|
+
privilege at all.
|
|
198
|
+
|
|
199
|
+
**The hook now resolves the rows a write touches before deciding.** By-id writes
|
|
200
|
+
are unchanged (the driver writes by primary key, so the rest of `where` must not
|
|
201
|
+
narrow the verdict). A predicate write is decided by intersecting the caller's
|
|
202
|
+
predicate with the records that are actually locked — which is also what keeps
|
|
203
|
+
it cheap: the query is bounded by the object's **pending approvals**, never by
|
|
204
|
+
the update's match set, so a mass update of 50 000 unlocked rows costs one
|
|
205
|
+
bookkeeping probe and is allowed. An unscoped `multi` update over the whole
|
|
206
|
+
table reaches every locked row of the object and is refused while any is held.
|
|
207
|
+
|
|
208
|
+
**Fail-closed, both ways.** Past 1 000 locked records — the bound the attachment
|
|
209
|
+
and comment guards use — or if the intersection query fails, the write is
|
|
210
|
+
refused rather than allowed: the lock could not prove the write misses a locked
|
|
211
|
+
row. The approvals bookkeeping being unreadable at all stays the one fail-open,
|
|
212
|
+
as before: this hook is global over every object, so a kernel without
|
|
213
|
+
`sys_approval_request` would otherwise refuse every update in the deployment.
|
|
214
|
+
Both the bookkeeping and the match-set resolution are read under a **system**
|
|
215
|
+
context — a guard's own input must never be narrowed by the caller's
|
|
216
|
+
visibility, since a locked row you cannot read is still a row you may not write.
|
|
217
|
+
|
|
218
|
+
**Every exemption moved with the guard**, which is the other way this class of
|
|
219
|
+
fix goes wrong — a guard extended to more rows that carries only its deny rules
|
|
220
|
+
turns a fail-open into a false-positive. `isSystem`, the `admin` override, the
|
|
221
|
+
`approvalStatusField` status mirror, `lockRecord: false` and the owning run's
|
|
222
|
+
`flowRunId` (#3456 / #3712) all decide a predicate write exactly as they decide
|
|
223
|
+
a by-id write, each pinned by tests on both predicate shapes. Refusals now name
|
|
224
|
+
the record and object that are locked.
|
|
225
|
+
|
|
226
|
+
- c2a1134: fix(approvals): find the zombie requests nothing was looking at (#4469)
|
|
227
|
+
|
|
228
|
+
#4460 stopped new zombies being produced; the rows already stuck had no mechanism
|
|
229
|
+
to find or release them. The failure shape (#4420) is a request flipped to
|
|
230
|
+
`approved` / `rejected` / `returned` whose `flow_run_id` points at a run that no
|
|
231
|
+
longer exists — the decision landed, the flow never moved. Any deployment on
|
|
232
|
+
17.0.0-rc.1 that hit the wiring hole and crossed a restart mid-approval can be
|
|
233
|
+
carrying these rows.
|
|
234
|
+
|
|
235
|
+
`releaseDeadRunRequests` could not see them, and the reason is worth stating
|
|
236
|
+
plainly: it scans `status: 'pending'`, and the very step that zombifies a request
|
|
237
|
+
is the one that takes it OUT of `pending`. The act of breaking it removed it from
|
|
238
|
+
the only sweeper's field of view — a large part of why this class of failure
|
|
239
|
+
stayed silent. It could not have answered the question even if it had looked: its
|
|
240
|
+
liveness oracle is `getRun`, which reads the execution LOG and returns `null` for
|
|
241
|
+
a perfectly ALIVE suspended run after a restart. It treats `null` as alive
|
|
242
|
+
(conservative, and correct for what it does) — which is exactly why it has no way
|
|
243
|
+
to say "this run is really gone".
|
|
244
|
+
|
|
245
|
+
Adds `ApprovalService.inspectStrandedRequests()`, which uses BOTH oracles and
|
|
246
|
+
reports only rows that fail both:
|
|
247
|
+
|
|
248
|
+
- `hasSuspendedRun(runId) === false` — the suspension store itself says no live
|
|
249
|
+
pause exists. It THROWS when the store cannot be read, and that case is
|
|
250
|
+
SKIPPED and counted as `undetermined`, never condemned: an unreadable store
|
|
251
|
+
means "unknown", and a storage outage must not be published as a lost run.
|
|
252
|
+
- `getRun(runId) == null` — no terminal history row either. A run that merely
|
|
253
|
+
finished is not stranded; a request whose run neither waits nor ever completed
|
|
254
|
+
is.
|
|
255
|
+
|
|
256
|
+
**It reports; it never rewrites.** No status is changed and no run is cancelled.
|
|
257
|
+
The decision genuinely happened — a human approved or rejected — and silently
|
|
258
|
+
rolling it back would make the audit trail disagree with the facts. The report
|
|
259
|
+
carries what an operator needs to decide: which requests are stuck at which step,
|
|
260
|
+
and what the mirrored status field on the business record still reads (usually
|
|
261
|
+
the stale value the user is staring at). Whether to re-run the downstream actions
|
|
262
|
+
or re-open the approval is a judgement call this cannot make.
|
|
263
|
+
|
|
264
|
+
It rides the existing escalation/dead-run sweep clock, so the finding surfaces in
|
|
265
|
+
the logs without an operator knowing to go looking for it. `recalled` is
|
|
266
|
+
deliberately out of scope: a recall abandons its run on purpose, and reporting
|
|
267
|
+
those would bury the real findings under expected ones.
|
|
268
|
+
|
|
269
|
+
New export: `StrandedApprovalRequest` (the report row shape).
|
|
270
|
+
|
|
271
|
+
- 25784cf: fix(automation,approvals): 节点类型校验推迟到插件贡献完成之后 —— approval flow 不再被误报"运行时会失败" (#4771)
|
|
272
|
+
|
|
273
|
+
showcase 每次冷启都打印 8 条断言:这些 flow "will fail at execution time"。8 条全是假的。
|
|
274
|
+
`AutomationServicePlugin.start()` 从 ObjectQL registry 拉起 flow 并**当场**校验节点类型,而
|
|
275
|
+
`ApprovalsServicePlugin.start()` 在 0.8 秒后才注册 `approval` 执行器 —— 校验器在词汇表还没
|
|
276
|
+
成型的时候就下了结论。
|
|
277
|
+
|
|
278
|
+
真正的代价不是噪音,是信号丢失:**真的没装 approvals 插件**的部署会得到一模一样的 8 条告警,
|
|
279
|
+
所以这条 warn 无法区分"健康"和"坏掉",信噪比为 0。
|
|
280
|
+
|
|
281
|
+
ADR-0018 明确把节点词汇表定义为**开放、可运行时扩展**的(插件通过
|
|
282
|
+
`registerNodeExecutor(type)` 贡献类型)。因此校验只在词汇表**封闭**的那一刻才成立:
|
|
283
|
+
|
|
284
|
+
- `AutomationEngine.sealNodeTypeVocabulary()` —— 宣告词汇表封闭,对**所有**已注册 flow 跑一次
|
|
285
|
+
权威校验,每个有问题的 flow warn 一条。`AutomationServicePlugin` 在 `kernel:bootstrapped`
|
|
286
|
+
调用它(严格晚于每个插件的 `start()` 和每个 `kernel:ready` handler —— 本插件自己的
|
|
287
|
+
`kernel:ready` 还会再注册一批 flow,别的插件也可能在它的 `kernel:ready` 里贡献执行器)。
|
|
288
|
+
- `AutomationEngine.getUnknownNodeTypeAudit(): UnknownNodeTypeAuditEntry[]` —— 同一发现的
|
|
289
|
+
**状态**形态,供 host(CLI 启动摘要、健康检查)直接读,而不是去 grep 日志。与
|
|
290
|
+
`getTriggerBindingAudit()` 同一套路。
|
|
291
|
+
- 封闭之后 `registerFlow` **恢复即时告警**:Studio 发布 / dev reload 进正在运行的服务器时,
|
|
292
|
+
词汇表确实是完整的,那句断言此时为真。所以这是时序修复,不是把告警静音。
|
|
293
|
+
|
|
294
|
+
告警文案也随之改成它现在能承诺的事:"Every plugin has started, so nothing will register them
|
|
295
|
+
now — these nodes fail at execution time with NO_EXECUTOR",并给出补救动作。
|
|
296
|
+
|
|
297
|
+
一并修掉同一缺陷类的另一半:`ApprovalsServicePlugin` 在**拿不到 automation 引擎**时,把
|
|
298
|
+
"`approval` 节点没注册"记成 `info` —— 而 dev 的默认日志级别是 `warn`,于是**真降级发生时反而
|
|
299
|
+
看不见**(#4632:静默降级必须响亮)。现在是 `warn`,写明后果(该部署里每个 ADR-0019 approval
|
|
300
|
+
flow 都会以 NO_EXECUTOR 失败)和补救(装 `@objectstack/service-automation`)。`catch` 同时收窄
|
|
301
|
+
到"服务查找"这一步,`registerApprovalNode` 内部真出错时会以自己的身份抛出,而不再被贴上
|
|
302
|
+
"no automation engine" 的错误标签;`automation` 服务存在但不接受节点执行器的分支从前**一条日志
|
|
303
|
+
都不打**,现在同样 warn。
|
|
304
|
+
|
|
305
|
+
**嵌入式 host 注意**:直接 `new AutomationEngine()` 而不经过 `AutomationServicePlugin` 的宿主,
|
|
306
|
+
需要在自己的插件都装好之后调用一次 `sealNodeTypeVocabulary()`,才能拿到这条告警(以及之后的
|
|
307
|
+
即时校验)。
|
|
308
|
+
|
|
309
|
+
- Updated dependencies [430dcc2]
|
|
310
|
+
- Updated dependencies [e6ac4bd]
|
|
311
|
+
- Updated dependencies [80334c7]
|
|
312
|
+
- Updated dependencies [ce5242c]
|
|
313
|
+
- Updated dependencies [a7163ea]
|
|
314
|
+
- Updated dependencies [e6e9379]
|
|
315
|
+
- Updated dependencies [98877c9]
|
|
316
|
+
- Updated dependencies [98877c9]
|
|
317
|
+
- Updated dependencies [c44dd5e]
|
|
318
|
+
- Updated dependencies [e6b1b69]
|
|
319
|
+
- Updated dependencies [ad047d2]
|
|
320
|
+
- Updated dependencies [2826d1e]
|
|
321
|
+
- Updated dependencies [5a84d41]
|
|
322
|
+
- Updated dependencies [20b1a9e]
|
|
323
|
+
- Updated dependencies [203a449]
|
|
324
|
+
- Updated dependencies [ac37fc6]
|
|
325
|
+
- Updated dependencies [4820f55]
|
|
326
|
+
- Updated dependencies [462d9c4]
|
|
327
|
+
- Updated dependencies [7d21581]
|
|
328
|
+
- Updated dependencies [f2445c9]
|
|
329
|
+
- Updated dependencies [23338c3]
|
|
330
|
+
- Updated dependencies [5b843fb]
|
|
331
|
+
- Updated dependencies [b4487aa]
|
|
332
|
+
- Updated dependencies [65ca83a]
|
|
333
|
+
- Updated dependencies [67bf2e2]
|
|
334
|
+
- Updated dependencies [c6d1cb4]
|
|
335
|
+
- Updated dependencies [36030ff]
|
|
336
|
+
- Updated dependencies [6117f7b]
|
|
337
|
+
- Updated dependencies [e533b0b]
|
|
338
|
+
- Updated dependencies [cdf4d9a]
|
|
339
|
+
- Updated dependencies [aee1806]
|
|
340
|
+
- Updated dependencies [c13350b]
|
|
341
|
+
- Updated dependencies [c13350b]
|
|
342
|
+
- Updated dependencies [9ca2d85]
|
|
343
|
+
- Updated dependencies [c13350b]
|
|
344
|
+
- Updated dependencies [891d345]
|
|
345
|
+
- Updated dependencies [a52e2ef]
|
|
346
|
+
- Updated dependencies [5293114]
|
|
347
|
+
- Updated dependencies [20bc357]
|
|
348
|
+
- Updated dependencies [5966c2a]
|
|
349
|
+
- Updated dependencies [2382580]
|
|
350
|
+
- Updated dependencies [d9fa683]
|
|
351
|
+
- Updated dependencies [3c7bcc0]
|
|
352
|
+
- Updated dependencies [4b6cac7]
|
|
353
|
+
- Updated dependencies [7631964]
|
|
354
|
+
- Updated dependencies [ac471a0]
|
|
355
|
+
- Updated dependencies [60ae58e]
|
|
356
|
+
- Updated dependencies [ce92674]
|
|
357
|
+
- Updated dependencies [9f601e8]
|
|
358
|
+
- Updated dependencies [51c5227]
|
|
359
|
+
- Updated dependencies [a4a85c8]
|
|
360
|
+
- Updated dependencies [07a4e26]
|
|
361
|
+
- Updated dependencies [ec975f1]
|
|
362
|
+
- Updated dependencies [eb4204b]
|
|
363
|
+
- Updated dependencies [4f13be2]
|
|
364
|
+
- Updated dependencies [61cc079]
|
|
365
|
+
- Updated dependencies [0e96e46]
|
|
366
|
+
- Updated dependencies [b25a116]
|
|
367
|
+
- Updated dependencies [d52d4fe]
|
|
368
|
+
- Updated dependencies [742cebb]
|
|
369
|
+
- Updated dependencies [ce92674]
|
|
370
|
+
- Updated dependencies [cf2c9b7]
|
|
371
|
+
- Updated dependencies [833b512]
|
|
372
|
+
- Updated dependencies [0f9faa2]
|
|
373
|
+
- Updated dependencies [7cf42fe]
|
|
374
|
+
- Updated dependencies [5966c2a]
|
|
375
|
+
- Updated dependencies [f78dd83]
|
|
376
|
+
- Updated dependencies [a2cd18a]
|
|
377
|
+
- Updated dependencies [4638aaa]
|
|
378
|
+
- Updated dependencies [0222d3c]
|
|
379
|
+
- Updated dependencies [071d0dc]
|
|
380
|
+
- Updated dependencies [0a936ea]
|
|
381
|
+
- Updated dependencies [023c00b]
|
|
382
|
+
- Updated dependencies [155507e]
|
|
383
|
+
- Updated dependencies [7bba90b]
|
|
384
|
+
- Updated dependencies [7e05d8e]
|
|
385
|
+
- Updated dependencies [061406d]
|
|
386
|
+
- Updated dependencies [c1f344b]
|
|
387
|
+
- Updated dependencies [9c93465]
|
|
388
|
+
- Updated dependencies [ebb209c]
|
|
389
|
+
- Updated dependencies [65f184b]
|
|
390
|
+
- Updated dependencies [63b33e6]
|
|
391
|
+
- Updated dependencies [2a44c1d]
|
|
392
|
+
- Updated dependencies [695cfbd]
|
|
393
|
+
- Updated dependencies [7445149]
|
|
394
|
+
- Updated dependencies [071d0dc]
|
|
395
|
+
- Updated dependencies [0848bea]
|
|
396
|
+
- Updated dependencies [d51bed2]
|
|
397
|
+
- Updated dependencies [b8b3c64]
|
|
398
|
+
- Updated dependencies [0c0fbd9]
|
|
399
|
+
- Updated dependencies [f3141d8]
|
|
400
|
+
- Updated dependencies [5a84d41]
|
|
401
|
+
- Updated dependencies [fd3013a]
|
|
402
|
+
- Updated dependencies [21676eb]
|
|
403
|
+
- Updated dependencies [e336549]
|
|
404
|
+
- Updated dependencies [d40f43a]
|
|
405
|
+
- Updated dependencies [e5e7ee0]
|
|
406
|
+
- Updated dependencies [a2ebea2]
|
|
407
|
+
- Updated dependencies [800bdb0]
|
|
408
|
+
- Updated dependencies [04f1182]
|
|
409
|
+
- Updated dependencies [5647006]
|
|
410
|
+
- Updated dependencies [38f7e4f]
|
|
411
|
+
- Updated dependencies [c57f3cf]
|
|
412
|
+
- Updated dependencies [97faca3]
|
|
413
|
+
- Updated dependencies [ad5fe25]
|
|
414
|
+
- Updated dependencies [ea90179]
|
|
415
|
+
- Updated dependencies [ce92674]
|
|
416
|
+
- Updated dependencies [5ef0b5b]
|
|
417
|
+
- Updated dependencies [48fbacb]
|
|
418
|
+
- Updated dependencies [355e951]
|
|
419
|
+
- Updated dependencies [dadb43f]
|
|
420
|
+
- @objectstack/spec@17.0.0-rc.2
|
|
421
|
+
- @objectstack/platform-objects@17.0.0-rc.2
|
|
422
|
+
- @objectstack/core@17.0.0-rc.2
|
|
423
|
+
- @objectstack/types@17.0.0-rc.2
|
|
424
|
+
- @objectstack/metadata-core@17.0.0-rc.2
|
|
425
|
+
- @objectstack/formula@17.0.0-rc.2
|
|
426
|
+
|
|
427
|
+
## 17.0.0-rc.1
|
|
428
|
+
|
|
429
|
+
### Minor Changes
|
|
430
|
+
|
|
431
|
+
- f5a4ef0: refactor!: ADR-0112 batch 2 — sweep the lowercase error-code emitters (#4003)
|
|
432
|
+
|
|
433
|
+
Continues #3841 per ADR-0112. Batch 1 (#3988) settled the vocabulary and closed
|
|
434
|
+
the set; this batch moves the emitters that still spoke lowercase `snake_case`
|
|
435
|
+
onto it.
|
|
436
|
+
|
|
437
|
+
**Wire-visible change.** Error codes on these surfaces change spelling. Generic
|
|
438
|
+
conditions collapse onto the standard catalog rather than keeping a synonym:
|
|
439
|
+
`unauthorized`/`unauthenticated` → `UNAUTHENTICATED`, `forbidden` →
|
|
440
|
+
`PERMISSION_DENIED`, `not_found` → `RESOURCE_NOT_FOUND`, `internal` →
|
|
441
|
+
`INTERNAL_ERROR`, `unavailable` → `SERVICE_UNAVAILABLE`, `not_supported` →
|
|
442
|
+
`NOT_IMPLEMENTED`, `bad_request` → `INVALID_REQUEST`. Domain conditions get codes
|
|
443
|
+
registered in `ERROR_CODE_LEDGER` (`MARKETPLACE_STORAGE_FAILED`,
|
|
444
|
+
`PLUGIN_MANIFEST_INVALID`, `ITEM_LOCKED`, `DELIVERY_NOT_ELIGIBLE`, …). Swept:
|
|
445
|
+
`cloud-connection`, `plugin-auth`, `hono`, `metadata-protocol`, `rest`,
|
|
446
|
+
`service-messaging`, `service-automation`, `trigger-api`.
|
|
447
|
+
|
|
448
|
+
Branch on `error.code` values rather than pattern-matching their case: the
|
|
449
|
+
console's fix for the same rename (objectui#2977) reads codes case-insensitively
|
|
450
|
+
for exactly this reason, and that is the pattern to copy in your own consumers if
|
|
451
|
+
you support servers on both sides of the change.
|
|
452
|
+
|
|
453
|
+
**Four routes stop putting a code in the message slot.** The webhook redeliver
|
|
454
|
+
route, the API-trigger webhook, and two `rest` routes answered
|
|
455
|
+
`{ success: false, error: '<code>', message }` — the code occupying `error`, the
|
|
456
|
+
declared object envelope nowhere. They now emit `error: { code, message }`, and
|
|
457
|
+
three API-trigger branches gained a message they never had. Clients reading
|
|
458
|
+
`body.error` as a string on those routes must read `body.error.code`.
|
|
459
|
+
|
|
460
|
+
**`ConnectorErrorCategory` / `ConnectorRetryStrategy`** (ADR-0112 D9a):
|
|
461
|
+
`@objectstack/spec` exported two mutually incompatible `ErrorCategory` types and
|
|
462
|
+
two `RetryStrategy` types. The connector-side pair is renamed; importers of the
|
|
463
|
+
`integration` subpath update the name. Side effect: the api-side `ErrorCategory`
|
|
464
|
+
and `RetryStrategy` now appear in the generated API reference at all — the name
|
|
465
|
+
collision had been silently dropping them.
|
|
466
|
+
|
|
467
|
+
**`OAUTH_REGISTER_FAILED` replaces an unbounded code source.** The OAuth client
|
|
468
|
+
registration route put better-auth's arbitrary `body.error` string straight into
|
|
469
|
+
`error.code`. The code is now ours and the upstream discriminator moved to
|
|
470
|
+
`details.upstreamError`.
|
|
471
|
+
|
|
472
|
+
**Not swept, deliberately.** `sys_metadata_audit.code` keeps its lowercase values
|
|
473
|
+
(ADR-0112 D6b): it is persisted audit history, and the same column holds
|
|
474
|
+
non-error outcomes (`ok`, `lock_override`). Diagnostics records that ship inside a
|
|
475
|
+
200 keep theirs (D6c), as do field-level codes (D6, #3977) and the CLI's
|
|
476
|
+
`--json` output contract.
|
|
477
|
+
|
|
478
|
+
A `check:error-code-casing` CI guard now fails on a new lowercase literal in a
|
|
479
|
+
code position, since the ledger's casing rule can only police codes that someone
|
|
480
|
+
registers.
|
|
481
|
+
|
|
482
|
+
- 91f4c78: feat(approvals,spec): structured reassign hand-off parties on `sys_approval_action` (#4365)
|
|
483
|
+
|
|
484
|
+
A reassign's audit row used to encode "who handed the slot to whom" only inside
|
|
485
|
+
a default free-text comment — `"<from_id> → <to_id>"`, two raw user ids — which
|
|
486
|
+
clients could neither parse reliably nor render readably, so the approvals
|
|
487
|
+
timeline showed opaque identifier soup for the single most important fact of
|
|
488
|
+
the entry.
|
|
489
|
+
|
|
490
|
+
- `sys_approval_action` gains `reassign_from` / `reassign_to`
|
|
491
|
+
(`lookup('sys_user')`), written by `ApprovalService.reassign()`.
|
|
492
|
+
- `comment` is pure user input again: nothing is invented when the actor
|
|
493
|
+
supplies none.
|
|
494
|
+
- `listActions()` resolves both parties' display names into
|
|
495
|
+
`reassign_from_name` / `reassign_to_name`, alongside the existing
|
|
496
|
+
`actor_name`, so timelines can render "from A to B" without extra lookups.
|
|
497
|
+
- `ApprovalActionRow` (spec contract) declares the four new fields.
|
|
498
|
+
|
|
499
|
+
Pre-existing rows keep their legacy comment; clients should prefer the
|
|
500
|
+
structured fields when present and fall back to `comment` otherwise.
|
|
501
|
+
|
|
502
|
+
- cd6b9f2: `decisionOutputs` entries may now be declared `required` (objectui#2955). A typed entry `{ key, label?, type?, multiple?, required?: true }` tells the runtime — not just the decision UI — that an approver must supply the value: an **approve** carrying no value, or a blank one (`''`, whitespace, `[]`, an array of blanks), is rejected with `VALIDATION_FAILED` before any write, so the audit row and the request are untouched and the run can never resume past the node with the key missing.
|
|
503
|
+
|
|
504
|
+
That gap is what the flag closes. `decisionOutputs` exists so a decision can route the next step (`approvers: [{ type: 'expression', value: 'vars.lead_review.next_reviewers' }]`), but nothing made the approver actually answer: a skipped output resumed the run with the key absent, and the next node either faulted with `EXPRESSION_FAILED` or resolved an empty slate and stalled on `onEmptyApprovers: 'admin_rescue'` — long after the one person who could have filled it in had moved on. `onEmptyApprovers` was the only backstop, and it is a recovery mechanism, not a contract.
|
|
505
|
+
|
|
506
|
+
**Reject never requires them.** The run leaves down the `reject` edge, where nothing reads the outputs — demanding routing data to say "no" would trap the rejection. Outputs still ride a reject when the approver filled them in.
|
|
507
|
+
|
|
508
|
+
**No elevation bypass.** A one-click email action link and an `auto_approve` SLA escalation both fail the same way rather than advancing into a node that would resolve nobody; the escalation sweep already isolates a throwing request, so that decision stays pending and visibly overdue instead of silently breaking the run downstream. Enforcement is per decision, so on a `unanimous` / `quorum` node every approver supplies the required outputs and the finalizing decision's values are what the flow resumes with.
|
|
509
|
+
|
|
510
|
+
`required` rides `normalizeDecisionOutputs`, so it reaches clients on `decision_output_defs` — a decision UI marks the field required and blocks locally instead of round-tripping to a 400. The console side ships in objectui#2955.
|
|
511
|
+
|
|
512
|
+
### Patch Changes
|
|
513
|
+
|
|
514
|
+
- 820eff9: fix(spec,plugin-approvals): the two approval vocabularies are derived, not hand-matched (#3786)
|
|
515
|
+
|
|
516
|
+
`sys_approval_request.status` and `sys_approval_action.action` spelled their
|
|
517
|
+
option lists out — five values and twelve — each under a "Keep in sync with
|
|
518
|
+
`ApprovalStatus` / `ApprovalActionKind` (spec/contracts)" comment, while the
|
|
519
|
+
contract held the same sets as bare type unions. Seventeen strings matched by
|
|
520
|
+
hand across a package boundary, with nothing checking them. They did all still
|
|
521
|
+
agree; the sweep that found them (#3786) verified that verbatim before changing
|
|
522
|
+
anything.
|
|
523
|
+
|
|
524
|
+
Agreeing is not the same as being held, and both directions of drift are quiet:
|
|
525
|
+
|
|
526
|
+
- a value the **column** accepts and the contract omits is invisible to every
|
|
527
|
+
consumer typed against the contract — the row exists and nothing can narrow it;
|
|
528
|
+
- a value the **contract** declares and the column rejects surfaces only at write
|
|
529
|
+
time, on whichever tenant first reaches that transition.
|
|
530
|
+
|
|
531
|
+
An audit vocabulary is a bad place for either. So the contract now publishes the
|
|
532
|
+
lists as values — `APPROVAL_STATUSES` and `APPROVAL_ACTION_KINDS` — with
|
|
533
|
+
`ApprovalStatus` / `ApprovalActionKind` derived from them via
|
|
534
|
+
`(typeof X)[number]`, and the two columns spread the constants. The per-entry
|
|
535
|
+
rationale (which action kinds move the flow, which are thread-only, why
|
|
536
|
+
`returned` differs from `recalled`) moved onto the constants, where the values
|
|
537
|
+
live.
|
|
538
|
+
|
|
539
|
+
**New exports, no behaviour change.** The emitted option lists are byte-identical
|
|
540
|
+
— verified against the built artifact before and after. Existing imports of the
|
|
541
|
+
two types are unaffected; the types resolve to the same unions.
|
|
542
|
+
|
|
543
|
+
`approval-vocabularies.test.ts` pins the qualifier that derivation alone cannot:
|
|
544
|
+
the columns agree with the contract _while the spread is there_, and the test
|
|
545
|
+
fails if either is re-inlined as a literal that has drifted. It also guards the
|
|
546
|
+
guard (an unresolvable import would compare two empty lists and pass) and asserts
|
|
547
|
+
the two vocabularies stay distinct, since a copy-paste pointing one column at the
|
|
548
|
+
other constant would satisfy "derived from the contract" while being the wrong
|
|
549
|
+
vocabulary entirely.
|
|
550
|
+
|
|
551
|
+
Verified by mutation in both directions: adding a value to `APPROVAL_STATUSES`
|
|
552
|
+
propagates into the built `sys_approval_request.status` options (the derivation
|
|
553
|
+
is live, not a stale build), and re-inlining a drifted literal fails
|
|
554
|
+
`sys_approval_request.status offers exactly the contract statuses, in order`.
|
|
555
|
+
|
|
556
|
+
- 2e836de: chore(packaging): CHANGELOG.md ships in every npm tarball (#4261)
|
|
557
|
+
|
|
558
|
+
The AGENTS.md post-task checklist requires breaking changesets to carry their
|
|
559
|
+
FROM → TO migration because "this text ships to consumers as `CHANGELOG.md`
|
|
560
|
+
inside the npm package and is what an upgrading agent greps after the tombstone
|
|
561
|
+
error." That delivery path was severed for 68 of the 69 publishable packages:
|
|
562
|
+
npm packs `package.json` / `README*` / `LICENSE*` unconditionally but — unlike
|
|
563
|
+
older npm versions — not `CHANGELOG.md`, and the canonical
|
|
564
|
+
`"files": ["dist", "README.md"]` whitelist never named it. Measured on npm
|
|
565
|
+
10.9.7: `npm pack --dry-run` on `@objectstack/types` shipped 3 files while its
|
|
566
|
+
70KB `CHANGELOG.md` stayed behind. Only `@objectstack/spec` listed it
|
|
567
|
+
explicitly.
|
|
568
|
+
|
|
569
|
+
The tombstone-error scenario is precisely the one where the repo is out of
|
|
570
|
+
reach — the upgrading agent has `node_modules` and nothing else — so the
|
|
571
|
+
migration text has to ride in the tarball. Every publishable package now
|
|
572
|
+
declares `CHANGELOG.md` in `files`, and the canonical whitelist is
|
|
573
|
+
`["dist", "README.md", "CHANGELOG.md"]`.
|
|
574
|
+
|
|
575
|
+
The other half is the gate: `check:published-files` gains a fifth invariant,
|
|
576
|
+
COMPLETE — a whitelist that fails to cover `CHANGELOG.md` fails the
|
|
577
|
+
always-required lint job, so the next package cannot silently sever the path
|
|
578
|
+
again. `@objectstack/spec`'s per-package EXTRA_ENTRIES exemption dissolves
|
|
579
|
+
into the canonical set.
|
|
580
|
+
|
|
581
|
+
Consumer-visible change: one more file per install (the package's changelog,
|
|
582
|
+
e.g. 70.8KB for `@objectstack/types`), and `grep -r "removed key"
|
|
583
|
+
node_modules/@objectstack/*/CHANGELOG.md` now finds the migration it was
|
|
584
|
+
promised.
|
|
585
|
+
|
|
586
|
+
- b5f9397: fix(sharing,runtime): a `sort` passed straight to the engine never ordered anything; migrate every in-repo engine call to canonical QueryAST keys (#4346)
|
|
587
|
+
|
|
588
|
+
Two changes with different weights, from one sweep of every in-repo engine
|
|
589
|
+
call site that still speaks a deprecated alias.
|
|
590
|
+
|
|
591
|
+
**The bug — three dropped sorts.** #4346 made the engine fold `filter`→`where`
|
|
592
|
+
and `top`→`limit` on all six methods. The other four pairs in
|
|
593
|
+
`RPC_QUERY_ALIAS_SLOTS` (`select`, `sort`, `skip`, `populate`) are folded at
|
|
594
|
+
the RPC/wire layer only — their values need shape lowering that belongs to
|
|
595
|
+
those layers — and a **direct `engine.find()` never crosses that layer**. Three
|
|
596
|
+
call sites passed `sort` there, so it rode onto the AST untouched, every
|
|
597
|
+
driver's `Array.isArray(query.orderBy)` guard declined to emit an ORDER BY, and
|
|
598
|
+
the query returned an ordinary-looking, arbitrarily-ordered result:
|
|
599
|
+
|
|
600
|
+
| call site | asked for | actually got |
|
|
601
|
+
| ----------------------------------- | ------------------------------------------------- | --------------------------- |
|
|
602
|
+
| `share-link-routes.ts` | shared AI conversation messages, `created_at asc` | messages in arbitrary order |
|
|
603
|
+
| `runtime/domains/share-links.ts` | same route, runtime-domain copy | same |
|
|
604
|
+
| `share-link-service.ts` `listLinks` | the 200 most recent share links | an arbitrary 200 |
|
|
605
|
+
|
|
606
|
+
All three combine the dropped sort with a `limit` — the "latest N" shape whose
|
|
607
|
+
failure #4226 spelled out: an unapplied sort returns rows in arbitrary order,
|
|
608
|
+
which `limit` then slices into an arbitrary page. #4226 fixed that in the wire
|
|
609
|
+
normalizer; these calls sit one layer below it. `listLinks` had no test at all,
|
|
610
|
+
which is why it went unnoticed. Now pinned — on the option bag the engine
|
|
611
|
+
receives, not on row order, because the failure is that the key never becomes
|
|
612
|
+
`orderBy` and a fake engine honouring either spelling would pass either way.
|
|
613
|
+
|
|
614
|
+
**The cleanup — 27 no-op renames.** Every remaining in-repo engine call passing
|
|
615
|
+
`filter` now passes `where` (approvals 5, auth 2, reports 6, sharing 11,
|
|
616
|
+
webhooks 2, plus the one `filters` in a spec doc example). These are strict
|
|
617
|
+
no-ops since #4346 folds the alias — the point is that the framework stops
|
|
618
|
+
depending on a spelling it asks users to migrate off, which is a prerequisite
|
|
619
|
+
for ever retiring the aliases. Service-level `filter` PARAMETERS (each
|
|
620
|
+
service's own public API, e.g. `listRequests(filter)`) are deliberately
|
|
621
|
+
untouched — those are not engine option bags.
|
|
622
|
+
|
|
623
|
+
Two of the renamed calls were live victims of the #4346 bug rather than
|
|
624
|
+
cosmetic: `auth-manager`'s `stampIdentitySource` read the table's first row via
|
|
625
|
+
`findOne({filter})` and counted the whole table via `count({filter})`, so a
|
|
626
|
+
federated sign-in never stamped `source: 'idp_provisioned'`. #4346 already
|
|
627
|
+
corrected the behaviour; this makes the call say what it means.
|
|
628
|
+
|
|
629
|
+
- 9881074: fix(batch): the background walks seek instead of counting, so they stop skipping rows (#4363)
|
|
630
|
+
|
|
631
|
+
#4363 made a single paged read a partition of its result set. It could not make
|
|
632
|
+
a _walk_ one: seven background scans paged with a growing `offset` while writing
|
|
633
|
+
to the very rows they were reading, and an offset counts into a set those writes
|
|
634
|
+
are changing. Rows slide past the cursor and are never visited.
|
|
635
|
+
|
|
636
|
+
That is not a slow page in any of these — it is a wrong answer wearing the shape
|
|
637
|
+
of a clean run:
|
|
638
|
+
|
|
639
|
+
- **`rebuildApproverIndex`** built its desired state by walking
|
|
640
|
+
`sys_approval_request WHERE status = 'pending'` with no `orderBy` at all, then
|
|
641
|
+
**deleted** every index row that state did not explain. A skipped request
|
|
642
|
+
meant an approver silently dropped from someone's queue. (The loop beside it
|
|
643
|
+
ordered by `created_at` — not unique, so its pages were never a partition
|
|
644
|
+
either.)
|
|
645
|
+
- **`verifyFileReferences`** decides which files nothing references. A record it
|
|
646
|
+
never visits is reported as an unreferenced file.
|
|
647
|
+
- **`backfillFileReferences`** and the **pinyin companion backfill** rewrite
|
|
648
|
+
each row they read, so their own writes were shifting the set out from under
|
|
649
|
+
the cursor. Records were left unconverted and unsearchable by a run that
|
|
650
|
+
reported success.
|
|
651
|
+
- **`scanValueShapes`** exists to vouch that no stored value is off-shape, and
|
|
652
|
+
it opens a migration gate on that evidence.
|
|
653
|
+
|
|
654
|
+
All of them now go through `keysetWalk` (`@objectstack/types`): order by a
|
|
655
|
+
unique key, and seek past the last one instead of counting from the start. A
|
|
656
|
+
row's key does not move when the row is updated, and cannot be shifted when
|
|
657
|
+
another is deleted, so the walk is stable under exactly the mutation these
|
|
658
|
+
functions perform. It is also O(n) rather than O(n²/page) — measured on
|
|
659
|
+
Postgres over 2M rows, deep pages cost ~1.1 s by offset against ~0.09 s by seek.
|
|
660
|
+
|
|
661
|
+
One deliberate non-conversion: the REST **export** stream keeps its offset. It
|
|
662
|
+
honors a caller-chosen sort, and a keyset walk would have to re-order the export
|
|
663
|
+
by `id` to seek — changing what the user asked for to fix a cost. Its pages are
|
|
664
|
+
already a partition since #4363; only the depth cost remains.
|
|
665
|
+
|
|
666
|
+
`keysetWalk` merges the cursor with `$and` rather than spreading it into the
|
|
667
|
+
caller's filter, so a walk whose own `where` constrains the key column
|
|
668
|
+
(`{ id: { $in: [...] } }`) keeps that constraint instead of having it silently
|
|
669
|
+
overwritten. When a `max` cap is set it reads one row beyond the cap to tell
|
|
670
|
+
"the cap stopped us" from "the source ended exactly there" — without that, a
|
|
671
|
+
walk that read everything still reports `truncated`, and a caller acting on it
|
|
672
|
+
goes looking for rows that were never withheld.
|
|
673
|
+
|
|
674
|
+
The storage suites' fake engines now **throw** on an `offset` instead of serving
|
|
675
|
+
one, so the conversion is pinned rather than merely passing.
|
|
676
|
+
|
|
677
|
+
- cc2de0e: chore(packaging): 20 packages stop publishing their sources, tests and build tooling (#4248)
|
|
678
|
+
|
|
679
|
+
These 20 packages declared no `files` field, so npm fell back to packing the
|
|
680
|
+
whole package directory. `npm pack --dry-run` on `@objectstack/plugin-webhooks`
|
|
681
|
+
listed **21 files** — 15 under `src/`, three of them unit tests
|
|
682
|
+
(`auto-enqueuer.test.ts`, `bootstrap-declared-webhooks.test.ts`, …), plus the
|
|
683
|
+
build-time `scripts/i18n-extract.config.ts`. `dist/` lands on top of that at
|
|
684
|
+
publish time rather than instead of it, so consumers were installing the
|
|
685
|
+
TypeScript sources and the test suite alongside the artifact they asked for.
|
|
686
|
+
|
|
687
|
+
Each now declares `"files": ["dist", "README.md"]`, matching the 29 packages
|
|
688
|
+
that already did. Nothing a consumer imports moves: every `main` / `types` /
|
|
689
|
+
`exports` target in all 20 already resolved inside `dist/`, which the new
|
|
690
|
+
`check:published-files` guard verifies rather than assumes. The visible change
|
|
691
|
+
is a smaller install and a smaller dependency-scanning surface — `npm pack` on
|
|
692
|
+
`@objectstack/plugin-webhooks` now yields 2 files plus `dist/`.
|
|
693
|
+
|
|
694
|
+
The other half of the fix is the gate. Half the packages declaring `files` and
|
|
695
|
+
half not was the #3786 shape — a hand-copied convention with nothing enforcing
|
|
696
|
+
it, where whoever forgets the line gets no signal at all. `check:published-files`
|
|
697
|
+
(new, wired into the always-required `lint` job) holds every non-private
|
|
698
|
+
workspace package to four invariants: `files` is **declared**; it is
|
|
699
|
+
**sufficient** (covers every entry point, so tightening a whitelist cannot ship
|
|
700
|
+
a package that fails to resolve); it is **minimal** (admits no test, test-harness
|
|
701
|
+
config or build script); and anything beyond `dist` + `README.md` is
|
|
702
|
+
**registered** with a reason, reconciled in both directions so a stale exemption
|
|
703
|
+
is an error rather than dead text. `@objectstack/spec` is the one package with
|
|
704
|
+
registered extras — its `.zod.ts` sources, JSON Schemas, liveness ledgers and
|
|
705
|
+
`CHANGELOG.md` are product, not build input.
|
|
706
|
+
|
|
707
|
+
This also closes an assumption #4206 was resting on. Excluding `<pkg>/scripts/**`
|
|
708
|
+
from the docs-drift implementation test is sound only while no package publishes
|
|
709
|
+
`scripts/` as runtime code; that held, but it held because someone read all three
|
|
710
|
+
offenders by hand. It is now checked on every PR.
|
|
711
|
+
|
|
712
|
+
- Updated dependencies [6a67d7a]
|
|
713
|
+
- Updated dependencies [0ecc656]
|
|
714
|
+
- Updated dependencies [06772eb]
|
|
715
|
+
- Updated dependencies [270650f]
|
|
716
|
+
- Updated dependencies [3aef718]
|
|
717
|
+
- Updated dependencies [1ea6bce]
|
|
718
|
+
- Updated dependencies [c1dcacd]
|
|
719
|
+
- Updated dependencies [ad303ed]
|
|
720
|
+
- Updated dependencies [32ccb23]
|
|
721
|
+
- Updated dependencies [f5a4ef0]
|
|
722
|
+
- Updated dependencies [2d3e255]
|
|
723
|
+
- Updated dependencies [7d7521f]
|
|
724
|
+
- Updated dependencies [5dc4d02]
|
|
725
|
+
- Updated dependencies [05154a1]
|
|
726
|
+
- Updated dependencies [9b6fe7c]
|
|
727
|
+
- Updated dependencies [8c711fb]
|
|
728
|
+
- Updated dependencies [09e4547]
|
|
729
|
+
- Updated dependencies [91f4c78]
|
|
730
|
+
- Updated dependencies [820eff9]
|
|
731
|
+
- Updated dependencies [8d895ff]
|
|
732
|
+
- Updated dependencies [f6472d7]
|
|
733
|
+
- Updated dependencies [78caf51]
|
|
734
|
+
- Updated dependencies [62a789b]
|
|
735
|
+
- Updated dependencies [789ad63]
|
|
736
|
+
- Updated dependencies [2af1988]
|
|
737
|
+
- Updated dependencies [0af50a3]
|
|
738
|
+
- Updated dependencies [2e836de]
|
|
739
|
+
- Updated dependencies [12a19a8]
|
|
740
|
+
- Updated dependencies [41dcda3]
|
|
741
|
+
- Updated dependencies [c8124e5]
|
|
742
|
+
- Updated dependencies [a1a4140]
|
|
743
|
+
- Updated dependencies [c20b875]
|
|
744
|
+
- Updated dependencies [2a37694]
|
|
745
|
+
- Updated dependencies [217e2e6]
|
|
746
|
+
- Updated dependencies [86a71d1]
|
|
747
|
+
- Updated dependencies [d5c75e2]
|
|
748
|
+
- Updated dependencies [03d26f7]
|
|
749
|
+
- Updated dependencies [4384921]
|
|
750
|
+
- Updated dependencies [3c628ce]
|
|
751
|
+
- Updated dependencies [7cb922e]
|
|
752
|
+
- Updated dependencies [1d22114]
|
|
753
|
+
- Updated dependencies [b5f9397]
|
|
754
|
+
- Updated dependencies [ed77493]
|
|
755
|
+
- Updated dependencies [58a03d2]
|
|
756
|
+
- Updated dependencies [dc530b4]
|
|
757
|
+
- Updated dependencies [e59786e]
|
|
758
|
+
- Updated dependencies [bcf1112]
|
|
759
|
+
- Updated dependencies [9774b78]
|
|
760
|
+
- Updated dependencies [b07d829]
|
|
761
|
+
- Updated dependencies [a648e96]
|
|
762
|
+
- Updated dependencies [a47ac06]
|
|
763
|
+
- Updated dependencies [e4c61a7]
|
|
764
|
+
- Updated dependencies [cc60165]
|
|
765
|
+
- Updated dependencies [081aa6f]
|
|
766
|
+
- Updated dependencies [91f4c78]
|
|
767
|
+
- Updated dependencies [e8d0c21]
|
|
768
|
+
- Updated dependencies [45dc446]
|
|
769
|
+
- Updated dependencies [c1d44f7]
|
|
770
|
+
- Updated dependencies [ab9fb5c]
|
|
771
|
+
- Updated dependencies [f985b3f]
|
|
772
|
+
- Updated dependencies [9a4932a]
|
|
773
|
+
- Updated dependencies [f9fc874]
|
|
774
|
+
- Updated dependencies [011b386]
|
|
775
|
+
- Updated dependencies [9881074]
|
|
776
|
+
- Updated dependencies [7777e8f]
|
|
777
|
+
- Updated dependencies [507b92a]
|
|
778
|
+
- Updated dependencies [7309c81]
|
|
779
|
+
- Updated dependencies [20bc1ec]
|
|
780
|
+
- Updated dependencies [90c2b15]
|
|
781
|
+
- Updated dependencies [39eb01b]
|
|
782
|
+
- Updated dependencies [42eeb7d]
|
|
783
|
+
- Updated dependencies [01e124d]
|
|
784
|
+
- Updated dependencies [7ce02eb]
|
|
785
|
+
- Updated dependencies [a13827e]
|
|
786
|
+
- Updated dependencies [7733604]
|
|
787
|
+
- Updated dependencies [40e420f]
|
|
788
|
+
- Updated dependencies [d13004a]
|
|
789
|
+
- Updated dependencies [be7360c]
|
|
790
|
+
- Updated dependencies [cc2de0e]
|
|
791
|
+
- Updated dependencies [5b47ab5]
|
|
792
|
+
- Updated dependencies [b09d8d9]
|
|
793
|
+
- Updated dependencies [b09d8d9]
|
|
794
|
+
- Updated dependencies [8675db6]
|
|
795
|
+
- Updated dependencies [b09d8d9]
|
|
796
|
+
- Updated dependencies [3eb1b2b]
|
|
797
|
+
- Updated dependencies [59b85c0]
|
|
798
|
+
- Updated dependencies [6e357ed]
|
|
799
|
+
- Updated dependencies [d6938bf]
|
|
800
|
+
- Updated dependencies [31e0be9]
|
|
801
|
+
- Updated dependencies [4bfd455]
|
|
802
|
+
- Updated dependencies [ffd2ce2]
|
|
803
|
+
- Updated dependencies [62f8017]
|
|
804
|
+
- Updated dependencies [a831df1]
|
|
805
|
+
- Updated dependencies [f752ee3]
|
|
806
|
+
- Updated dependencies [a1b61e0]
|
|
807
|
+
- Updated dependencies [cd6b9f2]
|
|
808
|
+
- Updated dependencies [2cb6d3c]
|
|
809
|
+
- Updated dependencies [af2a095]
|
|
810
|
+
- Updated dependencies [ec796d5]
|
|
811
|
+
- Updated dependencies [e87fea1]
|
|
812
|
+
- Updated dependencies [c65e529]
|
|
813
|
+
- Updated dependencies [3ca34c1]
|
|
814
|
+
- Updated dependencies [239c3a3]
|
|
815
|
+
- Updated dependencies [94a0bbc]
|
|
816
|
+
- Updated dependencies [d6bfb3d]
|
|
817
|
+
- Updated dependencies [a2266a6]
|
|
818
|
+
- Updated dependencies [d25a0ec]
|
|
819
|
+
- Updated dependencies [667b83e]
|
|
820
|
+
- Updated dependencies [627b188]
|
|
821
|
+
- Updated dependencies [8d4eae7]
|
|
822
|
+
- Updated dependencies [857a6cf]
|
|
823
|
+
- Updated dependencies [65a3a84]
|
|
824
|
+
- Updated dependencies [d5749d7]
|
|
825
|
+
- Updated dependencies [ccd9397]
|
|
826
|
+
- Updated dependencies [bca935b]
|
|
827
|
+
- Updated dependencies [d92c72d]
|
|
828
|
+
- Updated dependencies [c54c822]
|
|
829
|
+
- Updated dependencies [8dcc0f5]
|
|
830
|
+
- Updated dependencies [75b9e51]
|
|
831
|
+
- Updated dependencies [0a2f233]
|
|
832
|
+
- Updated dependencies [8621cdd]
|
|
833
|
+
- Updated dependencies [6f23667]
|
|
834
|
+
- Updated dependencies [5d21a48]
|
|
835
|
+
- Updated dependencies [19365b7]
|
|
836
|
+
- Updated dependencies [b7ed26d]
|
|
837
|
+
- Updated dependencies [68dea0b]
|
|
838
|
+
- Updated dependencies [64f8cbe]
|
|
839
|
+
- Updated dependencies [b3a3d83]
|
|
840
|
+
- Updated dependencies [7a55913]
|
|
841
|
+
- Updated dependencies [35accbf]
|
|
842
|
+
- Updated dependencies [6038de7]
|
|
843
|
+
- Updated dependencies [eb95d97]
|
|
844
|
+
- Updated dependencies [e4c2dc8]
|
|
845
|
+
- Updated dependencies [1bd2795]
|
|
846
|
+
- Updated dependencies [8186a70]
|
|
847
|
+
- Updated dependencies [a329cca]
|
|
848
|
+
- Updated dependencies [6eec18c]
|
|
849
|
+
- Updated dependencies [4d7bebf]
|
|
850
|
+
- Updated dependencies [821ac7a]
|
|
851
|
+
- Updated dependencies [8f81731]
|
|
852
|
+
- Updated dependencies [4965bfa]
|
|
853
|
+
- Updated dependencies [8b50cb3]
|
|
854
|
+
- Updated dependencies [8c2db68]
|
|
855
|
+
- Updated dependencies [22b5e54]
|
|
856
|
+
- Updated dependencies [0166bd5]
|
|
857
|
+
- Updated dependencies [9b702dc]
|
|
858
|
+
- Updated dependencies [ab16331]
|
|
859
|
+
- @objectstack/spec@17.0.0-rc.1
|
|
860
|
+
- @objectstack/platform-objects@17.0.0-rc.1
|
|
861
|
+
- @objectstack/core@17.0.0-rc.1
|
|
862
|
+
- @objectstack/metadata-core@17.0.0-rc.1
|
|
863
|
+
- @objectstack/formula@17.0.0-rc.1
|
|
864
|
+
- @objectstack/types@17.0.0-rc.1
|
|
865
|
+
|
|
3
866
|
## 17.0.0-rc.0
|
|
4
867
|
|
|
5
868
|
### Minor Changes
|