@objectstack/plugin-webhooks 17.0.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 +426 -0
- package/dist/{chunk-Q4FEMGD6.cjs → chunk-DRHJ2M45.cjs} +61 -2
- package/dist/chunk-DRHJ2M45.cjs.map +1 -0
- package/dist/{chunk-GDCWDVDT.js → chunk-XERWWQKN.js} +61 -2
- package/dist/{chunk-GDCWDVDT.js.map → chunk-XERWWQKN.js.map} +1 -1
- package/dist/index.cjs +250 -98
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +140 -6
- package/dist/index.d.ts +140 -6
- package/dist/index.js +187 -35
- package/dist/index.js.map +1 -1
- package/dist/schema.cjs +2 -2
- package/dist/schema.d.cts +1193 -267
- package/dist/schema.d.ts +1193 -267
- package/dist/schema.js +1 -1
- package/dist/{translations-U32QIEPB.js → translations-IAKF6NAP.js} +1 -1
- package/dist/translations-IAKF6NAP.js.map +1 -0
- package/dist/{translations-H5ZYI6YP.cjs → translations-IHRALWSP.cjs} +1 -1
- package/dist/translations-IHRALWSP.cjs.map +1 -0
- package/package.json +7 -7
- package/dist/chunk-Q4FEMGD6.cjs.map +0 -1
- package/dist/translations-H5ZYI6YP.cjs.map +0 -1
- package/dist/translations-U32QIEPB.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,431 @@
|
|
|
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
|
+
|
|
215
|
+
## 17.1.0
|
|
216
|
+
|
|
217
|
+
### Patch Changes
|
|
218
|
+
|
|
219
|
+
- 90417a8: chore(plugin-webhooks): `sys_webhook` declares its data-API exposure explicitly — recording the posture, not narrowing it (#9756)
|
|
220
|
+
|
|
221
|
+
`sys_webhook` shipped with no `enable` block at all, so it kept the full default
|
|
222
|
+
data API. Three cards each noticed and each named the narrowing as the next
|
|
223
|
+
step — #7799 (the signing secret), #7986 (the custom headers), #8025 option 2
|
|
224
|
+
(the URL) — and each assumed a later one would write the line. None did, and the
|
|
225
|
+
last of them closed `completed` with the line still unwritten. The posture was
|
|
226
|
+
never a judgement; it was a default nobody had written down.
|
|
227
|
+
|
|
228
|
+
It is written down now:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
enable: { apiMethods: ['get', 'list', 'create', 'update', 'delete', 'bulk'] }
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**The effective surface is unchanged, and that is the honest headline.** The set
|
|
235
|
+
is derived from a census of who actually reaches the object, taken before
|
|
236
|
+
anything was edited:
|
|
237
|
+
|
|
238
|
+
| consumer | reaches it through | needs |
|
|
239
|
+
|:---|:---|:---|
|
|
240
|
+
| Setup/Studio console — `nav_webhooks`, four list views, `userActions` create/edit/delete | REST `/api/v1/data/sys_webhook` (gated) | `get` `list` `create` `update` `delete` |
|
|
241
|
+
| Operator predicate write — "deactivate every webhook on an object" (#4639) | REST `updateMany`/`deleteMany` (gated on `bulk`) | `bulk` |
|
|
242
|
+
| `AutoEnqueuer`, `bootstrapDeclaredWebhooks`, the provenance stamp, `redeliver-guard`, the secret sweep | `engine.*` and lifecycle hooks — ObjectQL directly, which never consults `enable.apiMethods` | ungated |
|
|
243
|
+
|
|
244
|
+
Every primitive is required by a real consumer, so the set is all six — whose
|
|
245
|
+
operation closure is what the absent block already produced. Nothing that was
|
|
246
|
+
reachable becomes unreachable, and `/me/permissions` reports the identical
|
|
247
|
+
`apiOperations` array. No caller needs to change anything.
|
|
248
|
+
|
|
249
|
+
⛔ **Do not read this as the read-surface narrowing those three cards asked
|
|
250
|
+
for.** It is not one, and `apiMethods` cannot be one here: `url` (#8025 —
|
|
251
|
+
won't-fix on masking, because the URL is the routing key an operator must be
|
|
252
|
+
able to see, search, sort and edit) and a legacy row's un-migrated
|
|
253
|
+
`definition_json.headers` (#7986 — still read, and warned about, by
|
|
254
|
+
`readLegacyHeaders`) are served by `get`/`list`, which is exactly what the admin
|
|
255
|
+
console requires. Any set that removes them removes the admin surface too. The
|
|
256
|
+
sibling `sys_http_delivery` can hold `['get','list']` because it is engine-owned
|
|
257
|
+
and never authored; `sys_webhook` is a first-class admin authoring surface.
|
|
258
|
+
|
|
259
|
+
The equality above is pinned in `sys-webhook-api-exposure.test.ts` rather than
|
|
260
|
+
left as a claim, so a later change that does move the surface has to say so.
|
|
261
|
+
- b278695: fix(webhooks): refuse a malformed `sys_webhook.headers_secret` at the write door instead of at the next delivery (#8566)
|
|
262
|
+
|
|
263
|
+
<!-- adr-0087: not-required (no-migration-prescription) Nothing authorable is
|
|
264
|
+
renamed, retired or tombstoned. This adds a runtime validation hook on one
|
|
265
|
+
plugin-owned object's existing column; the authoring envelope
|
|
266
|
+
(`webhook.zod.ts`), the field declaration and every stored shape are untouched.
|
|
267
|
+
The accept-set narrows, but only over values that were already unusable at
|
|
268
|
+
delivery time (see below), so there is no configuration for a migration to
|
|
269
|
+
prescribe a rewrite of. -->
|
|
270
|
+
|
|
271
|
+
`sys_webhook.headers_secret` is a `Field.secret()` whose plaintext is **not** an
|
|
272
|
+
opaque blob: it is a serialized header map with a required shape — a flat JSON
|
|
273
|
+
object of string values — and `parseStoredHeaders` is its only reader. Nothing
|
|
274
|
+
validated that shape on the way in. The ordinary data API accepted any string,
|
|
275
|
+
encrypted it like any other secret, minted a real `sys_secret` row, and left the
|
|
276
|
+
column holding a perfectly valid `secret:` ref that read back as the mask with
|
|
277
|
+
`active: true`.
|
|
278
|
+
|
|
279
|
+
Measured on a real engine through `engine.update()` — the ordinary data API, no
|
|
280
|
+
privileged access — every one of these was **accepted** and is a value the
|
|
281
|
+
plugin can never use: `{}`, `[]`, `{"X-Count": 5}`, a nested object, and
|
|
282
|
+
`{X-Team: crm}` (a typo). The field is directly admin-authorable and its own
|
|
283
|
+
description instructs the author to type a JSON object into it, which makes a
|
|
284
|
+
typo the *expected* failure rather than an exotic one.
|
|
285
|
+
|
|
286
|
+
**This is not an exposure fix and must not be read as one.** #8558/#8565 already
|
|
287
|
+
closed the consumer half: a webhook whose stored header map does not come back
|
|
288
|
+
as a flat string map parks the subscription and reports at `error`, rather than
|
|
289
|
+
delivering header-less with a valid signature. Nothing leaks, and nothing is
|
|
290
|
+
silently lost today. What this changes is **when the author finds out** — at the
|
|
291
|
+
write door where they typed it, instead of at the next matching record change,
|
|
292
|
+
an unbounded time later and in a different surface.
|
|
293
|
+
|
|
294
|
+
**What is refused:** a `headers_secret` plaintext that does not parse back as a
|
|
295
|
+
flat JSON object of string values with at least one entry, with a located
|
|
296
|
+
ADR-0112 `VALIDATION_ERROR` / 400 naming `sys_webhook.headers_secret`, quoting
|
|
297
|
+
the shape the field's own description asks for, and diagnosing the specific
|
|
298
|
+
spelling (invalid JSON / an array / an empty object / which key's value is not a
|
|
299
|
+
string). ⛔ The message never echoes the rejected value — this column carries
|
|
300
|
+
credentials, and quoting the input would print an `Authorization: Bearer …` into
|
|
301
|
+
logs and error bodies, re-opening in the diagnostic exactly the exposure #7986
|
|
302
|
+
moved this field onto the encrypted channel to close. It names header *keys* and
|
|
303
|
+
value *types* only.
|
|
304
|
+
|
|
305
|
+
**What stays accepted, byte for byte:** every valid flat string map (as JSON
|
|
306
|
+
text, or as an authored object the engine serializes into the same form); `null`
|
|
307
|
+
to clear; an omitted key to leave the stored value unchanged; and an **echoed
|
|
308
|
+
read-mask**, so the ordinary Setup-form round-trip (GET a row, edit an unrelated
|
|
309
|
+
field, PATCH it back) is untouched. `""` is deliberately passed through to
|
|
310
|
+
#8559's `EmptyCredentialWriteError` rather than re-refused here — one door, one
|
|
311
|
+
owner, one message.
|
|
312
|
+
|
|
313
|
+
**Where it runs, and why that is the whole mechanism:** a `beforeInsert` /
|
|
314
|
+
`beforeUpdate` hook on `sys_webhook`, bound by `WebhookOutboxPlugin` before its
|
|
315
|
+
first seeded write. It has to run *before* the engine's `encryptSecretFields` —
|
|
316
|
+
one step later the plaintext is gone and the column holds an opaque ref, so a
|
|
317
|
+
validator behind it would have nothing left to validate. The suite measures that
|
|
318
|
+
ordering rather than asserting it: every refusal pins that **no `sys_secret`
|
|
319
|
+
cipher row was minted**, which is only true if the gate ran first.
|
|
320
|
+
|
|
321
|
+
A hook rather than checks on the plugin's own write paths
|
|
322
|
+
(`bootstrapDeclaredWebhooks` / `headersPatch` / the migration sweep), because a
|
|
323
|
+
direct `PATCH /api/v1/data/sys_webhook` goes through none of them and that is
|
|
324
|
+
the measured trigger. Those paths inherit the validation through the hook and
|
|
325
|
+
deliberately carry no second check.
|
|
326
|
+
|
|
327
|
+
A general `secret`-channel plaintext validator — letting any `secret`-typed
|
|
328
|
+
field declare its own plaintext shape — is the principled generalization and is
|
|
329
|
+
recorded as the **promotion path**, not built here: it becomes the shape the
|
|
330
|
+
moment a second shaped-plaintext `secret` field exists (maintainer ruling
|
|
331
|
+
2026-08-13; one consumer does not justify a general capability).
|
|
332
|
+
- Updated dependencies [56656aa]
|
|
333
|
+
- Updated dependencies [07e630e]
|
|
334
|
+
- Updated dependencies [2f65b1b]
|
|
335
|
+
- Updated dependencies [720ee95]
|
|
336
|
+
- Updated dependencies [f287435]
|
|
337
|
+
- Updated dependencies [2782805]
|
|
338
|
+
- Updated dependencies [e43d63a]
|
|
339
|
+
- Updated dependencies [9aa8890]
|
|
340
|
+
- Updated dependencies [7c9c1dd]
|
|
341
|
+
- Updated dependencies [75b7c24]
|
|
342
|
+
- Updated dependencies [d5552ca]
|
|
343
|
+
- Updated dependencies [d9813a9]
|
|
344
|
+
- Updated dependencies [8640fb2]
|
|
345
|
+
- Updated dependencies [2420641]
|
|
346
|
+
- Updated dependencies [2ad91c3]
|
|
347
|
+
- Updated dependencies [f57fb38]
|
|
348
|
+
- Updated dependencies [00777a0]
|
|
349
|
+
- Updated dependencies [d491625]
|
|
350
|
+
- Updated dependencies [420804d]
|
|
351
|
+
- Updated dependencies [716ac9b]
|
|
352
|
+
- Updated dependencies [a38408a]
|
|
353
|
+
- Updated dependencies [62b1427]
|
|
354
|
+
- Updated dependencies [7ea1372]
|
|
355
|
+
- Updated dependencies [23abe27]
|
|
356
|
+
- Updated dependencies [985a9cd]
|
|
357
|
+
- Updated dependencies [5f5e234]
|
|
358
|
+
- Updated dependencies [a8189ae]
|
|
359
|
+
- Updated dependencies [26e70fb]
|
|
360
|
+
- Updated dependencies [42b05af]
|
|
361
|
+
- Updated dependencies [2b292ce]
|
|
362
|
+
- Updated dependencies [abcf853]
|
|
363
|
+
- Updated dependencies [8b9eba5]
|
|
364
|
+
- Updated dependencies [d575779]
|
|
365
|
+
- Updated dependencies [94f7ef8]
|
|
366
|
+
- Updated dependencies [c5ac5e4]
|
|
367
|
+
- Updated dependencies [a777944]
|
|
368
|
+
- Updated dependencies [dd88e1c]
|
|
369
|
+
- Updated dependencies [856527c]
|
|
370
|
+
- Updated dependencies [870f710]
|
|
371
|
+
- Updated dependencies [79c46da]
|
|
372
|
+
- Updated dependencies [7ff3975]
|
|
373
|
+
- Updated dependencies [29d055b]
|
|
374
|
+
- Updated dependencies [65589d6]
|
|
375
|
+
- Updated dependencies [2c86fe3]
|
|
376
|
+
- Updated dependencies [e196c6a]
|
|
377
|
+
- Updated dependencies [24173e9]
|
|
378
|
+
- Updated dependencies [4ab7523]
|
|
379
|
+
- Updated dependencies [19539b4]
|
|
380
|
+
- Updated dependencies [f8eb736]
|
|
381
|
+
- Updated dependencies [11b779e]
|
|
382
|
+
- Updated dependencies [739fe5b]
|
|
383
|
+
- Updated dependencies [4bfe1a5]
|
|
384
|
+
- Updated dependencies [2065e31]
|
|
385
|
+
- Updated dependencies [b69d0f5]
|
|
386
|
+
- Updated dependencies [4d47afe]
|
|
387
|
+
- Updated dependencies [e4e5c6e]
|
|
388
|
+
- Updated dependencies [9a56784]
|
|
389
|
+
- Updated dependencies [d00d2f6]
|
|
390
|
+
- Updated dependencies [df0c12d]
|
|
391
|
+
- Updated dependencies [44738f7]
|
|
392
|
+
- Updated dependencies [d31785f]
|
|
393
|
+
- Updated dependencies [c308a4f]
|
|
394
|
+
- Updated dependencies [e2899f6]
|
|
395
|
+
- Updated dependencies [3851f87]
|
|
396
|
+
- Updated dependencies [2a29caa]
|
|
397
|
+
- Updated dependencies [09a6eee]
|
|
398
|
+
- Updated dependencies [1a7f907]
|
|
399
|
+
- Updated dependencies [cd455c8]
|
|
400
|
+
- Updated dependencies [e1bb0ca]
|
|
401
|
+
- Updated dependencies [30d3752]
|
|
402
|
+
- Updated dependencies [c80e7ae]
|
|
403
|
+
- Updated dependencies [09a9a8a]
|
|
404
|
+
- Updated dependencies [07026cf]
|
|
405
|
+
- Updated dependencies [5d4f3d5]
|
|
406
|
+
- Updated dependencies [4d80e8b]
|
|
407
|
+
- Updated dependencies [30b1c63]
|
|
408
|
+
- Updated dependencies [079b457]
|
|
409
|
+
- Updated dependencies [e43b211]
|
|
410
|
+
- Updated dependencies [890b38f]
|
|
411
|
+
- Updated dependencies [8bee54b]
|
|
412
|
+
- Updated dependencies [7a537ce]
|
|
413
|
+
- Updated dependencies [593c4bf]
|
|
414
|
+
- Updated dependencies [ff08691]
|
|
415
|
+
- Updated dependencies [60e0f90]
|
|
416
|
+
- Updated dependencies [90c5285]
|
|
417
|
+
- Updated dependencies [402c125]
|
|
418
|
+
- Updated dependencies [7901b2d]
|
|
419
|
+
- Updated dependencies [56bca91]
|
|
420
|
+
- Updated dependencies [79394d7]
|
|
421
|
+
- Updated dependencies [730fd9a]
|
|
422
|
+
- Updated dependencies [44bc51d]
|
|
423
|
+
- Updated dependencies [73cfddf]
|
|
424
|
+
- Updated dependencies [d634e66]
|
|
425
|
+
- @objectstack/spec@17.1.0
|
|
426
|
+
- @objectstack/core@17.1.0
|
|
427
|
+
- @objectstack/service-messaging@17.1.0
|
|
428
|
+
|
|
3
429
|
## 17.0.0
|
|
4
430
|
|
|
5
431
|
### Minor Changes
|
|
@@ -251,10 +251,69 @@ var SysWebhook = _data.ObjectSchema.create({
|
|
|
251
251
|
{ fields: ["name"], unique: "organization" },
|
|
252
252
|
{ fields: ["object_name"] },
|
|
253
253
|
{ fields: ["active", "object_name"] }
|
|
254
|
-
]
|
|
254
|
+
],
|
|
255
|
+
/**
|
|
256
|
+
* [#9756] The data-API exposure of this object, declared EXPLICITLY.
|
|
257
|
+
*
|
|
258
|
+
* ## Why the block exists
|
|
259
|
+
*
|
|
260
|
+
* Three cards observed that `sys_webhook` declared no `enable` block at all
|
|
261
|
+
* and each named narrowing its read surface as the next step — #7799 (the
|
|
262
|
+
* signing secret), #7986 (the custom headers) and #8025 option 2 (the URL) —
|
|
263
|
+
* and each assumed a later one would write the line. None did. The condition
|
|
264
|
+
* held not because anyone judged the full default API correct here, but
|
|
265
|
+
* because the omission was never anybody's deliverable. That is the standard
|
|
266
|
+
* #8025 set and #9756 quotes back: *an omission is not a decision unless
|
|
267
|
+
* someone wrote it down.* This block is that decision, written down.
|
|
268
|
+
*
|
|
269
|
+
* ## The census the set is derived from (#9756, measured before writing)
|
|
270
|
+
*
|
|
271
|
+
* | consumer | reaches this object through | needs |
|
|
272
|
+
* |:---|:---|:---|
|
|
273
|
+
* | Setup/Studio console — `nav_webhooks` (`webhook-outbox-plugin.ts`), the four list views above, `userActions` create/edit/delete | REST `/api/v1/data/sys_webhook` — the gated data API | `get` `list` `create` `update` `delete` |
|
|
274
|
+
* | Operator predicate write — "deactivate every webhook on an object" (#4639, for which `AutoEnqueuer.handleSelfHealEvent` carries a `data.records.*` branch built expressly for this gesture) | REST `updateMany` / `deleteMany`, both gated on the `bulk` primitive | `bulk` |
|
|
275
|
+
* | `AutoEnqueuer` cache refresh, `bootstrapDeclaredWebhooks`, `stampWebhookProvenance`, `redeliver-guard`, `migrateLegacyWebhookSecrets`, the `headers_secret` write gate | `engine.find/findOne/insert/update` and lifecycle hooks — ObjectQL directly, which never consults `enable.apiMethods` | ungated: unaffected by anything declared here |
|
|
276
|
+
*
|
|
277
|
+
* ⇒ every primitive is required by a real, measured consumer, so the set is
|
|
278
|
+
* all six. No consumer outside the admin/operator surface was found.
|
|
279
|
+
*
|
|
280
|
+
* ## ⛔ This narrows NOTHING — do not read it as if it did
|
|
281
|
+
*
|
|
282
|
+
* `resolveEffectiveApiMethods` (`@objectstack/spec/data`) seeds the
|
|
283
|
+
* `unrestricted` branch with the very same `API_PRIMITIVES` set, so the six
|
|
284
|
+
* primitives resolve to the operation closure the *absent* block already
|
|
285
|
+
* produced. The serialized effective set (`/me/permissions`, the 405
|
|
286
|
+
* `allowed` array) is byte-identical, and no route or `callData` action
|
|
287
|
+
* reaches an operation whose answer differs. Only `mode` changes,
|
|
288
|
+
* `unrestricted` → `restricted`.
|
|
289
|
+
*
|
|
290
|
+
* So the presence of this block is NOT evidence that the reachable cleartext
|
|
291
|
+
* on this object was reduced. It was not, and `apiMethods` is the wrong
|
|
292
|
+
* instrument for it: `url` (#8025 — won't-fix on masking, because the URL is
|
|
293
|
+
* the routing key an operator must be able to see, search, sort and edit) and
|
|
294
|
+
* a legacy row's un-migrated `definition_json.headers` (#7986 —
|
|
295
|
+
* `readLegacyHeaders` in `auto-enqueuer.ts` still reads them and warns) are
|
|
296
|
+
* both served by `get`/`list`, which is exactly what the console requires.
|
|
297
|
+
* Any set that removes them removes the admin surface with them. A survey
|
|
298
|
+
* that greps this file for `enable:` and stops is measuring the wrong thing;
|
|
299
|
+
* #9756's report carries the census that says so.
|
|
300
|
+
*
|
|
301
|
+
* Contrast the sibling `sys_http_delivery` (`['get','list']`,
|
|
302
|
+
* `service-messaging`), whose narrowing is real: that table is engine-owned —
|
|
303
|
+
* written only by `SqlHttpOutbox` through context-less raw-engine writes,
|
|
304
|
+
* never authored — so closing its write surface costs nothing. `sys_webhook`
|
|
305
|
+
* is a first-class admin authoring surface. That is the whole difference, and
|
|
306
|
+
* it is why the sibling's shape could not simply be copied here.
|
|
307
|
+
*
|
|
308
|
+
* Pinned — the census, the no-narrowing equality, and the registration-time
|
|
309
|
+
* survival of every write verb — in `sys-webhook-api-exposure.test.ts`.
|
|
310
|
+
*/
|
|
311
|
+
enable: {
|
|
312
|
+
apiMethods: ["get", "list", "create", "update", "delete", "bulk"]
|
|
313
|
+
}
|
|
255
314
|
});
|
|
256
315
|
|
|
257
316
|
|
|
258
317
|
|
|
259
318
|
exports.SysWebhook = SysWebhook;
|
|
260
|
-
//# sourceMappingURL=chunk-
|
|
319
|
+
//# sourceMappingURL=chunk-DRHJ2M45.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/objectstack/objectstack/packages/plugins/plugin-webhooks/dist/chunk-DRHJ2M45.cjs","../src/sys-webhook.object.ts"],"names":[],"mappings":"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","file":"/home/runner/work/objectstack/objectstack/packages/plugins/plugin-webhooks/dist/chunk-DRHJ2M45.cjs","sourcesContent":[null,"// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * sys_webhook — Outbound HTTP integration configuration (runtime).\n *\n * Persists a single {@link Webhook} envelope per row so administrators\n * can author, enable/disable, and edit webhook subscriptions from the\n * Studio UI without code changes. The canonical Zod schema for the\n * `definition_json` envelope lives at `@objectstack/spec/automation/webhook`.\n *\n * ## Two authoring doors, one row\n * Rows land here two ways, distinguished by the `managed_by` provenance column:\n * - **admin** — created/edited directly through this object's CRUD UI.\n * - **package** — declared in code (`defineStack({ webhooks })` /\n * `defineWebhook()`) and materialized on boot by\n * `bootstrapDeclaredWebhooks` (#3461). Re-seeded every boot, but an admin\n * edit stamps `customized: true` and freezes the row (seed-not-clobber,\n * mirrors `sys_sharing_rule` #2909).\n *\n * One row per `name`. This plugin's {@link AutoEnqueuer} loads active rows on\n * boot + on `sys_webhook:changed` events, and turns matching `data.record.*`\n * events into deliveries on the shared `service-messaging` HTTP outbox\n * (ADR-0018 M3 — `sys_http_delivery`, drained by the messaging dispatcher).\n *\n * Ownership (ADR-0029 K2.a): this object is **owned by\n * `@objectstack/plugin-webhooks`** — the plugin that consumes these rows. It\n * used to live in the `@objectstack/platform-objects` monolith and be imported\n * here; the definition now lives with its owner so the plugin ships both data\n * and behavior as one unit.\n *\n * Platform-wide on purpose: every project (standalone, single-tenant,\n * cloud) can integrate with external systems (Slack, Stripe, internal\n * services) the same way.\n *\n * @namespace sys\n */\nexport const SysWebhook = ObjectSchema.create({\n name: 'sys_webhook',\n label: 'Webhook',\n pluralLabel: 'Webhooks',\n icon: 'webhook',\n isSystem: true,\n managedBy: 'config',\n // Authoring a webhook from the UI requires a structured form for the\n // headers / auth / retry / payload blocks — the generic JSON textarea\n // is acceptable as a v1 until a dedicated builder lands. Re-enable\n // create/edit/delete so admins can at least toggle `active` and edit\n // simple URL/method fields without round-tripping through code.\n userActions: { create: true, edit: true, delete: true, import: false },\n description: 'Outbound HTTP webhook subscription. Declared in code via defineStack({ webhooks }) / defineWebhook() (materialized into rows on boot) or authored directly in the Studio editor; dispatched by the webhook auto-enqueuer onto the shared HTTP outbox.',\n displayNameField: 'name',\n nameField: 'name', // [ADR-0079] canonical primary-title pointer (mirrors deprecated displayNameField)\n titleFormat: '{label}',\n highlightFields: ['name', 'object_name', 'url', 'active', 'updated_at'],\n\n listViews: {\n active: {\n type: 'grid',\n name: 'active',\n label: 'Active',\n data: { provider: 'object', object: 'sys_webhook' },\n columns: ['label', 'object_name', 'url', 'method', 'active', 'updated_at'],\n filter: [{ field: 'active', operator: 'equals', value: true }],\n sort: [{ field: 'label', order: 'asc' }],\n pagination: { pageSize: 50 },\n },\n inactive: {\n type: 'grid',\n name: 'inactive',\n label: 'Inactive',\n data: { provider: 'object', object: 'sys_webhook' },\n columns: ['label', 'object_name', 'url', 'method', 'active', 'updated_at'],\n filter: [{ field: 'active', operator: 'equals', value: false }],\n sort: [{ field: 'label', order: 'asc' }],\n pagination: { pageSize: 50 },\n },\n by_object: {\n type: 'grid',\n name: 'by_object',\n label: 'By Object',\n data: { provider: 'object', object: 'sys_webhook' },\n columns: ['object_name', 'label', 'url', 'active', 'updated_at'],\n sort: [{ field: 'object_name', order: 'asc' }, { field: 'label', order: 'asc' }],\n grouping: { fields: [{ field: 'object_name', order: 'asc', collapsed: false }] },\n pagination: { pageSize: 100 },\n },\n all_webhooks: {\n type: 'grid',\n name: 'all_webhooks',\n label: 'All',\n data: { provider: 'object', object: 'sys_webhook' },\n columns: ['label', 'object_name', 'url', 'method', 'active', 'updated_at'],\n sort: [{ field: 'label', order: 'asc' }],\n pagination: { pageSize: 50 },\n },\n },\n\n fields: {\n id: Field.text({ label: 'Webhook ID', required: true, readonly: true, group: 'System' }),\n\n name: Field.text({\n label: 'Name',\n required: true,\n maxLength: 100,\n // [#8554] \"unique per organization\", not bare \"unique\" — the bare wording\n // described the installation-wide index this card removed.\n description: 'snake_case name, unique per organization — referenced in logs and audit',\n group: 'Definition',\n }),\n\n label: Field.text({\n label: 'Display Label',\n required: false,\n maxLength: 200,\n group: 'Definition',\n }),\n\n object_name: Field.text({\n label: 'Object',\n required: false,\n maxLength: 100,\n // Object picker (same widget as sys_sharing_rule) instead of a free-text\n // machine name. Falls back to a text input when the widget is unavailable.\n widget: 'object-ref',\n description: 'Short object name whose record events (create/update/delete) fire this webhook',\n group: 'Definition',\n }),\n\n triggers: Field.select(\n ['create', 'update', 'delete', 'bulk_update', 'bulk_delete'],\n {\n label: 'Triggers',\n required: false,\n // Multi-select instead of a hand-typed comma-separated string. Stored as\n // an array; the auto-enqueuer parser also tolerates the legacy\n // comma-separated / JSON-string forms so existing rows keep working.\n multiple: true,\n // [#4639] `bulk_*` fire on predicate writes (`multi: true`), whose\n // delivery carries `matched` instead of a record — opt-in precisely\n // because that body is a different shape. Kept in step with\n // `WebhookTriggerType` (`@objectstack/spec/automation`), which is the\n // contract the auto-enqueuer validates against.\n description: 'Record events that fire this webhook (bulk_* deliver a count, not a record)',\n group: 'Definition',\n },\n ),\n\n url: Field.text({\n label: 'Target URL',\n required: true,\n maxLength: 2048,\n description: 'External endpoint that receives the POST',\n group: 'Definition',\n }),\n\n method: Field.select(\n ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],\n {\n label: 'HTTP Method',\n required: true,\n // Select instead of free text. Option values are lowercased by the\n // Field.select helper (get/post/…); the auto-enqueuer upper-cases the\n // resolved method before delivery, so existing 'POST' rows and the\n // lowercase option values both normalise correctly.\n defaultValue: 'post',\n description: 'HTTP method used for the callback request',\n group: 'Definition',\n },\n ),\n\n description: Field.textarea({ label: 'Description', required: false, group: 'Definition' }),\n\n active: Field.boolean({\n label: 'Active',\n required: true,\n defaultValue: true,\n description: 'Inactive webhooks are skipped by the dispatcher',\n group: 'Definition',\n }),\n\n definition_json: Field.textarea({\n label: 'Definition',\n required: true,\n description: 'Serialised Webhook JSON (see @objectstack/spec/automation/webhook) — timeout and the rest of the authored envelope. Credentials are NOT stored here: the signing secret lives in the encrypted `signing_secret` field and the custom headers in the encrypted `headers_secret` field.',\n group: 'Definition',\n }),\n\n /**\n * [#7986] Custom HTTP headers, in the engine's ENCRYPTED credential channel.\n *\n * The sibling passenger #7799 left on the blob it emptied. That card was\n * framed as \"the signing secret is in cleartext\" and was fixed exactly as\n * framed — but the COLUMN was the problem, and `headers` is the ordinary\n * place an `Authorization: Bearer …` goes. `definition_json` is an ordinary\n * textarea on an admin-authorable object with no restrictive\n * `enable.apiMethods` at all, so `GET /api/v1/data/sys_webhook` returned the\n * header map to every persona that can read the object, with none of the\n * retention bound that eventually ages out `sys_http_delivery`'s copies.\n *\n * The WHOLE map moves rather than the credential-looking entries, because\n * only some entries are credentials and the platform cannot tell which:\n * guessing from the header name is fail-OPEN on exactly the custom spellings\n * (`X-Acme-Token`) most likely to be one, and letting the author declare\n * which are sensitive is a change to the authoring envelope\n * (`webhook.zod.ts`) that belongs to the spec seat. See\n * `webhook-headers.ts` for the full comparison.\n *\n * Stored as the SERIALIZED map (the encrypted channel carries a string);\n * the enqueuer recovers and re-parses it server-side through\n * `engine.resolveSecretField()` when it refreshes its subscription cache,\n * on the same refresh that recovers the signing secret.\n *\n * Fail-closed by construction, the same way `signing_secret` is: with no\n * CryptoProvider the engine REFUSES the write rather than falling back to\n * cleartext, and a stored map that cannot be decrypted DROPS the\n * subscription rather than delivering it with its headers silently missing.\n */\n headers_secret: Field.secret({\n label: 'Custom Headers',\n required: false,\n description:\n 'Custom HTTP headers sent with each delivery, as a JSON object ({\"Authorization\": \"Bearer …\"}). '\n + 'Encrypted at rest into sys_secret; reads return a mask, never the headers. Leave the mask '\n + 'untouched to keep the current value.',\n group: 'Definition',\n }),\n\n /**\n * [#7799] HMAC signing key, in the engine's ENCRYPTED credential channel.\n *\n * It used to ride inside `definition_json` as cleartext, because that column\n * is where the seeder parked the whole authored envelope. `definition_json`\n * is an ordinary textarea on an admin-authorable object with no restrictive\n * `enable.apiMethods`, so `GET /api/v1/data/sys_webhook` handed the key back\n * to every persona that can read the object — and that key is the receiver's\n * ONLY proof a delivery came from us. Same exposure class as #7722's\n * per-attempt copies, minus the retention window that eventually aged those\n * out.\n *\n * `type: 'secret'` moves it onto the channel built for this: the engine\n * encrypts on write via the registered `ICryptoProvider`, stores the\n * ciphertext as a `sys_secret` row, keeps only an opaque `secret:<id>` ref\n * on this column, and returns the mask on every read path. The enqueuer\n * recovers the plaintext server-side through `engine.resolveSecretField()`\n * when it refreshes its subscription cache.\n *\n * Fail-closed by construction: with no CryptoProvider wired the engine\n * REFUSES the write rather than falling back to cleartext, so the seeder\n * skips that webhook loudly instead of re-opening the hole in a new column.\n */\n signing_secret: Field.secret({\n label: 'Signing Secret',\n required: false,\n description:\n 'HMAC-SHA256 key used to sign deliveries (X-Objectstack-Signature). Encrypted at rest into '\n + 'sys_secret; reads return a mask, never the key. Leave the mask untouched to keep the current value.',\n group: 'Definition',\n }),\n\n // ── Provenance (#3461 — record-authoritative seed-not-clobber) ──\n // Mirrors sys_sharing_rule (#2909). Both columns are `readonly`: the\n // engine strips them from non-system payloads (forge/clear-proof), while\n // bootstrapDeclaredWebhooks and the provenance stamp hook write with\n // isSystem. Deliberately NOT a write gate: webhooks are a first-class admin\n // authoring/tuning surface — admins may edit or deactivate a package row;\n // the seeder simply stops overwriting it once `customized` is stamped.\n managed_by: Field.select(\n ['platform', 'package', 'admin'],\n {\n label: 'Managed By',\n required: false,\n readonly: true,\n defaultValue: 'admin',\n description:\n 'Record provenance: platform = framework built-in / package = app/package-declared ' +\n '(boot-seeded from defineStack webhooks) / admin = created in Setup.',\n group: 'System',\n },\n ),\n\n customized: Field.boolean({\n label: 'Customized',\n required: false,\n readonly: true,\n defaultValue: false,\n description:\n 'Set when an admin edits a package-declared webhook; boot seeding will no longer ' +\n 'overwrite the row (a deactivated noisy webhook survives redeploys). Meaningless on admin rows.',\n group: 'System',\n }),\n\n created_at: Field.datetime({\n label: 'Created At',\n required: true,\n defaultValue: 'NOW()',\n readonly: true,\n group: 'System',\n }),\n\n updated_at: Field.datetime({ label: 'Updated At', required: false, group: 'System' }),\n },\n\n indexes: [\n // [#8554] Scope spelled EXPLICITLY (ADR-0120 D1). On a DECLARED index bare\n // `unique: true` is the positional spelling of `'global'` — the listed\n // columns verbatim — so this was an installation-wide key on a tenant-scoped\n // object. Measured live before the fix: org_jia 201 / org_yi 409\n // UNIQUE_VIOLATION on the same name / org_yi unused name 201 / org_yi's own\n // GET on the colliding name 0 rows. Webhooks are named by admins from the\n // UI, so two organizations both wanting `order_created_hook` is ordinary.\n { fields: ['name'], unique: 'organization' },\n { fields: ['object_name'] },\n { fields: ['active', 'object_name'] },\n ],\n\n /**\n * [#9756] The data-API exposure of this object, declared EXPLICITLY.\n *\n * ## Why the block exists\n *\n * Three cards observed that `sys_webhook` declared no `enable` block at all\n * and each named narrowing its read surface as the next step — #7799 (the\n * signing secret), #7986 (the custom headers) and #8025 option 2 (the URL) —\n * and each assumed a later one would write the line. None did. The condition\n * held not because anyone judged the full default API correct here, but\n * because the omission was never anybody's deliverable. That is the standard\n * #8025 set and #9756 quotes back: *an omission is not a decision unless\n * someone wrote it down.* This block is that decision, written down.\n *\n * ## The census the set is derived from (#9756, measured before writing)\n *\n * | consumer | reaches this object through | needs |\n * |:---|:---|:---|\n * | Setup/Studio console — `nav_webhooks` (`webhook-outbox-plugin.ts`), the four list views above, `userActions` create/edit/delete | REST `/api/v1/data/sys_webhook` — the gated data API | `get` `list` `create` `update` `delete` |\n * | Operator predicate write — \"deactivate every webhook on an object\" (#4639, for which `AutoEnqueuer.handleSelfHealEvent` carries a `data.records.*` branch built expressly for this gesture) | REST `updateMany` / `deleteMany`, both gated on the `bulk` primitive | `bulk` |\n * | `AutoEnqueuer` cache refresh, `bootstrapDeclaredWebhooks`, `stampWebhookProvenance`, `redeliver-guard`, `migrateLegacyWebhookSecrets`, the `headers_secret` write gate | `engine.find/findOne/insert/update` and lifecycle hooks — ObjectQL directly, which never consults `enable.apiMethods` | ungated: unaffected by anything declared here |\n *\n * ⇒ every primitive is required by a real, measured consumer, so the set is\n * all six. No consumer outside the admin/operator surface was found.\n *\n * ## ⛔ This narrows NOTHING — do not read it as if it did\n *\n * `resolveEffectiveApiMethods` (`@objectstack/spec/data`) seeds the\n * `unrestricted` branch with the very same `API_PRIMITIVES` set, so the six\n * primitives resolve to the operation closure the *absent* block already\n * produced. The serialized effective set (`/me/permissions`, the 405\n * `allowed` array) is byte-identical, and no route or `callData` action\n * reaches an operation whose answer differs. Only `mode` changes,\n * `unrestricted` → `restricted`.\n *\n * So the presence of this block is NOT evidence that the reachable cleartext\n * on this object was reduced. It was not, and `apiMethods` is the wrong\n * instrument for it: `url` (#8025 — won't-fix on masking, because the URL is\n * the routing key an operator must be able to see, search, sort and edit) and\n * a legacy row's un-migrated `definition_json.headers` (#7986 —\n * `readLegacyHeaders` in `auto-enqueuer.ts` still reads them and warns) are\n * both served by `get`/`list`, which is exactly what the console requires.\n * Any set that removes them removes the admin surface with them. A survey\n * that greps this file for `enable:` and stops is measuring the wrong thing;\n * #9756's report carries the census that says so.\n *\n * Contrast the sibling `sys_http_delivery` (`['get','list']`,\n * `service-messaging`), whose narrowing is real: that table is engine-owned —\n * written only by `SqlHttpOutbox` through context-less raw-engine writes,\n * never authored — so closing its write surface costs nothing. `sys_webhook`\n * is a first-class admin authoring surface. That is the whole difference, and\n * it is why the sibling's shape could not simply be copied here.\n *\n * Pinned — the census, the no-narrowing equality, and the registration-time\n * survival of every write verb — in `sys-webhook-api-exposure.test.ts`.\n */\n enable: {\n apiMethods: ['get', 'list', 'create', 'update', 'delete', 'bulk'],\n },\n});\n"]}
|
|
@@ -251,10 +251,69 @@ var SysWebhook = ObjectSchema.create({
|
|
|
251
251
|
{ fields: ["name"], unique: "organization" },
|
|
252
252
|
{ fields: ["object_name"] },
|
|
253
253
|
{ fields: ["active", "object_name"] }
|
|
254
|
-
]
|
|
254
|
+
],
|
|
255
|
+
/**
|
|
256
|
+
* [#9756] The data-API exposure of this object, declared EXPLICITLY.
|
|
257
|
+
*
|
|
258
|
+
* ## Why the block exists
|
|
259
|
+
*
|
|
260
|
+
* Three cards observed that `sys_webhook` declared no `enable` block at all
|
|
261
|
+
* and each named narrowing its read surface as the next step — #7799 (the
|
|
262
|
+
* signing secret), #7986 (the custom headers) and #8025 option 2 (the URL) —
|
|
263
|
+
* and each assumed a later one would write the line. None did. The condition
|
|
264
|
+
* held not because anyone judged the full default API correct here, but
|
|
265
|
+
* because the omission was never anybody's deliverable. That is the standard
|
|
266
|
+
* #8025 set and #9756 quotes back: *an omission is not a decision unless
|
|
267
|
+
* someone wrote it down.* This block is that decision, written down.
|
|
268
|
+
*
|
|
269
|
+
* ## The census the set is derived from (#9756, measured before writing)
|
|
270
|
+
*
|
|
271
|
+
* | consumer | reaches this object through | needs |
|
|
272
|
+
* |:---|:---|:---|
|
|
273
|
+
* | Setup/Studio console — `nav_webhooks` (`webhook-outbox-plugin.ts`), the four list views above, `userActions` create/edit/delete | REST `/api/v1/data/sys_webhook` — the gated data API | `get` `list` `create` `update` `delete` |
|
|
274
|
+
* | Operator predicate write — "deactivate every webhook on an object" (#4639, for which `AutoEnqueuer.handleSelfHealEvent` carries a `data.records.*` branch built expressly for this gesture) | REST `updateMany` / `deleteMany`, both gated on the `bulk` primitive | `bulk` |
|
|
275
|
+
* | `AutoEnqueuer` cache refresh, `bootstrapDeclaredWebhooks`, `stampWebhookProvenance`, `redeliver-guard`, `migrateLegacyWebhookSecrets`, the `headers_secret` write gate | `engine.find/findOne/insert/update` and lifecycle hooks — ObjectQL directly, which never consults `enable.apiMethods` | ungated: unaffected by anything declared here |
|
|
276
|
+
*
|
|
277
|
+
* ⇒ every primitive is required by a real, measured consumer, so the set is
|
|
278
|
+
* all six. No consumer outside the admin/operator surface was found.
|
|
279
|
+
*
|
|
280
|
+
* ## ⛔ This narrows NOTHING — do not read it as if it did
|
|
281
|
+
*
|
|
282
|
+
* `resolveEffectiveApiMethods` (`@objectstack/spec/data`) seeds the
|
|
283
|
+
* `unrestricted` branch with the very same `API_PRIMITIVES` set, so the six
|
|
284
|
+
* primitives resolve to the operation closure the *absent* block already
|
|
285
|
+
* produced. The serialized effective set (`/me/permissions`, the 405
|
|
286
|
+
* `allowed` array) is byte-identical, and no route or `callData` action
|
|
287
|
+
* reaches an operation whose answer differs. Only `mode` changes,
|
|
288
|
+
* `unrestricted` → `restricted`.
|
|
289
|
+
*
|
|
290
|
+
* So the presence of this block is NOT evidence that the reachable cleartext
|
|
291
|
+
* on this object was reduced. It was not, and `apiMethods` is the wrong
|
|
292
|
+
* instrument for it: `url` (#8025 — won't-fix on masking, because the URL is
|
|
293
|
+
* the routing key an operator must be able to see, search, sort and edit) and
|
|
294
|
+
* a legacy row's un-migrated `definition_json.headers` (#7986 —
|
|
295
|
+
* `readLegacyHeaders` in `auto-enqueuer.ts` still reads them and warns) are
|
|
296
|
+
* both served by `get`/`list`, which is exactly what the console requires.
|
|
297
|
+
* Any set that removes them removes the admin surface with them. A survey
|
|
298
|
+
* that greps this file for `enable:` and stops is measuring the wrong thing;
|
|
299
|
+
* #9756's report carries the census that says so.
|
|
300
|
+
*
|
|
301
|
+
* Contrast the sibling `sys_http_delivery` (`['get','list']`,
|
|
302
|
+
* `service-messaging`), whose narrowing is real: that table is engine-owned —
|
|
303
|
+
* written only by `SqlHttpOutbox` through context-less raw-engine writes,
|
|
304
|
+
* never authored — so closing its write surface costs nothing. `sys_webhook`
|
|
305
|
+
* is a first-class admin authoring surface. That is the whole difference, and
|
|
306
|
+
* it is why the sibling's shape could not simply be copied here.
|
|
307
|
+
*
|
|
308
|
+
* Pinned — the census, the no-narrowing equality, and the registration-time
|
|
309
|
+
* survival of every write verb — in `sys-webhook-api-exposure.test.ts`.
|
|
310
|
+
*/
|
|
311
|
+
enable: {
|
|
312
|
+
apiMethods: ["get", "list", "create", "update", "delete", "bulk"]
|
|
313
|
+
}
|
|
255
314
|
});
|
|
256
315
|
|
|
257
316
|
export {
|
|
258
317
|
SysWebhook
|
|
259
318
|
};
|
|
260
|
-
//# sourceMappingURL=chunk-
|
|
319
|
+
//# sourceMappingURL=chunk-XERWWQKN.js.map
|