@objectstack/plugin-approvals 17.0.0-rc.5 → 17.0.0
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 +3144 -0
- package/dist/index.d.mts +1621 -1695
- package/dist/index.d.ts +1621 -1695
- package/dist/index.js +94 -45
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +96 -47
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,3149 @@
|
|
|
1
1
|
# @objectstack/plugin-approvals
|
|
2
2
|
|
|
3
|
+
## 17.0.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 14252d3: feat(approvals): cross-organization approver targeting — a plant document can
|
|
8
|
+
require a group-side sign-off (ADR-0105 D9)
|
|
9
|
+
|
|
10
|
+
One organization id used to decide three different things at once in
|
|
11
|
+
`openNodeRequest`: where the request row lives, where its inbox index rows
|
|
12
|
+
live, and **where its approvers are looked up**. The first two are the
|
|
13
|
+
request's own organization by definition. The third is not — a group CFO holds
|
|
14
|
+
her `cfo` position in the GROUP organization while the purchase order she signs
|
|
15
|
+
off lives in the PLANT organization. `expandPositionUsers('cfo', <plant>)`
|
|
16
|
+
matched nobody, the slot fell back to the dead `position:cfo` literal, and a
|
|
17
|
+
group escalation could not be expressed at all.
|
|
18
|
+
|
|
19
|
+
An approver may now declare which organization's directory resolves it:
|
|
20
|
+
|
|
21
|
+
```yaml
|
|
22
|
+
approvers:
|
|
23
|
+
- { type: position, value: plant_manager, group: plant }
|
|
24
|
+
- { type: position, value: cfo, organization: $root, group: finance }
|
|
25
|
+
behavior: per_group
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- **`$root` / `$parent`** walk D6's `parent_organization_id` tree, so the two
|
|
29
|
+
common intents need **no deployment knowledge** — flow metadata is portable
|
|
30
|
+
across environments while organization ids are minted per deployment. A slug
|
|
31
|
+
covers what the symbols cannot, notably a **sibling** organization (a
|
|
32
|
+
shared-services centre approving payables for every plant).
|
|
33
|
+
- Declared **per approver**, so one node can require a plant manager and a
|
|
34
|
+
group CFO in parallel. A node-level form cannot express that without
|
|
35
|
+
splitting into serial nodes, which changes the semantics.
|
|
36
|
+
- **Bounded, not free:** the target must share a `parent_organization_id` root
|
|
37
|
+
with the request's organization. The rule reads only the organization tree —
|
|
38
|
+
never the submitter — so one flow routes identically for everyone.
|
|
39
|
+
|
|
40
|
+
Everything else fails loudly rather than quietly:
|
|
41
|
+
|
|
42
|
+
- a non-`group` posture **refuses** the declaration (a `group` → `isolated`
|
|
43
|
+
migration must not silently reroute approvals);
|
|
44
|
+
- an approver type with no org-scoped directory (`user` / `field` / `manager` /
|
|
45
|
+
`team`) refuses it too, and a new `approval-approver-cross-org-unsupported`
|
|
46
|
+
lint catches that at author time;
|
|
47
|
+
- a targeted approver holding no membership in the request's organization is
|
|
48
|
+
dropped with a warning naming them — D2's union wall would otherwise hide the
|
|
49
|
+
request from someone already routed to, so the node's existing
|
|
50
|
+
`onEmptyApprovers` policy takes over instead of leaving an unopenable task.
|
|
51
|
+
|
|
52
|
+
Nothing changes for an approver without `organization`: same resolution, same
|
|
53
|
+
queries, no extra reads.
|
|
54
|
+
|
|
55
|
+
- f5a4ef0: refactor!: ADR-0112 batch 2 — sweep the lowercase error-code emitters (#4003)
|
|
56
|
+
|
|
57
|
+
Continues #3841 per ADR-0112. Batch 1 (#3988) settled the vocabulary and closed
|
|
58
|
+
the set; this batch moves the emitters that still spoke lowercase `snake_case`
|
|
59
|
+
onto it.
|
|
60
|
+
|
|
61
|
+
**Wire-visible change.** Error codes on these surfaces change spelling. Generic
|
|
62
|
+
conditions collapse onto the standard catalog rather than keeping a synonym:
|
|
63
|
+
`unauthorized`/`unauthenticated` → `UNAUTHENTICATED`, `forbidden` →
|
|
64
|
+
`PERMISSION_DENIED`, `not_found` → `RESOURCE_NOT_FOUND`, `internal` →
|
|
65
|
+
`INTERNAL_ERROR`, `unavailable` → `SERVICE_UNAVAILABLE`, `not_supported` →
|
|
66
|
+
`NOT_IMPLEMENTED`, `bad_request` → `INVALID_REQUEST`. Domain conditions get codes
|
|
67
|
+
registered in `ERROR_CODE_LEDGER` (`MARKETPLACE_STORAGE_FAILED`,
|
|
68
|
+
`PLUGIN_MANIFEST_INVALID`, `ITEM_LOCKED`, `DELIVERY_NOT_ELIGIBLE`, …). Swept:
|
|
69
|
+
`cloud-connection`, `plugin-auth`, `hono`, `metadata-protocol`, `rest`,
|
|
70
|
+
`service-messaging`, `service-automation`, `trigger-api`.
|
|
71
|
+
|
|
72
|
+
Branch on `error.code` values rather than pattern-matching their case: the
|
|
73
|
+
console's fix for the same rename (objectui#2977) reads codes case-insensitively
|
|
74
|
+
for exactly this reason, and that is the pattern to copy in your own consumers if
|
|
75
|
+
you support servers on both sides of the change.
|
|
76
|
+
|
|
77
|
+
**Four routes stop putting a code in the message slot.** The webhook redeliver
|
|
78
|
+
route, the API-trigger webhook, and two `rest` routes answered
|
|
79
|
+
`{ success: false, error: '<code>', message }` — the code occupying `error`, the
|
|
80
|
+
declared object envelope nowhere. They now emit `error: { code, message }`, and
|
|
81
|
+
three API-trigger branches gained a message they never had. Clients reading
|
|
82
|
+
`body.error` as a string on those routes must read `body.error.code`.
|
|
83
|
+
|
|
84
|
+
**`ConnectorErrorCategory` / `ConnectorRetryStrategy`** (ADR-0112 D9a):
|
|
85
|
+
`@objectstack/spec` exported two mutually incompatible `ErrorCategory` types and
|
|
86
|
+
two `RetryStrategy` types. The connector-side pair is renamed; importers of the
|
|
87
|
+
`integration` subpath update the name. Side effect: the api-side `ErrorCategory`
|
|
88
|
+
and `RetryStrategy` now appear in the generated API reference at all — the name
|
|
89
|
+
collision had been silently dropping them.
|
|
90
|
+
|
|
91
|
+
**`OAUTH_REGISTER_FAILED` replaces an unbounded code source.** The OAuth client
|
|
92
|
+
registration route put better-auth's arbitrary `body.error` string straight into
|
|
93
|
+
`error.code`. The code is now ours and the upstream discriminator moved to
|
|
94
|
+
`details.upstreamError`.
|
|
95
|
+
|
|
96
|
+
**Not swept, deliberately.** `sys_metadata_audit.code` keeps its lowercase values
|
|
97
|
+
(ADR-0112 D6b): it is persisted audit history, and the same column holds
|
|
98
|
+
non-error outcomes (`ok`, `lock_override`). Diagnostics records that ship inside a
|
|
99
|
+
200 keep theirs (D6c), as do field-level codes (D6, #3977) and the CLI's
|
|
100
|
+
`--json` output contract.
|
|
101
|
+
|
|
102
|
+
A `check:error-code-casing` CI guard now fails on a new lowercase literal in a
|
|
103
|
+
code position, since the ledger's casing rule can only police codes that someone
|
|
104
|
+
registers.
|
|
105
|
+
|
|
106
|
+
- f92096b: fix(approvals): an approval action is recorded against the authenticated caller, never a body field (#3800)
|
|
107
|
+
|
|
108
|
+
Every mutating approvals entrypoint takes an `actorId`, and the REST routes
|
|
109
|
+
filled it from `body.actorId ?? body.actor_id ?? context.userId` — so the body
|
|
110
|
+
won. The service then authorized _that value_: `pending_approvers.includes(
|
|
111
|
+
input.actorId)` for a decision, `submitter_id === actorId` for a recall. It never
|
|
112
|
+
checked that the value named the caller.
|
|
113
|
+
|
|
114
|
+
So any authenticated user could POST `{"actorId": "<someone else>"}` and have
|
|
115
|
+
that person's approval recorded, the request finalized, and the owning flow run
|
|
116
|
+
resumed down the `approve` edge — or name a request's submitter and recall it.
|
|
117
|
+
With `api.requireAuth` unset the anonymous-deny never fires either, so an
|
|
118
|
+
unauthenticated request could do the same.
|
|
119
|
+
|
|
120
|
+
#3783 drew this line for the _data-write_ identity and called the audit-row half
|
|
121
|
+
"tolerable". It was not: the same unchecked string was the authorization key, so
|
|
122
|
+
naming someone else was not a mislabelled audit row, it was how you got through
|
|
123
|
+
the door.
|
|
124
|
+
|
|
125
|
+
The actor is now resolved server-side (`ApprovalService.resolveActor`) on all
|
|
126
|
+
nine entrypoints — `decide` / `decideNode`, `recall`, `sendBack`, `resubmit`,
|
|
127
|
+
`reassign`, `remind`, `requestInfo`, `comment`.
|
|
128
|
+
|
|
129
|
+
**The rule is not "`actorId` must equal `context.userId`."** A slot can
|
|
130
|
+
legitimately be keyed by something else: the approver resolver stores the
|
|
131
|
+
`type:value` literal when a graph lookup finds no holders, and the Console picks
|
|
132
|
+
from the caller's own identity list — user id, email, or `role:<r>`. The rule is
|
|
133
|
+
**"the actor must be an identity the server can prove belongs to the caller"**:
|
|
134
|
+
|
|
135
|
+
- A **system** context keeps its explicit actor. The SLA sweep's reserved
|
|
136
|
+
`system:sla` sentinel and the ADR-0043 action link — whose single-use hashed
|
|
137
|
+
token binds exactly one approver — are unchanged. They are the only callers
|
|
138
|
+
holding a trustworthy actor with no session behind them.
|
|
139
|
+
- A caller with **no identity at all** is now refused. This is the anonymous case
|
|
140
|
+
above.
|
|
141
|
+
- **No `actorId`, or one naming the caller**, resolves to the caller. This is the
|
|
142
|
+
common path and what the Console already sends.
|
|
143
|
+
- **Any other value** is accepted only when the server can prove the caller holds
|
|
144
|
+
it — `position:<p>` / `role:<p>` against the positions on the resolved authz
|
|
145
|
+
context, or the caller's own email (one lazy `sys_user` read, taken only when
|
|
146
|
+
nothing cheaper matched). Otherwise `FORBIDDEN`.
|
|
147
|
+
|
|
148
|
+
REST still forwards the body value; it is now a _hint_ the service validates,
|
|
149
|
+
which is what keeps the email and `type:value` slot cases working.
|
|
150
|
+
|
|
151
|
+
**Upgrade note.** A client that deliberately sent another user's `actorId` now
|
|
152
|
+
gets `403 FORBIDDEN` instead of silently succeeding. Send the action as the
|
|
153
|
+
acting user's own session — the field can be omitted entirely, and the caller is
|
|
154
|
+
used. Server-to-server callers that legitimately act for someone else should
|
|
155
|
+
present a system context, as the SLA sweep and the action link already do.
|
|
156
|
+
|
|
157
|
+
This also makes two existing claims true that were previously aspirational: the
|
|
158
|
+
approval object's declared actions say "`actorId` defaults to the caller
|
|
159
|
+
server-side… the service remains the authority on who may act", and
|
|
160
|
+
`attachViewers` documents `can_act` as mirroring "the exact authorization the
|
|
161
|
+
decision methods enforce".
|
|
162
|
+
|
|
163
|
+
- 2826d1e: fix(automation,approvals): an approval decision can no longer succeed while its flow stays parked (#4420)
|
|
164
|
+
|
|
165
|
+
A flow paused at an `approval` node, a deploy, then an approver clicking
|
|
166
|
+
Approve: the request row flipped to `approved`, the UI toasted success — and
|
|
167
|
+
the flow never moved. No next-stage request, no error, the record's mirrored
|
|
168
|
+
status frozen mid-workflow. Approval flows pause for days by design, so a
|
|
169
|
+
restart mid-flight is the normal case: every release could quietly zombify
|
|
170
|
+
every in-flight approval, with the approvers none the wiser.
|
|
171
|
+
|
|
172
|
+
Durable suspended runs (#1518) had shipped and were not the missing piece. Two
|
|
173
|
+
other things were.
|
|
174
|
+
|
|
175
|
+
**The wiring could enable a store over a table nobody had created.** Object
|
|
176
|
+
registration and store activation resolve different services in different
|
|
177
|
+
phases — `manifest` at `init()`, `objectql` at `start()` — and the plugin
|
|
178
|
+
declared no ordering. Composed ahead of ObjectQL, `init()` found no `manifest`,
|
|
179
|
+
warned, and continued; `start()` then attached the DB-backed store anyway. Every
|
|
180
|
+
suspend failed with `no such table: sys_automation_run` into a log line nobody
|
|
181
|
+
read, pauses silently stayed in memory, and the next restart lost them all.
|
|
182
|
+
Now: `AutomationServicePlugin` declares `optionalDependencies:
|
|
183
|
+
['com.objectstack.engine.objectql']` (order-if-present, per ADR-0116 — an
|
|
184
|
+
engine-less kernel must still boot); a registration missed at `init()` is
|
|
185
|
+
retried at `start()`, which still lands before ObjectQL's schema sync; the
|
|
186
|
+
store is never attached when registration did not happen, and says so at
|
|
187
|
+
**error** level instead of warning; the table is probed once at boot so a
|
|
188
|
+
broken setup surfaces there rather than one failed write at a time; and a
|
|
189
|
+
failed durable write of a paused run is logged at error — it is data loss in
|
|
190
|
+
waiting, not a warning.
|
|
191
|
+
|
|
192
|
+
**A reported resume failure read as success.** `AutomationEngine.resume()`
|
|
193
|
+
answers a lost run by _returning_ `{ success: false }`, never by throwing.
|
|
194
|
+
`ApprovalService` discarded that return value, and `decide()` counted only a
|
|
195
|
+
thrown error as failure — so a decision against a dead run came back
|
|
196
|
+
`resumed: true`, HTTP 200. Resume failures are now classified
|
|
197
|
+
(`RUN_NOT_FOUND`, `STORE_UNAVAILABLE`, `RESUME_IN_PROGRESS`, joining
|
|
198
|
+
`PERMISSION_DENIED` / `INVALID_SIGNAL`), so a run that is gone for good is
|
|
199
|
+
distinguishable from a store that is merely unreachable, and the raw resume
|
|
200
|
+
route maps them to 404 / 503 / 409.
|
|
201
|
+
|
|
202
|
+
Approvals acts on them. A new `AutomationEngine.hasSuspendedRun(runId)` — which
|
|
203
|
+
reads the suspension store, unlike `getRun()`, and throws rather than answering
|
|
204
|
+
`false` when the store is unreadable — pre-flights every flow-advancing
|
|
205
|
+
operation (`decide`, `sendBack`, `resubmit`) **before its first write**, so the
|
|
206
|
+
zombie half-state is never created rather than merely reported: the decision
|
|
207
|
+
fails with `RESUME_TARGET_LOST` (HTTP 409) and the request stays actionable. A
|
|
208
|
+
resume that fails after the decision is durable can no longer be undone, but it
|
|
209
|
+
now throws `RESUME_FAILED` (HTTP 500) naming the stranded run instead of
|
|
210
|
+
reporting success. A concurrent duplicate resume stays benign — the engine's
|
|
211
|
+
idempotency guard is doing its job — and reports through the new optional
|
|
212
|
+
`resumeError` field. Recall and revise-window cancellation stay non-fatal by
|
|
213
|
+
design (they abandon the request), but log at error with the reason instead of
|
|
214
|
+
swallowing it. Compositions with no automation engine attached are unaffected.
|
|
215
|
+
|
|
216
|
+
Existing zombie requests from affected deployments (already `approved`, run
|
|
217
|
+
stranded) are not repaired by this change — `releaseDeadRunRequests` only
|
|
218
|
+
sweeps requests that are still `pending`.
|
|
219
|
+
|
|
220
|
+
- 91f4c78: feat(approvals,spec): structured reassign hand-off parties on `sys_approval_action` (#4365)
|
|
221
|
+
|
|
222
|
+
A reassign's audit row used to encode "who handed the slot to whom" only inside
|
|
223
|
+
a default free-text comment — `"<from_id> → <to_id>"`, two raw user ids — which
|
|
224
|
+
clients could neither parse reliably nor render readably, so the approvals
|
|
225
|
+
timeline showed opaque identifier soup for the single most important fact of
|
|
226
|
+
the entry.
|
|
227
|
+
|
|
228
|
+
- `sys_approval_action` gains `reassign_from` / `reassign_to`
|
|
229
|
+
(`lookup('sys_user')`), written by `ApprovalService.reassign()`.
|
|
230
|
+
- `comment` is pure user input again: nothing is invented when the actor
|
|
231
|
+
supplies none.
|
|
232
|
+
- `listActions()` resolves both parties' display names into
|
|
233
|
+
`reassign_from_name` / `reassign_to_name`, alongside the existing
|
|
234
|
+
`actor_name`, so timelines can render "from A to B" without extra lookups.
|
|
235
|
+
- `ApprovalActionRow` (spec contract) declares the four new fields.
|
|
236
|
+
|
|
237
|
+
Pre-existing rows keep their legacy comment; clients should prefer the
|
|
238
|
+
structured fields when present and fall back to `comment` otherwise.
|
|
239
|
+
|
|
240
|
+
- ddc2527: fix(approvals): the ADR-0044 revise window is a service-owned node type, not a bare `wait` (#3823)
|
|
241
|
+
|
|
242
|
+
#3801 gated `POST /api/v1/automation/:name/runs/:runId/resume` on the **node type**
|
|
243
|
+
that produced the suspension: an `approval` pause declares
|
|
244
|
+
`resumeAuthority: 'service'`, so it continues only through `ApprovalService`.
|
|
245
|
+
ADR-0044's **revise window** was the same trust boundary in a shape that key
|
|
246
|
+
could not see. Send-back parked the run on an ordinary `wait` node the flow
|
|
247
|
+
author placed — correctly `resumeAuthority: 'any'`, because a signal wait is
|
|
248
|
+
_meant_ to be resumable by an external producer — and `ApprovalService.resubmit`
|
|
249
|
+
was the only thing that checked anything about continuing it.
|
|
250
|
+
|
|
251
|
+
Demonstrated (not reasoned) against the real engine: a raw `resume(runId)` with
|
|
252
|
+
an **empty body**, from any caller, walked the `resubmit` back-edge into the
|
|
253
|
+
approval node and opened round N+1 with **no submitter check and no `resubmit`
|
|
254
|
+
audit row** (`['submit','revise']` — no third row, ever). Worse, when another
|
|
255
|
+
request was already pending on the record — the exact case `resubmit` refuses
|
|
256
|
+
with `DUPLICATE_REQUEST` _specifically to keep the run alive_ — the raw resume
|
|
257
|
+
went around that guard: the approval node's re-entry failed **after** the engine
|
|
258
|
+
consumed the suspension, and the run was **permanently destroyed** with its
|
|
259
|
+
round-N request stuck `returned` and no resubmit able to reach it.
|
|
260
|
+
|
|
261
|
+
The revise pause is therefore its own node type:
|
|
262
|
+
|
|
263
|
+
- **`approval_revise`** (`APPROVAL_REVISE_NODE_TYPE`), registered by
|
|
264
|
+
`@objectstack/plugin-approvals` alongside the `approval` node, declaring
|
|
265
|
+
`resumeAuthority: 'service'`. It stays a first-class box on the canvas, in the
|
|
266
|
+
run log and in the suspended-run store — only the _reuse_ of `wait` was wrong.
|
|
267
|
+
It takes **no config**: the window ends on the submitter's explicit resubmit,
|
|
268
|
+
never on a signal or timer. The `resumeAuthority` gate itself is unchanged.
|
|
269
|
+
- `sendBack` refuses a `revise` edge whose target is not an `approval_revise`
|
|
270
|
+
node, **before any mutation** (like the existing missing-`revise`-edge check),
|
|
271
|
+
so no run can be parked in a window something else can advance.
|
|
272
|
+
- New gating lint `flow-approval-revise-target-not-service-owned`
|
|
273
|
+
(severity `error`, on `os build` / `os validate` / `os lint` and the runtime
|
|
274
|
+
metadata publish gate) rejects the old shape at authoring time.
|
|
275
|
+
|
|
276
|
+
**Upgrading a flow authored against the original ADR-0044 D3.** One token:
|
|
277
|
+
|
|
278
|
+
- **FROM:** `{ id: 'wait_revision', type: 'wait', waitEventConfig: { eventType: 'signal', … } }`
|
|
279
|
+
- **TO:** `{ id: 'wait_revision', type: 'approval_revise' }` — drop
|
|
280
|
+
`waitEventConfig` / any `config`; the window has no event to wait on.
|
|
281
|
+
|
|
282
|
+
Until you do, such a flow keeps registering and running and its approvals stay
|
|
283
|
+
decidable (`approve` / `reject` / `recall` / `reassign` are untouched), but
|
|
284
|
+
**send-back is refused** with a message naming the node and this fix, and
|
|
285
|
+
re-publishing it reports the lint error. A run _already parked_ in a legacy
|
|
286
|
+
revise window keeps its recorded node type (a republish never re-types a live
|
|
287
|
+
pause) and is drained by `resubmit` or `recall` as usual.
|
|
288
|
+
|
|
289
|
+
ADR-0044's 2026-07-28 amendment records the reversal of its D3 and of its
|
|
290
|
+
`Alternatives` rejection of a service-owned revise pause, with the evidence
|
|
291
|
+
above; the implementation section there records what shipped, why the approval
|
|
292
|
+
node does not re-suspend itself instead, and why no ADR-0087 conversion was
|
|
293
|
+
added for the old shape.
|
|
294
|
+
|
|
295
|
+
- fb90784: fix(approvals): the status mirror names the human who caused the transition (#3783)
|
|
296
|
+
|
|
297
|
+
When an approval moves, the service writes the new status onto the business
|
|
298
|
+
record (`approvalStatusField`). That write is what fires the record-change flows
|
|
299
|
+
bound to that object — so it is the seam "when the invoice is approved, do X"
|
|
300
|
+
runs through. It presented a bare `{ isSystem: true }` context with **no
|
|
301
|
+
`userId`**, at six call sites that each know exactly who acted: a submitter
|
|
302
|
+
submitting, an approver approving, rejecting, sending back, recalling.
|
|
303
|
+
|
|
304
|
+
Combined with #3760 — which stopped letting a `runAs:'user'` run with no trigger
|
|
305
|
+
user touch data — that identity gap made the most natural approvals automation
|
|
306
|
+
there is unwritable in its obvious form. The cascade inherited no user, so its
|
|
307
|
+
data nodes were refused, and the author's only way forward was to declare
|
|
308
|
+
`runAs: 'system'` and take blanket elevation for a case where a perfectly good
|
|
309
|
+
scoped identity existed at the call site all along.
|
|
310
|
+
|
|
311
|
+
The mirror now carries the acting user. It stays `isSystem` — the record is
|
|
312
|
+
normally locked while its approval is live, so only a platform write can land the
|
|
313
|
+
status — because elevation and anonymity are separate choices, and this write
|
|
314
|
+
only ever needed the first. Cascades now run as the deciding user with RLS
|
|
315
|
+
enforced.
|
|
316
|
+
|
|
317
|
+
- **The identity is the authenticated principal, never the request body's
|
|
318
|
+
`actorId`.** `actorId` arrives from the caller (`body.actorId ?? context.userId`)
|
|
319
|
+
and is only checked against the pending approver slate, never against the
|
|
320
|
+
caller. That is tolerable on an audit row; promoting it to the identity of an
|
|
321
|
+
RLS-scoped write would have turned a mislabelled audit trail into identity
|
|
322
|
+
spoofing.
|
|
323
|
+
- **Approval-by-email-link is attributed too.** ADR-0043 action links carry no
|
|
324
|
+
session, so they used to decide as pure system. The single-use hashed token
|
|
325
|
+
binds exactly one approver and is re-checked against the live slate at
|
|
326
|
+
redemption — that is an authentication — so the redeemed decision now presents
|
|
327
|
+
that approver, and an emailed approval cascades identically to one made in the
|
|
328
|
+
UI.
|
|
329
|
+
- **The two machine-driven transitions stay user-less on purpose**: the SLA
|
|
330
|
+
escalation's auto-decision and the dead-run sweep. `system:sla` and
|
|
331
|
+
`system:dead-run` are reserved audit actors, not users, and presenting one as a
|
|
332
|
+
user would put a non-user in `updated_by` and in every downstream flow's
|
|
333
|
+
identity. A flow that wants to react to those declares `runAs:'system'` — the
|
|
334
|
+
honest answer, and now a deliberate one rather than an artefact.
|
|
335
|
+
- **Attribution only — the write is not newly org-scoped.** On an
|
|
336
|
+
ExecutionContext `tenantId` is a driver-scoping knob, not attribution
|
|
337
|
+
(ObjectQL turns it into a tenant predicate), so passing the request's org would
|
|
338
|
+
have silently no-op'd the mirror on a record whose org differs. The automation
|
|
339
|
+
engine already back-fills a run's `tenantId` from the resolved user's grants.
|
|
340
|
+
|
|
341
|
+
**Visible change:** the mirrored record's `updated_by` now names the acting user
|
|
342
|
+
instead of retaining its previous value — ObjectQL's audit stamping is gated on
|
|
343
|
+
the write context's `userId` alone, and `isSystem` buys no exemption. That is the
|
|
344
|
+
attribution this fix is for: the approver who set the record to `approved` is now
|
|
345
|
+
its last modifier.
|
|
346
|
+
|
|
347
|
+
- a6c3f38: feat(approvals): expose the pending node's `lockRecord` policy on the request row (#3814, objectui#2902)
|
|
348
|
+
|
|
349
|
+
An approval node declares `lockRecord` (default `true`), and the record-lock
|
|
350
|
+
`beforeUpdate` hook enforces exactly that: `lockRecord: false` and the record
|
|
351
|
+
stays writable for the whole time the node waits. The behavior was correct and
|
|
352
|
+
has been since Phase B — but it was **invisible to every client**.
|
|
353
|
+
|
|
354
|
+
`rowFromRequest` parses `node_config_json` and projects a whitelist out of it
|
|
355
|
+
(`__flowLabel`, `__nodeLabel`, `__round`, `escalation.timeoutHours`,
|
|
356
|
+
`decisionOutputs`). `lockRecord` was never in that list, and no other field on
|
|
357
|
+
`ApprovalRequestRow` carried the lock either. So the strongest thing a console
|
|
358
|
+
could learn from `GET /approvals/requests` was _"a pending request exists"_ —
|
|
359
|
+
from which it can only assume the record is locked.
|
|
360
|
+
|
|
361
|
+
That assumption is wrong on every opted-out node, and a flow that chains nodes
|
|
362
|
+
with different policies makes it visibly wrong: the same UI state renders for
|
|
363
|
+
"you may edit this" and "the server will reject your save with `RECORD_LOCKED`".
|
|
364
|
+
The console has no third option — guessing the other way would offer an edit
|
|
365
|
+
that dies on save.
|
|
366
|
+
|
|
367
|
+
`ApprovalRequestRow` now carries **`lock_record: boolean`**, read from the same
|
|
368
|
+
snapshot the hook reads, with the same `!== false` default. Present on every
|
|
369
|
+
service read (`openNodeRequest` / `getRequest` / `listRequests`), so the flag a
|
|
370
|
+
client renders and the rule the server applies cannot drift.
|
|
371
|
+
|
|
372
|
+
Additive and backward compatible — nothing to migrate. A client that wants
|
|
373
|
+
node-accurate lock state reads `request.lock_record`; treat `undefined` (an
|
|
374
|
+
older backend) as locked, which is the pre-existing behavior.
|
|
375
|
+
|
|
376
|
+
The showcase's `showcase_budget_approval` now declares `lockRecord: false` on
|
|
377
|
+
its single-approver Manager Review and keeps `true` on the multi-approver
|
|
378
|
+
Executive Review, so both policies are exercised in one flow.
|
|
379
|
+
|
|
380
|
+
- 5fa04fb: Point the account app's **Approvals** navigation entry at the Approvals Inbox component, and contribute an **Approvals Inbox** entry to Setup (#7234).
|
|
381
|
+
|
|
382
|
+
The entry point has not moved — the account menu still shows **Approvals** with the same
|
|
383
|
+
label and icon in every locale. Its destination has. It used to open the raw
|
|
384
|
+
`sys_approval_request` grid, which is an admin/diagnostic view of the engine's own table
|
|
385
|
+
and cannot show an approver a single decision button: every action on that object is gated
|
|
386
|
+
on `record.viewer.can_act || record.viewer.can_override`, and the `viewer` block is
|
|
387
|
+
attached only by the approvals REST path, never by the generic data API the object route
|
|
388
|
+
reads. The result was a correct-looking list of rows nobody could act on. The entry is now
|
|
389
|
+
`{ type: 'component', componentRef: 'approvals:inbox' }`, so it opens the full inbox —
|
|
390
|
+
decision actions, business vocabulary, node progress and the request drawer.
|
|
391
|
+
|
|
392
|
+
- **Account app**: `nav_account_approvals` becomes a component entry gated by
|
|
393
|
+
`requiresService: 'approvals'`, so it disappears where `plugin-approvals` is not
|
|
394
|
+
installed (the previous `requiresObject` gate does not apply to a component entry).
|
|
395
|
+
- **Setup**: `plugin-approvals` contributes a new **Approvals Inbox** entry at the top of
|
|
396
|
+
**Setup → Approvals**, above the three raw tables, which stay exactly as they were —
|
|
397
|
+
admin-gated by `manage_platform_settings` and now unambiguously the diagnostic surface.
|
|
398
|
+
Labels ship in all four locales (zh-CN 审批中心).
|
|
399
|
+
- `sys_approval_request` is no longer surfaced raw to end users anywhere.
|
|
400
|
+
- **Docs**: the approver's queue is documented as the Approvals Inbox, with a snippet for
|
|
401
|
+
mounting it in any business app — one navigation entry naming the component-registry key
|
|
402
|
+
`approvals:inbox`, never a console path.
|
|
403
|
+
|
|
404
|
+
Reaching the inbox end to end in the browser additionally requires the console pin bump,
|
|
405
|
+
tracked separately.
|
|
406
|
+
|
|
407
|
+
- d75edb9: Approval nodes now resolve `field` / `manager` approvers against the record's **live** state at node entry, not the trigger snapshot the flow froze at submit time (#3447). An earlier step — or the approver of an earlier step — can now write the field that routes a later step's approvers, enabling dynamic routing / dynamic co-sign (e.g. a lead reviewer picking which departments co-review, then those departments resolving as parallel approvers). Graph approvers (team / position / department / tier) already resolved live; this brings the in-record types into line.
|
|
408
|
+
|
|
409
|
+
Also fixes two latent defects on the same path: a multi-select user field now fans out into one approver slot per user (previously the array was stringified to a single bogus id), and out-of-office delegation is applied per fanned-out user (previously silently skipped for multi-value fields). When the record can't be re-read (hard-deleted mid-flow, or a backend that can't serve a point read), resolution falls back to the trigger snapshot and warns rather than wedging the flow.
|
|
410
|
+
|
|
411
|
+
- 57a3bb3: fix(automation,approvals): the run-resume route is gated by the node the run is parked on (#3801)
|
|
412
|
+
|
|
413
|
+
`POST /api/v1/automation/:name/runs/:runId/resume` forwarded a caller-supplied
|
|
414
|
+
`{ inputs, output, branchLabel }` straight into `AutomationEngine.resume`, and
|
|
415
|
+
`resumeInternal` validated **machine state only** — the concurrent-resume latch,
|
|
416
|
+
the run exists, the flow exists, the suspended node still exists. Nothing asked
|
|
417
|
+
_who was calling_.
|
|
418
|
+
|
|
419
|
+
Approval nodes suspend and resume through exactly that mechanism. So a resume
|
|
420
|
+
carrying `branchLabel: 'approve'` walked the approve edge with **no approver
|
|
421
|
+
check, no `sys_approval_action` row and no status mirror** — the
|
|
422
|
+
`sys_approval_request` row and the run then disagreed permanently. The only
|
|
423
|
+
thing standing between the route and the approvals rules was convention; the
|
|
424
|
+
showcase spelled it out in a comment ("decide via the approvals API, never a raw
|
|
425
|
+
engine `resume`"), and a comment in an example is not an access control.
|
|
426
|
+
|
|
427
|
+
Removing the route was not the fix: it is load-bearing for **screen flows** —
|
|
428
|
+
the UI flow-runner posts `{ inputs }` there to advance a paused `screen` node.
|
|
429
|
+
The gate therefore keys on **what the run is parked on**:
|
|
430
|
+
|
|
431
|
+
- `ActionDescriptor.resumeAuthority` (`'any'` | `'service'`, default `'any'`) —
|
|
432
|
+
a pausing node declares who may continue it. `approval` declares `'service'`.
|
|
433
|
+
- The engine refuses a `'service'` suspension unless the signal carries
|
|
434
|
+
`RESUME_AUTHORITY_SERVICE` (`@objectstack/spec/contracts`), a **symbol** the
|
|
435
|
+
owning service stamps in-process — a JSON body can never produce one, so the
|
|
436
|
+
transport cannot forge it. `ApprovalService` stamps it on the tail of a
|
|
437
|
+
decision it has already authorized and recorded.
|
|
438
|
+
- The gate follows a **subflow** pause down to the child the signal would
|
|
439
|
+
actually reach, so resuming the parent is not a way around it.
|
|
440
|
+
- Refusal returns `{ success: false, code: 'forbidden' }` and the route answers
|
|
441
|
+
**403**. Nothing is consumed — the request stays pending and the run stays
|
|
442
|
+
parked, so the real decision still lands.
|
|
443
|
+
|
|
444
|
+
`screen` and `wait` pauses are unchanged, as is every path that already went
|
|
445
|
+
through the approvals API. What changes for consumers:
|
|
446
|
+
|
|
447
|
+
- **FROM:** finishing an approval with
|
|
448
|
+
`client.automation.resume(flow, runId, { branchLabel: 'approve' })`
|
|
449
|
+
**TO:** `client.approvals.approve(requestId, …)` (or `.reject` / `.recall`).
|
|
450
|
+
The old call now answers 403 and changes nothing.
|
|
451
|
+
- Registering your own pausing node whose continuation belongs to a service
|
|
452
|
+
rather than to whoever holds the run id? Declare `resumeAuthority: 'service'`
|
|
453
|
+
on its descriptor and stamp `RESUME_AUTHORITY_SERVICE` on the signal from that
|
|
454
|
+
service.
|
|
455
|
+
|
|
456
|
+
A suspension now records the node type that produced it
|
|
457
|
+
(`SuspendedRun.nodeType` / `sys_automation_run.node_type`), captured at suspend
|
|
458
|
+
time so a flow republished mid-pause cannot re-type the node out from under the
|
|
459
|
+
gate; rows written before this fall back to the flow definition.
|
|
460
|
+
|
|
461
|
+
- 2fa4ca1: Dynamic approver routing for approval nodes (#3447 P2) — three new declarative capabilities:
|
|
462
|
+
|
|
463
|
+
**`expression` approvers.** A new approver type whose CEL expression resolves WHO approves at node entry, over exactly three roots: `current.*` (the record's live state), `trigger.*` (the submit-time snapshot) and `vars.*` (flow variables, incl. upstream node outputs). `record` and bare field names are rejected before evaluation — on this platform `record` always means "the record at event time", which is ambiguous at an approval node — with error messages that prescribe the correct spelling. The optional `resolveAs: 'user' | 'department' | 'position' | 'team'` re-expands each resolved id through the same graph lookups the static types use; with `behavior: 'per_group'` each intermediate value (e.g. each returned department) forms its own sign-off group. A missing key fails the node loudly; only a present-but-empty result counts as an empty slate.
|
|
464
|
+
|
|
465
|
+
**`onEmptyApprovers` policy.** What an empty resolved slate does, node-level, for all approver types: `admin_rescue` (default — request opens for privileged takeover, the #3424 behaviour), `fail` (node fails), or `auto_approve` (skip the request, continue down the `approve` edge with `output.autoApproved = true`). To support auto-approve, the automation engine now honours `NodeExecutionResult.branchLabel` on the synchronous completion path — the field existed but was only ever consumed via resume signals.
|
|
466
|
+
|
|
467
|
+
**Decision outputs.** `decide(..., { outputs })` hands structured data from the approver to the flow: the author declares allowed keys on the node (`decisionOutputs`), approvers fill values only, and accepted outputs resume the run as `<nodeId>.<key>` variables — a later approval node's expression can read `vars.<nodeId>.picked_departments`, closing "the previous approver picks the next step's approvers" without a record-field detour. Undeclared keys reject the decision; `decision`/`requestId` are reserved. Multi-approver tallies now always pin to the open-time approver snapshot (previously unanimous re-resolved at each decision against the payload snapshot).
|
|
468
|
+
|
|
469
|
+
Also: `collectCelRootIdentifiers` is exported from `@objectstack/formula` (shared by the new `os lint` rules and the runtime pre-check, so they can never drift), resolution inputs are audited on the request snapshot as `__resolvedFrom`, and three new lint rules gate expressions, empty-slate policies and reserved output keys at author time.
|
|
470
|
+
|
|
471
|
+
- 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.
|
|
472
|
+
|
|
473
|
+
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.
|
|
474
|
+
|
|
475
|
+
**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.
|
|
476
|
+
|
|
477
|
+
**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.
|
|
478
|
+
|
|
479
|
+
`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.
|
|
480
|
+
|
|
481
|
+
- 0848bea: feat(spec)!: retire the overloaded `managedBy: 'system'` bucket — the residue becomes `system-data` (#3355)
|
|
482
|
+
|
|
483
|
+
**FROM → TO: `managedBy: 'system'` → `managedBy: 'system-data'`.** One-line fix:
|
|
484
|
+
rename the value. Nothing else about the object changes. `os migrate meta --from 16`
|
|
485
|
+
rewrites it for you; stored metadata is CONVERTED by the ADR-0087 entry
|
|
486
|
+
`object-managed-by-system-to-system-data`, never silently reinterpreted.
|
|
487
|
+
|
|
488
|
+
ADR-0103 split the overloaded `system` bucket in v16, and it split it
|
|
489
|
+
**additively**: the 20 engine-owned objects moved to the new explicit
|
|
490
|
+
`engine-owned`, while the 8 admin/user-writable ones — the RBAC link tables
|
|
491
|
+
(`sys_user_position`, `sys_user_permission_set`, `sys_position_permission_set`),
|
|
492
|
+
`sys_user_preference`, `sys_approval_delegation`, and the three messaging config
|
|
493
|
+
grids — stayed behind on `system`. That was the right move for a v16 that could
|
|
494
|
+
not break authors, but it left the enum in a state where the surviving value
|
|
495
|
+
names the half that had already moved out: `system` sitting on precisely the
|
|
496
|
+
objects a user writes.
|
|
497
|
+
|
|
498
|
+
That is not a cosmetic complaint. An author choosing between `system` and
|
|
499
|
+
`engine-owned` had nothing in the vocabulary to choose _on_, so the bucket was
|
|
500
|
+
re-overloadable by anyone reading the name in good faith — a model author most
|
|
501
|
+
of all, since "system table" reads as "the engine owns this" in every other
|
|
502
|
+
codebase. `system-data` states both boundaries explicitly: the **schema** is the
|
|
503
|
+
platform's (versus `platform`, which is tenant-modelled), the **data** is the
|
|
504
|
+
admin's or the user's (versus `engine-owned`, where the engine owns both).
|
|
505
|
+
|
|
506
|
+
Because v16 already drained the engine side, the conversion is a **one-to-one
|
|
507
|
+
mechanical value rename** with no judgement call — by construction every
|
|
508
|
+
remaining `system` declaration is writable platform data.
|
|
509
|
+
|
|
510
|
+
**One deliberate consequence — the affordance default flips.** `system` defaulted
|
|
511
|
+
LOCKED and each of the 8 objects re-opened its writes with a
|
|
512
|
+
`userActions: { create: true, edit: true, delete: true }` block. `system-data`
|
|
513
|
+
defaults **WRITABLE** (full CRUD), because a bucket that exists to say "the data
|
|
514
|
+
is yours" should not make every member ask for it back. Those blocks are now
|
|
515
|
+
redundant and have been deleted from the 8 platform objects; keep `userActions`
|
|
516
|
+
only to **NARROW**. If you converted an object that carried no `userActions`, it
|
|
517
|
+
gains the generic affordances — the honest reading of the bucket it moved into.
|
|
518
|
+
|
|
519
|
+
**No enforcement moves.** The engine write guard, the `DelegatedAdminGate`, RLS
|
|
520
|
+
and permission sets all adjudicate off resolved affordances and the principal,
|
|
521
|
+
never off the bucket name. `system-data` simply joins `platform` / `config` as a
|
|
522
|
+
bucket the fail-closed guard does not cover, because a writable default has
|
|
523
|
+
nothing to close on. The 8 objects passed that guard before (via `userActions`)
|
|
524
|
+
and pass it now (via the bucket default), for the same resolved-affordance
|
|
525
|
+
reason.
|
|
526
|
+
|
|
527
|
+
`'system'` is **retired from the load path**: the enum rejects it with a
|
|
528
|
+
prescription naming `system-data` and the one-line fix. Absorbing it silently at
|
|
529
|
+
load would leave every author still writing the name this rename exists to
|
|
530
|
+
unteach.
|
|
531
|
+
|
|
532
|
+
- 57bab76: Typed `decisionOutputs` declarations (#3447 follow-up). A `decisionOutputs` entry may now be `{ key, label?, type: 'text' | 'user' | 'department' | 'position' | 'team', multiple? }` alongside the bare-string form — a typed entry tells the decision UI to render the matching record picker (id values; `multiple` collects an id array) instead of free text, turning "paste user ids" into "pick people". The type shapes only the input widget: the runtime whitelist works by `key` either way, via the new `normalizeDecisionOutputs` helper exported from `@objectstack/spec/automation` — the single reader of the union shape shared by the service, the request read, and `os lint`. The request read now carries `decision_output_defs` (normalized declarations) alongside the version-skew-safe `decision_outputs` key list.
|
|
533
|
+
|
|
534
|
+
### Patch Changes
|
|
535
|
+
|
|
536
|
+
- e5bd768: refactor(spec)!: retire `ActionDescriptor.isAsync` — a second spelling of `supportsPause` that nothing ever read (#6748, ADR-0049)
|
|
537
|
+
|
|
538
|
+
<!-- adr-0087: registered action-descriptor-is-async-retired -->
|
|
539
|
+
|
|
540
|
+
**FROM → TO:** `isAsync: true` → delete the key; declare `supportsPause: true` (plus the
|
|
541
|
+
`resumeAuthority` its pauses need) and return `suspend: true` from `execute()`.
|
|
542
|
+
`isAsync: false` → delete the key; there was never anything to preserve.
|
|
543
|
+
|
|
544
|
+
`ActionDescriptor.isAsync` declared "suspends the flow awaiting an external reply" and no
|
|
545
|
+
execution path read it. Measured fresh before removal across all three repos — objectstack,
|
|
546
|
+
objectui and cloud — with zero property reads: every hit was the declaration itself, a
|
|
547
|
+
generated baseline, one of five shipped descriptors WRITING it, a fixture pinning the
|
|
548
|
+
shape, or prose. Declaring it never made a node suspend; omitting it never stopped one.
|
|
549
|
+
|
|
550
|
+
This is the remove leg of the ADR-0049 disposition its sibling took the other way. The two
|
|
551
|
+
keys said the same thing — "this node type can suspend the run" — and #6667 split them by
|
|
552
|
+
evidence: `supportsPause` became an enforced fact (`AutomationEngine` now refuses a
|
|
553
|
+
suspension whose type does not declare it, at the one seam every suspension passes
|
|
554
|
+
through), while `isAsync` had no consumer to grow into. Keeping both would leave the
|
|
555
|
+
platform publishing two names for one capability with only one of them honoured — and
|
|
556
|
+
`screen` declared BOTH, so a plugin author copying it had no way to tell which.
|
|
557
|
+
|
|
558
|
+
The retirement kit:
|
|
559
|
+
|
|
560
|
+
- **Tombstone, not deletion** (`retiredKey()`): `ActionDescriptorSchema` is not `.strict()`,
|
|
561
|
+
so a plain delete would let existing descriptors parse clean and lose the key in silence
|
|
562
|
+
(the ADR-0104 shape). Authoring `isAsync` now fails `tsc` at the descriptor literal and
|
|
563
|
+
fails the parse inside `defineActionDescriptor()` — with the prescription in the message.
|
|
564
|
+
- **ADR-0087 D3 `SemanticMigration`** (`action-descriptor-is-async-retired`) plus the exact
|
|
565
|
+
`RETIRED_KEYS_BY_MAJOR` entry. No D2 conversion, deliberately: a descriptor is published
|
|
566
|
+
from an executor's TypeScript and never stored in stack metadata, so there is no source
|
|
567
|
+
for `os migrate meta` to rewrite — the `EnhancedApiError.fieldErrors` disposition.
|
|
568
|
+
- The five shipped writers stop writing it (`screen`, `map`, `wait`, `approval`,
|
|
569
|
+
`approval_revise`); the descriptors they publish lose the key, which is why the two
|
|
570
|
+
runtime packages appear here.
|
|
571
|
+
- Generated baselines (`authorable-surface/automation.json` gains `[RETIRED]`,
|
|
572
|
+
`authorable-defaults/automation.json` loses the default line), `spec-changes.json`, the
|
|
573
|
+
upgrade guide and the reference docs regenerated.
|
|
574
|
+
|
|
575
|
+
No runtime behaviour changes — that impossibility is the reason for the removal. The same
|
|
576
|
+
commit also corrects `supportsPause`'s TSDoc, which still described itself as a declaration
|
|
577
|
+
no execution path reads; #6667 made that false (#6749).
|
|
578
|
+
|
|
579
|
+
- d058594: fix(approvals): refuse `organization` on directory-less approver types instead
|
|
580
|
+
of silently ignoring it (ADR-0105 D9)
|
|
581
|
+
|
|
582
|
+
`user`, `field` and `manager` return EARLY in `resolveApproverSpec` — they name
|
|
583
|
+
a person outright rather than expanding a directory. D9's org resolution was
|
|
584
|
+
placed after those returns, so an `organization` declared on one of them never
|
|
585
|
+
reached the check: it was silently INERT.
|
|
586
|
+
|
|
587
|
+
That is the one behaviour ADR-0105 D9 rules out and the authoring docs
|
|
588
|
+
explicitly promise against ("`organization` on those is refused at runtime").
|
|
589
|
+
The `os lint` rule caught it at author time, but the runtime claim was false —
|
|
590
|
+
and a stored flow that predates the lint, or one assembled programmatically,
|
|
591
|
+
got no signal at all.
|
|
592
|
+
|
|
593
|
+
Resolution now happens at the top of `resolveApproverSpec`, above every early
|
|
594
|
+
return, so the refusal reaches all three types. The ordinary path is unchanged
|
|
595
|
+
and still costs nothing: with no `organization` declared the resolver returns
|
|
596
|
+
the request's organization without reading anything.
|
|
597
|
+
|
|
598
|
+
Found by cloud's group-posture dogfood driving a real `group` boot — the
|
|
599
|
+
resolver's own unit tests could not see it, because they call the resolver
|
|
600
|
+
directly and never traverse the early return.
|
|
601
|
+
|
|
602
|
+
- 879ea13: ADR-0105 Phase 0 + Phase 1: group tenancy posture; organization scope as a
|
|
603
|
+
first-class authorization dimension.
|
|
604
|
+
|
|
605
|
+
> This release carries BREAKING spec removals (see "Enforce-or-remove" below)
|
|
606
|
+
> but is recorded as `minor`: every publishable package is in the Changesets
|
|
607
|
+
> lockstep group, so one `major` would promote the whole monorepo. Breaking
|
|
608
|
+
> changes ship as `minor` during the launch window — the migration notes below
|
|
609
|
+
> are what reach consumers in `CHANGELOG.md`.
|
|
610
|
+
|
|
611
|
+
## Tenancy is now a spectrum (D1)
|
|
612
|
+
|
|
613
|
+
`single | group | isolated`, resolved by the `tenancy` service and selected with
|
|
614
|
+
the new `OS_TENANCY_POSTURE` env var. Existing deployments are unchanged:
|
|
615
|
+
`OS_TENANCY_POSTURE` unset derives the posture from `OS_MULTI_ORG_ENABLED`
|
|
616
|
+
(`true` ⇒ `isolated`, else `single`). An unrecognized value throws at boot
|
|
617
|
+
rather than silently landing in a posture with no organization wall.
|
|
618
|
+
|
|
619
|
+
- `single` — no wall (unchanged).
|
|
620
|
+
- `group` — **new.** Organizations are membership boundaries over one shared
|
|
621
|
+
dataset; Layer 0 becomes `organization_id IN accessible_org_ids` (union / MOAC
|
|
622
|
+
semantics). Enforced by the OPEN engine.
|
|
623
|
+
- `isolated` — today's `multi`, renamed. Behavior, enterprise `org-scoping`
|
|
624
|
+
probe and degraded-boot handling all unchanged.
|
|
625
|
+
|
|
626
|
+
## Organization scope is a first-class context field (D2)
|
|
627
|
+
|
|
628
|
+
`ExecutionContext.accessible_org_ids` — every organization the caller holds a
|
|
629
|
+
currently-valid membership in (ADR-0091 validity windows) — is resolved once by
|
|
630
|
+
`resolveAuthzContext` and carried by every transport. The `group` wall reads it
|
|
631
|
+
directly; RLS policies may reference it as
|
|
632
|
+
`organization_id IN (current_user.accessible_org_ids)`. An empty or absent set
|
|
633
|
+
fails the wall closed.
|
|
634
|
+
|
|
635
|
+
Only the Layer 0 PREDICATE widens. Composition is untouched: the wall is still
|
|
636
|
+
computed independently of the RLS compiler, AND-composed outermost, and
|
|
637
|
+
crossable only by a true `PLATFORM_ADMIN` on a posture-permitting object — so
|
|
638
|
+
ADR-0095's W1/W2 invariants hold in every posture.
|
|
639
|
+
|
|
640
|
+
## Two P0 correctness fixes (D3, D4) — behavior changes
|
|
641
|
+
|
|
642
|
+
**D3 — app-authored org-scoped RLS policies are no longer silently dropped**
|
|
643
|
+
(finding F1, framework#3539). `collectRLSPolicies` used to strip any policy whose
|
|
644
|
+
`using` contained the substring `current_user.organization_id` when isolation was
|
|
645
|
+
inactive, which swallowed app-authored policies as well as the platform's own.
|
|
646
|
+
Stripping is now decided by PROVENANCE (identity against the shipped
|
|
647
|
+
declaration). **Upgrade impact:** in a deployment with no organization wall, an
|
|
648
|
+
app-authored policy referencing the active organization is now RETAINED and
|
|
649
|
+
fails closed (zero rows) with a one-time warning, where it previously vanished
|
|
650
|
+
and the object read unscoped. `getReadFilter` shared the defect, so analytics and
|
|
651
|
+
raw-SQL consumers were affected too. If a policy was only ever meant for
|
|
652
|
+
multi-org, delete it or install `@objectstack/organizations`.
|
|
653
|
+
|
|
654
|
+
**D4 — `viewAllRecords`/`modifyAllRecords` never cross an organization
|
|
655
|
+
boundary** (finding F2, framework#3540). Under a wall-less posture nothing
|
|
656
|
+
bounded the wildcard superuser bits `organization_admin` carries, so a
|
|
657
|
+
deployment that accumulated organizations (personal orgs on signup) made every
|
|
658
|
+
owner/admin an environment-wide superuser. `auto-org-admin-grant` now grants a
|
|
659
|
+
de-VAMA'd `organization_admin_no_bypass` variant when no wall is enforced, and
|
|
660
|
+
revokes the superseded variant whenever the posture changes. **Upgrade impact:**
|
|
661
|
+
in `single` posture an org owner/admin keeps full CRUD but loses the blanket
|
|
662
|
+
ownership/sharing/RLS bypass. Deliberate deployment-wide visibility remains
|
|
663
|
+
available through `admin_full_access` or an explicitly authored permission set —
|
|
664
|
+
it just stops being a side effect of a better-auth membership role.
|
|
665
|
+
|
|
666
|
+
## Engine-owned organization stamping (D5)
|
|
667
|
+
|
|
668
|
+
Under any wall-enforcing posture the engine stamps `organization_id` from the
|
|
669
|
+
caller's active organization on an insert that omits it, and validates every
|
|
670
|
+
supplied value against the wall. Idempotent with the enterprise auto-stamp
|
|
671
|
+
(neither overwrites a supplied value). This also closes a real hole: the
|
|
672
|
+
pre-existing post-image check required a non-array payload, so a BULK insert
|
|
673
|
+
could carry a forged `organization_id` per row. One forged row now denies the
|
|
674
|
+
whole write.
|
|
675
|
+
|
|
676
|
+
## Group structure, extension fields and red-line lints (D6, D7)
|
|
677
|
+
|
|
678
|
+
- `sys_organization` gains `parent_organization_id` and `sort_order` — a
|
|
679
|
+
**reporting dimension only**.
|
|
680
|
+
- New lint `validateOrgAxisRedLines` (`org-axis-permission-inheritance`,
|
|
681
|
+
`org-axis-cross-org-bu-grant`), wired into `os lint` / `os compile` /
|
|
682
|
+
`os validate`: an RLS policy or sharing rule that walks the org tree is an
|
|
683
|
+
error, as is a business-unit grant on a platform-global object.
|
|
684
|
+
- Extension fields on better-auth-managed objects ride the existing ADR-0092
|
|
685
|
+
whitelist. A new guard derives better-auth's real field surface from
|
|
686
|
+
`getAuthTables()` at the pinned version and fails the build on any name
|
|
687
|
+
collision, so a library upgrade cannot silently take ownership of a column.
|
|
688
|
+
|
|
689
|
+
## Enforce-or-remove (D11) — BREAKING
|
|
690
|
+
|
|
691
|
+
Both removals are of surface that had **zero runtime consumers**, so no
|
|
692
|
+
behavior changes; authoring them is now a no-op instead of a lint warning.
|
|
693
|
+
|
|
694
|
+
- **`PermissionSet.contextVariables` — REMOVED.** The RLS compiler never read
|
|
695
|
+
it. FROM → TO: a set a policy needs as `field IN (current_user.<key>)` is now
|
|
696
|
+
supplied by a registered membership resolver (below); a constant belongs in
|
|
697
|
+
the policy itself as a literal (`status = 'published'`).
|
|
698
|
+
- **`Territory` / `TerritoryModel` / `TerritoryType` (`security/territory.zod.ts`)
|
|
699
|
+
— REMOVED.** No runtime object, stack field or resolver existed. FROM → TO:
|
|
700
|
+
matrix requirements are served by multi-position × business-unit anchoring; a
|
|
701
|
+
generalized dimension-security module will arrive with its own ADR.
|
|
702
|
+
- **`ExecutionContext.rlsMembership` — PRODUCTIZED.** The bag the compiler has
|
|
703
|
+
merged since ADR-0056 finally has a producer: register an
|
|
704
|
+
`IRlsMembershipResolver` (`@objectstack/spec/contracts`) under the
|
|
705
|
+
`rls-membership-resolver` service, declaring the keys it owns. Fail-closed by
|
|
706
|
+
construction — an unresolved key makes its policies drop out. Kernel-owned
|
|
707
|
+
keys (`accessible_org_ids`, `org_user_ids`, …) are reserved and cannot be
|
|
708
|
+
overwritten from this seam.
|
|
709
|
+
|
|
710
|
+
## Edition boundary (D12)
|
|
711
|
+
|
|
712
|
+
The `group` posture's enforcement primitives ship OPEN — the union wall,
|
|
713
|
+
`accessible_org_ids` resolution, D5 stamping/validation, the D3/D4 correctness
|
|
714
|
+
fixes and the D6 lints — because the correctness of a wall is never a paid
|
|
715
|
+
feature (cloud ADR-0016 铁律「强制免费、治理收费」). `isolated` keeps its existing
|
|
716
|
+
enterprise `org-scoping` probe, so the current commercial boundary for
|
|
717
|
+
legal-entity isolation is unchanged by this release.
|
|
718
|
+
|
|
719
|
+
- 2ba560a: fix(plugin-approvals): give the decision actions a visual hierarchy (objectui#2762 P1-5)
|
|
720
|
+
|
|
721
|
+
The `sys_approval_request` decision actions all declared as equal-weight
|
|
722
|
+
buttons, so the drawer's action bar rendered five identical outlined
|
|
723
|
+
buttons with no emphasis on the primary path. `approval_approve` now
|
|
724
|
+
declares `variant: 'primary'` and `approval_reject` declares
|
|
725
|
+
`variant: 'danger'`, so a metadata-driven renderer highlights Approve and
|
|
726
|
+
styles Reject as destructive — matching the hierarchy the mobile card
|
|
727
|
+
already has. Pure metadata; the secondary levers stay unstyled (tertiary).
|
|
728
|
+
|
|
729
|
+
- 2dda6e7: fix(plugin-approvals): localize the declared decision-action labels (objectui#2762 P0-3)
|
|
730
|
+
|
|
731
|
+
The Approval Center's decision drawer rendered the `sys_approval_request`
|
|
732
|
+
declared actions with their literal metadata labels — English **Approve /
|
|
733
|
+
Reject / Reassign / Send back / Request info** in a zh-CN workspace, sitting
|
|
734
|
+
next to the same page's localized 通过 / 拒绝 inbox buttons. The plugin's
|
|
735
|
+
translation bundle covered fields and views but had no `_actions` node, so
|
|
736
|
+
the console's `_actions.<name>.label` resolution had nothing to hit.
|
|
737
|
+
|
|
738
|
+
- Re-ran `os i18n extract` against the plugin's config: the bundles now carry
|
|
739
|
+
`_actions` translations (label, confirmText, successMessage, param labels
|
|
740
|
+
and helpText) for all eight decision actions — `approval_approve`,
|
|
741
|
+
`approval_reject`, `approval_reassign`, `approval_send_back`,
|
|
742
|
+
`approval_request_info`, `approval_remind`, `approval_recall`,
|
|
743
|
+
`approval_resubmit` — in zh-CN, ja-JP and es-ES (en keeps the metadata
|
|
744
|
+
literals).
|
|
745
|
+
- The extract also surfaced other untranslated gaps, now filled in all three
|
|
746
|
+
locales: the `returned` status option, the `sys_approval_action.action`
|
|
747
|
+
audit options (`reassign` / `remind` / `request_info` / `comment` /
|
|
748
|
+
`revise` / `resubmit` / `ooo_substitute`), the `attachments` field, and the
|
|
749
|
+
`my_pending` / `recent` view empty states.
|
|
750
|
+
|
|
751
|
+
- 474fe39: feat(approvals): declare approver value bindings; retire `queue` approver authoring (#3508)
|
|
752
|
+
|
|
753
|
+
- `@objectstack/spec` exports `APPROVER_VALUE_BINDINGS` — the single declaration of how a
|
|
754
|
+
designer must source each approver row's `value`: `user`/`team`/`department`/`position`
|
|
755
|
+
are DATA-record lookups on the system directory objects (`sys_user` / `sys_team` /
|
|
756
|
+
`sys_business_unit` / `sys_position`; `position` commits the machine **name**, the
|
|
757
|
+
others the row id), `org_membership_level` is a closed enum (`ORG_MEMBERSHIP_LEVELS`),
|
|
758
|
+
`manager` is auto-resolved, `field` names a trigger-object field, and `queue` is
|
|
759
|
+
unsupported. Also exports `NON_AUTHORABLE_APPROVER_TYPES`.
|
|
760
|
+
- `queue` approver type is deprecated-for-authoring: it still parses (stored flows keep
|
|
761
|
+
loading and rendering) but is published in `xEnumDeprecated`, so designers stop
|
|
762
|
+
offering it — the runtime has no queue resolution and the slot routes to nobody. The
|
|
763
|
+
approver `value` xRef now also maps `manager`, so designers can render its
|
|
764
|
+
auto-resolved state. No authored key is removed; nothing to migrate. If a flow carries
|
|
765
|
+
`{ type: 'queue' }`, replace it with `team` / `department` / `position` (or a concrete
|
|
766
|
+
`user`) until a real ownership-queue implementation lands.
|
|
767
|
+
- `@objectstack/plugin-approvals` now warns at resolution time when a stored `queue`
|
|
768
|
+
approver is skipped.
|
|
769
|
+
- `@objectstack/lint` adds `approval-approver-type-unsupported` (warning) for approver
|
|
770
|
+
types that are declared but not implemented by the runtime.
|
|
771
|
+
|
|
772
|
+
- 0bc685a: fix(approvals): return decision attachments as file values, not "[object Object]" (#3504)
|
|
773
|
+
|
|
774
|
+
`sys_approval_action.attachments` is a `Field.file`, so the column **stores an
|
|
775
|
+
opaque `sys_file` id** (ADR-0104 D3 — the stored form of every media field). The
|
|
776
|
+
ObjectQL read path resolves that id into its expanded
|
|
777
|
+
`{ id, name, size, mimeType, url }` form on the way out. But `rowFromAction`
|
|
778
|
+
mapped the column with `.map(String)`, collapsing each expanded value to the
|
|
779
|
+
literal string `"[object Object]"`. Every `listActions` consumer (the approval
|
|
780
|
+
inbox timeline) then received garbage: the attachment chip had no filename and
|
|
781
|
+
its id was `"[object Object]"`, so opening it 404'd.
|
|
782
|
+
|
|
783
|
+
- `ApprovalActionRow.attachments` is now `ApprovalActionAttachment[]` — the
|
|
784
|
+
expanded file value plus its id, so a consumer can label and open an
|
|
785
|
+
attachment without needing read access to the system `sys_file` object (which
|
|
786
|
+
regular approvers do not have).
|
|
787
|
+
- Three read forms are accepted: the expanded value (the normal case), a bare id
|
|
788
|
+
(nothing to expand it into — storage service absent, file not committed), and
|
|
789
|
+
a legacy inline blob written before file-as-reference (`file_id` /
|
|
790
|
+
`mime_type`), until the backfill converts it. The id test reuses the
|
|
791
|
+
platform's `isFileIdToken`, so this and the engine's read resolver cannot
|
|
792
|
+
disagree about what counts as an id.
|
|
793
|
+
- The decision _input_ (`ApprovalDecisionInput.attachments`) is unchanged — it
|
|
794
|
+
still takes fileId strings, which is also exactly what the column stores. Only
|
|
795
|
+
the read shape changed.
|
|
796
|
+
|
|
797
|
+
- b949059: fix(approvals): a dead approval run no longer leaves the record RECORD_LOCKED (#3456)
|
|
798
|
+
|
|
799
|
+
The record lock is keyed on a **pending** `sys_approval_request`, and it could
|
|
800
|
+
not tell _the run that owns that request_ from _an unrelated user editing the
|
|
801
|
+
record_. So a flow that touched its own target record while its own approval was
|
|
802
|
+
still pending — a manual `resume` with no decision, or a node that writes the
|
|
803
|
+
record between opening the approval and the decision — died on its own
|
|
804
|
+
`RECORD_LOCKED`, and the record stayed locked behind the dead run. Recovery
|
|
805
|
+
existed (#3424 lets an admin `recall`/`reject` to release it) but nothing made it
|
|
806
|
+
self-healing.
|
|
807
|
+
|
|
808
|
+
Both halves are now closed.
|
|
809
|
+
|
|
810
|
+
**Prevention — the owning run may write its own record.** The automation engine
|
|
811
|
+
stamps `flowRunId` onto the run context at setup, alongside `runAs`, and it
|
|
812
|
+
travels with every data node's ObjectQL context into `ctx.provenance`. The lock
|
|
813
|
+
hook exempts a write whose `flowRunId` matches the pending request's `flow_run_id`.
|
|
814
|
+
It is keyed on run identity rather than elevation on purpose: a `runAs:'user'`
|
|
815
|
+
run stays fully RLS-scoped while it writes. `flowRunId` is pure provenance —
|
|
816
|
+
server-constructed like `isSystem`, never client-supplied, evaluated by no
|
|
817
|
+
security middleware, and the only write it permits is to the one record its own
|
|
818
|
+
run already holds a pending request against.
|
|
819
|
+
|
|
820
|
+
**Recovery — a sweep releases records held by runs that died anyway.** A pending
|
|
821
|
+
request whose owning run has reached a terminal state (`completed`, `failed`,
|
|
822
|
+
`cancelled`, `timed_out`) can never be decided, so it is finalised as `recalled`
|
|
823
|
+
— releasing the lock — and audited under the reserved actor `system:dead-run`
|
|
824
|
+
with the run and its status in the comment, so it is never mistaken for a
|
|
825
|
+
submitter's withdrawal. It runs on the existing approvals sweep clock, which also
|
|
826
|
+
covers the case no in-band handler can: a run killed by a process crash.
|
|
827
|
+
|
|
828
|
+
The sweep is fail-safe by construction. It acts only on an explicit terminal
|
|
829
|
+
status from a closed set; `paused` (the normal state of a live approval),
|
|
830
|
+
`running`, an unrecognised status, an unknown run, a `getRun` that throws, and a
|
|
831
|
+
deployment with no automation engine are all read as "still alive". The failure
|
|
832
|
+
mode is "a dead run's lock survives until an admin recalls it" — today's
|
|
833
|
+
behaviour — never "a live approval is destroyed".
|
|
834
|
+
|
|
835
|
+
Also fixes `AutomationEngine.getRun`, which returned the **first** log entry for
|
|
836
|
+
a run id rather than the latest. A run that pauses and later finishes records two
|
|
837
|
+
entries under one id, so every suspend-then-finish run — every approval, screen
|
|
838
|
+
and wait flow — reported itself as `paused` forever, both on the Runs
|
|
839
|
+
observability surface and to this sweep.
|
|
840
|
+
|
|
841
|
+
One shape was left out here and closed separately in #3712: a `runAs:'user'` run
|
|
842
|
+
with no trigger user (a schedule) resolved no ObjectQL context at all, so it
|
|
843
|
+
carried no `flowRunId` and stayed subject to the lock. It now passes a
|
|
844
|
+
provenance-only context — the run id and nothing the security middleware keys on
|
|
845
|
+
— so it is attributable without acquiring a principal, and its documented
|
|
846
|
+
unscoped posture (#1888) is unchanged.
|
|
847
|
+
|
|
848
|
+
- 7abdd74: approvals: rejecting or recalling a request now opens ONE dialog instead of two
|
|
849
|
+
|
|
850
|
+
`sys_approval_request`'s `approval_reject` and `approval_recall` actions declared
|
|
851
|
+
both `confirmText` and `params`. The console action runner chains confirmation
|
|
852
|
+
**then** param collection, both awaited, so a single decision opened a confirm
|
|
853
|
+
prompt, then a second dialog the approver never asked for — and nothing was sent
|
|
854
|
+
until that second Confirm, while the first prompt already read as "the action is
|
|
855
|
+
running".
|
|
856
|
+
|
|
857
|
+
Each action now carries its confirm question in the action's top-level
|
|
858
|
+
`description` (the key added in #7367), which the param dialog renders under its
|
|
859
|
+
title. The wording is unchanged in all four shipped locales — including the
|
|
860
|
+
finality warning "A rejection is final for every approver." — so one decision is
|
|
861
|
+
one condition, one wording, one dialog, and nothing is sent until its own Confirm.
|
|
862
|
+
|
|
863
|
+
- be1c52c: fix(approvals): admin override for a request routed to an unstaffed approver (#3424)
|
|
864
|
+
|
|
865
|
+
An `approval` node routed to a `position` (or `team`/`department`) with **no
|
|
866
|
+
holders** resolved to only the unresolvable `position:<name>` literal in
|
|
867
|
+
`pending_approvers` — no concrete user was in the slate. Every normal
|
|
868
|
+
`decide` / `reassign` / `recall` then returned `FORBIDDEN` (not a pending
|
|
869
|
+
approver) and, with `lockRecord`, the target record stayed `RECORD_LOCKED`
|
|
870
|
+
forever: a data-availability dead-end with no in-product recovery (the only exit
|
|
871
|
+
was editing the DB by hand). Very easy to hit in fresh/demo orgs (positions
|
|
872
|
+
seeded, holders not) and whenever a role is vacated in production.
|
|
873
|
+
|
|
874
|
+
A **platform or tenant admin** — the same posture the engine's superuser bypass
|
|
875
|
+
already trusts — may now act on any _pending_ request to release it: **approve,
|
|
876
|
+
reject, reassign** it to a real approver, or **recall** it. The override finalizes
|
|
877
|
+
the request (which releases the record lock, keyed on a pending request); a
|
|
878
|
+
tenant admin's authority is org-scoped, a platform admin's is not, and the
|
|
879
|
+
decision is audited under the admin's own id. An admin approval is authoritative,
|
|
880
|
+
finalizing the node even under `unanimous` / `quorum` / `per_group` rather than
|
|
881
|
+
counting as one vote among the (empty) slate.
|
|
882
|
+
|
|
883
|
+
- `sys_approval_request.viewer` gains `can_override` (server-computed): true for a
|
|
884
|
+
privileged admin on a pending request. The `approve` / `reject` / `reassign`
|
|
885
|
+
declared actions OR it into their `visible` gate, so the console surfaces the
|
|
886
|
+
recovery path without a hand-wired button. Existing approver/submitter gating is
|
|
887
|
+
unchanged.
|
|
888
|
+
- `openNodeRequest` now logs a loud warning when a node resolves to **no concrete
|
|
889
|
+
approver**, so the misconfiguration is visible instead of silently locking the
|
|
890
|
+
record. The literal-fallback behavior (kept for 15.x slot back-compat) is
|
|
891
|
+
otherwise unchanged.
|
|
892
|
+
|
|
893
|
+
- c5ff96d: fix(approvals): a schedule-triggered run can write its own locked record (#3712)
|
|
894
|
+
|
|
895
|
+
#3456 let the run that opened a pending approval write its own target record,
|
|
896
|
+
keyed on `flowRunId`. It worked for every run that resolves an identity and
|
|
897
|
+
missed the one that doesn't: an effective `runAs:'user'` run with **no trigger
|
|
898
|
+
user** — a schedule being the canonical case — passed no ObjectQL context at
|
|
899
|
+
all, so nothing carried the run id and the run still died on its own
|
|
900
|
+
`RECORD_LOCKED`.
|
|
901
|
+
|
|
902
|
+
The blocker was never the lock. It was that "no identity" and "no context" were
|
|
903
|
+
the same thing on the wire, so a run could not say _who it was_ without also
|
|
904
|
+
claiming _what it was allowed to do_.
|
|
905
|
+
|
|
906
|
+
**A run with no principal now passes provenance alone.**
|
|
907
|
+
`resolveRunDataContext` returns `{ flowRunId }` — no `userId`, no `positions`,
|
|
908
|
+
no `permissions`, not even `isSystem: false`. Every principal gate keys on one
|
|
909
|
+
of those fields (the elevation short-circuit on `isSystem`, the ADR-0103
|
|
910
|
+
engine-owned write guard and the ADR-0090 D12 delegated-admin gate on `userId`,
|
|
911
|
+
the empty-principal fall-open on all three), so this context authorizes
|
|
912
|
+
**identically to no context at all**. The run keeps the documented #1888
|
|
913
|
+
unscoped posture, its loud `[runAs]` warning, and the
|
|
914
|
+
`flow-schedule-runas-unscoped` build-time lint. Nothing about what it may touch
|
|
915
|
+
changed — only that it can now be attributed.
|
|
916
|
+
|
|
917
|
+
**Provenance moved out of the hook session, into `ctx.provenance`.** `session`
|
|
918
|
+
answers _who is calling_ and is absent when no identity envelope was supplied —
|
|
919
|
+
a distinction real gates depend on (the attachment access gate skips bare-kernel
|
|
920
|
+
writes on exactly that test). Folding a run id into `session` would have forced
|
|
921
|
+
an identity-less run to present an empty session, silently turning "no caller"
|
|
922
|
+
into "an anonymous caller" and narrowing the #1888 fail-open for attachments
|
|
923
|
+
alone. `HookContext.provenance.flowRunId` says what produced the write; the
|
|
924
|
+
approvals lock reads it there.
|
|
925
|
+
|
|
926
|
+
Also relaxes `BaseEngineOptionsSchema.context` to a partial envelope
|
|
927
|
+
(`ExecutionContextInput`). `positions`/`permissions`/`isSystem` carry parse-time
|
|
928
|
+
defaults, which made them _required_ on a caller-supplied option and asserted
|
|
929
|
+
something untrue — that every data-engine context carries a principal. Callers
|
|
930
|
+
have always passed slices (`{ isSystem: true }` for a system read); the type now
|
|
931
|
+
says so.
|
|
932
|
+
|
|
933
|
+
Migration: nothing to change unless you read the run id inside a hook. If you
|
|
934
|
+
wrote `ctx.session.flowRunId`, read `ctx.provenance.flowRunId` instead — the
|
|
935
|
+
field never shipped under the old name.
|
|
936
|
+
|
|
937
|
+
- 5a84d41: fix(approvals): record an admin override of a staffed approver slate AS an override (#4466)
|
|
938
|
+
|
|
939
|
+
An admin who is not in a request's `pending_approvers` may still act on it — the
|
|
940
|
+
`#3424` privileged-override path exists so a request routed to an unstaffed
|
|
941
|
+
position, or to approvers who have all left, is not undecidable forever. The
|
|
942
|
+
override is defensible; what was not is what the audit trail recorded.
|
|
943
|
+
|
|
944
|
+
`sys_approval_action` had no override column at all. So an admin overriding a
|
|
945
|
+
properly-staffed slate wrote a row **byte-for-byte identical** to the designated
|
|
946
|
+
approver approving normally: a reader of the timeline saw `approve` by the admin
|
|
947
|
+
and could not tell whether the admin _was_ an approver or _overrode_ the ones who
|
|
948
|
+
were, and the bypassed approver's later `409 INVALID_STATE` was the only trace —
|
|
949
|
+
existing only if they happened to try. The platform knows at decision time (it
|
|
950
|
+
took the `isOverrideActor` branch to admit the call at all), so this was dropped
|
|
951
|
+
information, not unavailable information. The whole point of an approval record
|
|
952
|
+
is to answer "who authorized this, and were they entitled to?".
|
|
953
|
+
|
|
954
|
+
`sys_approval_action` now carries **`via_override`** (boolean, optional), set on
|
|
955
|
+
exactly the actions admitted by that branch — `decideNode`'s approve/reject and
|
|
956
|
+
`reassign`'s admin rescue. It is surfaced on `ApprovalActionRow.via_override`
|
|
957
|
+
(`@objectstack/spec/contracts`), returned by `listActions`, and added to the
|
|
958
|
+
object's `highlightFields` and two grid list views so a timeline can say
|
|
959
|
+
"overrode the approver slate" instead of rendering it as an ordinary approval.
|
|
960
|
+
|
|
961
|
+
Three distinctions the column keeps apart deliberately:
|
|
962
|
+
|
|
963
|
+
- **`true`** — the actor held no slot in the slate and was admitted only by the
|
|
964
|
+
override branch.
|
|
965
|
+
- **`false`** — checked, and it was not an override. An admin who _is_ a
|
|
966
|
+
designated approver is approving normally and records `false`: the marker is
|
|
967
|
+
about which branch admitted the call, not about whether the actor holds admin
|
|
968
|
+
rights.
|
|
969
|
+
- **absent** — a row written before this column existed. "Not recorded" is not
|
|
970
|
+
the same claim as "not an override", so `rowFromAction` maps `null` to
|
|
971
|
+
`undefined` rather than to `false`.
|
|
972
|
+
|
|
973
|
+
Additive and nullable, so this needs no data migration: existing rows keep
|
|
974
|
+
working and simply read as unrecorded. Levelled `patch` rather than `minor`
|
|
975
|
+
because nothing an author writes changes — but note it _is_ an observable
|
|
976
|
+
behaviour change on a read surface: `listActions` responses and the
|
|
977
|
+
`sys_approval_action` grid views now carry a field consumers did not see before,
|
|
978
|
+
and `sys_approval_action` gains a column on next schema sync.
|
|
979
|
+
|
|
980
|
+
- d2a8695: fix(approvals)!: an approval request is visible to its participants, not to the whole tenant (#3590)
|
|
981
|
+
|
|
982
|
+
`getRequest` / `listRequests` / `countRequests` deliberately query with
|
|
983
|
+
`SYSTEM_CTX` to bypass RLS — as the code comments say, the approver-visibility
|
|
984
|
+
rule spans identity forms RLS cannot model cleanly, so it has to be expressed in
|
|
985
|
+
the service. Only the **tenant** half of that rule was ever applied. The
|
|
986
|
+
participant half was named in the comment and never written, so **any
|
|
987
|
+
authenticated user could read any approval request in their tenant** — its
|
|
988
|
+
payload snapshot, its full decision history, and (once decision attachments
|
|
989
|
+
derived their access from the request, #3580) its files.
|
|
990
|
+
|
|
991
|
+
`approverId` on `listRequests` is a _filter_, not authorization: omitting it
|
|
992
|
+
returned the whole tenant.
|
|
993
|
+
|
|
994
|
+
A caller now sees a request when they are a participant — the submitter, a
|
|
995
|
+
current approver (via the normalized approver index, so every identity form the
|
|
996
|
+
write path recorded is covered), or someone who has already acted on it (a past
|
|
997
|
+
approver whose slot has moved on, a commenter). Admins with override authority
|
|
998
|
+
keep the unrestricted view the "all requests" console surface depends on, and a
|
|
999
|
+
tokenless context sees nothing.
|
|
1000
|
+
|
|
1001
|
+
Keying on the concrete user id is sufficient rather than an approximation:
|
|
1002
|
+
position/team/manager/field approvers are resolved to concrete user ids at open
|
|
1003
|
+
time, and the `type:value` literal is only the fallback for a spec that resolved
|
|
1004
|
+
to _nobody_ — a slot no one can act on either way. So this cannot hide a request
|
|
1005
|
+
from someone who could actually act on it.
|
|
1006
|
+
|
|
1007
|
+
**A write path's own result is not re-gated.** Every operation echoes back the
|
|
1008
|
+
request it just changed; the operation already authorized itself, and re-asking
|
|
1009
|
+
would answer wrong for a context carrying no `userId` (a flow-driven resume, a
|
|
1010
|
+
service-to-service call), turning a successful write into `null`.
|
|
1011
|
+
|
|
1012
|
+
Marked breaking because a client that listed requests without an `approverId`
|
|
1013
|
+
filter and expected the whole tenant will now receive only its own — which is
|
|
1014
|
+
the point.
|
|
1015
|
+
|
|
1016
|
+
- 84e7be9: feat(plugin-approvals): expose per-group membership of pending approvers (objectui#2807)
|
|
1017
|
+
|
|
1018
|
+
`per_group` (会签) requests now carry `pending_approver_groups` on the
|
|
1019
|
+
enriched row — a map from each still-pending approver id to the group key(s)
|
|
1020
|
+
it fills (e.g. `{ "u_devadmin": ["finance", "legal"] }`). A client can label
|
|
1021
|
+
each "waiting on" chip with the group it represents instead of showing
|
|
1022
|
+
duplicate, context-free names.
|
|
1023
|
+
|
|
1024
|
+
- Resolved in `attachDecisionProgress` from the same open-time
|
|
1025
|
+
`__approverGroups` snapshot the `decision_progress` groups already use, so
|
|
1026
|
+
the two never disagree.
|
|
1027
|
+
- Only the **pending** slots are mapped (a resolved approver has left
|
|
1028
|
+
`pending_approvers`), and **synthetic** (unnamed, `#N`) group keys are
|
|
1029
|
+
dropped — a `· #0` sub-tag would be noise.
|
|
1030
|
+
- Absent for non-`per_group` behaviors. Display-only; the engine's
|
|
1031
|
+
finalization tally stays authoritative.
|
|
1032
|
+
- Added to the `ApprovalRequestRow` contract in `@objectstack/spec`.
|
|
1033
|
+
|
|
1034
|
+
- 0b795da: fix(approvals): the record lock now holds for predicate (`multi`) updates (#4778)
|
|
1035
|
+
|
|
1036
|
+
The ADR-0019 record lock — "while a record has a pending `sys_approval_request`,
|
|
1037
|
+
block edits to it" — was enforced only for updates that reach the hook with an
|
|
1038
|
+
`input.id`. The engine extracts that id from a **scalar** `where.id` alone; an
|
|
1039
|
+
operator object (`{ $in: [...] }`) or any other predicate is a multi-row write
|
|
1040
|
+
that routes to `updateMany` and arrives with no id. The hook opened with
|
|
1041
|
+
`if (!id) return`, so it read _"no row was resolved"_ as _"there is nothing to
|
|
1042
|
+
authorize"_ when the truth was _"nothing was ever queried"_.
|
|
1043
|
+
|
|
1044
|
+
Rewriting the very same edit as `multi: true` therefore walked straight past the
|
|
1045
|
+
lock:
|
|
1046
|
+
|
|
1047
|
+
```ts
|
|
1048
|
+
// rec_1 carries a pending approval, lockRecord is not disabled
|
|
1049
|
+
await ql.update(
|
|
1050
|
+
"crm_opportunity",
|
|
1051
|
+
{ amount: 999 },
|
|
1052
|
+
{ where: { id: "rec_1" } }
|
|
1053
|
+
); // RECORD_LOCKED
|
|
1054
|
+
await ql.update(
|
|
1055
|
+
"crm_opportunity",
|
|
1056
|
+
{ amount: 999 },
|
|
1057
|
+
{ where: { id: { $in: ["rec_1"] } }, multi: true }
|
|
1058
|
+
); // went through
|
|
1059
|
+
await ql.update(
|
|
1060
|
+
"crm_opportunity",
|
|
1061
|
+
{ amount: 999 },
|
|
1062
|
+
{ where: { name: "x" }, multi: true }
|
|
1063
|
+
); // went through
|
|
1064
|
+
```
|
|
1065
|
+
|
|
1066
|
+
No privilege was needed for that bypass — not an `admin` role, not `isSystem`,
|
|
1067
|
+
not `lockRecord: false`, not a whitelisted `approvalStatusField`. Every caller
|
|
1068
|
+
shape that can spell a predicate (SDK, ObjectQL, a flow's `update_record`) could
|
|
1069
|
+
produce it. It is the same fail-open reasoning fixed for `sys_attachment`
|
|
1070
|
+
(#4757) and `sys_comment` (#4630), in the one place where it needed no
|
|
1071
|
+
privilege at all.
|
|
1072
|
+
|
|
1073
|
+
**The hook now resolves the rows a write touches before deciding.** By-id writes
|
|
1074
|
+
are unchanged (the driver writes by primary key, so the rest of `where` must not
|
|
1075
|
+
narrow the verdict). A predicate write is decided by intersecting the caller's
|
|
1076
|
+
predicate with the records that are actually locked — which is also what keeps
|
|
1077
|
+
it cheap: the query is bounded by the object's **pending approvals**, never by
|
|
1078
|
+
the update's match set, so a mass update of 50 000 unlocked rows costs one
|
|
1079
|
+
bookkeeping probe and is allowed. An unscoped `multi` update over the whole
|
|
1080
|
+
table reaches every locked row of the object and is refused while any is held.
|
|
1081
|
+
|
|
1082
|
+
**Fail-closed, both ways.** Past 1 000 locked records — the bound the attachment
|
|
1083
|
+
and comment guards use — or if the intersection query fails, the write is
|
|
1084
|
+
refused rather than allowed: the lock could not prove the write misses a locked
|
|
1085
|
+
row. The approvals bookkeeping being unreadable at all stays the one fail-open,
|
|
1086
|
+
as before: this hook is global over every object, so a kernel without
|
|
1087
|
+
`sys_approval_request` would otherwise refuse every update in the deployment.
|
|
1088
|
+
Both the bookkeeping and the match-set resolution are read under a **system**
|
|
1089
|
+
context — a guard's own input must never be narrowed by the caller's
|
|
1090
|
+
visibility, since a locked row you cannot read is still a row you may not write.
|
|
1091
|
+
|
|
1092
|
+
**Every exemption moved with the guard**, which is the other way this class of
|
|
1093
|
+
fix goes wrong — a guard extended to more rows that carries only its deny rules
|
|
1094
|
+
turns a fail-open into a false-positive. `isSystem`, the `admin` override, the
|
|
1095
|
+
`approvalStatusField` status mirror, `lockRecord: false` and the owning run's
|
|
1096
|
+
`flowRunId` (#3456 / #3712) all decide a predicate write exactly as they decide
|
|
1097
|
+
a by-id write, each pinned by tests on both predicate shapes. Refusals now name
|
|
1098
|
+
the record and object that are locked.
|
|
1099
|
+
|
|
1100
|
+
- 820eff9: fix(spec,plugin-approvals): the two approval vocabularies are derived, not hand-matched (#3786)
|
|
1101
|
+
|
|
1102
|
+
`sys_approval_request.status` and `sys_approval_action.action` spelled their
|
|
1103
|
+
option lists out — five values and twelve — each under a "Keep in sync with
|
|
1104
|
+
`ApprovalStatus` / `ApprovalActionKind` (spec/contracts)" comment, while the
|
|
1105
|
+
contract held the same sets as bare type unions. Seventeen strings matched by
|
|
1106
|
+
hand across a package boundary, with nothing checking them. They did all still
|
|
1107
|
+
agree; the sweep that found them (#3786) verified that verbatim before changing
|
|
1108
|
+
anything.
|
|
1109
|
+
|
|
1110
|
+
Agreeing is not the same as being held, and both directions of drift are quiet:
|
|
1111
|
+
|
|
1112
|
+
- a value the **column** accepts and the contract omits is invisible to every
|
|
1113
|
+
consumer typed against the contract — the row exists and nothing can narrow it;
|
|
1114
|
+
- a value the **contract** declares and the column rejects surfaces only at write
|
|
1115
|
+
time, on whichever tenant first reaches that transition.
|
|
1116
|
+
|
|
1117
|
+
An audit vocabulary is a bad place for either. So the contract now publishes the
|
|
1118
|
+
lists as values — `APPROVAL_STATUSES` and `APPROVAL_ACTION_KINDS` — with
|
|
1119
|
+
`ApprovalStatus` / `ApprovalActionKind` derived from them via
|
|
1120
|
+
`(typeof X)[number]`, and the two columns spread the constants. The per-entry
|
|
1121
|
+
rationale (which action kinds move the flow, which are thread-only, why
|
|
1122
|
+
`returned` differs from `recalled`) moved onto the constants, where the values
|
|
1123
|
+
live.
|
|
1124
|
+
|
|
1125
|
+
**New exports, no behaviour change.** The emitted option lists are byte-identical
|
|
1126
|
+
— verified against the built artifact before and after. Existing imports of the
|
|
1127
|
+
two types are unaffected; the types resolve to the same unions.
|
|
1128
|
+
|
|
1129
|
+
`approval-vocabularies.test.ts` pins the qualifier that derivation alone cannot:
|
|
1130
|
+
the columns agree with the contract _while the spread is there_, and the test
|
|
1131
|
+
fails if either is re-inlined as a literal that has drifted. It also guards the
|
|
1132
|
+
guard (an unresolvable import would compare two empty lists and pass) and asserts
|
|
1133
|
+
the two vocabularies stay distinct, since a copy-paste pointing one column at the
|
|
1134
|
+
other constant would satisfy "derived from the contract" while being the wrong
|
|
1135
|
+
vocabulary entirely.
|
|
1136
|
+
|
|
1137
|
+
Verified by mutation in both directions: adding a value to `APPROVAL_STATUSES`
|
|
1138
|
+
propagates into the built `sys_approval_request.status` options (the derivation
|
|
1139
|
+
is live, not a stale build), and re-inlining a drifted literal fails
|
|
1140
|
+
`sys_approval_request.status offers exactly the contract statuses, in order`.
|
|
1141
|
+
|
|
1142
|
+
- 7e5ac28: fix(approvals): 删除两处读 `session.roles` 的 admin 豁免 —— 记录锁与委托守卫回到单一权限词汇 (#4839)
|
|
1143
|
+
|
|
1144
|
+
`plugin-approvals` 的 `lifecycle-hooks.ts` 里有两处 admin 豁免,都读
|
|
1145
|
+
`ctx.session.roles`:审批**记录锁**的 `bindApprovalLockHook`,以及
|
|
1146
|
+
`sys_approval_delegation` 的 `bindDelegationWriteGuard`。两处都已删除。
|
|
1147
|
+
|
|
1148
|
+
**这不是行为变更。** `session.roles` 在整个平台没有生产者 —— ObjectQL 的
|
|
1149
|
+
`buildSession()` 逐字段构造 session,从不写 `roles` —— 所以两个分支在任何真实引擎
|
|
1150
|
+
路径上都是死代码,记录锁一直就对 admin 生效,委托一直就只能本人管理。删除让代码
|
|
1151
|
+
说出运行时本来就在做的事(spec 的 `HookContext` 声明了 `roles`,消费方在读,生产方
|
|
1152
|
+
从不写:典型的 declared ≠ enforced)。
|
|
1153
|
+
|
|
1154
|
+
**为什么不是「改用正确判据」而是删除。** `roles.includes('admin')` 还是第二套权限
|
|
1155
|
+
方言:本仓库的权限一律由 ADR-0095 词汇裁决(能力授予 `permissions`、任职
|
|
1156
|
+
`positions`、由其派生的 posture),ADR-0090 D3 更是直接禁掉 `role` 这个拼法。同包的
|
|
1157
|
+
`ApprovalService.isOverrideActor` 已经这么做了。维护者裁定两处都取「删除」而非改判据:
|
|
1158
|
+
|
|
1159
|
+
- **记录锁**:admin 释放锁定记录的正规路径已经存在(#3424 —— `recall` /
|
|
1160
|
+
`decideNode` 驳回 / `reassign`,全部由 `isOverrideActor` 把关并留痕
|
|
1161
|
+
`via_override`)。让审批终结来释放锁,记录就永远不会在审批在途时被改写 —— 这正是
|
|
1162
|
+
合规场景购买记录锁所要的保证。
|
|
1163
|
+
- **委托**:最终语义确定为**仅本人管理**(`delegator_id` 必须等于写入者;只有 system
|
|
1164
|
+
上下文旁路)。审批人临时不可用时,替他处置**在途**审批用的是
|
|
1165
|
+
`reassign`(把该审批人的名额交给替代人,连 per_group 分组归属一起带过去)/
|
|
1166
|
+
`recall` / 驳回。反过来,「替别人建一条委托」本来也做不到这件事:委托只在请求
|
|
1167
|
+
**开启**时(`resolveApproverSpec` 内的 `applyOooDelegation`)被查询,对已经挂在该
|
|
1168
|
+
审批人名下的在途审批毫无作用。
|
|
1169
|
+
|
|
1170
|
+
新增 `admin-exemption-retired.test.ts`,把上述证据变成可执行断言,并加了一道源码级
|
|
1171
|
+
pin:本包非测试源码中不得再出现 `roles` 标识符或与字符串 `'admin'` 的比较。
|
|
1172
|
+
|
|
1173
|
+
spec 侧 `session.roles` 的退役(至此零消费方)按 ADR-0049 enforce-or-remove 另立协议
|
|
1174
|
+
单处理,不在本次改动内。
|
|
1175
|
+
|
|
1176
|
+
- 19e1a8f: fix(approvals): an approval decision can no longer strand a flow run silently when no automation engine is attached (#4420)
|
|
1177
|
+
|
|
1178
|
+
#4420's fix closed every path by which a decision could be recorded while its
|
|
1179
|
+
flow stayed parked — except one, and it is the one where none of the new guards
|
|
1180
|
+
could run. Every guard it added (`assertRunResumable`'s pre-flight, the
|
|
1181
|
+
`RESUME_TARGET_LOST` refusal, the `RESUME_FAILED` throw) hangs off the
|
|
1182
|
+
automation engine. In a process where **no engine is attached**, all of them
|
|
1183
|
+
were skipped by the same `typeof this.automation?.resume === 'function'`
|
|
1184
|
+
condition that wrapped the resume itself — so the decision was written, the
|
|
1185
|
+
mirrored status field advanced, and the call answered HTTP 200 with
|
|
1186
|
+
`resumed: false` and **nothing logged at all**. That is #4420's reported
|
|
1187
|
+
symptom exactly, reproduced in the one composition its fix could not see.
|
|
1188
|
+
|
|
1189
|
+
The composition is reachable the same way the original bug was: a flow parks at
|
|
1190
|
+
an `approval` node in a process that has the automation service, and the
|
|
1191
|
+
decision arrives in one that does not (the plugin failed to init, or the host
|
|
1192
|
+
was recomposed between releases). The request row still carries a
|
|
1193
|
+
`flow_run_id` — which is the row's own declaration that a run is parked on this
|
|
1194
|
+
decision.
|
|
1195
|
+
|
|
1196
|
+
**What changes.** The decision still stands. Rolling it back is not on the
|
|
1197
|
+
table (a human really decided, and the row is durable by then), and refusing
|
|
1198
|
+
every such call would break the standalone approvals compositions the
|
|
1199
|
+
pre-flight deliberately protects — so `finalized` and `resumed` are unchanged
|
|
1200
|
+
for every existing caller. What changes is that the gap is no longer silent:
|
|
1201
|
+
|
|
1202
|
+
- it is logged at **`error`**, per the durability rule in `AGENTS.md` —
|
|
1203
|
+
persisted state and runtime state disagree while nothing looks broken from
|
|
1204
|
+
the outside, which is the class that rule exists for;
|
|
1205
|
+
- the response carries **`resumeError`**, so `resumed: false` arrives with its
|
|
1206
|
+
reason and the stranded run's id instead of leaving the caller to guess
|
|
1207
|
+
whether a resume was even attempted.
|
|
1208
|
+
|
|
1209
|
+
It reuses the already-registered `RESUME_FAILED` code and the existing resume
|
|
1210
|
+
message shape rather than introducing a new vocabulary — the fact being
|
|
1211
|
+
reported (an outcome recorded whose run did not advance) is the same one.
|
|
1212
|
+
|
|
1213
|
+
Applied at all five sites that resume a recorded outcome: `decide`, the
|
|
1214
|
+
revision-limit auto-rejection, `sendBack`, `resubmit`, and both branches of
|
|
1215
|
+
`recall` (whose revise-window path needs `cancelRun` rather than `resume`).
|
|
1216
|
+
|
|
1217
|
+
A request that names **no** run is unaffected and stays quiet — there is
|
|
1218
|
+
nothing parked on it, and reporting one there would be the mirror-image
|
|
1219
|
+
failure that trains operators to skim `error`.
|
|
1220
|
+
|
|
1221
|
+
- debc23a: feat(approvals): enrich inbox rows with `payload_labels` (snapshot field labels)
|
|
1222
|
+
|
|
1223
|
+
The approvals inbox summary title-cased raw snapshot machine keys
|
|
1224
|
+
(`assessment_status` → "Assessment Status") because the API sent no field
|
|
1225
|
+
labels. `ApprovalService.enrichRows` now attaches `payload_labels` (snapshot
|
|
1226
|
+
field key → the target object's field label), symmetric with the existing
|
|
1227
|
+
`payload_display` (which resolves the values), and `ApprovalRequestRow` gains
|
|
1228
|
+
the field. For a single-locale project the schema label is already the
|
|
1229
|
+
localized string, so a client can render the human field name (e.g. "考核状态")
|
|
1230
|
+
instead of a prettified English key.
|
|
1231
|
+
|
|
1232
|
+
- f40c5b4: refactor(plugin-approvals,plugin-reports): enforcement implementations annotate the full `ExecutionContext` (#7135)
|
|
1233
|
+
|
|
1234
|
+
The services half of #7070, mirroring what PR #7140 did for
|
|
1235
|
+
`plugin-sharing` / `plugin-audit`. #6523 converged 36 contract signatures onto
|
|
1236
|
+
the complete `resolveAuthzContext` envelope, applying the #6206 ruling —
|
|
1237
|
+
enforcement adjudicates on the whole envelope, never a per-site subset. The
|
|
1238
|
+
implementations behind those contracts still annotated their own parameters
|
|
1239
|
+
with the six-field shape the contracts used to name, so nothing they could
|
|
1240
|
+
_read_ had widened.
|
|
1241
|
+
|
|
1242
|
+
`ApprovalService`, the approval flow-node provider and `ReportService` now
|
|
1243
|
+
declare `ExecutionContext` on all 43 of those positions, and the casts the
|
|
1244
|
+
narrow annotation forced are gone:
|
|
1245
|
+
|
|
1246
|
+
- `isOverrideActor()` read the derived `posture` (ADR-0095) through an
|
|
1247
|
+
unchecked `(context as any)`. That gate decides whether a platform or tenant
|
|
1248
|
+
admin may release a STUCK approval — one routed to an unstaffed position, the
|
|
1249
|
+
only in-product recovery from a permanently locked record — so an erasure sat
|
|
1250
|
+
directly on an enforcement input: a mistyped rung would have compiled and
|
|
1251
|
+
silently denied every override. It is a declared read now.
|
|
1252
|
+
- Both services' `SYSTEM_CTX` is typed as the envelope and passed as itself,
|
|
1253
|
+
retiring the `SYSTEM_CTX as unknown as …` double casts at the three sites
|
|
1254
|
+
that hand it to a contract method.
|
|
1255
|
+
- The `(context as any).userId` / `.tenantId` reads in `ApprovalService` now
|
|
1256
|
+
read declared fields.
|
|
1257
|
+
- `OwnerContextResolver` returns the envelope, which is what a scheduled report
|
|
1258
|
+
actually resolves for its owner (#2849 / #2980).
|
|
1259
|
+
|
|
1260
|
+
**No runtime behaviour changes.** The values were always complete — this
|
|
1261
|
+
family's damage was type-side — so every gate answers exactly what it answered
|
|
1262
|
+
before. Method parameters only WIDEN what they accept, so no caller is
|
|
1263
|
+
affected, and no public export changes shape.
|
|
1264
|
+
|
|
1265
|
+
Casts deliberately kept, and now documented where they sit: `organizationId`
|
|
1266
|
+
is not a field of the envelope at all — that spelling has its own history
|
|
1267
|
+
(#5858 / `check:org-identifier`) and was held out of this change by #7070. In
|
|
1268
|
+
`approval-node.ts` the single remaining assertion exists only because the
|
|
1269
|
+
literal names that key; it was reduced from `as unknown as …` to a single
|
|
1270
|
+
`as ExecutionContext`, which still requires the literal to be comparable to
|
|
1271
|
+
the envelope.
|
|
1272
|
+
|
|
1273
|
+
Because a re-narrowed annotation would compile, ship and pass every test in
|
|
1274
|
+
these packages, the convergence is pinned by a new compile-time module per
|
|
1275
|
+
package, `exec-context-annotation.pin.ts`: it hands each parameter a fresh
|
|
1276
|
+
literal naming envelope-only fields (`posture`, `accessible_org_ids`,
|
|
1277
|
+
`org_user_ids`), which TypeScript's excess-property check rejects the moment a
|
|
1278
|
+
parameter narrows back, plus negative cases so a parameter erased to `any`
|
|
1279
|
+
cannot pass either.
|
|
1280
|
+
|
|
1281
|
+
The exported `SharingExecutionContext` type itself is NOT removed here: it is
|
|
1282
|
+
defined in `packages/spec`, which is single-owner, so its retirement is a
|
|
1283
|
+
separate follow-up.
|
|
1284
|
+
|
|
1285
|
+
- c2a1134: fix(approvals): find the zombie requests nothing was looking at (#4469)
|
|
1286
|
+
|
|
1287
|
+
#4460 stopped new zombies being produced; the rows already stuck had no mechanism
|
|
1288
|
+
to find or release them. The failure shape (#4420) is a request flipped to
|
|
1289
|
+
`approved` / `rejected` / `returned` whose `flow_run_id` points at a run that no
|
|
1290
|
+
longer exists — the decision landed, the flow never moved. Any deployment on
|
|
1291
|
+
17.0.0-rc.1 that hit the wiring hole and crossed a restart mid-approval can be
|
|
1292
|
+
carrying these rows.
|
|
1293
|
+
|
|
1294
|
+
`releaseDeadRunRequests` could not see them, and the reason is worth stating
|
|
1295
|
+
plainly: it scans `status: 'pending'`, and the very step that zombifies a request
|
|
1296
|
+
is the one that takes it OUT of `pending`. The act of breaking it removed it from
|
|
1297
|
+
the only sweeper's field of view — a large part of why this class of failure
|
|
1298
|
+
stayed silent. It could not have answered the question even if it had looked: its
|
|
1299
|
+
liveness oracle is `getRun`, which reads the execution LOG and returns `null` for
|
|
1300
|
+
a perfectly ALIVE suspended run after a restart. It treats `null` as alive
|
|
1301
|
+
(conservative, and correct for what it does) — which is exactly why it has no way
|
|
1302
|
+
to say "this run is really gone".
|
|
1303
|
+
|
|
1304
|
+
Adds `ApprovalService.inspectStrandedRequests()`, which uses BOTH oracles and
|
|
1305
|
+
reports only rows that fail both:
|
|
1306
|
+
|
|
1307
|
+
- `hasSuspendedRun(runId) === false` — the suspension store itself says no live
|
|
1308
|
+
pause exists. It THROWS when the store cannot be read, and that case is
|
|
1309
|
+
SKIPPED and counted as `undetermined`, never condemned: an unreadable store
|
|
1310
|
+
means "unknown", and a storage outage must not be published as a lost run.
|
|
1311
|
+
- `getRun(runId) == null` — no terminal history row either. A run that merely
|
|
1312
|
+
finished is not stranded; a request whose run neither waits nor ever completed
|
|
1313
|
+
is.
|
|
1314
|
+
|
|
1315
|
+
**It reports; it never rewrites.** No status is changed and no run is cancelled.
|
|
1316
|
+
The decision genuinely happened — a human approved or rejected — and silently
|
|
1317
|
+
rolling it back would make the audit trail disagree with the facts. The report
|
|
1318
|
+
carries what an operator needs to decide: which requests are stuck at which step,
|
|
1319
|
+
and what the mirrored status field on the business record still reads (usually
|
|
1320
|
+
the stale value the user is staring at). Whether to re-run the downstream actions
|
|
1321
|
+
or re-open the approval is a judgement call this cannot make.
|
|
1322
|
+
|
|
1323
|
+
It rides the existing escalation/dead-run sweep clock, so the finding surfaces in
|
|
1324
|
+
the logs without an operator knowing to go looking for it. `recalled` is
|
|
1325
|
+
deliberately out of scope: a recall abandons its run on purpose, and reporting
|
|
1326
|
+
those would bury the real findings under expected ones.
|
|
1327
|
+
|
|
1328
|
+
New export: `StrandedApprovalRequest` (the report row shape).
|
|
1329
|
+
|
|
1330
|
+
- 0f8ad09: feat(spec)+fix(approvals): publish approver value data sources, order the type enum for authors, stop silent dead approver slots (#3508 / #3807 follow-ups)
|
|
1331
|
+
|
|
1332
|
+
Four follow-ups from browser-verifying the #3508 approver work end to end.
|
|
1333
|
+
|
|
1334
|
+
**`APPROVER_VALUE_SOURCES` — the designer stops guessing where candidates live.**
|
|
1335
|
+
`xRef.map` only ever named a picker KIND (`'team'`), never where that picker's
|
|
1336
|
+
rows come from, so the designer carried its own copy of the data contract — and
|
|
1337
|
+
the first copy was wrong: every directory kind was wired to `GET
|
|
1338
|
+
/api/v1/meta/:type`, the metadata REGISTRY, which does not hold `sys_user` /
|
|
1339
|
+
`sys_team` / `sys_business_unit` / `sys_position` rows. Candidates came back
|
|
1340
|
+
empty and the control degraded to free text (#3508). The binding is now
|
|
1341
|
+
projected onto the published JSON schema as `xRef.sources` — `{ source: 'data',
|
|
1342
|
+
object, valueField }` for the record-backed kinds, the closed enum inline for
|
|
1343
|
+
`org_membership_level` — derived from `APPROVER_VALUE_BINDINGS` so the two
|
|
1344
|
+
cannot drift, and inheriting its `satisfies` exhaustiveness (a new
|
|
1345
|
+
`ApproverType` member that declares no source is a compile error). Presentation
|
|
1346
|
+
— which field to show, whether to open a people-picker, what subtitle to use —
|
|
1347
|
+
stays a renderer decision.
|
|
1348
|
+
|
|
1349
|
+
**`ApproverType` declaration order is now the authoring recommendation.**
|
|
1350
|
+
objectui#2834 argued for leading with indirect bindings and shipped that order
|
|
1351
|
+
in its own options array — which the Studio inspector never reads: it derives
|
|
1352
|
+
the picker from this enum via the published schema, so `user` still came first.
|
|
1353
|
+
The intent only takes effect if the enum carries it, so the enum now reads
|
|
1354
|
+
`manager, position, department, team, field, expression, org_membership_level,
|
|
1355
|
+
user` (deprecated `role` / `queue` still parse and stay out of every picker via
|
|
1356
|
+
`xEnumDeprecated`). Binding one specific person is the least portable choice an
|
|
1357
|
+
author can make — it breaks when the flow moves to another environment (that id
|
|
1358
|
+
does not exist there) and again when that person leaves.
|
|
1359
|
+
|
|
1360
|
+
**A graph approver that expands to nobody no longer does it in silence.**
|
|
1361
|
+
`queue` already warned (#3508); every OTHER graph type — `team`, `department`,
|
|
1362
|
+
`position`, `org_membership_level`, `manager` — fell back to the same
|
|
1363
|
+
unactionable `type:value` literal without a word. That silence is what let
|
|
1364
|
+
#3807 hide for as long as it did: the request opened with an empty slate and
|
|
1365
|
+
the first symptom was a permanently stuck approval (#3424). The fallback stays
|
|
1366
|
+
(15.x slots and substring fixtures depend on it); it now logs the type, value
|
|
1367
|
+
and organization that produced it. `user` / `field` stay quiet — they take the
|
|
1368
|
+
id they were given and never had an "expanded to nobody" state.
|
|
1369
|
+
|
|
1370
|
+
**`plugin-sharing`'s identical org scope is pinned by tests.**
|
|
1371
|
+
`BusinessUnitGraphService.orgScope` has the same strict `organization_id`
|
|
1372
|
+
equality #3807 fixed in approvals. It is unreachable today — every materialized
|
|
1373
|
+
`sys_sharing_rule` carries `organization_id = null`, so the filter is skipped —
|
|
1374
|
+
and widening an authorization path on a defect that cannot currently fire is
|
|
1375
|
+
not a change to make blind. New tests lock both the reachable paths and the
|
|
1376
|
+
divergence itself, so if sharing ever adopts the null-org=env-wide reading it
|
|
1377
|
+
is a deliberate edit to a named test rather than a silent behaviour change.
|
|
1378
|
+
|
|
1379
|
+
- 2e836de: chore(packaging): CHANGELOG.md ships in every npm tarball (#4261)
|
|
1380
|
+
|
|
1381
|
+
The AGENTS.md post-task checklist requires breaking changesets to carry their
|
|
1382
|
+
FROM → TO migration because "this text ships to consumers as `CHANGELOG.md`
|
|
1383
|
+
inside the npm package and is what an upgrading agent greps after the tombstone
|
|
1384
|
+
error." That delivery path was severed for 68 of the 69 publishable packages:
|
|
1385
|
+
npm packs `package.json` / `README*` / `LICENSE*` unconditionally but — unlike
|
|
1386
|
+
older npm versions — not `CHANGELOG.md`, and the canonical
|
|
1387
|
+
`"files": ["dist", "README.md"]` whitelist never named it. Measured on npm
|
|
1388
|
+
10.9.7: `npm pack --dry-run` on `@objectstack/types` shipped 3 files while its
|
|
1389
|
+
70KB `CHANGELOG.md` stayed behind. Only `@objectstack/spec` listed it
|
|
1390
|
+
explicitly.
|
|
1391
|
+
|
|
1392
|
+
The tombstone-error scenario is precisely the one where the repo is out of
|
|
1393
|
+
reach — the upgrading agent has `node_modules` and nothing else — so the
|
|
1394
|
+
migration text has to ride in the tarball. Every publishable package now
|
|
1395
|
+
declares `CHANGELOG.md` in `files`, and the canonical whitelist is
|
|
1396
|
+
`["dist", "README.md", "CHANGELOG.md"]`.
|
|
1397
|
+
|
|
1398
|
+
The other half is the gate: `check:published-files` gains a fifth invariant,
|
|
1399
|
+
COMPLETE — a whitelist that fails to cover `CHANGELOG.md` fails the
|
|
1400
|
+
always-required lint job, so the next package cannot silently sever the path
|
|
1401
|
+
again. `@objectstack/spec`'s per-package EXTRA_ENTRIES exemption dissolves
|
|
1402
|
+
into the canonical set.
|
|
1403
|
+
|
|
1404
|
+
Consumer-visible change: one more file per install (the package's changelog,
|
|
1405
|
+
e.g. 70.8KB for `@objectstack/types`), and `grep -r "removed key"
|
|
1406
|
+
node_modules/@objectstack/*/CHANGELOG.md` now finds the migration it was
|
|
1407
|
+
promised.
|
|
1408
|
+
|
|
1409
|
+
- 376a061: Surface the approval node's author-declared `decisionOutputs` keys on the request read as `ApprovalRequestRow.decision_outputs` (#3447 P2 UI enablement). The set varies per request (each node declares its own), so it rides the row rather than the object's static action params — a decision UI renders one input per key and POSTs `outputs` with the decision.
|
|
1410
|
+
- 3ea7271: fix(approvals): a `department` approver resolves against env-wide business units (#3807)
|
|
1411
|
+
|
|
1412
|
+
`expandBusinessUnitUsers` scoped its `sys_business_unit` reads with a strict
|
|
1413
|
+
`organization_id = <request org>` equality, so a unit whose `organization_id`
|
|
1414
|
+
is `null` was invisible: the seed check found no row, the expansion returned
|
|
1415
|
+
`[]`, and the approver fell back to the dead `department:<id>` literal that
|
|
1416
|
+
routes to nobody.
|
|
1417
|
+
|
|
1418
|
+
That is the normal case, not an edge case. An app's org tree is seeded, and a
|
|
1419
|
+
seed cannot know the organization id the runtime mints at boot, so every seeded
|
|
1420
|
+
unit carries `organization_id = null` — while an approval request always
|
|
1421
|
+
carries an org. Every business unit a flow author could pick therefore resolved
|
|
1422
|
+
to nobody, silently: the request opens, the slate is empty, and (with
|
|
1423
|
+
`lockRecord`) the record stays locked with no one able to act (#3424 is the
|
|
1424
|
+
downstream shape of the same dead end). Verified against a live showcase stack:
|
|
1425
|
+
a `{ type: 'department', value: 'bu_hq_finance' }` approver produced
|
|
1426
|
+
`pending_approvers: "department:bu_hq_finance"` while the unit's member sat
|
|
1427
|
+
right there in `sys_business_unit_member`.
|
|
1428
|
+
|
|
1429
|
+
Both the seed check and the subtree descent now scope to **this org ∪
|
|
1430
|
+
env-wide** — `$or: [{ organization_id: <org> }, { organization_id: null }]` —
|
|
1431
|
+
the same predicate `sys_metadata`'s pending-draft listing settled on for the
|
|
1432
|
+
identical reason (a strict equality silently dropping env-wide rows). The wall
|
|
1433
|
+
between two organizations is unchanged: another org's unit still fails the
|
|
1434
|
+
match, and a null-org parent does not drag another org's child unit into the
|
|
1435
|
+
subtree.
|
|
1436
|
+
|
|
1437
|
+
Note the same strict-equality scope exists in `plugin-sharing`'s
|
|
1438
|
+
`BusinessUnitGraphService.orgScope`. It is not reachable today — every
|
|
1439
|
+
materialized `sys_sharing_rule` row carries `organization_id = null`, so the
|
|
1440
|
+
filter is skipped — and is left alone here rather than widen an
|
|
1441
|
+
access-granting path on a defect that cannot currently fire.
|
|
1442
|
+
|
|
1443
|
+
- 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)
|
|
1444
|
+
|
|
1445
|
+
Two changes with different weights, from one sweep of every in-repo engine
|
|
1446
|
+
call site that still speaks a deprecated alias.
|
|
1447
|
+
|
|
1448
|
+
**The bug — three dropped sorts.** #4346 made the engine fold `filter`→`where`
|
|
1449
|
+
and `top`→`limit` on all six methods. The other four pairs in
|
|
1450
|
+
`RPC_QUERY_ALIAS_SLOTS` (`select`, `sort`, `skip`, `populate`) are folded at
|
|
1451
|
+
the RPC/wire layer only — their values need shape lowering that belongs to
|
|
1452
|
+
those layers — and a **direct `engine.find()` never crosses that layer**. Three
|
|
1453
|
+
call sites passed `sort` there, so it rode onto the AST untouched, every
|
|
1454
|
+
driver's `Array.isArray(query.orderBy)` guard declined to emit an ORDER BY, and
|
|
1455
|
+
the query returned an ordinary-looking, arbitrarily-ordered result:
|
|
1456
|
+
|
|
1457
|
+
| call site | asked for | actually got |
|
|
1458
|
+
| ----------------------------------- | ------------------------------------------------- | --------------------------- |
|
|
1459
|
+
| `share-link-routes.ts` | shared AI conversation messages, `created_at asc` | messages in arbitrary order |
|
|
1460
|
+
| `runtime/domains/share-links.ts` | same route, runtime-domain copy | same |
|
|
1461
|
+
| `share-link-service.ts` `listLinks` | the 200 most recent share links | an arbitrary 200 |
|
|
1462
|
+
|
|
1463
|
+
All three combine the dropped sort with a `limit` — the "latest N" shape whose
|
|
1464
|
+
failure #4226 spelled out: an unapplied sort returns rows in arbitrary order,
|
|
1465
|
+
which `limit` then slices into an arbitrary page. #4226 fixed that in the wire
|
|
1466
|
+
normalizer; these calls sit one layer below it. `listLinks` had no test at all,
|
|
1467
|
+
which is why it went unnoticed. Now pinned — on the option bag the engine
|
|
1468
|
+
receives, not on row order, because the failure is that the key never becomes
|
|
1469
|
+
`orderBy` and a fake engine honouring either spelling would pass either way.
|
|
1470
|
+
|
|
1471
|
+
**The cleanup — 27 no-op renames.** Every remaining in-repo engine call passing
|
|
1472
|
+
`filter` now passes `where` (approvals 5, auth 2, reports 6, sharing 11,
|
|
1473
|
+
webhooks 2, plus the one `filters` in a spec doc example). These are strict
|
|
1474
|
+
no-ops since #4346 folds the alias — the point is that the framework stops
|
|
1475
|
+
depending on a spelling it asks users to migrate off, which is a prerequisite
|
|
1476
|
+
for ever retiring the aliases. Service-level `filter` PARAMETERS (each
|
|
1477
|
+
service's own public API, e.g. `listRequests(filter)`) are deliberately
|
|
1478
|
+
untouched — those are not engine option bags.
|
|
1479
|
+
|
|
1480
|
+
Two of the renamed calls were live victims of the #4346 bug rather than
|
|
1481
|
+
cosmetic: `auth-manager`'s `stampIdentitySource` read the table's first row via
|
|
1482
|
+
`findOne({filter})` and counted the whole table via `count({filter})`, so a
|
|
1483
|
+
federated sign-in never stamped `source: 'idp_provisioned'`. #4346 already
|
|
1484
|
+
corrected the behaviour; this makes the call say what it means.
|
|
1485
|
+
|
|
1486
|
+
- deb538f: fix(storage): let an object delegate file-read authorization to its service
|
|
1487
|
+
|
|
1488
|
+
Fixes a regression from the governed-download change (ADR-0104 D3 wave 2): a
|
|
1489
|
+
**legitimate approver could see a decision attachment's filename but got 403
|
|
1490
|
+
opening it**, found by driving app-showcase in a browser as a real non-admin
|
|
1491
|
+
approver.
|
|
1492
|
+
|
|
1493
|
+
Cause: a field-owned file's download was authorized by testing whether the
|
|
1494
|
+
caller can READ the owning row. For an ordinary business object that is right —
|
|
1495
|
+
row readability _is_ the access rule. For `sys_approval_action` it is the wrong
|
|
1496
|
+
authority: the audit table is deliberately closed to ordinary approver
|
|
1497
|
+
positions (`operation 'find' … is not permitted for positions [auditor,
|
|
1498
|
+
everyone]`), so the test denied the very approver the attachment was filed for.
|
|
1499
|
+
The approvals _service_ has always had the real rule, which is why the timeline
|
|
1500
|
+
listing the attachment returned 200 while the bytes returned 403.
|
|
1501
|
+
|
|
1502
|
+
An object may now name a service to answer the question instead:
|
|
1503
|
+
|
|
1504
|
+
- `ObjectSchema.fileAccessDelegate` — a kernel service that authorizes
|
|
1505
|
+
downloads of files owned by that object's media fields.
|
|
1506
|
+
- `IFileAccessDelegate.authorizeFileRead(recordId, context)` — the contract.
|
|
1507
|
+
- `sys_approval_action` declares `'approvals'`; `ApprovalService.authorizeFileRead`
|
|
1508
|
+
reuses the _same_ gate `listActions` applies (visibility of the parent
|
|
1509
|
+
request) rather than inventing a second, looser rule for the bytes.
|
|
1510
|
+
|
|
1511
|
+
**Fails closed**: a declared delegate that is missing or does not implement the
|
|
1512
|
+
method denies, rather than silently reverting to the raw read it was declared to
|
|
1513
|
+
replace. Objects without the declaration are unchanged.
|
|
1514
|
+
|
|
1515
|
+
Verified in the browser against app-showcase, both sides of the gate: the
|
|
1516
|
+
approver now downloads the real PDF (200), and an anonymous request is still
|
|
1517
|
+
refused (401) — the anonymous capability URL the original change closed stays
|
|
1518
|
+
closed. A decision attachment ends up exactly as readable as the decision it
|
|
1519
|
+
hangs off: never more, and no longer less.
|
|
1520
|
+
|
|
1521
|
+
- 25784cf: fix(automation,approvals): 节点类型校验推迟到插件贡献完成之后 —— approval flow 不再被误报"运行时会失败" (#4771)
|
|
1522
|
+
|
|
1523
|
+
showcase 每次冷启都打印 8 条断言:这些 flow "will fail at execution time"。8 条全是假的。
|
|
1524
|
+
`AutomationServicePlugin.start()` 从 ObjectQL registry 拉起 flow 并**当场**校验节点类型,而
|
|
1525
|
+
`ApprovalsServicePlugin.start()` 在 0.8 秒后才注册 `approval` 执行器 —— 校验器在词汇表还没
|
|
1526
|
+
成型的时候就下了结论。
|
|
1527
|
+
|
|
1528
|
+
真正的代价不是噪音,是信号丢失:**真的没装 approvals 插件**的部署会得到一模一样的 8 条告警,
|
|
1529
|
+
所以这条 warn 无法区分"健康"和"坏掉",信噪比为 0。
|
|
1530
|
+
|
|
1531
|
+
ADR-0018 明确把节点词汇表定义为**开放、可运行时扩展**的(插件通过
|
|
1532
|
+
`registerNodeExecutor(type)` 贡献类型)。因此校验只在词汇表**封闭**的那一刻才成立:
|
|
1533
|
+
|
|
1534
|
+
- `AutomationEngine.sealNodeTypeVocabulary()` —— 宣告词汇表封闭,对**所有**已注册 flow 跑一次
|
|
1535
|
+
权威校验,每个有问题的 flow warn 一条。`AutomationServicePlugin` 在 `kernel:bootstrapped`
|
|
1536
|
+
调用它(严格晚于每个插件的 `start()` 和每个 `kernel:ready` handler —— 本插件自己的
|
|
1537
|
+
`kernel:ready` 还会再注册一批 flow,别的插件也可能在它的 `kernel:ready` 里贡献执行器)。
|
|
1538
|
+
- `AutomationEngine.getUnknownNodeTypeAudit(): UnknownNodeTypeAuditEntry[]` —— 同一发现的
|
|
1539
|
+
**状态**形态,供 host(CLI 启动摘要、健康检查)直接读,而不是去 grep 日志。与
|
|
1540
|
+
`getTriggerBindingAudit()` 同一套路。
|
|
1541
|
+
- 封闭之后 `registerFlow` **恢复即时告警**:Studio 发布 / dev reload 进正在运行的服务器时,
|
|
1542
|
+
词汇表确实是完整的,那句断言此时为真。所以这是时序修复,不是把告警静音。
|
|
1543
|
+
|
|
1544
|
+
告警文案也随之改成它现在能承诺的事:"Every plugin has started, so nothing will register them
|
|
1545
|
+
now — these nodes fail at execution time with NO_EXECUTOR",并给出补救动作。
|
|
1546
|
+
|
|
1547
|
+
一并修掉同一缺陷类的另一半:`ApprovalsServicePlugin` 在**拿不到 automation 引擎**时,把
|
|
1548
|
+
"`approval` 节点没注册"记成 `info` —— 而 dev 的默认日志级别是 `warn`,于是**真降级发生时反而
|
|
1549
|
+
看不见**(#4632:静默降级必须响亮)。现在是 `warn`,写明后果(该部署里每个 ADR-0019 approval
|
|
1550
|
+
flow 都会以 NO_EXECUTOR 失败)和补救(装 `@objectstack/service-automation`)。`catch` 同时收窄
|
|
1551
|
+
到"服务查找"这一步,`registerApprovalNode` 内部真出错时会以自己的身份抛出,而不再被贴上
|
|
1552
|
+
"no automation engine" 的错误标签;`automation` 服务存在但不接受节点执行器的分支从前**一条日志
|
|
1553
|
+
都不打**,现在同样 warn。
|
|
1554
|
+
|
|
1555
|
+
**嵌入式 host 注意**:直接 `new AutomationEngine()` 而不经过 `AutomationServicePlugin` 的宿主,
|
|
1556
|
+
需要在自己的插件都装好之后调用一次 `sealNodeTypeVocabulary()`,才能拿到这条告警(以及之后的
|
|
1557
|
+
即时校验)。
|
|
1558
|
+
|
|
1559
|
+
- 5d12675: Unify the approval-status vocabulary across the `sys_approval_request` i18n bundles (#7232).
|
|
1560
|
+
|
|
1561
|
+
A request rendered through the generic object surfaces used a different word than the same
|
|
1562
|
+
request in the Approvals Inbox and in the account-app navigation. The bundles now say what
|
|
1563
|
+
those surfaces already say:
|
|
1564
|
+
|
|
1565
|
+
- **zh-CN**: the `status` option `pending` reads 待审批 (was 待处理), and the `my_pending`
|
|
1566
|
+
view reads 待我审批 (was 我的待办), matching the account-app nav entry; the view's
|
|
1567
|
+
empty-state title was aligned to the same wording.
|
|
1568
|
+
- **en**: the `status` options are humanized — `Pending` / `Approved` / `Rejected` /
|
|
1569
|
+
`Recalled` / `Returned` — instead of shipping the raw enum values as labels.
|
|
1570
|
+
- **ja-JP / es-ES**: the `my_pending` view label now matches the nav wording (承認待ち /
|
|
1571
|
+
Aprobaciones pendientes).
|
|
1572
|
+
|
|
1573
|
+
Status **values** are unchanged — this is display wording only, so no stored data, filter,
|
|
1574
|
+
or API payload is affected.
|
|
1575
|
+
|
|
1576
|
+
- 8af76ae: The i18n extractor's default locale now tracks the source instead of merging (#8543), and the approval vocabularies carry authored English labels in the contract (#8580).
|
|
1577
|
+
|
|
1578
|
+
- `os i18n extract` merge mode no longer applies to the default locale: `en` is a copy of the source, not a translation, so an edited label/description/help now reaches the regenerated `en` bundle instead of being silently shadowed by the stale entry forever (53 stale entries had accumulated across 6 packages under the old behavior; all rewritten here). Translated locales (`zh-CN` / `ja-JP` / `es-ES`) keep merge semantics exactly as before — no existing translation is overwritten.
|
|
1579
|
+
- Bare-string and label-less select options now seed through the extractor's derived channel: the machine value still seeds the skeleton, but the coverage gate no longer demands "translations" of machine identifiers, and a copied value can no longer masquerade as authored display text.
|
|
1580
|
+
- New `@objectstack/spec/contracts` exports `APPROVAL_STATUS_LABELS` and `APPROVAL_ACTION_KIND_LABELS`: the authored English for `sys_approval_request.status` (previously living only in the generated `en` bundle) and `sys_approval_action.action` (previously shipping raw machine values such as `submit` / `request_info` — the #7232 humanization missed this sibling field). Both columns derive their option labels from these maps; the regenerated `en` bundles copy them verbatim.
|
|
1581
|
+
|
|
1582
|
+
- 9881074: fix(batch): the background walks seek instead of counting, so they stop skipping rows (#4363)
|
|
1583
|
+
|
|
1584
|
+
#4363 made a single paged read a partition of its result set. It could not make
|
|
1585
|
+
a _walk_ one: seven background scans paged with a growing `offset` while writing
|
|
1586
|
+
to the very rows they were reading, and an offset counts into a set those writes
|
|
1587
|
+
are changing. Rows slide past the cursor and are never visited.
|
|
1588
|
+
|
|
1589
|
+
That is not a slow page in any of these — it is a wrong answer wearing the shape
|
|
1590
|
+
of a clean run:
|
|
1591
|
+
|
|
1592
|
+
- **`rebuildApproverIndex`** built its desired state by walking
|
|
1593
|
+
`sys_approval_request WHERE status = 'pending'` with no `orderBy` at all, then
|
|
1594
|
+
**deleted** every index row that state did not explain. A skipped request
|
|
1595
|
+
meant an approver silently dropped from someone's queue. (The loop beside it
|
|
1596
|
+
ordered by `created_at` — not unique, so its pages were never a partition
|
|
1597
|
+
either.)
|
|
1598
|
+
- **`verifyFileReferences`** decides which files nothing references. A record it
|
|
1599
|
+
never visits is reported as an unreferenced file.
|
|
1600
|
+
- **`backfillFileReferences`** and the **pinyin companion backfill** rewrite
|
|
1601
|
+
each row they read, so their own writes were shifting the set out from under
|
|
1602
|
+
the cursor. Records were left unconverted and unsearchable by a run that
|
|
1603
|
+
reported success.
|
|
1604
|
+
- **`scanValueShapes`** exists to vouch that no stored value is off-shape, and
|
|
1605
|
+
it opens a migration gate on that evidence.
|
|
1606
|
+
|
|
1607
|
+
All of them now go through `keysetWalk` (`@objectstack/types`): order by a
|
|
1608
|
+
unique key, and seek past the last one instead of counting from the start. A
|
|
1609
|
+
row's key does not move when the row is updated, and cannot be shifted when
|
|
1610
|
+
another is deleted, so the walk is stable under exactly the mutation these
|
|
1611
|
+
functions perform. It is also O(n) rather than O(n²/page) — measured on
|
|
1612
|
+
Postgres over 2M rows, deep pages cost ~1.1 s by offset against ~0.09 s by seek.
|
|
1613
|
+
|
|
1614
|
+
One deliberate non-conversion: the REST **export** stream keeps its offset. It
|
|
1615
|
+
honors a caller-chosen sort, and a keyset walk would have to re-order the export
|
|
1616
|
+
by `id` to seek — changing what the user asked for to fix a cost. Its pages are
|
|
1617
|
+
already a partition since #4363; only the depth cost remains.
|
|
1618
|
+
|
|
1619
|
+
`keysetWalk` merges the cursor with `$and` rather than spreading it into the
|
|
1620
|
+
caller's filter, so a walk whose own `where` constrains the key column
|
|
1621
|
+
(`{ id: { $in: [...] } }`) keeps that constraint instead of having it silently
|
|
1622
|
+
overwritten. When a `max` cap is set it reads one row beyond the cap to tell
|
|
1623
|
+
"the cap stopped us" from "the source ended exactly there" — without that, a
|
|
1624
|
+
walk that read everything still reports `truncated`, and a caller acting on it
|
|
1625
|
+
goes looking for rows that were never withheld.
|
|
1626
|
+
|
|
1627
|
+
The storage suites' fake engines now **throw** on an `offset` instead of serving
|
|
1628
|
+
one, so the conversion is pinned rather than merely passing.
|
|
1629
|
+
|
|
1630
|
+
- cc2de0e: chore(packaging): 20 packages stop publishing their sources, tests and build tooling (#4248)
|
|
1631
|
+
|
|
1632
|
+
These 20 packages declared no `files` field, so npm fell back to packing the
|
|
1633
|
+
whole package directory. `npm pack --dry-run` on `@objectstack/plugin-webhooks`
|
|
1634
|
+
listed **21 files** — 15 under `src/`, three of them unit tests
|
|
1635
|
+
(`auto-enqueuer.test.ts`, `bootstrap-declared-webhooks.test.ts`, …), plus the
|
|
1636
|
+
build-time `scripts/i18n-extract.config.ts`. `dist/` lands on top of that at
|
|
1637
|
+
publish time rather than instead of it, so consumers were installing the
|
|
1638
|
+
TypeScript sources and the test suite alongside the artifact they asked for.
|
|
1639
|
+
|
|
1640
|
+
Each now declares `"files": ["dist", "README.md"]`, matching the 29 packages
|
|
1641
|
+
that already did. Nothing a consumer imports moves: every `main` / `types` /
|
|
1642
|
+
`exports` target in all 20 already resolved inside `dist/`, which the new
|
|
1643
|
+
`check:published-files` guard verifies rather than assumes. The visible change
|
|
1644
|
+
is a smaller install and a smaller dependency-scanning surface — `npm pack` on
|
|
1645
|
+
`@objectstack/plugin-webhooks` now yields 2 files plus `dist/`.
|
|
1646
|
+
|
|
1647
|
+
The other half of the fix is the gate. Half the packages declaring `files` and
|
|
1648
|
+
half not was the #3786 shape — a hand-copied convention with nothing enforcing
|
|
1649
|
+
it, where whoever forgets the line gets no signal at all. `check:published-files`
|
|
1650
|
+
(new, wired into the always-required `lint` job) holds every non-private
|
|
1651
|
+
workspace package to four invariants: `files` is **declared**; it is
|
|
1652
|
+
**sufficient** (covers every entry point, so tightening a whitelist cannot ship
|
|
1653
|
+
a package that fails to resolve); it is **minimal** (admits no test, test-harness
|
|
1654
|
+
config or build script); and anything beyond `dist` + `README.md` is
|
|
1655
|
+
**registered** with a reason, reconciled in both directions so a stale exemption
|
|
1656
|
+
is an error rather than dead text. `@objectstack/spec` is the one package with
|
|
1657
|
+
registered extras — its `.zod.ts` sources, JSON Schemas, liveness ledgers and
|
|
1658
|
+
`CHANGELOG.md` are product, not build input.
|
|
1659
|
+
|
|
1660
|
+
This also closes an assumption #4206 was resting on. Excluding `<pkg>/scripts/**`
|
|
1661
|
+
from the docs-drift implementation test is sound only while no package publishes
|
|
1662
|
+
`scripts/` as runtime code; that held, but it held because someone read all three
|
|
1663
|
+
offenders by hand. It is now checked on every PR.
|
|
1664
|
+
|
|
1665
|
+
- db48ad5: fix(security,approvals,metadata-core): restore batch routes on the eight objects the #3391 P1 companion fix missed (#3026)
|
|
1666
|
+
|
|
1667
|
+
The #3391 P1 contract made the bulk gate `bulk ∧ derived(child)`: a batch
|
|
1668
|
+
request is admitted only when the object grants the `bulk` **primitive** and the
|
|
1669
|
+
batched child operation is itself allowed. Before that, the `*Many` routes
|
|
1670
|
+
checked only the child verb, so a boilerplate CRUD-five whitelist
|
|
1671
|
+
(`['get','list','create','update','delete']`) batched fine.
|
|
1672
|
+
|
|
1673
|
+
The companion fix — adding the `bulk` primitive wherever an explicit whitelist
|
|
1674
|
+
survived — was applied only inside `platform-objects`. Eight objects carrying
|
|
1675
|
+
the same boilerplate live in other packages and kept the gap, so `/batch`,
|
|
1676
|
+
`createMany`, `updateMany` and `deleteMany` answered `405
|
|
1677
|
+
OBJECT_API_METHOD_NOT_ALLOWED` on objects whose single-record create/update/
|
|
1678
|
+
delete were wide open. `data-objectstack` rethrows that 405 without falling back
|
|
1679
|
+
to per-row writes, which surfaced as a hard error on multi-select delete in the
|
|
1680
|
+
Setup grids.
|
|
1681
|
+
|
|
1682
|
+
Objects reclaimed (whitelist now `['get','list','create','update','delete','bulk']`):
|
|
1683
|
+
`sys_capability`, `sys_permission_set`, `sys_position`,
|
|
1684
|
+
`sys_position_permission_set`, `sys_user_permission_set`, `sys_user_position`
|
|
1685
|
+
(plugin-security); `sys_approval_delegation` (plugin-approvals);
|
|
1686
|
+
`sys_view_definition` (metadata-core).
|
|
1687
|
+
|
|
1688
|
+
No new authority is granted: `bulk` only permits batching verbs each object
|
|
1689
|
+
already exposes one record at a time, and every batched row still passes the
|
|
1690
|
+
same row- and field-level permission checks. The whitelists stay explicit rather
|
|
1691
|
+
than being deleted — seven of the eight are `managedBy`, and
|
|
1692
|
+
`reconcileManagedApiMethods` (ADR-0103 D3) early-returns on a non-array
|
|
1693
|
+
`apiMethods`, so dropping the line would silently disable the managed-write
|
|
1694
|
+
backstop.
|
|
1695
|
+
|
|
1696
|
+
- dadd1ad: refactor(spec,plugin-sharing): retire the exported `SharingExecutionContext` type (#7218)
|
|
1697
|
+
|
|
1698
|
+
<!-- adr-0087: registered sharing-execution-context-retired -->
|
|
1699
|
+
|
|
1700
|
+
**BREAKING — public surface removal.** `SharingExecutionContext` is deleted from
|
|
1701
|
+
`@objectstack/spec` (`contracts/sharing-service`) and from
|
|
1702
|
+
`@objectstack/plugin-sharing`, which re-exported it. Both `api-surface/` and
|
|
1703
|
+
`export-origins/` snapshots are regenerated accordingly.
|
|
1704
|
+
|
|
1705
|
+
This is the deferred deletion recorded when #7070 split the convergence in two.
|
|
1706
|
+
#6523 / PR #7068 converged 36 contract signatures onto the full
|
|
1707
|
+
`resolveAuthzContext` envelope (`ExecutionContext`), applying the #6206 ruling —
|
|
1708
|
+
enforcement adjudicates on the whole envelope, never a per-site subset. The
|
|
1709
|
+
consumer halves then re-annotated the implementations: PR #7140 (identity:
|
|
1710
|
+
`plugin-sharing`, `plugin-audit`) and PR #7206 (services: `plugin-approvals`,
|
|
1711
|
+
`plugin-reports`). Both landed with the type still exported, because it is
|
|
1712
|
+
DEFINED in `packages/spec` and that package's retirement is the spec seat's to
|
|
1713
|
+
make. Nothing declares it any more, so it goes.
|
|
1714
|
+
|
|
1715
|
+
**Migration.** Anyone who imported `SharingExecutionContext` from either package
|
|
1716
|
+
should import `ExecutionContext` from `@objectstack/spec` instead — the type the
|
|
1717
|
+
contracts have declared since #7068. The old shape was six optional fields, all
|
|
1718
|
+
of which exist on the envelope with the same names and types, so a value that
|
|
1719
|
+
satisfied the retired type already satisfies `ExecutionContext`; only the
|
|
1720
|
+
spelling of the annotation changes.
|
|
1721
|
+
|
|
1722
|
+
**No runtime behaviour changes.** The type was erased at compile time and no
|
|
1723
|
+
signature's accepted shape moved: the contracts already took the wide envelope.
|
|
1724
|
+
|
|
1725
|
+
**What the retirement did NOT remove — the reason to read the pins.** Deleting
|
|
1726
|
+
the type does not make re-narrowing a compile error. Structural subtyping still
|
|
1727
|
+
accepts a six-field context where the envelope is expected, so the boundary is
|
|
1728
|
+
held by the declared parameter type plus the pins, exactly as before. The three
|
|
1729
|
+
`exec-context-annotation.pin.ts` files (`plugin-sharing`, `plugin-approvals`,
|
|
1730
|
+
`plugin-reports`) told their failure story as "the parameter narrows back to
|
|
1731
|
+
`SharingExecutionContext`", which a deletion would have quietly hollowed out.
|
|
1732
|
+
Each now keeps the retired six-field shape as a local, non-exported SPECIMEN
|
|
1733
|
+
type and refutes every enforcement parameter against it by type identity, so a
|
|
1734
|
+
re-narrowing under ANY name is red — alongside the fresh-literal
|
|
1735
|
+
excess-property checks they already carried. `sharing-service.test.ts` in
|
|
1736
|
+
`packages/spec` is re-anchored the same way, and its "twin unchanged in shape"
|
|
1737
|
+
case becomes a "twin stays retired" case. The narrative the retired type's doc
|
|
1738
|
+
block carried (the measured `(context as any).posture` specimen, and why tsc
|
|
1739
|
+
cannot police this) moves to the module doc of `contracts/sharing-service`,
|
|
1740
|
+
which the contracts and pins now point at.
|
|
1741
|
+
|
|
1742
|
+
- 2465133: fix(plugins): sweep the service-lookup erasures out of the plugin composition roots, and fix the two alias-only HTTP reads it exposed (#4251 B5)
|
|
1743
|
+
|
|
1744
|
+
Batch B5 of the #4251 sweep: the seven remaining `packages/plugins/*` composition
|
|
1745
|
+
roots. 35 lookup sites that had been erased to `any` now carry the slot's
|
|
1746
|
+
contract, so the compiler checks what each plugin actually calls on the service
|
|
1747
|
+
it resolved. The ratchet drops 143 sites / 32 files to 108 / 25.
|
|
1748
|
+
|
|
1749
|
+
**Two real defects, both of the shape this sweep exists to find.** Approvals'
|
|
1750
|
+
actionable-link pages (ADR-0043) and sharing's public share-link REST routes each
|
|
1751
|
+
read the HTTP server under `http-server` _only_ — the deprecated alias. The
|
|
1752
|
+
ledger records `http.server` as canonical and as the only name present on every
|
|
1753
|
+
provider path: `runtime.ts`'s `config.server` path registers no alias at all. On
|
|
1754
|
+
that path both lookups threw, the surrounding `catch` swallowed it, and the
|
|
1755
|
+
routes silently never mounted — approval e-mail action links 404'd and the
|
|
1756
|
+
share-link surface was absent, with nothing in the log to say so. Both reads are
|
|
1757
|
+
now canonical-first with the alias as fallback, each name in its own `try`
|
|
1758
|
+
because `getService` throws on an empty slot (so `a() ?? b()` inside one `try`
|
|
1759
|
+
never reaches `b` — the same correction #4393 made in metadata and
|
|
1760
|
+
cloud-connection).
|
|
1761
|
+
|
|
1762
|
+
Typing choices follow the batch method: pure data-plane consumers take the
|
|
1763
|
+
narrow contract (`IDataEngine` in reports), consumers that bind hook or
|
|
1764
|
+
middleware seams take the engine seen whole (`IObjectQLEngine` in approvals,
|
|
1765
|
+
sharing and pinyin-search), and slots with no contract get a **named** local
|
|
1766
|
+
surface rather than `any` — plugin-email's `MailSettingsSurface`, and the
|
|
1767
|
+
surfaces the consuming packages already declared (`ApprovalMessagingSurface`,
|
|
1768
|
+
`SharingSecurityProbe`, `ReportEmail`). A named surface that omits a member
|
|
1769
|
+
still makes the compiler name every call site; `any` says nothing.
|
|
1770
|
+
|
|
1771
|
+
No behaviour change beyond the two alias reads. No contract changes.
|
|
1772
|
+
|
|
1773
|
+
- 83c161f: feat(automation)!: a flow run with no trigger user may no longer touch data (#3760)
|
|
1774
|
+
|
|
1775
|
+
An effective `runAs:'user'` run that resolves **no trigger user** used to execute
|
|
1776
|
+
its data nodes **UNSCOPED** — it presented no principal, and the data security
|
|
1777
|
+
middleware skips when there is no principal, so the run read and wrote every row.
|
|
1778
|
+
`runAs:'user'` is an access-_narrowing_ declaration; failing to resolve it must
|
|
1779
|
+
never resolve to a grant (ADR-0049). It now **refuses** the operation
|
|
1780
|
+
(`UnscopedRunDataAccessError`), naming `runAs:'system'` as the fix.
|
|
1781
|
+
|
|
1782
|
+
**This was never really about schedules.** The docs, the spec, the runtime
|
|
1783
|
+
warning and the lint all described a schedule-shaped problem, and the lint only
|
|
1784
|
+
ever matched that shape. But the runtime predicate is "no user", and the
|
|
1785
|
+
commonest way to have no user is a **record-change flow fired by a write that
|
|
1786
|
+
carried none**: `isSystem` does _not_ suppress trigger dispatch — only
|
|
1787
|
+
`skipTriggers` does, and exactly three first-party paths set it — so every
|
|
1788
|
+
plugin/service system write, the approvals status mirror, and a `runAs:'system'`
|
|
1789
|
+
flow's own data node dispatched record-change flows with `userId: undefined`.
|
|
1790
|
+
Ordinary users reach those writes routinely (submitting for approval mirrors a
|
|
1791
|
+
status onto the target record), so the fail-open was reachable by unprivileged
|
|
1792
|
+
input and was the common case, not the rare one.
|
|
1793
|
+
|
|
1794
|
+
Deliberately **not** implemented as "inherit the triggering write's posture and
|
|
1795
|
+
run as `isSystem`". That reads like a relabel but is a privilege escalation: the
|
|
1796
|
+
security middleware's `isSystem` short-circuit fires _before_ its
|
|
1797
|
+
package-managed-row, system-row, audience-anchor and delegated-admin gates, all
|
|
1798
|
+
of which a principal-less context still has to clear. Such a run cannot write
|
|
1799
|
+
`sys_user_position` today; as `isSystem` it could. "Unscoped" was never
|
|
1800
|
+
equivalent to "system".
|
|
1801
|
+
|
|
1802
|
+
**Breaking — how to migrate.** A flow that reacts to system writes and needs to
|
|
1803
|
+
act beyond one user's grants declares `runAs: 'system'`, making the elevation
|
|
1804
|
+
explicit and audit-attributable. Otherwise ensure the trigger supplies a user.
|
|
1805
|
+
Flows that touch no data are unaffected (`runAs` is moot), and the failure is
|
|
1806
|
+
isolated: the trigger already swallows flow errors, so the originating write
|
|
1807
|
+
still succeeds. The engine warns at run _setup_, before any node executes.
|
|
1808
|
+
|
|
1809
|
+
**#3712's user-less provenance path is subsumed, not broken.** That fix let a
|
|
1810
|
+
run with no trigger user write its own approval-locked record by carrying a
|
|
1811
|
+
provenance-only ObjectQL context (the run id, nothing else). Such a run can no
|
|
1812
|
+
longer perform a data operation at all — presenting no principal is exactly what
|
|
1813
|
+
made the write unscoped — so it is refused before the lock is consulted. The
|
|
1814
|
+
capability survives via the explicit route: a schedule that must write records
|
|
1815
|
+
declares `runAs:'system'`, which the lock hook exempts on its own `isSystem`
|
|
1816
|
+
branch. The `flowRunId` exemption itself stays live and load-bearing for what
|
|
1817
|
+
#3703 built it for — a `runAs:'user'` run that _does_ have a user — where the
|
|
1818
|
+
exemption is still provenance rather than privilege.
|
|
1819
|
+
|
|
1820
|
+
Also in this change:
|
|
1821
|
+
|
|
1822
|
+
- **`flow-schedule-runas-unscoped` → `flow-runas-unscoped`, and it now fails the
|
|
1823
|
+
build.** It read as a gate and behaved as a comment — `os compile` documented
|
|
1824
|
+
that the flow lint "NEVER fails the build" — which is close to no net at all
|
|
1825
|
+
for the audience it protects, very often an AI generating flows in bulk. It now
|
|
1826
|
+
also covers the other provably user-less triggers (`time_relative`, `api`), per
|
|
1827
|
+
ADR-0073 D5. It still cannot cover `record_change`, which is undecidable at
|
|
1828
|
+
authoring time — that is exactly why the runtime refusal exists.
|
|
1829
|
+
- **Three seed writes stopped firing automation.** The seed loader's pass-2
|
|
1830
|
+
deferred-reference back-fill and both of `AppPlugin`'s basic-insert fallbacks
|
|
1831
|
+
inlined a bare `{ isSystem: true }` instead of the shared seed options, so they
|
|
1832
|
+
seeded with record-change automation live — the self-trigger vector
|
|
1833
|
+
`skipTriggers` exists to prevent, on the writes that skipped it.
|
|
1834
|
+
- **ADR-0073 amended.** Its severity rationale ("an unprivileged user cannot
|
|
1835
|
+
trigger a schedule, so there is no untrusted-input path") is falsified, and its
|
|
1836
|
+
rejection of fail-closed ("breaks legitimate scheduled CRUD — 2/3 example flows
|
|
1837
|
+
relied on the default") expired when those flows were fixed to declare
|
|
1838
|
+
`runAs:'system'`. Refusal is an interim posture, forward-compatible with the
|
|
1839
|
+
ADR's `automation` principal: when that lands, the refusal point becomes the
|
|
1840
|
+
place that resolves it.
|
|
1841
|
+
|
|
1842
|
+
- Updated dependencies [50616d9]
|
|
1843
|
+
- Updated dependencies [430dcc2]
|
|
1844
|
+
- Updated dependencies [690ccf2]
|
|
1845
|
+
- Updated dependencies [6a67d7a]
|
|
1846
|
+
- Updated dependencies [098f4bb]
|
|
1847
|
+
- Updated dependencies [333a374]
|
|
1848
|
+
- Updated dependencies [9fe9c1d]
|
|
1849
|
+
- Updated dependencies [3d5c090]
|
|
1850
|
+
- Updated dependencies [e5bd768]
|
|
1851
|
+
- Updated dependencies [08b5a3d]
|
|
1852
|
+
- Updated dependencies [e027b3e]
|
|
1853
|
+
- Updated dependencies [e6ac4bd]
|
|
1854
|
+
- Updated dependencies [c2429b0]
|
|
1855
|
+
- Updated dependencies [445a0c2]
|
|
1856
|
+
- Updated dependencies [d99aeb3]
|
|
1857
|
+
- Updated dependencies [f6609e6]
|
|
1858
|
+
- Updated dependencies [4727eb8]
|
|
1859
|
+
- Updated dependencies [a70358a]
|
|
1860
|
+
- Updated dependencies [0ecc656]
|
|
1861
|
+
- Updated dependencies [06772eb]
|
|
1862
|
+
- Updated dependencies [d4e0809]
|
|
1863
|
+
- Updated dependencies [80334c7]
|
|
1864
|
+
- Updated dependencies [f63cd09]
|
|
1865
|
+
- Updated dependencies [97e7e3c]
|
|
1866
|
+
- Updated dependencies [ce5242c]
|
|
1867
|
+
- Updated dependencies [a7163ea]
|
|
1868
|
+
- Updated dependencies [e6e9379]
|
|
1869
|
+
- Updated dependencies [5823d59]
|
|
1870
|
+
- Updated dependencies [3140f9c]
|
|
1871
|
+
- Updated dependencies [9500ba4]
|
|
1872
|
+
- Updated dependencies [fa3d0cf]
|
|
1873
|
+
- Updated dependencies [af5a224]
|
|
1874
|
+
- Updated dependencies [71f76e1]
|
|
1875
|
+
- Updated dependencies [37b1346]
|
|
1876
|
+
- Updated dependencies [99736a0]
|
|
1877
|
+
- Updated dependencies [fe67e34]
|
|
1878
|
+
- Updated dependencies [fdb4f50]
|
|
1879
|
+
- Updated dependencies [270650f]
|
|
1880
|
+
- Updated dependencies [3aef718]
|
|
1881
|
+
- Updated dependencies [1bd5652]
|
|
1882
|
+
- Updated dependencies [14252d3]
|
|
1883
|
+
- Updated dependencies [7fb436c]
|
|
1884
|
+
- Updated dependencies [879ea13]
|
|
1885
|
+
- Updated dependencies [8828b9e]
|
|
1886
|
+
- Updated dependencies [1ea6bce]
|
|
1887
|
+
- Updated dependencies [c1dcacd]
|
|
1888
|
+
- Updated dependencies [ad303ed]
|
|
1889
|
+
- Updated dependencies [32ccb23]
|
|
1890
|
+
- Updated dependencies [f5a4ef0]
|
|
1891
|
+
- Updated dependencies [2d3e255]
|
|
1892
|
+
- Updated dependencies [a8940e4]
|
|
1893
|
+
- Updated dependencies [7d7521f]
|
|
1894
|
+
- Updated dependencies [5dc4d02]
|
|
1895
|
+
- Updated dependencies [f724f69]
|
|
1896
|
+
- Updated dependencies [98877c9]
|
|
1897
|
+
- Updated dependencies [98877c9]
|
|
1898
|
+
- Updated dependencies [53068c1]
|
|
1899
|
+
- Updated dependencies [ee58392]
|
|
1900
|
+
- Updated dependencies [f16e54e]
|
|
1901
|
+
- Updated dependencies [c44dd5e]
|
|
1902
|
+
- Updated dependencies [06be54e]
|
|
1903
|
+
- Updated dependencies [28ad90e]
|
|
1904
|
+
- Updated dependencies [76d74ec]
|
|
1905
|
+
- Updated dependencies [201b31f]
|
|
1906
|
+
- Updated dependencies [e6b1b69]
|
|
1907
|
+
- Updated dependencies [259459d]
|
|
1908
|
+
- Updated dependencies [3f7f14e]
|
|
1909
|
+
- Updated dependencies [e2616e0]
|
|
1910
|
+
- Updated dependencies [6fdc5c6]
|
|
1911
|
+
- Updated dependencies [8b9d71e]
|
|
1912
|
+
- Updated dependencies [05154a1]
|
|
1913
|
+
- Updated dependencies [33f5e23]
|
|
1914
|
+
- Updated dependencies [259af21]
|
|
1915
|
+
- Updated dependencies [f8644c7]
|
|
1916
|
+
- Updated dependencies [306ca50]
|
|
1917
|
+
- Updated dependencies [840ee4b]
|
|
1918
|
+
- Updated dependencies [978fed2]
|
|
1919
|
+
- Updated dependencies [cfc293f]
|
|
1920
|
+
- Updated dependencies [587fc91]
|
|
1921
|
+
- Updated dependencies [de70b42]
|
|
1922
|
+
- Updated dependencies [9b6fe7c]
|
|
1923
|
+
- Updated dependencies [64cd010]
|
|
1924
|
+
- Updated dependencies [fb3d99b]
|
|
1925
|
+
- Updated dependencies [1986594]
|
|
1926
|
+
- Updated dependencies [6968885]
|
|
1927
|
+
- Updated dependencies [eaed61f]
|
|
1928
|
+
- Updated dependencies [52200b4]
|
|
1929
|
+
- Updated dependencies [cdfbee2]
|
|
1930
|
+
- Updated dependencies [ad4af62]
|
|
1931
|
+
- Updated dependencies [debe2f6]
|
|
1932
|
+
- Updated dependencies [d44dbfa]
|
|
1933
|
+
- Updated dependencies [29c6c9d]
|
|
1934
|
+
- Updated dependencies [d21c001]
|
|
1935
|
+
- Updated dependencies [ad047d2]
|
|
1936
|
+
- Updated dependencies [8c711fb]
|
|
1937
|
+
- Updated dependencies [f1cc3a3]
|
|
1938
|
+
- Updated dependencies [09e4547]
|
|
1939
|
+
- Updated dependencies [97b0798]
|
|
1940
|
+
- Updated dependencies [474fe39]
|
|
1941
|
+
- Updated dependencies [0bc685a]
|
|
1942
|
+
- Updated dependencies [b949059]
|
|
1943
|
+
- Updated dependencies [2826d1e]
|
|
1944
|
+
- Updated dependencies [be1c52c]
|
|
1945
|
+
- Updated dependencies [c5ff96d]
|
|
1946
|
+
- Updated dependencies [5a84d41]
|
|
1947
|
+
- Updated dependencies [84e7be9]
|
|
1948
|
+
- Updated dependencies [91f4c78]
|
|
1949
|
+
- Updated dependencies [ddc2527]
|
|
1950
|
+
- Updated dependencies [820eff9]
|
|
1951
|
+
- Updated dependencies [a6c3f38]
|
|
1952
|
+
- Updated dependencies [5fa04fb]
|
|
1953
|
+
- Updated dependencies [debc23a]
|
|
1954
|
+
- Updated dependencies [0f8ad09]
|
|
1955
|
+
- Updated dependencies [553a47f]
|
|
1956
|
+
- Updated dependencies [43a7a8d]
|
|
1957
|
+
- Updated dependencies [a98085f]
|
|
1958
|
+
- Updated dependencies [20b1a9e]
|
|
1959
|
+
- Updated dependencies [344a22a]
|
|
1960
|
+
- Updated dependencies [4827e91]
|
|
1961
|
+
- Updated dependencies [8d895ff]
|
|
1962
|
+
- Updated dependencies [86f7a20]
|
|
1963
|
+
- Updated dependencies [a3a884d]
|
|
1964
|
+
- Updated dependencies [cfed092]
|
|
1965
|
+
- Updated dependencies [203a449]
|
|
1966
|
+
- Updated dependencies [8f9689f]
|
|
1967
|
+
- Updated dependencies [73f69dc]
|
|
1968
|
+
- Updated dependencies [04c56aa]
|
|
1969
|
+
- Updated dependencies [f6472d7]
|
|
1970
|
+
- Updated dependencies [57a3bb3]
|
|
1971
|
+
- Updated dependencies [b3efeb7]
|
|
1972
|
+
- Updated dependencies [ddd075a]
|
|
1973
|
+
- Updated dependencies [88154be]
|
|
1974
|
+
- Updated dependencies [e8dc61e]
|
|
1975
|
+
- Updated dependencies [9c82146]
|
|
1976
|
+
- Updated dependencies [5f9a987]
|
|
1977
|
+
- Updated dependencies [744b8f5]
|
|
1978
|
+
- Updated dependencies [9f5cc79]
|
|
1979
|
+
- Updated dependencies [ac37fc6]
|
|
1980
|
+
- Updated dependencies [9f060e5]
|
|
1981
|
+
- Updated dependencies [bc17d39]
|
|
1982
|
+
- Updated dependencies [2f3e793]
|
|
1983
|
+
- Updated dependencies [4820f55]
|
|
1984
|
+
- Updated dependencies [462d9c4]
|
|
1985
|
+
- Updated dependencies [78caf51]
|
|
1986
|
+
- Updated dependencies [7d21581]
|
|
1987
|
+
- Updated dependencies [37785ed]
|
|
1988
|
+
- Updated dependencies [62a789b]
|
|
1989
|
+
- Updated dependencies [2e284b2]
|
|
1990
|
+
- Updated dependencies [d8e8d9c]
|
|
1991
|
+
- Updated dependencies [789ad63]
|
|
1992
|
+
- Updated dependencies [f2445c9]
|
|
1993
|
+
- Updated dependencies [94e749b]
|
|
1994
|
+
- Updated dependencies [ea1d916]
|
|
1995
|
+
- Updated dependencies [2af1988]
|
|
1996
|
+
- Updated dependencies [0af50a3]
|
|
1997
|
+
- Updated dependencies [1b49eaf]
|
|
1998
|
+
- Updated dependencies [ae31a19]
|
|
1999
|
+
- Updated dependencies [b230e5e]
|
|
2000
|
+
- Updated dependencies [5d24f4b]
|
|
2001
|
+
- Updated dependencies [29b94ed]
|
|
2002
|
+
- Updated dependencies [07c68b0]
|
|
2003
|
+
- Updated dependencies [f6cd635]
|
|
2004
|
+
- Updated dependencies [2e836de]
|
|
2005
|
+
- Updated dependencies [e0f300b]
|
|
2006
|
+
- Updated dependencies [0161c7f]
|
|
2007
|
+
- Updated dependencies [e900015]
|
|
2008
|
+
- Updated dependencies [db02d47]
|
|
2009
|
+
- Updated dependencies [b5bdf48]
|
|
2010
|
+
- Updated dependencies [23338c3]
|
|
2011
|
+
- Updated dependencies [12a19a8]
|
|
2012
|
+
- Updated dependencies [5b843fb]
|
|
2013
|
+
- Updated dependencies [62b6a2f]
|
|
2014
|
+
- Updated dependencies [7e5af5c]
|
|
2015
|
+
- Updated dependencies [5b4780b]
|
|
2016
|
+
- Updated dependencies [a933452]
|
|
2017
|
+
- Updated dependencies [9d1d9c7]
|
|
2018
|
+
- Updated dependencies [8140915]
|
|
2019
|
+
- Updated dependencies [a019e52]
|
|
2020
|
+
- Updated dependencies [e8f8f6c]
|
|
2021
|
+
- Updated dependencies [41dcda3]
|
|
2022
|
+
- Updated dependencies [7b48cf9]
|
|
2023
|
+
- Updated dependencies [b5404f4]
|
|
2024
|
+
- Updated dependencies [64fc6d5]
|
|
2025
|
+
- Updated dependencies [b746aa0]
|
|
2026
|
+
- Updated dependencies [b4487aa]
|
|
2027
|
+
- Updated dependencies [1007379]
|
|
2028
|
+
- Updated dependencies [65ca83a]
|
|
2029
|
+
- Updated dependencies [0bfdf46]
|
|
2030
|
+
- Updated dependencies [947d4f9]
|
|
2031
|
+
- Updated dependencies [f764691]
|
|
2032
|
+
- Updated dependencies [e120a5a]
|
|
2033
|
+
- Updated dependencies [e5bd2f6]
|
|
2034
|
+
- Updated dependencies [e9b5265]
|
|
2035
|
+
- Updated dependencies [e650d67]
|
|
2036
|
+
- Updated dependencies [121852d]
|
|
2037
|
+
- Updated dependencies [04476e7]
|
|
2038
|
+
- Updated dependencies [67bf2e2]
|
|
2039
|
+
- Updated dependencies [eaaf03c]
|
|
2040
|
+
- Updated dependencies [d17df80]
|
|
2041
|
+
- Updated dependencies [7d0e7b5]
|
|
2042
|
+
- Updated dependencies [c6d1cb4]
|
|
2043
|
+
- Updated dependencies [6513c17]
|
|
2044
|
+
- Updated dependencies [36030ff]
|
|
2045
|
+
- Updated dependencies [79228cd]
|
|
2046
|
+
- Updated dependencies [6117f7b]
|
|
2047
|
+
- Updated dependencies [87aca93]
|
|
2048
|
+
- Updated dependencies [e533b0b]
|
|
2049
|
+
- Updated dependencies [cdf4d9a]
|
|
2050
|
+
- Updated dependencies [aee1806]
|
|
2051
|
+
- Updated dependencies [c13350b]
|
|
2052
|
+
- Updated dependencies [c13350b]
|
|
2053
|
+
- Updated dependencies [2c1988c]
|
|
2054
|
+
- Updated dependencies [9ca2d85]
|
|
2055
|
+
- Updated dependencies [c13350b]
|
|
2056
|
+
- Updated dependencies [891d345]
|
|
2057
|
+
- Updated dependencies [c8124e5]
|
|
2058
|
+
- Updated dependencies [a52e2ef]
|
|
2059
|
+
- Updated dependencies [5293114]
|
|
2060
|
+
- Updated dependencies [376a061]
|
|
2061
|
+
- Updated dependencies [c142ced]
|
|
2062
|
+
- Updated dependencies [211abdb]
|
|
2063
|
+
- Updated dependencies [b3363e9]
|
|
2064
|
+
- Updated dependencies [eda599e]
|
|
2065
|
+
- Updated dependencies [a1a4140]
|
|
2066
|
+
- Updated dependencies [c20b875]
|
|
2067
|
+
- Updated dependencies [7c7e246]
|
|
2068
|
+
- Updated dependencies [2ef1807]
|
|
2069
|
+
- Updated dependencies [f35cdc5]
|
|
2070
|
+
- Updated dependencies [d03fe25]
|
|
2071
|
+
- Updated dependencies [2a37694]
|
|
2072
|
+
- Updated dependencies [217e2e6]
|
|
2073
|
+
- Updated dependencies [2672f85]
|
|
2074
|
+
- Updated dependencies [20bc357]
|
|
2075
|
+
- Updated dependencies [11066f6]
|
|
2076
|
+
- Updated dependencies [916af17]
|
|
2077
|
+
- Updated dependencies [84c86fb]
|
|
2078
|
+
- Updated dependencies [2a2a9fb]
|
|
2079
|
+
- Updated dependencies [86a71d1]
|
|
2080
|
+
- Updated dependencies [c001422]
|
|
2081
|
+
- Updated dependencies [77022a9]
|
|
2082
|
+
- Updated dependencies [d5c75e2]
|
|
2083
|
+
- Updated dependencies [03d26f7]
|
|
2084
|
+
- Updated dependencies [5966c2a]
|
|
2085
|
+
- Updated dependencies [2382580]
|
|
2086
|
+
- Updated dependencies [9ea2bc5]
|
|
2087
|
+
- Updated dependencies [a2e157c]
|
|
2088
|
+
- Updated dependencies [95c4227]
|
|
2089
|
+
- Updated dependencies [2a61116]
|
|
2090
|
+
- Updated dependencies [52760bf]
|
|
2091
|
+
- Updated dependencies [5543020]
|
|
2092
|
+
- Updated dependencies [880d343]
|
|
2093
|
+
- Updated dependencies [6e82972]
|
|
2094
|
+
- Updated dependencies [d4df105]
|
|
2095
|
+
- Updated dependencies [4615a18]
|
|
2096
|
+
- Updated dependencies [f505689]
|
|
2097
|
+
- Updated dependencies [d9fa683]
|
|
2098
|
+
- Updated dependencies [32d3800]
|
|
2099
|
+
- Updated dependencies [606d577]
|
|
2100
|
+
- Updated dependencies [4384921]
|
|
2101
|
+
- Updated dependencies [e2798fa]
|
|
2102
|
+
- Updated dependencies [3c628ce]
|
|
2103
|
+
- Updated dependencies [c2d9098]
|
|
2104
|
+
- Updated dependencies [0fd8556]
|
|
2105
|
+
- Updated dependencies [3c7bcc0]
|
|
2106
|
+
- Updated dependencies [4b6cac7]
|
|
2107
|
+
- Updated dependencies [7631964]
|
|
2108
|
+
- Updated dependencies [ac471a0]
|
|
2109
|
+
- Updated dependencies [60ae58e]
|
|
2110
|
+
- Updated dependencies [7f62706]
|
|
2111
|
+
- Updated dependencies [667fa44]
|
|
2112
|
+
- Updated dependencies [37e38d1]
|
|
2113
|
+
- Updated dependencies [e906126]
|
|
2114
|
+
- Updated dependencies [ce92674]
|
|
2115
|
+
- Updated dependencies [08363a0]
|
|
2116
|
+
- Updated dependencies [444de5b]
|
|
2117
|
+
- Updated dependencies [0f17114]
|
|
2118
|
+
- Updated dependencies [a227ed7]
|
|
2119
|
+
- Updated dependencies [7cb922e]
|
|
2120
|
+
- Updated dependencies [1d22114]
|
|
2121
|
+
- Updated dependencies [1eb13a0]
|
|
2122
|
+
- Updated dependencies [c52e608]
|
|
2123
|
+
- Updated dependencies [9613396]
|
|
2124
|
+
- Updated dependencies [3f7b4ff]
|
|
2125
|
+
- Updated dependencies [74155c7]
|
|
2126
|
+
- Updated dependencies [b5f9397]
|
|
2127
|
+
- Updated dependencies [db0d53c]
|
|
2128
|
+
- Updated dependencies [ed77493]
|
|
2129
|
+
- Updated dependencies [6908830]
|
|
2130
|
+
- Updated dependencies [8b06bba]
|
|
2131
|
+
- Updated dependencies [58a03d2]
|
|
2132
|
+
- Updated dependencies [2bacd1a]
|
|
2133
|
+
- Updated dependencies [e47b342]
|
|
2134
|
+
- Updated dependencies [4c54037]
|
|
2135
|
+
- Updated dependencies [dc530b4]
|
|
2136
|
+
- Updated dependencies [9f601e8]
|
|
2137
|
+
- Updated dependencies [6a9dec6]
|
|
2138
|
+
- Updated dependencies [0f7157b]
|
|
2139
|
+
- Updated dependencies [4dc1c7d]
|
|
2140
|
+
- Updated dependencies [d9bef45]
|
|
2141
|
+
- Updated dependencies [f598aa8]
|
|
2142
|
+
- Updated dependencies [4dfd002]
|
|
2143
|
+
- Updated dependencies [f549a0d]
|
|
2144
|
+
- Updated dependencies [51c5227]
|
|
2145
|
+
- Updated dependencies [82da264]
|
|
2146
|
+
- Updated dependencies [f586f1a]
|
|
2147
|
+
- Updated dependencies [77be690]
|
|
2148
|
+
- Updated dependencies [4ed7ed4]
|
|
2149
|
+
- Updated dependencies [9b9b70f]
|
|
2150
|
+
- Updated dependencies [f5a9bc2]
|
|
2151
|
+
- Updated dependencies [e59786e]
|
|
2152
|
+
- Updated dependencies [2fa4ca1]
|
|
2153
|
+
- Updated dependencies [bcf1112]
|
|
2154
|
+
- Updated dependencies [baeb4f0]
|
|
2155
|
+
- Updated dependencies [29488cc]
|
|
2156
|
+
- Updated dependencies [881a3cc]
|
|
2157
|
+
- Updated dependencies [f5a2320]
|
|
2158
|
+
- Updated dependencies [ad6317b]
|
|
2159
|
+
- Updated dependencies [811c30c]
|
|
2160
|
+
- Updated dependencies [a4a85c8]
|
|
2161
|
+
- Updated dependencies [859cb83]
|
|
2162
|
+
- Updated dependencies [07a4e26]
|
|
2163
|
+
- Updated dependencies [9774b78]
|
|
2164
|
+
- Updated dependencies [d5e9f6e]
|
|
2165
|
+
- Updated dependencies [8a88885]
|
|
2166
|
+
- Updated dependencies [deb538f]
|
|
2167
|
+
- Updated dependencies [b49ccfd]
|
|
2168
|
+
- Updated dependencies [5b89711]
|
|
2169
|
+
- Updated dependencies [85d95e7]
|
|
2170
|
+
- Updated dependencies [08cd163]
|
|
2171
|
+
- Updated dependencies [0c8a22f]
|
|
2172
|
+
- Updated dependencies [5f7669e]
|
|
2173
|
+
- Updated dependencies [becbe53]
|
|
2174
|
+
- Updated dependencies [b127c8b]
|
|
2175
|
+
- Updated dependencies [763931e]
|
|
2176
|
+
- Updated dependencies [ec975f1]
|
|
2177
|
+
- Updated dependencies [168f60f]
|
|
2178
|
+
- Updated dependencies [b07d829]
|
|
2179
|
+
- Updated dependencies [de9af8a]
|
|
2180
|
+
- Updated dependencies [eb4204b]
|
|
2181
|
+
- Updated dependencies [a80302a]
|
|
2182
|
+
- Updated dependencies [a648e96]
|
|
2183
|
+
- Updated dependencies [a47ac06]
|
|
2184
|
+
- Updated dependencies [e4c61a7]
|
|
2185
|
+
- Updated dependencies [cc60165]
|
|
2186
|
+
- Updated dependencies [474f131]
|
|
2187
|
+
- Updated dependencies [081aa6f]
|
|
2188
|
+
- Updated dependencies [91f4c78]
|
|
2189
|
+
- Updated dependencies [050cd82]
|
|
2190
|
+
- Updated dependencies [4d552af]
|
|
2191
|
+
- Updated dependencies [44d677c]
|
|
2192
|
+
- Updated dependencies [c32944d]
|
|
2193
|
+
- Updated dependencies [1dd780f]
|
|
2194
|
+
- Updated dependencies [e8d0c21]
|
|
2195
|
+
- Updated dependencies [244ca86]
|
|
2196
|
+
- Updated dependencies [546ab3c]
|
|
2197
|
+
- Updated dependencies [cafec0a]
|
|
2198
|
+
- Updated dependencies [58f3220]
|
|
2199
|
+
- Updated dependencies [07f1822]
|
|
2200
|
+
- Updated dependencies [c4df271]
|
|
2201
|
+
- Updated dependencies [c8d6f6e]
|
|
2202
|
+
- Updated dependencies [0b51bb6]
|
|
2203
|
+
- Updated dependencies [08f93bc]
|
|
2204
|
+
- Updated dependencies [d9971d3]
|
|
2205
|
+
- Updated dependencies [7dc1067]
|
|
2206
|
+
- Updated dependencies [4f13be2]
|
|
2207
|
+
- Updated dependencies [a41ba5c]
|
|
2208
|
+
- Updated dependencies [189854c]
|
|
2209
|
+
- Updated dependencies [0e3a226]
|
|
2210
|
+
- Updated dependencies [92a67f2]
|
|
2211
|
+
- Updated dependencies [9136327]
|
|
2212
|
+
- Updated dependencies [bf0ae99]
|
|
2213
|
+
- Updated dependencies [c7e7900]
|
|
2214
|
+
- Updated dependencies [eb3e650]
|
|
2215
|
+
- Updated dependencies [abeb375]
|
|
2216
|
+
- Updated dependencies [cb3b6cd]
|
|
2217
|
+
- Updated dependencies [73b7234]
|
|
2218
|
+
- Updated dependencies [d2b97c3]
|
|
2219
|
+
- Updated dependencies [61cc079]
|
|
2220
|
+
- Updated dependencies [45dc446]
|
|
2221
|
+
- Updated dependencies [0e96e46]
|
|
2222
|
+
- Updated dependencies [c1d44f7]
|
|
2223
|
+
- Updated dependencies [59b794f]
|
|
2224
|
+
- Updated dependencies [ef4efa8]
|
|
2225
|
+
- Updated dependencies [cbb6a5c]
|
|
2226
|
+
- Updated dependencies [fc3a36a]
|
|
2227
|
+
- Updated dependencies [ab9fb5c]
|
|
2228
|
+
- Updated dependencies [69787f0]
|
|
2229
|
+
- Updated dependencies [5d022a1]
|
|
2230
|
+
- Updated dependencies [042b9ee]
|
|
2231
|
+
- Updated dependencies [b25a116]
|
|
2232
|
+
- Updated dependencies [02dc076]
|
|
2233
|
+
- Updated dependencies [f985b3f]
|
|
2234
|
+
- Updated dependencies [795b6e1]
|
|
2235
|
+
- Updated dependencies [d52d4fe]
|
|
2236
|
+
- Updated dependencies [742cebb]
|
|
2237
|
+
- Updated dependencies [175d789]
|
|
2238
|
+
- Updated dependencies [f549a0d]
|
|
2239
|
+
- Updated dependencies [524151c]
|
|
2240
|
+
- Updated dependencies [427344c]
|
|
2241
|
+
- Updated dependencies [8af76ae]
|
|
2242
|
+
- Updated dependencies [1d4756e]
|
|
2243
|
+
- Updated dependencies [720c5ad]
|
|
2244
|
+
- Updated dependencies [a8d1e24]
|
|
2245
|
+
- Updated dependencies [b85cc54]
|
|
2246
|
+
- Updated dependencies [a36db28]
|
|
2247
|
+
- Updated dependencies [7a8476f]
|
|
2248
|
+
- Updated dependencies [518ca7a]
|
|
2249
|
+
- Updated dependencies [d1cabaa]
|
|
2250
|
+
- Updated dependencies [41642b0]
|
|
2251
|
+
- Updated dependencies [4cca74c]
|
|
2252
|
+
- Updated dependencies [88ef03e]
|
|
2253
|
+
- Updated dependencies [9a4932a]
|
|
2254
|
+
- Updated dependencies [3f8817a]
|
|
2255
|
+
- Updated dependencies [a2443e3]
|
|
2256
|
+
- Updated dependencies [e1554b1]
|
|
2257
|
+
- Updated dependencies [9e2caf3]
|
|
2258
|
+
- Updated dependencies [4856789]
|
|
2259
|
+
- Updated dependencies [81ce41a]
|
|
2260
|
+
- Updated dependencies [85e1e4e]
|
|
2261
|
+
- Updated dependencies [c3f4916]
|
|
2262
|
+
- Updated dependencies [55dbbba]
|
|
2263
|
+
- Updated dependencies [33e0385]
|
|
2264
|
+
- Updated dependencies [dac6a08]
|
|
2265
|
+
- Updated dependencies [72c3c86]
|
|
2266
|
+
- Updated dependencies [3670cf9]
|
|
2267
|
+
- Updated dependencies [2d8dba3]
|
|
2268
|
+
- Updated dependencies [7f1a635]
|
|
2269
|
+
- Updated dependencies [2205363]
|
|
2270
|
+
- Updated dependencies [09fe58d]
|
|
2271
|
+
- Updated dependencies [f9fc874]
|
|
2272
|
+
- Updated dependencies [d62f8eb]
|
|
2273
|
+
- Updated dependencies [d0a5ceb]
|
|
2274
|
+
- Updated dependencies [a7586cd]
|
|
2275
|
+
- Updated dependencies [4c5e80e]
|
|
2276
|
+
- Updated dependencies [4b5702a]
|
|
2277
|
+
- Updated dependencies [011b386]
|
|
2278
|
+
- Updated dependencies [e18a162]
|
|
2279
|
+
- Updated dependencies [e98fb14]
|
|
2280
|
+
- Updated dependencies [394b7a1]
|
|
2281
|
+
- Updated dependencies [ce92674]
|
|
2282
|
+
- Updated dependencies [0f2fdcd]
|
|
2283
|
+
- Updated dependencies [d6d1a50]
|
|
2284
|
+
- Updated dependencies [cf2c9b7]
|
|
2285
|
+
- Updated dependencies [8ffa8b9]
|
|
2286
|
+
- Updated dependencies [d127ff0]
|
|
2287
|
+
- Updated dependencies [674ac99]
|
|
2288
|
+
- Updated dependencies [833b512]
|
|
2289
|
+
- Updated dependencies [9881074]
|
|
2290
|
+
- Updated dependencies [1b9a53b]
|
|
2291
|
+
- Updated dependencies [36d90fc]
|
|
2292
|
+
- Updated dependencies [7777e8f]
|
|
2293
|
+
- Updated dependencies [9b86cf6]
|
|
2294
|
+
- Updated dependencies [d063a96]
|
|
2295
|
+
- Updated dependencies [8825a06]
|
|
2296
|
+
- Updated dependencies [5087ac6]
|
|
2297
|
+
- Updated dependencies [6965160]
|
|
2298
|
+
- Updated dependencies [677b591]
|
|
2299
|
+
- Updated dependencies [cf7c694]
|
|
2300
|
+
- Updated dependencies [ddd0f06]
|
|
2301
|
+
- Updated dependencies [d77d1b7]
|
|
2302
|
+
- Updated dependencies [0f9faa2]
|
|
2303
|
+
- Updated dependencies [2d1ddf0]
|
|
2304
|
+
- Updated dependencies [354b00f]
|
|
2305
|
+
- Updated dependencies [3de535b]
|
|
2306
|
+
- Updated dependencies [fe2e15a]
|
|
2307
|
+
- Updated dependencies [5b79a34]
|
|
2308
|
+
- Updated dependencies [502564d]
|
|
2309
|
+
- Updated dependencies [603cab8]
|
|
2310
|
+
- Updated dependencies [c757854]
|
|
2311
|
+
- Updated dependencies [471839d]
|
|
2312
|
+
- Updated dependencies [507b92a]
|
|
2313
|
+
- Updated dependencies [46365ab]
|
|
2314
|
+
- Updated dependencies [b508244]
|
|
2315
|
+
- Updated dependencies [df95346]
|
|
2316
|
+
- Updated dependencies [3dede58]
|
|
2317
|
+
- Updated dependencies [c6b6bb4]
|
|
2318
|
+
- Updated dependencies [594508e]
|
|
2319
|
+
- Updated dependencies [7cf42fe]
|
|
2320
|
+
- Updated dependencies [5966c2a]
|
|
2321
|
+
- Updated dependencies [59c544d]
|
|
2322
|
+
- Updated dependencies [0045682]
|
|
2323
|
+
- Updated dependencies [7309c81]
|
|
2324
|
+
- Updated dependencies [2f59da0]
|
|
2325
|
+
- Updated dependencies [7372d46]
|
|
2326
|
+
- Updated dependencies [5e247fd]
|
|
2327
|
+
- Updated dependencies [d56012f]
|
|
2328
|
+
- Updated dependencies [1a53a02]
|
|
2329
|
+
- Updated dependencies [f78dd83]
|
|
2330
|
+
- Updated dependencies [a2cd18a]
|
|
2331
|
+
- Updated dependencies [9051802]
|
|
2332
|
+
- Updated dependencies [20bc1ec]
|
|
2333
|
+
- Updated dependencies [1c625ca]
|
|
2334
|
+
- Updated dependencies [2f8328c]
|
|
2335
|
+
- Updated dependencies [a954634]
|
|
2336
|
+
- Updated dependencies [2a6c279]
|
|
2337
|
+
- Updated dependencies [9319586]
|
|
2338
|
+
- Updated dependencies [8c8f0df]
|
|
2339
|
+
- Updated dependencies [8ad609c]
|
|
2340
|
+
- Updated dependencies [bbee302]
|
|
2341
|
+
- Updated dependencies [90c2b15]
|
|
2342
|
+
- Updated dependencies [4638aaa]
|
|
2343
|
+
- Updated dependencies [0222d3c]
|
|
2344
|
+
- Updated dependencies [08863dd]
|
|
2345
|
+
- Updated dependencies [39eb01b]
|
|
2346
|
+
- Updated dependencies [071d0dc]
|
|
2347
|
+
- Updated dependencies [f293d45]
|
|
2348
|
+
- Updated dependencies [56664f5]
|
|
2349
|
+
- Updated dependencies [71f205d]
|
|
2350
|
+
- Updated dependencies [f067930]
|
|
2351
|
+
- Updated dependencies [414395b]
|
|
2352
|
+
- Updated dependencies [42eeb7d]
|
|
2353
|
+
- Updated dependencies [31cbe90]
|
|
2354
|
+
- Updated dependencies [6b7129a]
|
|
2355
|
+
- Updated dependencies [c5adfe1]
|
|
2356
|
+
- Updated dependencies [97ace2a]
|
|
2357
|
+
- Updated dependencies [26e1029]
|
|
2358
|
+
- Updated dependencies [0a936ea]
|
|
2359
|
+
- Updated dependencies [90bbf25]
|
|
2360
|
+
- Updated dependencies [023c00b]
|
|
2361
|
+
- Updated dependencies [eb91eba]
|
|
2362
|
+
- Updated dependencies [42da73d]
|
|
2363
|
+
- Updated dependencies [01e124d]
|
|
2364
|
+
- Updated dependencies [ef7b5ef]
|
|
2365
|
+
- Updated dependencies [9514767]
|
|
2366
|
+
- Updated dependencies [8f20201]
|
|
2367
|
+
- Updated dependencies [155507e]
|
|
2368
|
+
- Updated dependencies [643b7c7]
|
|
2369
|
+
- Updated dependencies [7bba90b]
|
|
2370
|
+
- Updated dependencies [8813b90]
|
|
2371
|
+
- Updated dependencies [108ba8d]
|
|
2372
|
+
- Updated dependencies [2a5f04a]
|
|
2373
|
+
- Updated dependencies [4f740b0]
|
|
2374
|
+
- Updated dependencies [030125b]
|
|
2375
|
+
- Updated dependencies [7ce02eb]
|
|
2376
|
+
- Updated dependencies [b4ad984]
|
|
2377
|
+
- Updated dependencies [e7a7506]
|
|
2378
|
+
- Updated dependencies [a9f32df]
|
|
2379
|
+
- Updated dependencies [aeb9b27]
|
|
2380
|
+
- Updated dependencies [7d27da0]
|
|
2381
|
+
- Updated dependencies [d0d5205]
|
|
2382
|
+
- Updated dependencies [1a15893]
|
|
2383
|
+
- Updated dependencies [b70e534]
|
|
2384
|
+
- Updated dependencies [7e05d8e]
|
|
2385
|
+
- Updated dependencies [8f1851e]
|
|
2386
|
+
- Updated dependencies [b4b2c7d]
|
|
2387
|
+
- Updated dependencies [fda61e4]
|
|
2388
|
+
- Updated dependencies [61ea810]
|
|
2389
|
+
- Updated dependencies [2233a85]
|
|
2390
|
+
- Updated dependencies [67452d1]
|
|
2391
|
+
- Updated dependencies [089767f]
|
|
2392
|
+
- Updated dependencies [a13827e]
|
|
2393
|
+
- Updated dependencies [66d99ec]
|
|
2394
|
+
- Updated dependencies [cb43296]
|
|
2395
|
+
- Updated dependencies [b61afc1]
|
|
2396
|
+
- Updated dependencies [79021fc]
|
|
2397
|
+
- Updated dependencies [7733604]
|
|
2398
|
+
- Updated dependencies [4921a95]
|
|
2399
|
+
- Updated dependencies [40e420f]
|
|
2400
|
+
- Updated dependencies [62dd69a]
|
|
2401
|
+
- Updated dependencies [d13004a]
|
|
2402
|
+
- Updated dependencies [be7360c]
|
|
2403
|
+
- Updated dependencies [e15e679]
|
|
2404
|
+
- Updated dependencies [2ab1257]
|
|
2405
|
+
- Updated dependencies [0fc6219]
|
|
2406
|
+
- Updated dependencies [061406d]
|
|
2407
|
+
- Updated dependencies [e4c8b6c]
|
|
2408
|
+
- Updated dependencies [acb10f6]
|
|
2409
|
+
- Updated dependencies [605e190]
|
|
2410
|
+
- Updated dependencies [c6c59f1]
|
|
2411
|
+
- Updated dependencies [b0e78a8]
|
|
2412
|
+
- Updated dependencies [f31cc8d]
|
|
2413
|
+
- Updated dependencies [f343dc4]
|
|
2414
|
+
- Updated dependencies [8269e32]
|
|
2415
|
+
- Updated dependencies [74f7339]
|
|
2416
|
+
- Updated dependencies [a6c35a2]
|
|
2417
|
+
- Updated dependencies [c2f1002]
|
|
2418
|
+
- Updated dependencies [4cc4fb7]
|
|
2419
|
+
- Updated dependencies [cc2de0e]
|
|
2420
|
+
- Updated dependencies [97b6658]
|
|
2421
|
+
- Updated dependencies [28d1eb7]
|
|
2422
|
+
- Updated dependencies [06770c0]
|
|
2423
|
+
- Updated dependencies [2c26040]
|
|
2424
|
+
- Updated dependencies [f758cec]
|
|
2425
|
+
- Updated dependencies [5b47ab5]
|
|
2426
|
+
- Updated dependencies [b09d8d9]
|
|
2427
|
+
- Updated dependencies [b09d8d9]
|
|
2428
|
+
- Updated dependencies [8675db6]
|
|
2429
|
+
- Updated dependencies [b09d8d9]
|
|
2430
|
+
- Updated dependencies [27358d5]
|
|
2431
|
+
- Updated dependencies [1c3da1f]
|
|
2432
|
+
- Updated dependencies [c1f344b]
|
|
2433
|
+
- Updated dependencies [db48ad5]
|
|
2434
|
+
- Updated dependencies [3eb1b2b]
|
|
2435
|
+
- Updated dependencies [9c93465]
|
|
2436
|
+
- Updated dependencies [a34fd2e]
|
|
2437
|
+
- Updated dependencies [ebb209c]
|
|
2438
|
+
- Updated dependencies [76bcb83]
|
|
2439
|
+
- Updated dependencies [59b85c0]
|
|
2440
|
+
- Updated dependencies [889ae47]
|
|
2441
|
+
- Updated dependencies [4f4c3fb]
|
|
2442
|
+
- Updated dependencies [78f0be8]
|
|
2443
|
+
- Updated dependencies [65f184b]
|
|
2444
|
+
- Updated dependencies [6e357ed]
|
|
2445
|
+
- Updated dependencies [d6938bf]
|
|
2446
|
+
- Updated dependencies [35f7fb4]
|
|
2447
|
+
- Updated dependencies [0410522]
|
|
2448
|
+
- Updated dependencies [63b33e6]
|
|
2449
|
+
- Updated dependencies [f163028]
|
|
2450
|
+
- Updated dependencies [814db6d]
|
|
2451
|
+
- Updated dependencies [a5302c7]
|
|
2452
|
+
- Updated dependencies [31e0be9]
|
|
2453
|
+
- Updated dependencies [4bfd455]
|
|
2454
|
+
- Updated dependencies [ffd2ce2]
|
|
2455
|
+
- Updated dependencies [2a44c1d]
|
|
2456
|
+
- Updated dependencies [7084313]
|
|
2457
|
+
- Updated dependencies [f07808c]
|
|
2458
|
+
- Updated dependencies [91cefb8]
|
|
2459
|
+
- Updated dependencies [7ffc3d3]
|
|
2460
|
+
- Updated dependencies [88346ba]
|
|
2461
|
+
- Updated dependencies [4631592]
|
|
2462
|
+
- Updated dependencies [62f8017]
|
|
2463
|
+
- Updated dependencies [32ff033]
|
|
2464
|
+
- Updated dependencies [a831df1]
|
|
2465
|
+
- Updated dependencies [f752ee3]
|
|
2466
|
+
- Updated dependencies [a1b61e0]
|
|
2467
|
+
- Updated dependencies [cd6b9f2]
|
|
2468
|
+
- Updated dependencies [2cb6d3c]
|
|
2469
|
+
- Updated dependencies [af2a095]
|
|
2470
|
+
- Updated dependencies [5ac93d4]
|
|
2471
|
+
- Updated dependencies [695cfbd]
|
|
2472
|
+
- Updated dependencies [0e043d8]
|
|
2473
|
+
- Updated dependencies [93f267f]
|
|
2474
|
+
- Updated dependencies [7445149]
|
|
2475
|
+
- Updated dependencies [ec796d5]
|
|
2476
|
+
- Updated dependencies [071d0dc]
|
|
2477
|
+
- Updated dependencies [0024abf]
|
|
2478
|
+
- Updated dependencies [8dd98bf]
|
|
2479
|
+
- Updated dependencies [e87fea1]
|
|
2480
|
+
- Updated dependencies [c65e529]
|
|
2481
|
+
- Updated dependencies [0848bea]
|
|
2482
|
+
- Updated dependencies [d51bed2]
|
|
2483
|
+
- Updated dependencies [dadd1ad]
|
|
2484
|
+
- Updated dependencies [acbf364]
|
|
2485
|
+
- Updated dependencies [3ca34c1]
|
|
2486
|
+
- Updated dependencies [7adc841]
|
|
2487
|
+
- Updated dependencies [239c3a3]
|
|
2488
|
+
- Updated dependencies [b8b3c64]
|
|
2489
|
+
- Updated dependencies [2f2e63c]
|
|
2490
|
+
- Updated dependencies [4845f85]
|
|
2491
|
+
- Updated dependencies [486d526]
|
|
2492
|
+
- Updated dependencies [94a0bbc]
|
|
2493
|
+
- Updated dependencies [bf1edef]
|
|
2494
|
+
- Updated dependencies [d6bfb3d]
|
|
2495
|
+
- Updated dependencies [8a9c079]
|
|
2496
|
+
- Updated dependencies [7b005b4]
|
|
2497
|
+
- Updated dependencies [cc3555e]
|
|
2498
|
+
- Updated dependencies [a2266a6]
|
|
2499
|
+
- Updated dependencies [d25a0ec]
|
|
2500
|
+
- Updated dependencies [89d7b35]
|
|
2501
|
+
- Updated dependencies [94f7b6a]
|
|
2502
|
+
- Updated dependencies [5c94f83]
|
|
2503
|
+
- Updated dependencies [ea936f3]
|
|
2504
|
+
- Updated dependencies [0c0fbd9]
|
|
2505
|
+
- Updated dependencies [667b83e]
|
|
2506
|
+
- Updated dependencies [f3141d8]
|
|
2507
|
+
- Updated dependencies [5487c20]
|
|
2508
|
+
- Updated dependencies [aa8b847]
|
|
2509
|
+
- Updated dependencies [7687f7b]
|
|
2510
|
+
- Updated dependencies [5a84d41]
|
|
2511
|
+
- Updated dependencies [fd3013a]
|
|
2512
|
+
- Updated dependencies [85ec26d]
|
|
2513
|
+
- Updated dependencies [73e576f]
|
|
2514
|
+
- Updated dependencies [f6476fc]
|
|
2515
|
+
- Updated dependencies [69ac82c]
|
|
2516
|
+
- Updated dependencies [4ac12ef]
|
|
2517
|
+
- Updated dependencies [833ed84]
|
|
2518
|
+
- Updated dependencies [a18abf3]
|
|
2519
|
+
- Updated dependencies [c6a4eeb]
|
|
2520
|
+
- Updated dependencies [1659072]
|
|
2521
|
+
- Updated dependencies [f450ae7]
|
|
2522
|
+
- Updated dependencies [abceb0d]
|
|
2523
|
+
- Updated dependencies [627b188]
|
|
2524
|
+
- Updated dependencies [8d4eae7]
|
|
2525
|
+
- Updated dependencies [c5a5996]
|
|
2526
|
+
- Updated dependencies [0c302a7]
|
|
2527
|
+
- Updated dependencies [b88f5e8]
|
|
2528
|
+
- Updated dependencies [857a6cf]
|
|
2529
|
+
- Updated dependencies [65a3a84]
|
|
2530
|
+
- Updated dependencies [6633337]
|
|
2531
|
+
- Updated dependencies [21676eb]
|
|
2532
|
+
- Updated dependencies [3f296bf]
|
|
2533
|
+
- Updated dependencies [e474853]
|
|
2534
|
+
- Updated dependencies [e9cb9ab]
|
|
2535
|
+
- Updated dependencies [42cc219]
|
|
2536
|
+
- Updated dependencies [d42a92f]
|
|
2537
|
+
- Updated dependencies [569611f]
|
|
2538
|
+
- Updated dependencies [51d74ad]
|
|
2539
|
+
- Updated dependencies [d7e0b42]
|
|
2540
|
+
- Updated dependencies [3510e4a]
|
|
2541
|
+
- Updated dependencies [d5749d7]
|
|
2542
|
+
- Updated dependencies [f00d8d4]
|
|
2543
|
+
- Updated dependencies [5326b36]
|
|
2544
|
+
- Updated dependencies [aa4b90d]
|
|
2545
|
+
- Updated dependencies [ccd9397]
|
|
2546
|
+
- Updated dependencies [503be86]
|
|
2547
|
+
- Updated dependencies [54299ca]
|
|
2548
|
+
- Updated dependencies [51a587d]
|
|
2549
|
+
- Updated dependencies [ae490ef]
|
|
2550
|
+
- Updated dependencies [e124711]
|
|
2551
|
+
- Updated dependencies [dc61def]
|
|
2552
|
+
- Updated dependencies [bca935b]
|
|
2553
|
+
- Updated dependencies [d92c72d]
|
|
2554
|
+
- Updated dependencies [c54c822]
|
|
2555
|
+
- Updated dependencies [8dcc0f5]
|
|
2556
|
+
- Updated dependencies [75b9e51]
|
|
2557
|
+
- Updated dependencies [f61c8cf]
|
|
2558
|
+
- Updated dependencies [e3ef52b]
|
|
2559
|
+
- Updated dependencies [0a2f233]
|
|
2560
|
+
- Updated dependencies [8621cdd]
|
|
2561
|
+
- Updated dependencies [251e888]
|
|
2562
|
+
- Updated dependencies [07f1822]
|
|
2563
|
+
- Updated dependencies [e336549]
|
|
2564
|
+
- Updated dependencies [3bb9340]
|
|
2565
|
+
- Updated dependencies [1e604c4]
|
|
2566
|
+
- Updated dependencies [04fab5e]
|
|
2567
|
+
- Updated dependencies [183b4c4]
|
|
2568
|
+
- Updated dependencies [7f713b6]
|
|
2569
|
+
- Updated dependencies [d40f43a]
|
|
2570
|
+
- Updated dependencies [2fdb36e]
|
|
2571
|
+
- Updated dependencies [e787608]
|
|
2572
|
+
- Updated dependencies [6f23667]
|
|
2573
|
+
- Updated dependencies [cde1975]
|
|
2574
|
+
- Updated dependencies [0bc685a]
|
|
2575
|
+
- Updated dependencies [20526f5]
|
|
2576
|
+
- Updated dependencies [efedd28]
|
|
2577
|
+
- Updated dependencies [5d21a48]
|
|
2578
|
+
- Updated dependencies [5278e11]
|
|
2579
|
+
- Updated dependencies [c5eef1d]
|
|
2580
|
+
- Updated dependencies [e5e7ee0]
|
|
2581
|
+
- Updated dependencies [23dba62]
|
|
2582
|
+
- Updated dependencies [e0f300b]
|
|
2583
|
+
- Updated dependencies [761a0ba]
|
|
2584
|
+
- Updated dependencies [c960170]
|
|
2585
|
+
- Updated dependencies [19365b7]
|
|
2586
|
+
- Updated dependencies [ba98e26]
|
|
2587
|
+
- Updated dependencies [b7ed26d]
|
|
2588
|
+
- Updated dependencies [a2ebea2]
|
|
2589
|
+
- Updated dependencies [800bdb0]
|
|
2590
|
+
- Updated dependencies [9d4dfc4]
|
|
2591
|
+
- Updated dependencies [1059965]
|
|
2592
|
+
- Updated dependencies [def5919]
|
|
2593
|
+
- Updated dependencies [ee264b2]
|
|
2594
|
+
- Updated dependencies [60b672e]
|
|
2595
|
+
- Updated dependencies [f104bab]
|
|
2596
|
+
- Updated dependencies [68dea0b]
|
|
2597
|
+
- Updated dependencies [6b441a8]
|
|
2598
|
+
- Updated dependencies [64f8cbe]
|
|
2599
|
+
- Updated dependencies [6cb81c7]
|
|
2600
|
+
- Updated dependencies [61282f9]
|
|
2601
|
+
- Updated dependencies [c073b8c]
|
|
2602
|
+
- Updated dependencies [ce0cfe9]
|
|
2603
|
+
- Updated dependencies [04f1182]
|
|
2604
|
+
- Updated dependencies [3a2dde7]
|
|
2605
|
+
- Updated dependencies [8c20f75]
|
|
2606
|
+
- Updated dependencies [be87153]
|
|
2607
|
+
- Updated dependencies [dd0f681]
|
|
2608
|
+
- Updated dependencies [60f0dd8]
|
|
2609
|
+
- Updated dependencies [a87c5cd]
|
|
2610
|
+
- Updated dependencies [a47f338]
|
|
2611
|
+
- Updated dependencies [b3a3d83]
|
|
2612
|
+
- Updated dependencies [7a55913]
|
|
2613
|
+
- Updated dependencies [078e28b]
|
|
2614
|
+
- Updated dependencies [35accbf]
|
|
2615
|
+
- Updated dependencies [6038de7]
|
|
2616
|
+
- Updated dependencies [fc5f536]
|
|
2617
|
+
- Updated dependencies [5647006]
|
|
2618
|
+
- Updated dependencies [e654bfd]
|
|
2619
|
+
- Updated dependencies [01a7337]
|
|
2620
|
+
- Updated dependencies [b45c71e]
|
|
2621
|
+
- Updated dependencies [d71ff32]
|
|
2622
|
+
- Updated dependencies [f8cfbb4]
|
|
2623
|
+
- Updated dependencies [6e6c872]
|
|
2624
|
+
- Updated dependencies [2598216]
|
|
2625
|
+
- Updated dependencies [11949fc]
|
|
2626
|
+
- Updated dependencies [2c7e62d]
|
|
2627
|
+
- Updated dependencies [eb95d97]
|
|
2628
|
+
- Updated dependencies [b098b0e]
|
|
2629
|
+
- Updated dependencies [4d00b13]
|
|
2630
|
+
- Updated dependencies [1363084]
|
|
2631
|
+
- Updated dependencies [fa5758e]
|
|
2632
|
+
- Updated dependencies [38f7e4f]
|
|
2633
|
+
- Updated dependencies [eb7613c]
|
|
2634
|
+
- Updated dependencies [c57f3cf]
|
|
2635
|
+
- Updated dependencies [ecc9110]
|
|
2636
|
+
- Updated dependencies [9aa5510]
|
|
2637
|
+
- Updated dependencies [e4c2dc8]
|
|
2638
|
+
- Updated dependencies [97faca3]
|
|
2639
|
+
- Updated dependencies [57bab76]
|
|
2640
|
+
- Updated dependencies [c89d18c]
|
|
2641
|
+
- Updated dependencies [1bd2795]
|
|
2642
|
+
- Updated dependencies [f7bd4e2]
|
|
2643
|
+
- Updated dependencies [694c350]
|
|
2644
|
+
- Updated dependencies [361bd5b]
|
|
2645
|
+
- Updated dependencies [aac90a5]
|
|
2646
|
+
- Updated dependencies [3da3da5]
|
|
2647
|
+
- Updated dependencies [1e6ab15]
|
|
2648
|
+
- Updated dependencies [b90086a]
|
|
2649
|
+
- Updated dependencies [129b378]
|
|
2650
|
+
- Updated dependencies [88f9d94]
|
|
2651
|
+
- Updated dependencies [8186a70]
|
|
2652
|
+
- Updated dependencies [a329cca]
|
|
2653
|
+
- Updated dependencies [c87ef70]
|
|
2654
|
+
- Updated dependencies [3cb0618]
|
|
2655
|
+
- Updated dependencies [32a0874]
|
|
2656
|
+
- Updated dependencies [6eec18c]
|
|
2657
|
+
- Updated dependencies [4d7bebf]
|
|
2658
|
+
- Updated dependencies [821ac7a]
|
|
2659
|
+
- Updated dependencies [8f81731]
|
|
2660
|
+
- Updated dependencies [7055c22]
|
|
2661
|
+
- Updated dependencies [785a748]
|
|
2662
|
+
- Updated dependencies [3af0354]
|
|
2663
|
+
- Updated dependencies [866ff16]
|
|
2664
|
+
- Updated dependencies [5a85e67]
|
|
2665
|
+
- Updated dependencies [4965bfa]
|
|
2666
|
+
- Updated dependencies [8b50cb3]
|
|
2667
|
+
- Updated dependencies [a0fdc56]
|
|
2668
|
+
- Updated dependencies [946a131]
|
|
2669
|
+
- Updated dependencies [b95577a]
|
|
2670
|
+
- Updated dependencies [0dcbc11]
|
|
2671
|
+
- Updated dependencies [d88f3e9]
|
|
2672
|
+
- Updated dependencies [ad5fe25]
|
|
2673
|
+
- Updated dependencies [c183a12]
|
|
2674
|
+
- Updated dependencies [83c161f]
|
|
2675
|
+
- Updated dependencies [d8c4957]
|
|
2676
|
+
- Updated dependencies [b9f930b]
|
|
2677
|
+
- Updated dependencies [f24cb83]
|
|
2678
|
+
- Updated dependencies [5dbbb92]
|
|
2679
|
+
- Updated dependencies [ea90179]
|
|
2680
|
+
- Updated dependencies [1818998]
|
|
2681
|
+
- Updated dependencies [ce92674]
|
|
2682
|
+
- Updated dependencies [5ef0b5b]
|
|
2683
|
+
- Updated dependencies [8c2db68]
|
|
2684
|
+
- Updated dependencies [22b5e54]
|
|
2685
|
+
- Updated dependencies [0166bd5]
|
|
2686
|
+
- Updated dependencies [3d4c545]
|
|
2687
|
+
- Updated dependencies [bb7cb41]
|
|
2688
|
+
- Updated dependencies [8064b07]
|
|
2689
|
+
- Updated dependencies [09ee21c]
|
|
2690
|
+
- Updated dependencies [4a56dbd]
|
|
2691
|
+
- Updated dependencies [289d04a]
|
|
2692
|
+
- Updated dependencies [f549a0d]
|
|
2693
|
+
- Updated dependencies [48fbacb]
|
|
2694
|
+
- Updated dependencies [06df4fa]
|
|
2695
|
+
- Updated dependencies [3fc2e48]
|
|
2696
|
+
- Updated dependencies [c9b809f]
|
|
2697
|
+
- Updated dependencies [e8f435c]
|
|
2698
|
+
- Updated dependencies [32386f8]
|
|
2699
|
+
- Updated dependencies [9b702dc]
|
|
2700
|
+
- Updated dependencies [ab16331]
|
|
2701
|
+
- Updated dependencies [41610f6]
|
|
2702
|
+
- Updated dependencies [69f1dfd]
|
|
2703
|
+
- Updated dependencies [bbe05de]
|
|
2704
|
+
- Updated dependencies [355e951]
|
|
2705
|
+
- Updated dependencies [a1dd1e4]
|
|
2706
|
+
- Updated dependencies [dadb43f]
|
|
2707
|
+
- Updated dependencies [3556b67]
|
|
2708
|
+
- @objectstack/spec@17.0.0
|
|
2709
|
+
- @objectstack/core@17.0.0
|
|
2710
|
+
- @objectstack/platform-objects@17.0.0
|
|
2711
|
+
- @objectstack/types@17.0.0
|
|
2712
|
+
- @objectstack/metadata-core@17.0.0
|
|
2713
|
+
- @objectstack/formula@17.0.0
|
|
2714
|
+
|
|
2715
|
+
## 17.0.0-rc.6
|
|
2716
|
+
|
|
2717
|
+
### Minor Changes
|
|
2718
|
+
|
|
2719
|
+
- 5fa04fb: Point the account app's **Approvals** navigation entry at the Approvals Inbox component, and contribute an **Approvals Inbox** entry to Setup (#7234).
|
|
2720
|
+
|
|
2721
|
+
The entry point has not moved — the account menu still shows **Approvals** with the same
|
|
2722
|
+
label and icon in every locale. Its destination has. It used to open the raw
|
|
2723
|
+
`sys_approval_request` grid, which is an admin/diagnostic view of the engine's own table
|
|
2724
|
+
and cannot show an approver a single decision button: every action on that object is gated
|
|
2725
|
+
on `record.viewer.can_act || record.viewer.can_override`, and the `viewer` block is
|
|
2726
|
+
attached only by the approvals REST path, never by the generic data API the object route
|
|
2727
|
+
reads. The result was a correct-looking list of rows nobody could act on. The entry is now
|
|
2728
|
+
`{ type: 'component', componentRef: 'approvals:inbox' }`, so it opens the full inbox —
|
|
2729
|
+
decision actions, business vocabulary, node progress and the request drawer.
|
|
2730
|
+
|
|
2731
|
+
- **Account app**: `nav_account_approvals` becomes a component entry gated by
|
|
2732
|
+
`requiresService: 'approvals'`, so it disappears where `plugin-approvals` is not
|
|
2733
|
+
installed (the previous `requiresObject` gate does not apply to a component entry).
|
|
2734
|
+
- **Setup**: `plugin-approvals` contributes a new **Approvals Inbox** entry at the top of
|
|
2735
|
+
**Setup → Approvals**, above the three raw tables, which stay exactly as they were —
|
|
2736
|
+
admin-gated by `manage_platform_settings` and now unambiguously the diagnostic surface.
|
|
2737
|
+
Labels ship in all four locales (zh-CN 审批中心).
|
|
2738
|
+
- `sys_approval_request` is no longer surfaced raw to end users anywhere.
|
|
2739
|
+
- **Docs**: the approver's queue is documented as the Approvals Inbox, with a snippet for
|
|
2740
|
+
mounting it in any business app — one navigation entry naming the component-registry key
|
|
2741
|
+
`approvals:inbox`, never a console path.
|
|
2742
|
+
|
|
2743
|
+
Reaching the inbox end to end in the browser additionally requires the console pin bump,
|
|
2744
|
+
tracked separately.
|
|
2745
|
+
|
|
2746
|
+
### Patch Changes
|
|
2747
|
+
|
|
2748
|
+
- e5bd768: refactor(spec)!: retire `ActionDescriptor.isAsync` — a second spelling of `supportsPause` that nothing ever read (#6748, ADR-0049)
|
|
2749
|
+
|
|
2750
|
+
<!-- adr-0087: registered action-descriptor-is-async-retired -->
|
|
2751
|
+
|
|
2752
|
+
**FROM → TO:** `isAsync: true` → delete the key; declare `supportsPause: true` (plus the
|
|
2753
|
+
`resumeAuthority` its pauses need) and return `suspend: true` from `execute()`.
|
|
2754
|
+
`isAsync: false` → delete the key; there was never anything to preserve.
|
|
2755
|
+
|
|
2756
|
+
`ActionDescriptor.isAsync` declared "suspends the flow awaiting an external reply" and no
|
|
2757
|
+
execution path read it. Measured fresh before removal across all three repos — objectstack,
|
|
2758
|
+
objectui and cloud — with zero property reads: every hit was the declaration itself, a
|
|
2759
|
+
generated baseline, one of five shipped descriptors WRITING it, a fixture pinning the
|
|
2760
|
+
shape, or prose. Declaring it never made a node suspend; omitting it never stopped one.
|
|
2761
|
+
|
|
2762
|
+
This is the remove leg of the ADR-0049 disposition its sibling took the other way. The two
|
|
2763
|
+
keys said the same thing — "this node type can suspend the run" — and #6667 split them by
|
|
2764
|
+
evidence: `supportsPause` became an enforced fact (`AutomationEngine` now refuses a
|
|
2765
|
+
suspension whose type does not declare it, at the one seam every suspension passes
|
|
2766
|
+
through), while `isAsync` had no consumer to grow into. Keeping both would leave the
|
|
2767
|
+
platform publishing two names for one capability with only one of them honoured — and
|
|
2768
|
+
`screen` declared BOTH, so a plugin author copying it had no way to tell which.
|
|
2769
|
+
|
|
2770
|
+
The retirement kit:
|
|
2771
|
+
|
|
2772
|
+
- **Tombstone, not deletion** (`retiredKey()`): `ActionDescriptorSchema` is not `.strict()`,
|
|
2773
|
+
so a plain delete would let existing descriptors parse clean and lose the key in silence
|
|
2774
|
+
(the ADR-0104 shape). Authoring `isAsync` now fails `tsc` at the descriptor literal and
|
|
2775
|
+
fails the parse inside `defineActionDescriptor()` — with the prescription in the message.
|
|
2776
|
+
- **ADR-0087 D3 `SemanticMigration`** (`action-descriptor-is-async-retired`) plus the exact
|
|
2777
|
+
`RETIRED_KEYS_BY_MAJOR` entry. No D2 conversion, deliberately: a descriptor is published
|
|
2778
|
+
from an executor's TypeScript and never stored in stack metadata, so there is no source
|
|
2779
|
+
for `os migrate meta` to rewrite — the `EnhancedApiError.fieldErrors` disposition.
|
|
2780
|
+
- The five shipped writers stop writing it (`screen`, `map`, `wait`, `approval`,
|
|
2781
|
+
`approval_revise`); the descriptors they publish lose the key, which is why the two
|
|
2782
|
+
runtime packages appear here.
|
|
2783
|
+
- Generated baselines (`authorable-surface/automation.json` gains `[RETIRED]`,
|
|
2784
|
+
`authorable-defaults/automation.json` loses the default line), `spec-changes.json`, the
|
|
2785
|
+
upgrade guide and the reference docs regenerated.
|
|
2786
|
+
|
|
2787
|
+
No runtime behaviour changes — that impossibility is the reason for the removal. The same
|
|
2788
|
+
commit also corrects `supportsPause`'s TSDoc, which still described itself as a declaration
|
|
2789
|
+
no execution path reads; #6667 made that false (#6749).
|
|
2790
|
+
|
|
2791
|
+
- f40c5b4: refactor(plugin-approvals,plugin-reports): enforcement implementations annotate the full `ExecutionContext` (#7135)
|
|
2792
|
+
|
|
2793
|
+
The services half of #7070, mirroring what PR #7140 did for
|
|
2794
|
+
`plugin-sharing` / `plugin-audit`. #6523 converged 36 contract signatures onto
|
|
2795
|
+
the complete `resolveAuthzContext` envelope, applying the #6206 ruling —
|
|
2796
|
+
enforcement adjudicates on the whole envelope, never a per-site subset. The
|
|
2797
|
+
implementations behind those contracts still annotated their own parameters
|
|
2798
|
+
with the six-field shape the contracts used to name, so nothing they could
|
|
2799
|
+
_read_ had widened.
|
|
2800
|
+
|
|
2801
|
+
`ApprovalService`, the approval flow-node provider and `ReportService` now
|
|
2802
|
+
declare `ExecutionContext` on all 43 of those positions, and the casts the
|
|
2803
|
+
narrow annotation forced are gone:
|
|
2804
|
+
|
|
2805
|
+
- `isOverrideActor()` read the derived `posture` (ADR-0095) through an
|
|
2806
|
+
unchecked `(context as any)`. That gate decides whether a platform or tenant
|
|
2807
|
+
admin may release a STUCK approval — one routed to an unstaffed position, the
|
|
2808
|
+
only in-product recovery from a permanently locked record — so an erasure sat
|
|
2809
|
+
directly on an enforcement input: a mistyped rung would have compiled and
|
|
2810
|
+
silently denied every override. It is a declared read now.
|
|
2811
|
+
- Both services' `SYSTEM_CTX` is typed as the envelope and passed as itself,
|
|
2812
|
+
retiring the `SYSTEM_CTX as unknown as …` double casts at the three sites
|
|
2813
|
+
that hand it to a contract method.
|
|
2814
|
+
- The `(context as any).userId` / `.tenantId` reads in `ApprovalService` now
|
|
2815
|
+
read declared fields.
|
|
2816
|
+
- `OwnerContextResolver` returns the envelope, which is what a scheduled report
|
|
2817
|
+
actually resolves for its owner (#2849 / #2980).
|
|
2818
|
+
|
|
2819
|
+
**No runtime behaviour changes.** The values were always complete — this
|
|
2820
|
+
family's damage was type-side — so every gate answers exactly what it answered
|
|
2821
|
+
before. Method parameters only WIDEN what they accept, so no caller is
|
|
2822
|
+
affected, and no public export changes shape.
|
|
2823
|
+
|
|
2824
|
+
Casts deliberately kept, and now documented where they sit: `organizationId`
|
|
2825
|
+
is not a field of the envelope at all — that spelling has its own history
|
|
2826
|
+
(#5858 / `check:org-identifier`) and was held out of this change by #7070. In
|
|
2827
|
+
`approval-node.ts` the single remaining assertion exists only because the
|
|
2828
|
+
literal names that key; it was reduced from `as unknown as …` to a single
|
|
2829
|
+
`as ExecutionContext`, which still requires the literal to be comparable to
|
|
2830
|
+
the envelope.
|
|
2831
|
+
|
|
2832
|
+
Because a re-narrowed annotation would compile, ship and pass every test in
|
|
2833
|
+
these packages, the convergence is pinned by a new compile-time module per
|
|
2834
|
+
package, `exec-context-annotation.pin.ts`: it hands each parameter a fresh
|
|
2835
|
+
literal naming envelope-only fields (`posture`, `accessible_org_ids`,
|
|
2836
|
+
`org_user_ids`), which TypeScript's excess-property check rejects the moment a
|
|
2837
|
+
parameter narrows back, plus negative cases so a parameter erased to `any`
|
|
2838
|
+
cannot pass either.
|
|
2839
|
+
|
|
2840
|
+
The exported `SharingExecutionContext` type itself is NOT removed here: it is
|
|
2841
|
+
defined in `packages/spec`, which is single-owner, so its retirement is a
|
|
2842
|
+
separate follow-up.
|
|
2843
|
+
|
|
2844
|
+
- 5d12675: Unify the approval-status vocabulary across the `sys_approval_request` i18n bundles (#7232).
|
|
2845
|
+
|
|
2846
|
+
A request rendered through the generic object surfaces used a different word than the same
|
|
2847
|
+
request in the Approvals Inbox and in the account-app navigation. The bundles now say what
|
|
2848
|
+
those surfaces already say:
|
|
2849
|
+
|
|
2850
|
+
- **zh-CN**: the `status` option `pending` reads 待审批 (was 待处理), and the `my_pending`
|
|
2851
|
+
view reads 待我审批 (was 我的待办), matching the account-app nav entry; the view's
|
|
2852
|
+
empty-state title was aligned to the same wording.
|
|
2853
|
+
- **en**: the `status` options are humanized — `Pending` / `Approved` / `Rejected` /
|
|
2854
|
+
`Recalled` / `Returned` — instead of shipping the raw enum values as labels.
|
|
2855
|
+
- **ja-JP / es-ES**: the `my_pending` view label now matches the nav wording (承認待ち /
|
|
2856
|
+
Aprobaciones pendientes).
|
|
2857
|
+
|
|
2858
|
+
Status **values** are unchanged — this is display wording only, so no stored data, filter,
|
|
2859
|
+
or API payload is affected.
|
|
2860
|
+
|
|
2861
|
+
- dadd1ad: refactor(spec,plugin-sharing): retire the exported `SharingExecutionContext` type (#7218)
|
|
2862
|
+
|
|
2863
|
+
<!-- adr-0087: registered sharing-execution-context-retired -->
|
|
2864
|
+
|
|
2865
|
+
**BREAKING — public surface removal.** `SharingExecutionContext` is deleted from
|
|
2866
|
+
`@objectstack/spec` (`contracts/sharing-service`) and from
|
|
2867
|
+
`@objectstack/plugin-sharing`, which re-exported it. Both `api-surface/` and
|
|
2868
|
+
`export-origins/` snapshots are regenerated accordingly.
|
|
2869
|
+
|
|
2870
|
+
This is the deferred deletion recorded when #7070 split the convergence in two.
|
|
2871
|
+
#6523 / PR #7068 converged 36 contract signatures onto the full
|
|
2872
|
+
`resolveAuthzContext` envelope (`ExecutionContext`), applying the #6206 ruling —
|
|
2873
|
+
enforcement adjudicates on the whole envelope, never a per-site subset. The
|
|
2874
|
+
consumer halves then re-annotated the implementations: PR #7140 (identity:
|
|
2875
|
+
`plugin-sharing`, `plugin-audit`) and PR #7206 (services: `plugin-approvals`,
|
|
2876
|
+
`plugin-reports`). Both landed with the type still exported, because it is
|
|
2877
|
+
DEFINED in `packages/spec` and that package's retirement is the spec seat's to
|
|
2878
|
+
make. Nothing declares it any more, so it goes.
|
|
2879
|
+
|
|
2880
|
+
**Migration.** Anyone who imported `SharingExecutionContext` from either package
|
|
2881
|
+
should import `ExecutionContext` from `@objectstack/spec` instead — the type the
|
|
2882
|
+
contracts have declared since #7068. The old shape was six optional fields, all
|
|
2883
|
+
of which exist on the envelope with the same names and types, so a value that
|
|
2884
|
+
satisfied the retired type already satisfies `ExecutionContext`; only the
|
|
2885
|
+
spelling of the annotation changes.
|
|
2886
|
+
|
|
2887
|
+
**No runtime behaviour changes.** The type was erased at compile time and no
|
|
2888
|
+
signature's accepted shape moved: the contracts already took the wide envelope.
|
|
2889
|
+
|
|
2890
|
+
**What the retirement did NOT remove — the reason to read the pins.** Deleting
|
|
2891
|
+
the type does not make re-narrowing a compile error. Structural subtyping still
|
|
2892
|
+
accepts a six-field context where the envelope is expected, so the boundary is
|
|
2893
|
+
held by the declared parameter type plus the pins, exactly as before. The three
|
|
2894
|
+
`exec-context-annotation.pin.ts` files (`plugin-sharing`, `plugin-approvals`,
|
|
2895
|
+
`plugin-reports`) told their failure story as "the parameter narrows back to
|
|
2896
|
+
`SharingExecutionContext`", which a deletion would have quietly hollowed out.
|
|
2897
|
+
Each now keeps the retired six-field shape as a local, non-exported SPECIMEN
|
|
2898
|
+
type and refutes every enforcement parameter against it by type identity, so a
|
|
2899
|
+
re-narrowing under ANY name is red — alongside the fresh-literal
|
|
2900
|
+
excess-property checks they already carried. `sharing-service.test.ts` in
|
|
2901
|
+
`packages/spec` is re-anchored the same way, and its "twin unchanged in shape"
|
|
2902
|
+
case becomes a "twin stays retired" case. The narrative the retired type's doc
|
|
2903
|
+
block carried (the measured `(context as any).posture` specimen, and why tsc
|
|
2904
|
+
cannot police this) moves to the module doc of `contracts/sharing-service`,
|
|
2905
|
+
which the contracts and pins now point at.
|
|
2906
|
+
|
|
2907
|
+
- 2465133: fix(plugins): sweep the service-lookup erasures out of the plugin composition roots, and fix the two alias-only HTTP reads it exposed (#4251 B5)
|
|
2908
|
+
|
|
2909
|
+
Batch B5 of the #4251 sweep: the seven remaining `packages/plugins/*` composition
|
|
2910
|
+
roots. 35 lookup sites that had been erased to `any` now carry the slot's
|
|
2911
|
+
contract, so the compiler checks what each plugin actually calls on the service
|
|
2912
|
+
it resolved. The ratchet drops 143 sites / 32 files to 108 / 25.
|
|
2913
|
+
|
|
2914
|
+
**Two real defects, both of the shape this sweep exists to find.** Approvals'
|
|
2915
|
+
actionable-link pages (ADR-0043) and sharing's public share-link REST routes each
|
|
2916
|
+
read the HTTP server under `http-server` _only_ — the deprecated alias. The
|
|
2917
|
+
ledger records `http.server` as canonical and as the only name present on every
|
|
2918
|
+
provider path: `runtime.ts`'s `config.server` path registers no alias at all. On
|
|
2919
|
+
that path both lookups threw, the surrounding `catch` swallowed it, and the
|
|
2920
|
+
routes silently never mounted — approval e-mail action links 404'd and the
|
|
2921
|
+
share-link surface was absent, with nothing in the log to say so. Both reads are
|
|
2922
|
+
now canonical-first with the alias as fallback, each name in its own `try`
|
|
2923
|
+
because `getService` throws on an empty slot (so `a() ?? b()` inside one `try`
|
|
2924
|
+
never reaches `b` — the same correction #4393 made in metadata and
|
|
2925
|
+
cloud-connection).
|
|
2926
|
+
|
|
2927
|
+
Typing choices follow the batch method: pure data-plane consumers take the
|
|
2928
|
+
narrow contract (`IDataEngine` in reports), consumers that bind hook or
|
|
2929
|
+
middleware seams take the engine seen whole (`IObjectQLEngine` in approvals,
|
|
2930
|
+
sharing and pinyin-search), and slots with no contract get a **named** local
|
|
2931
|
+
surface rather than `any` — plugin-email's `MailSettingsSurface`, and the
|
|
2932
|
+
surfaces the consuming packages already declared (`ApprovalMessagingSurface`,
|
|
2933
|
+
`SharingSecurityProbe`, `ReportEmail`). A named surface that omits a member
|
|
2934
|
+
still makes the compiler name every call site; `any` says nothing.
|
|
2935
|
+
|
|
2936
|
+
No behaviour change beyond the two alias reads. No contract changes.
|
|
2937
|
+
|
|
2938
|
+
- Updated dependencies [3d5c090]
|
|
2939
|
+
- Updated dependencies [e5bd768]
|
|
2940
|
+
- Updated dependencies [e027b3e]
|
|
2941
|
+
- Updated dependencies [c2429b0]
|
|
2942
|
+
- Updated dependencies [445a0c2]
|
|
2943
|
+
- Updated dependencies [f6609e6]
|
|
2944
|
+
- Updated dependencies [a70358a]
|
|
2945
|
+
- Updated dependencies [97e7e3c]
|
|
2946
|
+
- Updated dependencies [8828b9e]
|
|
2947
|
+
- Updated dependencies [53068c1]
|
|
2948
|
+
- Updated dependencies [ee58392]
|
|
2949
|
+
- Updated dependencies [f16e54e]
|
|
2950
|
+
- Updated dependencies [06be54e]
|
|
2951
|
+
- Updated dependencies [259459d]
|
|
2952
|
+
- Updated dependencies [3f7f14e]
|
|
2953
|
+
- Updated dependencies [6968885]
|
|
2954
|
+
- Updated dependencies [eaed61f]
|
|
2955
|
+
- Updated dependencies [debe2f6]
|
|
2956
|
+
- Updated dependencies [97b0798]
|
|
2957
|
+
- Updated dependencies [5fa04fb]
|
|
2958
|
+
- Updated dependencies [43a7a8d]
|
|
2959
|
+
- Updated dependencies [73f69dc]
|
|
2960
|
+
- Updated dependencies [04c56aa]
|
|
2961
|
+
- Updated dependencies [b3efeb7]
|
|
2962
|
+
- Updated dependencies [ddd075a]
|
|
2963
|
+
- Updated dependencies [88154be]
|
|
2964
|
+
- Updated dependencies [e8dc61e]
|
|
2965
|
+
- Updated dependencies [2f3e793]
|
|
2966
|
+
- Updated dependencies [d8e8d9c]
|
|
2967
|
+
- Updated dependencies [94e749b]
|
|
2968
|
+
- Updated dependencies [ea1d916]
|
|
2969
|
+
- Updated dependencies [ae31a19]
|
|
2970
|
+
- Updated dependencies [b230e5e]
|
|
2971
|
+
- Updated dependencies [5d24f4b]
|
|
2972
|
+
- Updated dependencies [29b94ed]
|
|
2973
|
+
- Updated dependencies [07c68b0]
|
|
2974
|
+
- Updated dependencies [f6cd635]
|
|
2975
|
+
- Updated dependencies [e0f300b]
|
|
2976
|
+
- Updated dependencies [62b6a2f]
|
|
2977
|
+
- Updated dependencies [5b4780b]
|
|
2978
|
+
- Updated dependencies [a933452]
|
|
2979
|
+
- Updated dependencies [8140915]
|
|
2980
|
+
- Updated dependencies [7b48cf9]
|
|
2981
|
+
- Updated dependencies [b5404f4]
|
|
2982
|
+
- Updated dependencies [f764691]
|
|
2983
|
+
- Updated dependencies [e120a5a]
|
|
2984
|
+
- Updated dependencies [e9b5265]
|
|
2985
|
+
- Updated dependencies [e650d67]
|
|
2986
|
+
- Updated dependencies [121852d]
|
|
2987
|
+
- Updated dependencies [04476e7]
|
|
2988
|
+
- Updated dependencies [79228cd]
|
|
2989
|
+
- Updated dependencies [b3363e9]
|
|
2990
|
+
- Updated dependencies [2ef1807]
|
|
2991
|
+
- Updated dependencies [d03fe25]
|
|
2992
|
+
- Updated dependencies [2672f85]
|
|
2993
|
+
- Updated dependencies [11066f6]
|
|
2994
|
+
- Updated dependencies [916af17]
|
|
2995
|
+
- Updated dependencies [84c86fb]
|
|
2996
|
+
- Updated dependencies [2a2a9fb]
|
|
2997
|
+
- Updated dependencies [a2e157c]
|
|
2998
|
+
- Updated dependencies [95c4227]
|
|
2999
|
+
- Updated dependencies [2a61116]
|
|
3000
|
+
- Updated dependencies [d4df105]
|
|
3001
|
+
- Updated dependencies [e2798fa]
|
|
3002
|
+
- Updated dependencies [0fd8556]
|
|
3003
|
+
- Updated dependencies [74155c7]
|
|
3004
|
+
- Updated dependencies [6908830]
|
|
3005
|
+
- Updated dependencies [8b06bba]
|
|
3006
|
+
- Updated dependencies [4c54037]
|
|
3007
|
+
- Updated dependencies [0f7157b]
|
|
3008
|
+
- Updated dependencies [d9bef45]
|
|
3009
|
+
- Updated dependencies [f549a0d]
|
|
3010
|
+
- Updated dependencies [82da264]
|
|
3011
|
+
- Updated dependencies [f586f1a]
|
|
3012
|
+
- Updated dependencies [9b9b70f]
|
|
3013
|
+
- Updated dependencies [f5a9bc2]
|
|
3014
|
+
- Updated dependencies [881a3cc]
|
|
3015
|
+
- Updated dependencies [ad6317b]
|
|
3016
|
+
- Updated dependencies [d5e9f6e]
|
|
3017
|
+
- Updated dependencies [8a88885]
|
|
3018
|
+
- Updated dependencies [5f7669e]
|
|
3019
|
+
- Updated dependencies [becbe53]
|
|
3020
|
+
- Updated dependencies [b127c8b]
|
|
3021
|
+
- Updated dependencies [a80302a]
|
|
3022
|
+
- Updated dependencies [474f131]
|
|
3023
|
+
- Updated dependencies [050cd82]
|
|
3024
|
+
- Updated dependencies [4d552af]
|
|
3025
|
+
- Updated dependencies [44d677c]
|
|
3026
|
+
- Updated dependencies [c32944d]
|
|
3027
|
+
- Updated dependencies [1dd780f]
|
|
3028
|
+
- Updated dependencies [cafec0a]
|
|
3029
|
+
- Updated dependencies [c8d6f6e]
|
|
3030
|
+
- Updated dependencies [92a67f2]
|
|
3031
|
+
- Updated dependencies [9136327]
|
|
3032
|
+
- Updated dependencies [bf0ae99]
|
|
3033
|
+
- Updated dependencies [cb3b6cd]
|
|
3034
|
+
- Updated dependencies [73b7234]
|
|
3035
|
+
- Updated dependencies [d2b97c3]
|
|
3036
|
+
- Updated dependencies [59b794f]
|
|
3037
|
+
- Updated dependencies [fc3a36a]
|
|
3038
|
+
- Updated dependencies [69787f0]
|
|
3039
|
+
- Updated dependencies [5d022a1]
|
|
3040
|
+
- Updated dependencies [042b9ee]
|
|
3041
|
+
- Updated dependencies [f549a0d]
|
|
3042
|
+
- Updated dependencies [a36db28]
|
|
3043
|
+
- Updated dependencies [3f8817a]
|
|
3044
|
+
- Updated dependencies [a2443e3]
|
|
3045
|
+
- Updated dependencies [e1554b1]
|
|
3046
|
+
- Updated dependencies [4856789]
|
|
3047
|
+
- Updated dependencies [c3f4916]
|
|
3048
|
+
- Updated dependencies [33e0385]
|
|
3049
|
+
- Updated dependencies [2205363]
|
|
3050
|
+
- Updated dependencies [09fe58d]
|
|
3051
|
+
- Updated dependencies [d0a5ceb]
|
|
3052
|
+
- Updated dependencies [e18a162]
|
|
3053
|
+
- Updated dependencies [d6d1a50]
|
|
3054
|
+
- Updated dependencies [d127ff0]
|
|
3055
|
+
- Updated dependencies [9b86cf6]
|
|
3056
|
+
- Updated dependencies [8825a06]
|
|
3057
|
+
- Updated dependencies [5087ac6]
|
|
3058
|
+
- Updated dependencies [6965160]
|
|
3059
|
+
- Updated dependencies [2d1ddf0]
|
|
3060
|
+
- Updated dependencies [354b00f]
|
|
3061
|
+
- Updated dependencies [3de535b]
|
|
3062
|
+
- Updated dependencies [fe2e15a]
|
|
3063
|
+
- Updated dependencies [c6b6bb4]
|
|
3064
|
+
- Updated dependencies [59c544d]
|
|
3065
|
+
- Updated dependencies [2f59da0]
|
|
3066
|
+
- Updated dependencies [5e247fd]
|
|
3067
|
+
- Updated dependencies [1a53a02]
|
|
3068
|
+
- Updated dependencies [a954634]
|
|
3069
|
+
- Updated dependencies [8ad609c]
|
|
3070
|
+
- Updated dependencies [bbee302]
|
|
3071
|
+
- Updated dependencies [08863dd]
|
|
3072
|
+
- Updated dependencies [56664f5]
|
|
3073
|
+
- Updated dependencies [31cbe90]
|
|
3074
|
+
- Updated dependencies [90bbf25]
|
|
3075
|
+
- Updated dependencies [eb91eba]
|
|
3076
|
+
- Updated dependencies [42da73d]
|
|
3077
|
+
- Updated dependencies [643b7c7]
|
|
3078
|
+
- Updated dependencies [d0d5205]
|
|
3079
|
+
- Updated dependencies [1a15893]
|
|
3080
|
+
- Updated dependencies [b70e534]
|
|
3081
|
+
- Updated dependencies [2233a85]
|
|
3082
|
+
- Updated dependencies [62dd69a]
|
|
3083
|
+
- Updated dependencies [e15e679]
|
|
3084
|
+
- Updated dependencies [2ab1257]
|
|
3085
|
+
- Updated dependencies [4cc4fb7]
|
|
3086
|
+
- Updated dependencies [28d1eb7]
|
|
3087
|
+
- Updated dependencies [2c26040]
|
|
3088
|
+
- Updated dependencies [f758cec]
|
|
3089
|
+
- Updated dependencies [78f0be8]
|
|
3090
|
+
- Updated dependencies [35f7fb4]
|
|
3091
|
+
- Updated dependencies [a5302c7]
|
|
3092
|
+
- Updated dependencies [7084313]
|
|
3093
|
+
- Updated dependencies [91cefb8]
|
|
3094
|
+
- Updated dependencies [0e043d8]
|
|
3095
|
+
- Updated dependencies [dadd1ad]
|
|
3096
|
+
- Updated dependencies [2f2e63c]
|
|
3097
|
+
- Updated dependencies [486d526]
|
|
3098
|
+
- Updated dependencies [89d7b35]
|
|
3099
|
+
- Updated dependencies [85ec26d]
|
|
3100
|
+
- Updated dependencies [f6476fc]
|
|
3101
|
+
- Updated dependencies [4ac12ef]
|
|
3102
|
+
- Updated dependencies [b88f5e8]
|
|
3103
|
+
- Updated dependencies [42cc219]
|
|
3104
|
+
- Updated dependencies [d42a92f]
|
|
3105
|
+
- Updated dependencies [51d74ad]
|
|
3106
|
+
- Updated dependencies [d7e0b42]
|
|
3107
|
+
- Updated dependencies [3510e4a]
|
|
3108
|
+
- Updated dependencies [aa4b90d]
|
|
3109
|
+
- Updated dependencies [54299ca]
|
|
3110
|
+
- Updated dependencies [dc61def]
|
|
3111
|
+
- Updated dependencies [251e888]
|
|
3112
|
+
- Updated dependencies [183b4c4]
|
|
3113
|
+
- Updated dependencies [2fdb36e]
|
|
3114
|
+
- Updated dependencies [e787608]
|
|
3115
|
+
- Updated dependencies [20526f5]
|
|
3116
|
+
- Updated dependencies [c5eef1d]
|
|
3117
|
+
- Updated dependencies [e0f300b]
|
|
3118
|
+
- Updated dependencies [761a0ba]
|
|
3119
|
+
- Updated dependencies [61282f9]
|
|
3120
|
+
- Updated dependencies [be87153]
|
|
3121
|
+
- Updated dependencies [60f0dd8]
|
|
3122
|
+
- Updated dependencies [a87c5cd]
|
|
3123
|
+
- Updated dependencies [a47f338]
|
|
3124
|
+
- Updated dependencies [2598216]
|
|
3125
|
+
- Updated dependencies [2c7e62d]
|
|
3126
|
+
- Updated dependencies [eb7613c]
|
|
3127
|
+
- Updated dependencies [ecc9110]
|
|
3128
|
+
- Updated dependencies [f7bd4e2]
|
|
3129
|
+
- Updated dependencies [361bd5b]
|
|
3130
|
+
- Updated dependencies [129b378]
|
|
3131
|
+
- Updated dependencies [88f9d94]
|
|
3132
|
+
- Updated dependencies [1818998]
|
|
3133
|
+
- Updated dependencies [3d4c545]
|
|
3134
|
+
- Updated dependencies [bb7cb41]
|
|
3135
|
+
- Updated dependencies [09ee21c]
|
|
3136
|
+
- Updated dependencies [f549a0d]
|
|
3137
|
+
- Updated dependencies [3fc2e48]
|
|
3138
|
+
- Updated dependencies [e8f435c]
|
|
3139
|
+
- Updated dependencies [41610f6]
|
|
3140
|
+
- @objectstack/spec@17.0.0-rc.6
|
|
3141
|
+
- @objectstack/platform-objects@17.0.0-rc.6
|
|
3142
|
+
- @objectstack/formula@17.0.0-rc.6
|
|
3143
|
+
- @objectstack/metadata-core@17.0.0-rc.6
|
|
3144
|
+
- @objectstack/core@17.0.0-rc.6
|
|
3145
|
+
- @objectstack/types@17.0.0-rc.6
|
|
3146
|
+
|
|
3
3147
|
## 17.0.0-rc.5
|
|
4
3148
|
|
|
5
3149
|
### Patch Changes
|