@objectstack/plugin-webhooks 17.1.0 → 17.2.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 CHANGED
@@ -1,5 +1,217 @@
1
1
  # @objectstack/plugin-webhooks
2
2
 
3
+ ## 17.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - cdaa72f: fix(service-messaging,plugin-webhooks): the `update`-op tenant-audit surface on the delivery outboxes is classified — `ack` is a dispatcher sweep, `redeliver` threads the caller's tenant (#10740)
8
+
9
+ **BREAKING** signature change on `IHttpOutbox.redeliver` and
10
+ `MessagingService.redeliverHttp`, shipped as `minor` under the repo's
11
+ launch-window convention for breaking changes.
12
+
13
+ `sys_http_delivery` and `sys_notification_delivery` carry three single-record
14
+ (`multi: false`) writes that the SQL driver audits under the **`update`** op —
15
+ a different op, and a different throttle key, from the `updateMany` half
16
+ classified previously. Their correct classifications are **opposite**, and
17
+ treating them as one sweep is the dangerous reading:
18
+
19
+ | site | reachable from | classification |
20
+ | --- | --- | --- |
21
+ | `SqlNotificationOutbox.ack` | dispatcher tick only | global sweep |
22
+ | `SqlHttpOutbox.ack` | dispatcher tick only | global sweep |
23
+ | `SqlHttpOutbox.redeliver` | `POST /api/v1/webhooks/redeliver` | request-contextual |
24
+
25
+ **The two `ack` sites** are declared global sweeps through a new
26
+ `dispatcherAckOptions()` helper, sibling to `dispatcherSweepOptions()` and
27
+ deliberately not the same function — that one returns `& { multi: true }`, so a
28
+ `multi: false` site cannot borrow it by accident. The warrant was re-derived
29
+ against the current tree rather than inherited: `ack` has exactly two callers,
30
+ both inside `runPartition()` on a `setInterval` tick holding a per-partition
31
+ cluster lock, so no request context exists to thread; and the row being acked
32
+ was claimed by a sweep that crosses organizations by construction
33
+ (`hash(refId | notificationId | digestKey) mod N` is a load-spreading key, and
34
+ one outbox per environment drains the whole queue). Passing the claimed row's
35
+ own `organization_id` is documented at the helper as the tempting wrong answer:
36
+ a predicate read off the row you are about to write matches exactly that row,
37
+ adds no isolation, and silences the audit anyway — the appearance of scoping
38
+ without the substance.
39
+
40
+ **`redeliver` is not that**, and it is the reason this shipped separately. The
41
+ route in front of it is served to any authenticated user, so on a walled
42
+ deployment (`OS_TENANCY_POSTURE=isolated|group`) an unscoped replay is an
43
+ authenticated user writing another organization's delivery row — the case the
44
+ tenant audit exists to catch. It now carries the caller's tenant, applied to
45
+ the rows it reads as well as the row it writes, and it must never be given
46
+ `bypassTenantAudit`: a scoped write and a bypassed write produce the same
47
+ silence in the log, so the flag would convert a detectable hole into an
48
+ undetectable one. The webhook route resolves the session's
49
+ `activeOrganizationId` and threads it.
50
+
51
+ Behaviour change at the endpoint: a delivery row outside the caller's
52
+ organization is now **not found** (`RESOURCE_NOT_FOUND`, HTTP 404) rather than
53
+ replayed. It is deliberately invisible rather than forbidden, so the endpoint
54
+ is not an existence oracle for other tenants' delivery ids. An in-tenant
55
+ redelivery is unchanged.
56
+
57
+ Migrating a caller: `redeliver(id, guard?)` becomes
58
+ `redeliver(id, { tenantId, guard? })`, and `redeliverHttp(id)` becomes
59
+ `redeliverHttp(id, { tenantId })`. `tenantId` is a **required** property typed
60
+ `string | undefined`, so omitting it does not compile — a caller with no tenant
61
+ has to write `tenantId: undefined` and mean it. That is the point of the shape:
62
+ an optional property would let the dangerous case, a request path that simply
63
+ forgot, type-check in silence. Passing `undefined` leaves the write unscoped
64
+ and the audit line still fires, which is the intended reporting behaviour on a
65
+ deployment that cannot resolve an organization for the caller.
66
+
67
+ <!-- adr-0087: not-required (runtime-interface-only packages/services/service-messaging/src/http-outbox.ts#IHttpOutbox, packages/services/service-messaging/src/http-outbox.ts#RedeliverOptions) The surface that changed shape is `IHttpOutbox.redeliver`, plus the new `RedeliverOptions` argument type beside it. Both are TypeScript declarations in a service package with no `packages/spec` schema behind them: no metadata author writes a `redeliver` key, there is no authorable spelling and no `retiredKey()` tombstone, so `os migrate meta` has no stack source to rewrite. The change is a required second argument on an in-process method — a compile error at every call site, which is the notification channel, not a silent runtime gap. `MessagingService.redeliverHttp` moves with it and is deliberately NOT in the list above: this gate refuses that symbol as unresolvable, because `packages/spec/src/api/protocol.zod.ts` mentions the class name in a prose comment about `MessagingService.listInbox` while neither declaring nor importing it. The claim would be true and the gate cannot check it, so it is stated here for a reviewer instead of asserted where it would read as verified. It is a thin delegate to `IHttpOutbox.redeliver` in the same package and carries no schema of its own either. -->
68
+ - e222a53: **BREAKING** (compile-time only): twelve logger sink types that declared an
69
+ optional `error` now declare a **non-optional** `warn`, so a durability report
70
+ always has somewhere to land (#9754, #10556).
71
+
72
+ `minor`, not `major`: during the launch window this stack ships breaking changes
73
+ as `minor` — every publishable package versions in lockstep, so a `major` would
74
+ promote the whole release. `patch` would be wrong in the other direction, because
75
+ this *can* break a consumer's build.
76
+
77
+ `error` stays optional on every one of these types — hosts legitimately inject
78
+ reduced sinks, and requiring `error` was measured and rejected as #9754 option C.
79
+ What changes is that its *absence* now has a declared, guaranteed destination.
80
+ Call sites keep the `logger?.warn?.(…)` spelling as the backstop for hosts the
81
+ type cannot reach, so **no runtime behaviour changes**: nothing that printed
82
+ before stops printing, and nothing silent starts printing.
83
+
84
+ ### Who has to change, and what to do
85
+
86
+ Only a caller that hands one of these sinks an object with **no `warn` method** —
87
+ for example `{ info }` or `{ error }` alone. Add a `warn` member; there is no
88
+ rename, no removal, and no stored value or metadata key to rewrite. Every
89
+ construction site inside this repo already supplied one, so the in-repo cost was
90
+ zero; the compile error is reserved for the callers that were silently discarding
91
+ these reports.
92
+
93
+ The affected types, by package:
94
+
95
+ - `@objectstack/cloud-connection` — the internal `PluginContext['logger']`
96
+ - `@objectstack/metadata-protocol` — `IndexMigrationLogger`
97
+ - `@objectstack/plugin-approvals` — the internal `MinimalLogger` of `lifecycle-hooks`
98
+ - `@objectstack/plugin-audit` — `AuthEventAuditLogger`, `ReadAuditLogger`
99
+ - `@objectstack/plugin-auth` — `ReconcileMembershipDeps['logger']`, the internal
100
+ `LoggerLike` of `member-role-canonical`, and `AuthManagerOptions['logger']`
101
+ - `@objectstack/plugin-email` — `ReclaimLogger`, via `ReclaimAttachmentContentOptions`
102
+ - `@objectstack/plugin-reports` — `ReportServiceOptions['logger']`
103
+ - `@objectstack/plugin-sharing` — the internal `MinimalLogger` of `bulk-recompute`,
104
+ `rule-hooks` and `record-share-cascade`
105
+ - `@objectstack/plugin-webhooks` — `OptionalLogger`, via `AutoEnqueuerOptions`
106
+ - `@objectstack/service-knowledge` — `KnowledgeLogger`
107
+
108
+ `AuthManagerOptions['logger']` is the one most likely to be reached from outside:
109
+ `AuthManager` is public surface, its `logger` option stays optional, and a logger
110
+ that *is* supplied must now carry `warn`. The only non-test construction site in
111
+ this repo passes the kernel `Logger`, whose `warn` is already required.
112
+
113
+ `ReportService` and `AutoEnqueuer` additionally stopped defaulting their logger
114
+ field to `{}`. The field is now honestly optional rather than holding an empty
115
+ object that declared it could report and discarded everything. Behaviour is
116
+ unchanged in both directions.
117
+
118
+ <!-- adr-0087: not-required (runtime-interface-only packages/plugins/plugin-auth/src/auth-manager.ts#AuthManagerOptions, packages/plugins/plugin-auth/src/reconcile-membership.ts#ReconcileMembershipDeps, packages/metadata-protocol/src/migrations/partial-index-probe.ts#IndexMigrationLogger, packages/plugins/plugin-audit/src/auth-event-audit.ts#AuthEventAuditLogger, packages/plugins/plugin-audit/src/read-audit.ts#ReadAuditLogger, packages/plugins/plugin-reports/src/report-service.ts#ReportServiceOptions, packages/services/service-knowledge/src/knowledge-service.ts#KnowledgeLogger) every tightened type is a plain TypeScript logger interface -- no Zod projection, no metadata surface, and none is referenced by one -- so `objectstack migrate meta` has nothing to rewrite. Nothing is removed or renamed and no stored value moves; the only consumer action is adding a `warn` member at a construction site the compiler names. -->
119
+
120
+ ### Patch Changes
121
+
122
+ - 047ac86: Five `Plugin` implementations now release their resources from `destroy()`, the
123
+ only teardown hook the kernel calls (#10772).
124
+
125
+ `Plugin` (`@objectstack/core`'s `types.ts`) declares `init()`, `start?(ctx)` and
126
+ `destroy?()`. `ObjectKernel.performShutdown()` and `LiteKernel.destroy()` walk
127
+ the plugins in reverse calling `plugin.destroy()` — and nothing anywhere calls
128
+ `stop()`, `dispose()`, `close()` or `shutdown()` on a plugin. Each of these five
129
+ spelled its teardown with one of those names instead, so what it released was
130
+ still held after `await kernel.shutdown()` had **resolved**:
131
+
132
+ | package | class | was spelled | what outlived shutdown |
133
+ |:--|:--|:--|:--|
134
+ | `@objectstack/metadata` | `MetadataPlugin` | `stop` (arrow property) | artifact watcher, `manager.dispose()`, repository handle |
135
+ | `@objectstack/runtime` | `AppPlugin` | `stop` (arrow property) | the `app:unregistered` catalog event, never emitted |
136
+ | `@objectstack/runtime` | `ExternalValidationPlugin` | `stop` (arrow property) | every armed drift-check `setInterval` |
137
+ | `@objectstack/plugin-email` | `EmailServicePlugin` | `dispose` | two metadata subscriptions, the SMTP transport, an engine binding |
138
+ | `@objectstack/plugin-webhooks` | `WebhookOutboxPlugin` | `dispose` | the auto-enqueuer (2 realtime subscriptions + a refresh interval) and two engine hooks |
139
+
140
+ `ExternalValidationPlugin` is the one with teeth: it is one of only two `Plugin`
141
+ implementations in the tree that own `setInterval` directly, it is mounted on
142
+ the real `os serve` path, and its `stop()`'s only caller anywhere was the class
143
+ itself re-arming. Measured against a real kernel, its drift checker performed
144
+ five further reads in the five intervals after a resolved shutdown — the #9371
145
+ mechanism verbatim. `WebhookOutboxPlugin.dispose()` had **zero** callers in the
146
+ entire repo, so its teardown had never run in any process at all.
147
+
148
+ **Nothing is removed and no signature narrows.** Each old name is retained as a
149
+ delegating alias, because it is public API of an exported class and an embedder
150
+ may have learned to call it directly precisely BECAUSE the kernel never did.
151
+ `stop` stays an arrow property where it was one (so a detached
152
+ `const { stop } = plugin` keeps working) and stays synchronous on
153
+ `ExternalValidationPlugin` (so a non-awaiting call site is unaffected). The two
154
+ `stop(ctx)` aliases widen their parameter to optional.
155
+
156
+ One behavioural note for direct callers, since `destroy()` takes no context:
157
+ `MetadataPlugin.stop(ctx)` and `AppPlugin.stop(ctx)` now use the context
158
+ captured in `init()` and ignore the argument. In a real composition these are
159
+ the same object. The visible difference is confined to a plugin whose `init()`
160
+ never ran — for `MetadataPlugin` a dropped `warn` line, for `AppPlugin` a
161
+ catalog event that is no longer emitted for an app that was never registered.
162
+ - Updated dependencies [6936d07]
163
+ - Updated dependencies [59eb04d]
164
+ - Updated dependencies [9f05b7d]
165
+ - Updated dependencies [3b2af5e]
166
+ - Updated dependencies [7d2d112]
167
+ - Updated dependencies [5fa0d72]
168
+ - Updated dependencies [8cc8401]
169
+ - Updated dependencies [02b3b07]
170
+ - Updated dependencies [8163a1c]
171
+ - Updated dependencies [cdaa72f]
172
+ - Updated dependencies [914c413]
173
+ - Updated dependencies [55809a0]
174
+ - Updated dependencies [ee2ff45]
175
+ - Updated dependencies [47cd3ec]
176
+ - Updated dependencies [52db1d1]
177
+ - Updated dependencies [5649efb]
178
+ - Updated dependencies [9d7d2de]
179
+ - Updated dependencies [c815c50]
180
+ - Updated dependencies [795ea05]
181
+ - Updated dependencies [900e489]
182
+ - Updated dependencies [2306a76]
183
+ - Updated dependencies [e5ea701]
184
+ - Updated dependencies [a40dcc1]
185
+ - Updated dependencies [def0d3e]
186
+ - Updated dependencies [8d0bb79]
187
+ - Updated dependencies [5acb58d]
188
+ - Updated dependencies [2e3cf95]
189
+ - Updated dependencies [4c93387]
190
+ - Updated dependencies [504c8d5]
191
+ - Updated dependencies [a037f7c]
192
+ - Updated dependencies [3ee8ddf]
193
+ - Updated dependencies [16cef97]
194
+ - Updated dependencies [a79bd35]
195
+ - Updated dependencies [6ceaa4b]
196
+ - Updated dependencies [15ea214]
197
+ - Updated dependencies [de19489]
198
+ - Updated dependencies [c684d00]
199
+ - Updated dependencies [923c424]
200
+ - Updated dependencies [1ec36b7]
201
+ - Updated dependencies [5f2e54c]
202
+ - Updated dependencies [189373b]
203
+ - Updated dependencies [35ad101]
204
+ - Updated dependencies [ceb33a9]
205
+ - Updated dependencies [73d9795]
206
+ - Updated dependencies [8012960]
207
+ - Updated dependencies [f34f56b]
208
+ - Updated dependencies [f399618]
209
+ - Updated dependencies [75e9301]
210
+ - Updated dependencies [2810695]
211
+ - @objectstack/spec@17.2.0
212
+ - @objectstack/core@17.2.0
213
+ - @objectstack/service-messaging@17.2.0
214
+
3
215
  ## 17.1.0
4
216
 
5
217
  ### Patch Changes