@objectstack/plugin-webhooks 17.0.0 → 17.1.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 +214 -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 +135 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -1
- package/dist/index.d.ts +66 -1
- package/dist/index.js +106 -3
- package/dist/index.js.map +1 -1
- package/dist/schema.cjs +2 -2
- package/dist/schema.d.cts +1188 -269
- package/dist/schema.d.ts +1188 -269
- 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,219 @@
|
|
|
1
1
|
# @objectstack/plugin-webhooks
|
|
2
2
|
|
|
3
|
+
## 17.1.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 90417a8: chore(plugin-webhooks): `sys_webhook` declares its data-API exposure explicitly — recording the posture, not narrowing it (#9756)
|
|
8
|
+
|
|
9
|
+
`sys_webhook` shipped with no `enable` block at all, so it kept the full default
|
|
10
|
+
data API. Three cards each noticed and each named the narrowing as the next
|
|
11
|
+
step — #7799 (the signing secret), #7986 (the custom headers), #8025 option 2
|
|
12
|
+
(the URL) — and each assumed a later one would write the line. None did, and the
|
|
13
|
+
last of them closed `completed` with the line still unwritten. The posture was
|
|
14
|
+
never a judgement; it was a default nobody had written down.
|
|
15
|
+
|
|
16
|
+
It is written down now:
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
enable: { apiMethods: ['get', 'list', 'create', 'update', 'delete', 'bulk'] }
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**The effective surface is unchanged, and that is the honest headline.** The set
|
|
23
|
+
is derived from a census of who actually reaches the object, taken before
|
|
24
|
+
anything was edited:
|
|
25
|
+
|
|
26
|
+
| consumer | reaches it through | needs |
|
|
27
|
+
|:---|:---|:---|
|
|
28
|
+
| Setup/Studio console — `nav_webhooks`, four list views, `userActions` create/edit/delete | REST `/api/v1/data/sys_webhook` (gated) | `get` `list` `create` `update` `delete` |
|
|
29
|
+
| Operator predicate write — "deactivate every webhook on an object" (#4639) | REST `updateMany`/`deleteMany` (gated on `bulk`) | `bulk` |
|
|
30
|
+
| `AutoEnqueuer`, `bootstrapDeclaredWebhooks`, the provenance stamp, `redeliver-guard`, the secret sweep | `engine.*` and lifecycle hooks — ObjectQL directly, which never consults `enable.apiMethods` | ungated |
|
|
31
|
+
|
|
32
|
+
Every primitive is required by a real consumer, so the set is all six — whose
|
|
33
|
+
operation closure is what the absent block already produced. Nothing that was
|
|
34
|
+
reachable becomes unreachable, and `/me/permissions` reports the identical
|
|
35
|
+
`apiOperations` array. No caller needs to change anything.
|
|
36
|
+
|
|
37
|
+
⛔ **Do not read this as the read-surface narrowing those three cards asked
|
|
38
|
+
for.** It is not one, and `apiMethods` cannot be one here: `url` (#8025 —
|
|
39
|
+
won't-fix on masking, because the URL is the routing key an operator must be
|
|
40
|
+
able to see, search, sort and edit) and a legacy row's un-migrated
|
|
41
|
+
`definition_json.headers` (#7986 — still read, and warned about, by
|
|
42
|
+
`readLegacyHeaders`) are served by `get`/`list`, which is exactly what the admin
|
|
43
|
+
console requires. Any set that removes them removes the admin surface too. The
|
|
44
|
+
sibling `sys_http_delivery` can hold `['get','list']` because it is engine-owned
|
|
45
|
+
and never authored; `sys_webhook` is a first-class admin authoring surface.
|
|
46
|
+
|
|
47
|
+
The equality above is pinned in `sys-webhook-api-exposure.test.ts` rather than
|
|
48
|
+
left as a claim, so a later change that does move the surface has to say so.
|
|
49
|
+
- b278695: fix(webhooks): refuse a malformed `sys_webhook.headers_secret` at the write door instead of at the next delivery (#8566)
|
|
50
|
+
|
|
51
|
+
<!-- adr-0087: not-required (no-migration-prescription) Nothing authorable is
|
|
52
|
+
renamed, retired or tombstoned. This adds a runtime validation hook on one
|
|
53
|
+
plugin-owned object's existing column; the authoring envelope
|
|
54
|
+
(`webhook.zod.ts`), the field declaration and every stored shape are untouched.
|
|
55
|
+
The accept-set narrows, but only over values that were already unusable at
|
|
56
|
+
delivery time (see below), so there is no configuration for a migration to
|
|
57
|
+
prescribe a rewrite of. -->
|
|
58
|
+
|
|
59
|
+
`sys_webhook.headers_secret` is a `Field.secret()` whose plaintext is **not** an
|
|
60
|
+
opaque blob: it is a serialized header map with a required shape — a flat JSON
|
|
61
|
+
object of string values — and `parseStoredHeaders` is its only reader. Nothing
|
|
62
|
+
validated that shape on the way in. The ordinary data API accepted any string,
|
|
63
|
+
encrypted it like any other secret, minted a real `sys_secret` row, and left the
|
|
64
|
+
column holding a perfectly valid `secret:` ref that read back as the mask with
|
|
65
|
+
`active: true`.
|
|
66
|
+
|
|
67
|
+
Measured on a real engine through `engine.update()` — the ordinary data API, no
|
|
68
|
+
privileged access — every one of these was **accepted** and is a value the
|
|
69
|
+
plugin can never use: `{}`, `[]`, `{"X-Count": 5}`, a nested object, and
|
|
70
|
+
`{X-Team: crm}` (a typo). The field is directly admin-authorable and its own
|
|
71
|
+
description instructs the author to type a JSON object into it, which makes a
|
|
72
|
+
typo the *expected* failure rather than an exotic one.
|
|
73
|
+
|
|
74
|
+
**This is not an exposure fix and must not be read as one.** #8558/#8565 already
|
|
75
|
+
closed the consumer half: a webhook whose stored header map does not come back
|
|
76
|
+
as a flat string map parks the subscription and reports at `error`, rather than
|
|
77
|
+
delivering header-less with a valid signature. Nothing leaks, and nothing is
|
|
78
|
+
silently lost today. What this changes is **when the author finds out** — at the
|
|
79
|
+
write door where they typed it, instead of at the next matching record change,
|
|
80
|
+
an unbounded time later and in a different surface.
|
|
81
|
+
|
|
82
|
+
**What is refused:** a `headers_secret` plaintext that does not parse back as a
|
|
83
|
+
flat JSON object of string values with at least one entry, with a located
|
|
84
|
+
ADR-0112 `VALIDATION_ERROR` / 400 naming `sys_webhook.headers_secret`, quoting
|
|
85
|
+
the shape the field's own description asks for, and diagnosing the specific
|
|
86
|
+
spelling (invalid JSON / an array / an empty object / which key's value is not a
|
|
87
|
+
string). ⛔ The message never echoes the rejected value — this column carries
|
|
88
|
+
credentials, and quoting the input would print an `Authorization: Bearer …` into
|
|
89
|
+
logs and error bodies, re-opening in the diagnostic exactly the exposure #7986
|
|
90
|
+
moved this field onto the encrypted channel to close. It names header *keys* and
|
|
91
|
+
value *types* only.
|
|
92
|
+
|
|
93
|
+
**What stays accepted, byte for byte:** every valid flat string map (as JSON
|
|
94
|
+
text, or as an authored object the engine serializes into the same form); `null`
|
|
95
|
+
to clear; an omitted key to leave the stored value unchanged; and an **echoed
|
|
96
|
+
read-mask**, so the ordinary Setup-form round-trip (GET a row, edit an unrelated
|
|
97
|
+
field, PATCH it back) is untouched. `""` is deliberately passed through to
|
|
98
|
+
#8559's `EmptyCredentialWriteError` rather than re-refused here — one door, one
|
|
99
|
+
owner, one message.
|
|
100
|
+
|
|
101
|
+
**Where it runs, and why that is the whole mechanism:** a `beforeInsert` /
|
|
102
|
+
`beforeUpdate` hook on `sys_webhook`, bound by `WebhookOutboxPlugin` before its
|
|
103
|
+
first seeded write. It has to run *before* the engine's `encryptSecretFields` —
|
|
104
|
+
one step later the plaintext is gone and the column holds an opaque ref, so a
|
|
105
|
+
validator behind it would have nothing left to validate. The suite measures that
|
|
106
|
+
ordering rather than asserting it: every refusal pins that **no `sys_secret`
|
|
107
|
+
cipher row was minted**, which is only true if the gate ran first.
|
|
108
|
+
|
|
109
|
+
A hook rather than checks on the plugin's own write paths
|
|
110
|
+
(`bootstrapDeclaredWebhooks` / `headersPatch` / the migration sweep), because a
|
|
111
|
+
direct `PATCH /api/v1/data/sys_webhook` goes through none of them and that is
|
|
112
|
+
the measured trigger. Those paths inherit the validation through the hook and
|
|
113
|
+
deliberately carry no second check.
|
|
114
|
+
|
|
115
|
+
A general `secret`-channel plaintext validator — letting any `secret`-typed
|
|
116
|
+
field declare its own plaintext shape — is the principled generalization and is
|
|
117
|
+
recorded as the **promotion path**, not built here: it becomes the shape the
|
|
118
|
+
moment a second shaped-plaintext `secret` field exists (maintainer ruling
|
|
119
|
+
2026-08-13; one consumer does not justify a general capability).
|
|
120
|
+
- Updated dependencies [56656aa]
|
|
121
|
+
- Updated dependencies [07e630e]
|
|
122
|
+
- Updated dependencies [2f65b1b]
|
|
123
|
+
- Updated dependencies [720ee95]
|
|
124
|
+
- Updated dependencies [f287435]
|
|
125
|
+
- Updated dependencies [2782805]
|
|
126
|
+
- Updated dependencies [e43d63a]
|
|
127
|
+
- Updated dependencies [9aa8890]
|
|
128
|
+
- Updated dependencies [7c9c1dd]
|
|
129
|
+
- Updated dependencies [75b7c24]
|
|
130
|
+
- Updated dependencies [d5552ca]
|
|
131
|
+
- Updated dependencies [d9813a9]
|
|
132
|
+
- Updated dependencies [8640fb2]
|
|
133
|
+
- Updated dependencies [2420641]
|
|
134
|
+
- Updated dependencies [2ad91c3]
|
|
135
|
+
- Updated dependencies [f57fb38]
|
|
136
|
+
- Updated dependencies [00777a0]
|
|
137
|
+
- Updated dependencies [d491625]
|
|
138
|
+
- Updated dependencies [420804d]
|
|
139
|
+
- Updated dependencies [716ac9b]
|
|
140
|
+
- Updated dependencies [a38408a]
|
|
141
|
+
- Updated dependencies [62b1427]
|
|
142
|
+
- Updated dependencies [7ea1372]
|
|
143
|
+
- Updated dependencies [23abe27]
|
|
144
|
+
- Updated dependencies [985a9cd]
|
|
145
|
+
- Updated dependencies [5f5e234]
|
|
146
|
+
- Updated dependencies [a8189ae]
|
|
147
|
+
- Updated dependencies [26e70fb]
|
|
148
|
+
- Updated dependencies [42b05af]
|
|
149
|
+
- Updated dependencies [2b292ce]
|
|
150
|
+
- Updated dependencies [abcf853]
|
|
151
|
+
- Updated dependencies [8b9eba5]
|
|
152
|
+
- Updated dependencies [d575779]
|
|
153
|
+
- Updated dependencies [94f7ef8]
|
|
154
|
+
- Updated dependencies [c5ac5e4]
|
|
155
|
+
- Updated dependencies [a777944]
|
|
156
|
+
- Updated dependencies [dd88e1c]
|
|
157
|
+
- Updated dependencies [856527c]
|
|
158
|
+
- Updated dependencies [870f710]
|
|
159
|
+
- Updated dependencies [79c46da]
|
|
160
|
+
- Updated dependencies [7ff3975]
|
|
161
|
+
- Updated dependencies [29d055b]
|
|
162
|
+
- Updated dependencies [65589d6]
|
|
163
|
+
- Updated dependencies [2c86fe3]
|
|
164
|
+
- Updated dependencies [e196c6a]
|
|
165
|
+
- Updated dependencies [24173e9]
|
|
166
|
+
- Updated dependencies [4ab7523]
|
|
167
|
+
- Updated dependencies [19539b4]
|
|
168
|
+
- Updated dependencies [f8eb736]
|
|
169
|
+
- Updated dependencies [11b779e]
|
|
170
|
+
- Updated dependencies [739fe5b]
|
|
171
|
+
- Updated dependencies [4bfe1a5]
|
|
172
|
+
- Updated dependencies [2065e31]
|
|
173
|
+
- Updated dependencies [b69d0f5]
|
|
174
|
+
- Updated dependencies [4d47afe]
|
|
175
|
+
- Updated dependencies [e4e5c6e]
|
|
176
|
+
- Updated dependencies [9a56784]
|
|
177
|
+
- Updated dependencies [d00d2f6]
|
|
178
|
+
- Updated dependencies [df0c12d]
|
|
179
|
+
- Updated dependencies [44738f7]
|
|
180
|
+
- Updated dependencies [d31785f]
|
|
181
|
+
- Updated dependencies [c308a4f]
|
|
182
|
+
- Updated dependencies [e2899f6]
|
|
183
|
+
- Updated dependencies [3851f87]
|
|
184
|
+
- Updated dependencies [2a29caa]
|
|
185
|
+
- Updated dependencies [09a6eee]
|
|
186
|
+
- Updated dependencies [1a7f907]
|
|
187
|
+
- Updated dependencies [cd455c8]
|
|
188
|
+
- Updated dependencies [e1bb0ca]
|
|
189
|
+
- Updated dependencies [30d3752]
|
|
190
|
+
- Updated dependencies [c80e7ae]
|
|
191
|
+
- Updated dependencies [09a9a8a]
|
|
192
|
+
- Updated dependencies [07026cf]
|
|
193
|
+
- Updated dependencies [5d4f3d5]
|
|
194
|
+
- Updated dependencies [4d80e8b]
|
|
195
|
+
- Updated dependencies [30b1c63]
|
|
196
|
+
- Updated dependencies [079b457]
|
|
197
|
+
- Updated dependencies [e43b211]
|
|
198
|
+
- Updated dependencies [890b38f]
|
|
199
|
+
- Updated dependencies [8bee54b]
|
|
200
|
+
- Updated dependencies [7a537ce]
|
|
201
|
+
- Updated dependencies [593c4bf]
|
|
202
|
+
- Updated dependencies [ff08691]
|
|
203
|
+
- Updated dependencies [60e0f90]
|
|
204
|
+
- Updated dependencies [90c5285]
|
|
205
|
+
- Updated dependencies [402c125]
|
|
206
|
+
- Updated dependencies [7901b2d]
|
|
207
|
+
- Updated dependencies [56bca91]
|
|
208
|
+
- Updated dependencies [79394d7]
|
|
209
|
+
- Updated dependencies [730fd9a]
|
|
210
|
+
- Updated dependencies [44bc51d]
|
|
211
|
+
- Updated dependencies [73cfddf]
|
|
212
|
+
- Updated dependencies [d634e66]
|
|
213
|
+
- @objectstack/spec@17.1.0
|
|
214
|
+
- @objectstack/core@17.1.0
|
|
215
|
+
- @objectstack/service-messaging@17.1.0
|
|
216
|
+
|
|
3
217
|
## 17.0.0
|
|
4
218
|
|
|
5
219
|
### 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
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/sys-webhook.object.ts"],"sourcesContent":["// 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"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/sys-webhook.object.ts"],"sourcesContent":["// 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"],"mappings":"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","names":[]}
|