@objectstack/plugin-webhooks 17.0.0-rc.0 → 17.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +632 -0
  2. package/dist/{chunk-6EPMRZ7I.js → chunk-3QGZLM3T.js} +8 -3
  3. package/dist/chunk-3QGZLM3T.js.map +1 -0
  4. package/dist/{chunk-QUTQSOQC.cjs → chunk-JQUVS5KK.cjs} +8 -3
  5. package/dist/chunk-JQUVS5KK.cjs.map +1 -0
  6. package/dist/index.cjs +182 -63
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.cts +37 -1
  9. package/dist/index.d.ts +37 -1
  10. package/dist/index.js +138 -19
  11. package/dist/index.js.map +1 -1
  12. package/dist/schema.cjs +2 -2
  13. package/dist/schema.d.cts +721 -548
  14. package/dist/schema.d.ts +721 -548
  15. package/dist/schema.js +1 -1
  16. package/dist/{translations-Y3CXWOTM.js → translations-2VGD4XRF.js} +17 -9
  17. package/dist/translations-2VGD4XRF.js.map +1 -0
  18. package/dist/{translations-CBQRUIS5.cjs → translations-BWS57U2V.cjs} +17 -9
  19. package/dist/translations-BWS57U2V.cjs.map +1 -0
  20. package/package.json +12 -6
  21. package/.turbo/turbo-build.log +0 -36
  22. package/dist/chunk-6EPMRZ7I.js.map +0 -1
  23. package/dist/chunk-QUTQSOQC.cjs.map +0 -1
  24. package/dist/translations-CBQRUIS5.cjs.map +0 -1
  25. package/dist/translations-Y3CXWOTM.js.map +0 -1
  26. package/scripts/i18n-extract.config.ts +0 -34
  27. package/src/auto-enqueuer.test.ts +0 -431
  28. package/src/auto-enqueuer.ts +0 -383
  29. package/src/bootstrap-declared-webhooks.test.ts +0 -283
  30. package/src/bootstrap-declared-webhooks.ts +0 -205
  31. package/src/index.ts +0 -29
  32. package/src/schema.ts +0 -21
  33. package/src/sys-webhook.object.ts +0 -231
  34. package/src/translations/bundle-ownership.test.ts +0 -41
  35. package/src/translations/en.objects.generated.ts +0 -101
  36. package/src/translations/es-ES.objects.generated.ts +0 -101
  37. package/src/translations/index.ts +0 -23
  38. package/src/translations/ja-JP.objects.generated.ts +0 -101
  39. package/src/translations/zh-CN.objects.generated.ts +0 -101
  40. package/src/webhook-outbox-plugin.ts +0 -305
  41. package/src/webhook-provenance.ts +0 -86
  42. package/tsconfig.json +0 -10
  43. package/tsup.config.ts +0 -14
package/CHANGELOG.md CHANGED
@@ -1,5 +1,637 @@
1
1
  # @objectstack/plugin-webhooks
2
2
 
3
+ ## 17.0.0-rc.2
4
+
5
+ ### Minor Changes
6
+
7
+ - f2445c9: feat(spec,objectql,client,plugin-webhooks): predicate writes get an honest bulk event contract (#4639)
8
+
9
+ A `multi: true` update/delete reaches `IDataDriver.updateMany` / `deleteMany`,
10
+ which are contracted to resolve an affected row COUNT and nothing else. That
11
+ satisfies neither `DataEvent.recordId` (required) nor `before` / `after` /
12
+ `changes`, so before #4626 the engine fabricated a per-record event with
13
+ `recordId: ''` and `after: <count>` — an event every schema-compliant consumer
14
+ must reject, and one the webhook enqueuer's `?? 'unknown'` fallback turned into
15
+ a real delivery naming an unidentifiable record. #4626 removed the fabrication
16
+ and published nothing instead: honest, but it left webhooks, knowledge sync and
17
+ `subscribeData` silent for every predicate write.
18
+
19
+ Bulk writes now get their **own** contract rather than impersonating a
20
+ per-record one or going dark:
21
+
22
+ - **New `BulkDataEvent`** (`@objectstack/spec/api`): `data.records.updated` /
23
+ `data.records.deleted` — note the plural — carrying `id`, `type`, `object`,
24
+ `matched`, `userId?`, `timestamp`. Deliberately a separate schema from
25
+ `DataEvent`, not a widened one: a consumer that receives
26
+ `data.records.updated` knows from the type alone that no `recordId` is
27
+ coming, instead of discovering an empty string at runtime.
28
+ - **Engine** publishes it from the `multi: true` branches of `update()` /
29
+ `delete()`, validated with `BulkDataEventSchema.parse` before publish. A
30
+ predicate that matched **zero** rows publishes nothing (no data changed — this
31
+ is what keeps an idle background sweep from becoming an hourly "0 records"
32
+ delivery), and a driver that resolves a non-count publishes nothing and warns
33
+ rather than asserting a number it cannot verify. Per-record writes are
34
+ untouched, including a scalar `where.id` with `multi: true`, which is still a
35
+ single-record target and still emits `data.record.deleted`.
36
+ - **Webhooks**: two new opt-in triggers, `bulk_update` and `bulk_delete`
37
+ (`WebhookTriggerType`, and the `sys_webhook.triggers` multi-select). They are
38
+ **not** extra sources for `create` / `update` / `delete`: the delivered body
39
+ has no `recordId` and no record, so routing it to existing per-record
40
+ subscribers would hand them a payload missing every field they read — the
41
+ same class of breakage as the old `recordId: ''`, from the other direction. A
42
+ webhook that wants both subscribes to both. Bulk deliveries dedup on the
43
+ producer's event uuid, since two sweeps in the same millisecond are genuinely
44
+ different events that a timestamp-based key would collapse.
45
+ - **Client SDK**: new `client.events.subscribeBulkData(object, cb)`, with the
46
+ same loud boundary validation as `subscribeData`. Kept a separate method for
47
+ the same reason — delivering a `BulkDataEvent` to a `(event: DataEvent) =>
48
+ void` callback would recreate exactly the "typed field, `undefined` at
49
+ runtime" defect #4626 removed. `subscribeData`'s own guard was also tightened
50
+ from `data.` to `data.record.`, so an aggregate event is ignored rather than
51
+ rejected as off-contract.
52
+ - **Knowledge sync** now says out loud that a predicate write leaves its index
53
+ stale. A knowledge index is a per-record projection and `matched: 40` names no
54
+ record, so no event shape could drive it — the durable fix is reconciliation,
55
+ tracked in #4672.
56
+
57
+ The event carries no `where` predicate. The only one available at publish time
58
+ is the middleware-composed AST, whose filter embeds the security layer's
59
+ injected row scoping (RLS, sharing) — publishing it would ship tenant scoping
60
+ internals to whatever external URL a webhook points at.
61
+
62
+ Also pays off a measurement debt from #4655, which claimed the write-path cost
63
+ of event publishing had been measured but never published the numbers:
64
+ `packages/objectql/src/engine-data-events.bench.ts` measures it. Against an
65
+ in-memory driver, publishing costs ~7–9µs per event (insert 0.021ms vs 0.012ms,
66
+ single-id update 0.013ms vs 0.007ms). A bulk write pays that **once** regardless
67
+ of how many rows matched (0.040ms vs 0.034ms over a 100-row match set), so its
68
+ relative cost shrinks as the match set grows.
69
+
70
+ ### Patch Changes
71
+
72
+ - 257d97a: ADR-0078 Phase 4, decided rather than deferred: the silent skips stop being silent at runtime. The registry — the one choke point every metadata door goes through — now emits a functional-completeness diagnostic at registration, and the webhook enqueuer's zero-trigger skip warns instead of returning `null` wordlessly.
73
+
74
+ **The Phase 4 ruling.** The phase had two halves, and they got opposite verdicts:
75
+
76
+ - **Generative rule sweep: rejected — not deferred.** A generator can enumerate candidates ("which optional keys might be load-bearing?") but cannot verify runtime skip sites, and a rule without its skip-site citation is a false prescription — this campaign shipped four of those and every one was caught by the verification pass a generator would skip. The route is structurally wrong; no amount of waiting produces the evidence that would fix it.
77
+ - **Registration-time diagnostics: built now.** The evidence was already in hand, not pending: #3896 (Setup authoring inserted `sys_sharing_rule` rows directly, bypassing the schema that "required" `criteria`) and cloud's `rowColor.mapping` (an `as never` cast bypassed tsc) prove that doors which skip Zod and lint are real. The author-time gate only protects metadata that passes through `os build` / `validate` / `lint`; `SchemaRegistry.registerObject` is where _every_ door converges — declared stacks, plugin objects, `extend` contributions, `saveMetaItem`, raw `registerObject` calls.
78
+
79
+ **Same predicate, same rule ids, different posture.** The registry calls the same `checkFieldCompleteness` that `validate-functional-completeness` uses, so the boot log carries the _same rule ids_ the lint reports (`field/summary-without-operations`, …) — an operator or an AI reading the log greps the id straight into the same docs and suppression story. But the registry **warns and never throws**: ADR-0078 §1's error severity means _the instance is dead_, not _the system is dead_ — an inert field must not kill a boot that thousands of healthy objects share. Errors block at author time; the registry's job is to make sure the silence never survives to runtime unobserved.
80
+
81
+ One line per object with every finding aggregated (not per request — the hot path stays free; not per finding — a three-dead-field object is one greppable line). Follows `warnStrippedLegacyApiMethods` (#3543) exactly: module-level once-per-object dedup, injectable `warn`, pure observation that never mutates the schema.
82
+
83
+ **The webhook skip now names itself.** `auto-enqueuer.ts`'s `if (triggers.size === 0) return null` sat under a comment blessing the empty case as "a manual-only webhook" — a mode #3196 removed (no manual fire path exists). The skip now warns with the author-time rule id (`webhook/without-triggers`), and the comment tells the truth. Only _active_ rows reach the parse (`where: { active: true }` — verified, not assumed), so a deliberately disabled webhook stays warning-free.
84
+
85
+ **Scope honesty:** field rules and the webhook rule get the runtime twin. `view/layout-without-binding` stays author-time-only — views don't register through this choke point and the renderer half of the evidence lives in objectui.
86
+
87
+ Tracked in #4544. This closes the ADR-0078 loop end to end: author-time error, runtime warning, one shared predicate deciding both.
88
+
89
+ - 462b713: fix(objectql,client): `subscribeData` callbacks receive real `DataEvent`s — the producer now fulfils the declared contract (#4626)
90
+
91
+ `@objectstack/spec/api`'s `DataEvent` declares top-level `id` (uuid,
92
+ required), `type`, `object`, `recordId` (required), `changes?`, `before?`,
93
+ `after?`, `userId?`, `timestamp`. But the producer (the ObjectQL engine)
94
+ published a raw `RealtimeEventPayload` envelope with `{ recordId, after,
95
+ changes }` nested under `payload` and never generated `id`/`userId`, while the
96
+ client SDK force-cast that envelope into the callback (`callback(event as any
97
+ as DataEvent)`). Subscribers who wrote `event.recordId` / `event.changes` —
98
+ exactly what the types promised — compiled green and read `undefined` at
99
+ runtime. The data-side twin of #4602.
100
+
101
+ Producer now fulfils the contract:
102
+
103
+ - `ObjectQL.insert()` / `update()` / `delete()` build a true `DataEvent`
104
+ (generated uuid `id`, flattened top-level fields, `userId` from the
105
+ execution context when the write names an actor) and validate it with
106
+ `DataEventSchema.parse` before publishing. The transport envelope is
107
+ unchanged (`RealtimeEventPayload`, with `payload` carrying the complete
108
+ `DataEvent`), so subscribers keep receiving `{ type, object, payload,
109
+ timestamp }` on the wire.
110
+ - A batch insert publishes one event **per record** (as before), each with its
111
+ own event id.
112
+ - **A multi-row write (`multi: true` → `updateMany` / `deleteMany`) now
113
+ publishes nothing.** Those driver methods return only an affected count, so
114
+ there is no record for a required `recordId` to name; the engine logs a
115
+ warning naming the gap instead of publishing the previous fabrication
116
+ (`recordId: ''`, `after: <affected count>`), which every schema-compliant
117
+ consumer had to reject. **Consequence: webhooks and knowledge sync no longer
118
+ fire for bulk writes** — they previously fired once with an unusable body. A
119
+ real bulk event contract is tracked in #4639.
120
+
121
+ Consumers validate or read the fulfilled shape instead of guessing:
122
+
123
+ - `@objectstack/client`'s `subscribeData` (and therefore
124
+ `@objectstack/client-react`'s `useDataSubscription` /
125
+ `useDataSubscriptionCallback` / `useAutoRefresh`, which delegate to it)
126
+ unwraps the envelope and runs `DataEventSchema.safeParse` at the boundary.
127
+ An off-contract payload is rejected loudly (handler error, callback never
128
+ invoked) — never coerced or passed through. The `as any as DataEvent`
129
+ double-cast is gone, and the `recordId` option now filters on the fulfilled
130
+ event.
131
+ - `@objectstack/plugin-webhooks`' auto-enqueuer reads the required
132
+ `recordId` directly; its `recordId ?? id ?? after?.id ?? before?.id ??
133
+ 'unknown'` fallback chain is gone, and an off-contract event is dropped with
134
+ a warning rather than delivered under the literal id `'unknown'`. Delivered
135
+ webhook bodies now also carry the event's `id`/`type`/`userId`; the record
136
+ itself stays nested under `after` and the envelope keys (`object`,
137
+ `recordId`, `action`, `timestamp`) still win.
138
+ - `@objectstack/service-knowledge`'s event sync reads the record from `after`
139
+ (create/update) and the id from `recordId` (delete) for `data.record.*`.
140
+ It previously indexed the envelope itself as if it were the row, and never
141
+ resolved an id for deletes.
142
+
143
+ - Updated dependencies [430dcc2]
144
+ - Updated dependencies [e6ac4bd]
145
+ - Updated dependencies [80334c7]
146
+ - Updated dependencies [ce5242c]
147
+ - Updated dependencies [a7163ea]
148
+ - Updated dependencies [e6e9379]
149
+ - Updated dependencies [98877c9]
150
+ - Updated dependencies [98877c9]
151
+ - Updated dependencies [e6b1b69]
152
+ - Updated dependencies [ad047d2]
153
+ - Updated dependencies [2826d1e]
154
+ - Updated dependencies [5a84d41]
155
+ - Updated dependencies [20b1a9e]
156
+ - Updated dependencies [203a449]
157
+ - Updated dependencies [ac37fc6]
158
+ - Updated dependencies [4820f55]
159
+ - Updated dependencies [462d9c4]
160
+ - Updated dependencies [7d21581]
161
+ - Updated dependencies [f2445c9]
162
+ - Updated dependencies [23338c3]
163
+ - Updated dependencies [5b843fb]
164
+ - Updated dependencies [b4487aa]
165
+ - Updated dependencies [65ca83a]
166
+ - Updated dependencies [67bf2e2]
167
+ - Updated dependencies [c6d1cb4]
168
+ - Updated dependencies [36030ff]
169
+ - Updated dependencies [6117f7b]
170
+ - Updated dependencies [e533b0b]
171
+ - Updated dependencies [cdf4d9a]
172
+ - Updated dependencies [aee1806]
173
+ - Updated dependencies [c13350b]
174
+ - Updated dependencies [c13350b]
175
+ - Updated dependencies [9ca2d85]
176
+ - Updated dependencies [c13350b]
177
+ - Updated dependencies [891d345]
178
+ - Updated dependencies [a52e2ef]
179
+ - Updated dependencies [5293114]
180
+ - Updated dependencies [20bc357]
181
+ - Updated dependencies [5966c2a]
182
+ - Updated dependencies [2382580]
183
+ - Updated dependencies [d9fa683]
184
+ - Updated dependencies [3c7bcc0]
185
+ - Updated dependencies [4b6cac7]
186
+ - Updated dependencies [7631964]
187
+ - Updated dependencies [ac471a0]
188
+ - Updated dependencies [60ae58e]
189
+ - Updated dependencies [ce92674]
190
+ - Updated dependencies [9f601e8]
191
+ - Updated dependencies [51c5227]
192
+ - Updated dependencies [a4a85c8]
193
+ - Updated dependencies [07a4e26]
194
+ - Updated dependencies [ec975f1]
195
+ - Updated dependencies [eb4204b]
196
+ - Updated dependencies [4f13be2]
197
+ - Updated dependencies [61cc079]
198
+ - Updated dependencies [0e96e46]
199
+ - Updated dependencies [d52d4fe]
200
+ - Updated dependencies [742cebb]
201
+ - Updated dependencies [ce92674]
202
+ - Updated dependencies [cf2c9b7]
203
+ - Updated dependencies [833b512]
204
+ - Updated dependencies [0f9faa2]
205
+ - Updated dependencies [7cf42fe]
206
+ - Updated dependencies [5966c2a]
207
+ - Updated dependencies [040ecd2]
208
+ - Updated dependencies [f78dd83]
209
+ - Updated dependencies [a2cd18a]
210
+ - Updated dependencies [4638aaa]
211
+ - Updated dependencies [0222d3c]
212
+ - Updated dependencies [071d0dc]
213
+ - Updated dependencies [0a936ea]
214
+ - Updated dependencies [023c00b]
215
+ - Updated dependencies [155507e]
216
+ - Updated dependencies [7bba90b]
217
+ - Updated dependencies [7e05d8e]
218
+ - Updated dependencies [061406d]
219
+ - Updated dependencies [c1f344b]
220
+ - Updated dependencies [9c93465]
221
+ - Updated dependencies [ebb209c]
222
+ - Updated dependencies [63b33e6]
223
+ - Updated dependencies [2a44c1d]
224
+ - Updated dependencies [695cfbd]
225
+ - Updated dependencies [7445149]
226
+ - Updated dependencies [071d0dc]
227
+ - Updated dependencies [0848bea]
228
+ - Updated dependencies [d51bed2]
229
+ - Updated dependencies [b8b3c64]
230
+ - Updated dependencies [0c0fbd9]
231
+ - Updated dependencies [f3141d8]
232
+ - Updated dependencies [5a84d41]
233
+ - Updated dependencies [fd3013a]
234
+ - Updated dependencies [21676eb]
235
+ - Updated dependencies [e336549]
236
+ - Updated dependencies [d40f43a]
237
+ - Updated dependencies [e5e7ee0]
238
+ - Updated dependencies [a2ebea2]
239
+ - Updated dependencies [800bdb0]
240
+ - Updated dependencies [04f1182]
241
+ - Updated dependencies [5647006]
242
+ - Updated dependencies [38f7e4f]
243
+ - Updated dependencies [c57f3cf]
244
+ - Updated dependencies [97faca3]
245
+ - Updated dependencies [ad5fe25]
246
+ - Updated dependencies [ea90179]
247
+ - Updated dependencies [ce92674]
248
+ - Updated dependencies [5ef0b5b]
249
+ - Updated dependencies [48fbacb]
250
+ - Updated dependencies [355e951]
251
+ - Updated dependencies [dadb43f]
252
+ - @objectstack/spec@17.0.0-rc.2
253
+ - @objectstack/core@17.0.0-rc.2
254
+ - @objectstack/service-messaging@17.0.0-rc.2
255
+
256
+ ## 17.0.0-rc.1
257
+
258
+ ### Minor Changes
259
+
260
+ - f5a4ef0: refactor!: ADR-0112 batch 2 — sweep the lowercase error-code emitters (#4003)
261
+
262
+ Continues #3841 per ADR-0112. Batch 1 (#3988) settled the vocabulary and closed
263
+ the set; this batch moves the emitters that still spoke lowercase `snake_case`
264
+ onto it.
265
+
266
+ **Wire-visible change.** Error codes on these surfaces change spelling. Generic
267
+ conditions collapse onto the standard catalog rather than keeping a synonym:
268
+ `unauthorized`/`unauthenticated` → `UNAUTHENTICATED`, `forbidden` →
269
+ `PERMISSION_DENIED`, `not_found` → `RESOURCE_NOT_FOUND`, `internal` →
270
+ `INTERNAL_ERROR`, `unavailable` → `SERVICE_UNAVAILABLE`, `not_supported` →
271
+ `NOT_IMPLEMENTED`, `bad_request` → `INVALID_REQUEST`. Domain conditions get codes
272
+ registered in `ERROR_CODE_LEDGER` (`MARKETPLACE_STORAGE_FAILED`,
273
+ `PLUGIN_MANIFEST_INVALID`, `ITEM_LOCKED`, `DELIVERY_NOT_ELIGIBLE`, …). Swept:
274
+ `cloud-connection`, `plugin-auth`, `hono`, `metadata-protocol`, `rest`,
275
+ `service-messaging`, `service-automation`, `trigger-api`.
276
+
277
+ Branch on `error.code` values rather than pattern-matching their case: the
278
+ console's fix for the same rename (objectui#2977) reads codes case-insensitively
279
+ for exactly this reason, and that is the pattern to copy in your own consumers if
280
+ you support servers on both sides of the change.
281
+
282
+ **Four routes stop putting a code in the message slot.** The webhook redeliver
283
+ route, the API-trigger webhook, and two `rest` routes answered
284
+ `{ success: false, error: '<code>', message }` — the code occupying `error`, the
285
+ declared object envelope nowhere. They now emit `error: { code, message }`, and
286
+ three API-trigger branches gained a message they never had. Clients reading
287
+ `body.error` as a string on those routes must read `body.error.code`.
288
+
289
+ **`ConnectorErrorCategory` / `ConnectorRetryStrategy`** (ADR-0112 D9a):
290
+ `@objectstack/spec` exported two mutually incompatible `ErrorCategory` types and
291
+ two `RetryStrategy` types. The connector-side pair is renamed; importers of the
292
+ `integration` subpath update the name. Side effect: the api-side `ErrorCategory`
293
+ and `RetryStrategy` now appear in the generated API reference at all — the name
294
+ collision had been silently dropping them.
295
+
296
+ **`OAUTH_REGISTER_FAILED` replaces an unbounded code source.** The OAuth client
297
+ registration route put better-auth's arbitrary `body.error` string straight into
298
+ `error.code`. The code is now ours and the upstream discriminator moved to
299
+ `details.upstreamError`.
300
+
301
+ **Not swept, deliberately.** `sys_metadata_audit.code` keeps its lowercase values
302
+ (ADR-0112 D6b): it is persisted audit history, and the same column holds
303
+ non-error outcomes (`ok`, `lock_override`). Diagnostics records that ship inside a
304
+ 200 keep theirs (D6c), as do field-level codes (D6, #3977) and the CLI's
305
+ `--json` output contract.
306
+
307
+ A `check:error-code-casing` CI guard now fails on a new lowercase literal in a
308
+ code position, since the ledger's casing rule can only police codes that someone
309
+ registers.
310
+
311
+ ### Patch Changes
312
+
313
+ - bb1ce2e: fix(plugin-auth,plugin-webhooks): retire a dead degrade branch and an implicit transitive dependency (ADR-0116 follow-ups, #4187)
314
+
315
+ Two concrete findings from the ADR-0116 consumer-side audit, plus the
316
+ authoring rule that would have prevented both.
317
+
318
+ **`plugin-auth` claimed a fallback it did not have.** `init()` ran
319
+ `const dataEngine = ctx.getService('data'); if (!dataEngine) { warn('No data
320
+ engine service found - auth will use in-memory storage') }`. That branch could
321
+ never execute: `getService` **throws** for an unregistered service rather than
322
+ returning `undefined`, and this plugin declares a hard dependency on ObjectQL
323
+ (which registers `data` unconditionally), so a kernel without the engine fails
324
+ even earlier with `Dependency … not found`. The branch is removed and the real
325
+ contract is declared — `requiresServices: ['data', 'manifest']` — which also
326
+ replaces a trailing `// manifest service required` comment with the
327
+ machine-checked form of the same claim. `AuthManager` keeps its own optional
328
+ `dataEngine` guards: it is usable outside the plugin.
329
+
330
+ **`plugin-webhook-outbox` was protected only transitively.** It resolves
331
+ `manifest` in `init()` with no fallback while depending on
332
+ `com.objectstack.service.messaging`, which in turn depends on ObjectQL, the
333
+ actual provider. That works today and would have broken silently the day
334
+ messaging stopped depending on the engine — surfacing as a crash inside an
335
+ unrelated plugin's init. It now declares `requiresServices: ['manifest']`
336
+ directly.
337
+
338
+ Neither change alters ordering or boot outcomes on any current composition:
339
+ both plugins were already ordered correctly. What changes is what a broken
340
+ composition _says_, and that the guarantees are now checked rather than
341
+ inherited.
342
+
343
+ Docs: `content/docs/plugins/anatomy.mdx` gains the three ADR-0116 fields and
344
+ the decision rule for resolving a service inside `init()` (hard dependency vs
345
+ `optionalDependencies` + `requiresServices`), including the two traps behind
346
+ these fixes — don't rely on a transitive provider, and don't write an
347
+ `if (!svc)` fallback after a bare `getService`. The api-registry example
348
+ declares the contract on all seven of its plugins instead of relying on
349
+ `kernel.use()` order.
350
+
351
+ - 2e836de: chore(packaging): CHANGELOG.md ships in every npm tarball (#4261)
352
+
353
+ The AGENTS.md post-task checklist requires breaking changesets to carry their
354
+ FROM → TO migration because "this text ships to consumers as `CHANGELOG.md`
355
+ inside the npm package and is what an upgrading agent greps after the tombstone
356
+ error." That delivery path was severed for 68 of the 69 publishable packages:
357
+ npm packs `package.json` / `README*` / `LICENSE*` unconditionally but — unlike
358
+ older npm versions — not `CHANGELOG.md`, and the canonical
359
+ `"files": ["dist", "README.md"]` whitelist never named it. Measured on npm
360
+ 10.9.7: `npm pack --dry-run` on `@objectstack/types` shipped 3 files while its
361
+ 70KB `CHANGELOG.md` stayed behind. Only `@objectstack/spec` listed it
362
+ explicitly.
363
+
364
+ The tombstone-error scenario is precisely the one where the repo is out of
365
+ reach — the upgrading agent has `node_modules` and nothing else — so the
366
+ migration text has to ride in the tarball. Every publishable package now
367
+ declares `CHANGELOG.md` in `files`, and the canonical whitelist is
368
+ `["dist", "README.md", "CHANGELOG.md"]`.
369
+
370
+ The other half is the gate: `check:published-files` gains a fifth invariant,
371
+ COMPLETE — a whitelist that fails to cover `CHANGELOG.md` fails the
372
+ always-required lint job, so the next package cannot silently sever the path
373
+ again. `@objectstack/spec`'s per-package EXTRA_ENTRIES exemption dissolves
374
+ into the canonical set.
375
+
376
+ Consumer-visible change: one more file per install (the package's changelog,
377
+ e.g. 70.8KB for `@objectstack/types`), and `grep -r "removed key"
378
+ node_modules/@objectstack/*/CHANGELOG.md` now finds the migration it was
379
+ promised.
380
+
381
+ - a225ef5: fix(runtime,webhooks): the path object wins on /data/:object/query, and the webhook envelope owns its keys (#3946)
382
+
383
+ Follow-up sweep for the shape behind #3897 and #3933 — a trusted, server-derived
384
+ value written into an object literal with a caller-controlled bag spread OVER
385
+ it. Both of those were in the same block of REST code, so the pattern was swept
386
+ across all 1313 non-test TypeScript files in `packages/`. Nine candidate sites;
387
+ one real, one worth hardening, seven verified clean (recorded in #3946 so the
388
+ next sweep does not re-litigate them).
389
+
390
+ **`POST /data/:object/query` (runtime dispatcher).** The `/data` domain built
391
+ `{ object: objectName, ...body }`, so `{"object":"other", …}` in the body moved
392
+ the read to a different object than the URL named.
393
+
394
+ This is NOT an authorization bypass, and the tests pin why: `callData` gates
395
+ API exposure on `params.object`, so the gate followed the body and agreed with
396
+ the read — an object hidden by `apiEnabled: false` was refused either way. What
397
+ broke is that the URL stopped describing the operation (audit trails, logs, and
398
+ anything keyed on the request path saw object A while object B was read), and
399
+ that one endpoint spoke a second dialect of the contract the REST side had just
400
+ standardised on: the path object wins. The other handlers in that file never had
401
+ the problem — they nest caller data (`data: body`, `query: normalized`) instead
402
+ of splatting it, and the GET-by-id branch already allowlists its query params
403
+ against exactly this pollution.
404
+
405
+ **Webhook delivery envelope.** `auto-enqueuer` built
406
+ `{ object, recordId, action, timestamp, ...payload }`, letting an event payload
407
+ rewrite the envelope a subscriber receives. Behaviour-neutral for the engine's
408
+ own publishers — `data.record.*` payloads are `{ recordId, after, changes }`
409
+ with record fields nested under `after`, so none of those four keys collide
410
+ today — but the shape was wrong, and the `payload.id` fallback right above it
411
+ suggests publishers that flatten record fields do exist. Envelope keys are
412
+ written last now.
413
+
414
+ - 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)
415
+
416
+ Two changes with different weights, from one sweep of every in-repo engine
417
+ call site that still speaks a deprecated alias.
418
+
419
+ **The bug — three dropped sorts.** #4346 made the engine fold `filter`→`where`
420
+ and `top`→`limit` on all six methods. The other four pairs in
421
+ `RPC_QUERY_ALIAS_SLOTS` (`select`, `sort`, `skip`, `populate`) are folded at
422
+ the RPC/wire layer only — their values need shape lowering that belongs to
423
+ those layers — and a **direct `engine.find()` never crosses that layer**. Three
424
+ call sites passed `sort` there, so it rode onto the AST untouched, every
425
+ driver's `Array.isArray(query.orderBy)` guard declined to emit an ORDER BY, and
426
+ the query returned an ordinary-looking, arbitrarily-ordered result:
427
+
428
+ | call site | asked for | actually got |
429
+ | ----------------------------------- | ------------------------------------------------- | --------------------------- |
430
+ | `share-link-routes.ts` | shared AI conversation messages, `created_at asc` | messages in arbitrary order |
431
+ | `runtime/domains/share-links.ts` | same route, runtime-domain copy | same |
432
+ | `share-link-service.ts` `listLinks` | the 200 most recent share links | an arbitrary 200 |
433
+
434
+ All three combine the dropped sort with a `limit` — the "latest N" shape whose
435
+ failure #4226 spelled out: an unapplied sort returns rows in arbitrary order,
436
+ which `limit` then slices into an arbitrary page. #4226 fixed that in the wire
437
+ normalizer; these calls sit one layer below it. `listLinks` had no test at all,
438
+ which is why it went unnoticed. Now pinned — on the option bag the engine
439
+ receives, not on row order, because the failure is that the key never becomes
440
+ `orderBy` and a fake engine honouring either spelling would pass either way.
441
+
442
+ **The cleanup — 27 no-op renames.** Every remaining in-repo engine call passing
443
+ `filter` now passes `where` (approvals 5, auth 2, reports 6, sharing 11,
444
+ webhooks 2, plus the one `filters` in a spec doc example). These are strict
445
+ no-ops since #4346 folds the alias — the point is that the framework stops
446
+ depending on a spelling it asks users to migrate off, which is a prerequisite
447
+ for ever retiring the aliases. Service-level `filter` PARAMETERS (each
448
+ service's own public API, e.g. `listRequests(filter)`) are deliberately
449
+ untouched — those are not engine option bags.
450
+
451
+ Two of the renamed calls were live victims of the #4346 bug rather than
452
+ cosmetic: `auth-manager`'s `stampIdentitySource` read the table's first row via
453
+ `findOne({filter})` and counted the whole table via `count({filter})`, so a
454
+ federated sign-in never stamped `source: 'idp_provisioned'`. #4346 already
455
+ corrected the behaviour; this makes the call say what it means.
456
+
457
+ - cc2de0e: chore(packaging): 20 packages stop publishing their sources, tests and build tooling (#4248)
458
+
459
+ These 20 packages declared no `files` field, so npm fell back to packing the
460
+ whole package directory. `npm pack --dry-run` on `@objectstack/plugin-webhooks`
461
+ listed **21 files** — 15 under `src/`, three of them unit tests
462
+ (`auto-enqueuer.test.ts`, `bootstrap-declared-webhooks.test.ts`, …), plus the
463
+ build-time `scripts/i18n-extract.config.ts`. `dist/` lands on top of that at
464
+ publish time rather than instead of it, so consumers were installing the
465
+ TypeScript sources and the test suite alongside the artifact they asked for.
466
+
467
+ Each now declares `"files": ["dist", "README.md"]`, matching the 29 packages
468
+ that already did. Nothing a consumer imports moves: every `main` / `types` /
469
+ `exports` target in all 20 already resolved inside `dist/`, which the new
470
+ `check:published-files` guard verifies rather than assumes. The visible change
471
+ is a smaller install and a smaller dependency-scanning surface — `npm pack` on
472
+ `@objectstack/plugin-webhooks` now yields 2 files plus `dist/`.
473
+
474
+ The other half of the fix is the gate. Half the packages declaring `files` and
475
+ half not was the #3786 shape — a hand-copied convention with nothing enforcing
476
+ it, where whoever forgets the line gets no signal at all. `check:published-files`
477
+ (new, wired into the always-required `lint` job) holds every non-private
478
+ workspace package to four invariants: `files` is **declared**; it is
479
+ **sufficient** (covers every entry point, so tightening a whitelist cannot ship
480
+ a package that fails to resolve); it is **minimal** (admits no test, test-harness
481
+ config or build script); and anything beyond `dist` + `README.md` is
482
+ **registered** with a reason, reconciled in both directions so a stale exemption
483
+ is an error rather than dead text. `@objectstack/spec` is the one package with
484
+ registered extras — its `.zod.ts` sources, JSON Schemas, liveness ledgers and
485
+ `CHANGELOG.md` are product, not build input.
486
+
487
+ This also closes an assumption #4206 was resting on. Excluding `<pkg>/scripts/**`
488
+ from the docs-drift implementation test is sound only while no package publishes
489
+ `scripts/` as runtime code; that held, but it held because someone read all three
490
+ offenders by hand. It is now checked on every PR.
491
+
492
+ - Updated dependencies [6a67d7a]
493
+ - Updated dependencies [0ecc656]
494
+ - Updated dependencies [06772eb]
495
+ - Updated dependencies [270650f]
496
+ - Updated dependencies [3aef718]
497
+ - Updated dependencies [1ea6bce]
498
+ - Updated dependencies [c1dcacd]
499
+ - Updated dependencies [ad303ed]
500
+ - Updated dependencies [32ccb23]
501
+ - Updated dependencies [f5a4ef0]
502
+ - Updated dependencies [2d3e255]
503
+ - Updated dependencies [7d7521f]
504
+ - Updated dependencies [5dc4d02]
505
+ - Updated dependencies [05154a1]
506
+ - Updated dependencies [9b6fe7c]
507
+ - Updated dependencies [8c711fb]
508
+ - Updated dependencies [09e4547]
509
+ - Updated dependencies [91f4c78]
510
+ - Updated dependencies [820eff9]
511
+ - Updated dependencies [8d895ff]
512
+ - Updated dependencies [f6472d7]
513
+ - Updated dependencies [78caf51]
514
+ - Updated dependencies [62a789b]
515
+ - Updated dependencies [789ad63]
516
+ - Updated dependencies [2af1988]
517
+ - Updated dependencies [0af50a3]
518
+ - Updated dependencies [2e836de]
519
+ - Updated dependencies [12a19a8]
520
+ - Updated dependencies [41dcda3]
521
+ - Updated dependencies [c8124e5]
522
+ - Updated dependencies [a1a4140]
523
+ - Updated dependencies [217e2e6]
524
+ - Updated dependencies [86a71d1]
525
+ - Updated dependencies [d5c75e2]
526
+ - Updated dependencies [03d26f7]
527
+ - Updated dependencies [4384921]
528
+ - Updated dependencies [3c628ce]
529
+ - Updated dependencies [7cb922e]
530
+ - Updated dependencies [1d22114]
531
+ - Updated dependencies [b5f9397]
532
+ - Updated dependencies [ed77493]
533
+ - Updated dependencies [58a03d2]
534
+ - Updated dependencies [dc530b4]
535
+ - Updated dependencies [e59786e]
536
+ - Updated dependencies [bcf1112]
537
+ - Updated dependencies [9774b78]
538
+ - Updated dependencies [b07d829]
539
+ - Updated dependencies [a648e96]
540
+ - Updated dependencies [a47ac06]
541
+ - Updated dependencies [e4c61a7]
542
+ - Updated dependencies [cc60165]
543
+ - Updated dependencies [081aa6f]
544
+ - Updated dependencies [91f4c78]
545
+ - Updated dependencies [e8d0c21]
546
+ - Updated dependencies [45dc446]
547
+ - Updated dependencies [c1d44f7]
548
+ - Updated dependencies [ab9fb5c]
549
+ - Updated dependencies [f985b3f]
550
+ - Updated dependencies [9a4932a]
551
+ - Updated dependencies [f9fc874]
552
+ - Updated dependencies [011b386]
553
+ - Updated dependencies [7777e8f]
554
+ - Updated dependencies [507b92a]
555
+ - Updated dependencies [7309c81]
556
+ - Updated dependencies [a8dcc37]
557
+ - Updated dependencies [20bc1ec]
558
+ - Updated dependencies [90c2b15]
559
+ - Updated dependencies [42eeb7d]
560
+ - Updated dependencies [01e124d]
561
+ - Updated dependencies [7ce02eb]
562
+ - Updated dependencies [a13827e]
563
+ - Updated dependencies [7733604]
564
+ - Updated dependencies [40e420f]
565
+ - Updated dependencies [d13004a]
566
+ - Updated dependencies [be7360c]
567
+ - Updated dependencies [5b47ab5]
568
+ - Updated dependencies [b09d8d9]
569
+ - Updated dependencies [b09d8d9]
570
+ - Updated dependencies [8675db6]
571
+ - Updated dependencies [b09d8d9]
572
+ - Updated dependencies [3eb1b2b]
573
+ - Updated dependencies [59b85c0]
574
+ - Updated dependencies [6e357ed]
575
+ - Updated dependencies [d6938bf]
576
+ - Updated dependencies [31e0be9]
577
+ - Updated dependencies [4bfd455]
578
+ - Updated dependencies [ffd2ce2]
579
+ - Updated dependencies [62f8017]
580
+ - Updated dependencies [a831df1]
581
+ - Updated dependencies [f752ee3]
582
+ - Updated dependencies [a1b61e0]
583
+ - Updated dependencies [cd6b9f2]
584
+ - Updated dependencies [2cb6d3c]
585
+ - Updated dependencies [af2a095]
586
+ - Updated dependencies [ec796d5]
587
+ - Updated dependencies [e87fea1]
588
+ - Updated dependencies [c65e529]
589
+ - Updated dependencies [3ca34c1]
590
+ - Updated dependencies [239c3a3]
591
+ - Updated dependencies [94a0bbc]
592
+ - Updated dependencies [d6bfb3d]
593
+ - Updated dependencies [a2266a6]
594
+ - Updated dependencies [d25a0ec]
595
+ - Updated dependencies [667b83e]
596
+ - Updated dependencies [627b188]
597
+ - Updated dependencies [8d4eae7]
598
+ - Updated dependencies [857a6cf]
599
+ - Updated dependencies [65a3a84]
600
+ - Updated dependencies [ccd9397]
601
+ - Updated dependencies [bca935b]
602
+ - Updated dependencies [d92c72d]
603
+ - Updated dependencies [c54c822]
604
+ - Updated dependencies [8dcc0f5]
605
+ - Updated dependencies [75b9e51]
606
+ - Updated dependencies [0a2f233]
607
+ - Updated dependencies [8621cdd]
608
+ - Updated dependencies [6f23667]
609
+ - Updated dependencies [5d21a48]
610
+ - Updated dependencies [19365b7]
611
+ - Updated dependencies [b7ed26d]
612
+ - Updated dependencies [b3a3d83]
613
+ - Updated dependencies [7a55913]
614
+ - Updated dependencies [35accbf]
615
+ - Updated dependencies [6038de7]
616
+ - Updated dependencies [eb95d97]
617
+ - Updated dependencies [e4c2dc8]
618
+ - Updated dependencies [1bd2795]
619
+ - Updated dependencies [8186a70]
620
+ - Updated dependencies [a329cca]
621
+ - Updated dependencies [6eec18c]
622
+ - Updated dependencies [4d7bebf]
623
+ - Updated dependencies [821ac7a]
624
+ - Updated dependencies [8f81731]
625
+ - Updated dependencies [8b50cb3]
626
+ - Updated dependencies [8c2db68]
627
+ - Updated dependencies [22b5e54]
628
+ - Updated dependencies [0166bd5]
629
+ - Updated dependencies [9b702dc]
630
+ - Updated dependencies [ab16331]
631
+ - @objectstack/spec@17.0.0-rc.1
632
+ - @objectstack/core@17.0.0-rc.1
633
+ - @objectstack/service-messaging@17.0.0-rc.1
634
+
3
635
  ## 17.0.0-rc.0
4
636
 
5
637
  ### Minor Changes
@@ -86,7 +86,7 @@ var SysWebhook = ObjectSchema.create({
86
86
  group: "Definition"
87
87
  }),
88
88
  triggers: Field.select(
89
- ["create", "update", "delete"],
89
+ ["create", "update", "delete", "bulk_update", "bulk_delete"],
90
90
  {
91
91
  label: "Triggers",
92
92
  required: false,
@@ -94,7 +94,12 @@ var SysWebhook = ObjectSchema.create({
94
94
  // an array; the auto-enqueuer parser also tolerates the legacy
95
95
  // comma-separated / JSON-string forms so existing rows keep working.
96
96
  multiple: true,
97
- description: "Record events that fire this webhook",
97
+ // [#4639] `bulk_*` fire on predicate writes (`multi: true`), whose
98
+ // delivery carries `matched` instead of a record — opt-in precisely
99
+ // because that body is a different shape. Kept in step with
100
+ // `WebhookTriggerType` (`@objectstack/spec/automation`), which is the
101
+ // contract the auto-enqueuer validates against.
102
+ description: "Record events that fire this webhook (bulk_* deliver a count, not a record)",
98
103
  group: "Definition"
99
104
  }
100
105
  ),
@@ -178,4 +183,4 @@ var SysWebhook = ObjectSchema.create({
178
183
  export {
179
184
  SysWebhook
180
185
  };
181
- //# sourceMappingURL=chunk-6EPMRZ7I.js.map
186
+ //# sourceMappingURL=chunk-3QGZLM3T.js.map