@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/dist/index.d.cts
CHANGED
|
@@ -32,7 +32,15 @@ type HttpEnqueueFn = (input: EnqueueHttpInput) => Promise<string>;
|
|
|
32
32
|
*/
|
|
33
33
|
interface OptionalLogger {
|
|
34
34
|
info?(msg: string, meta?: unknown): void;
|
|
35
|
-
|
|
35
|
+
/**
|
|
36
|
+
* The GUARANTEED fallback channel (#9754). `error` stays optional — hosts do
|
|
37
|
+
* inject reduced sinks — so `warn` is where a durability report lands when
|
|
38
|
+
* `error` is absent, and a fallback that may itself be missing is not a
|
|
39
|
+
* fallback. Call sites keep the `logger?.warn?.(…)` spelling as the backstop
|
|
40
|
+
* for hosts the TYPE cannot reach; `SweepLogger` in plugin-email's
|
|
41
|
+
* `outbox-sweep.ts` carries the full reasoning and the measurement.
|
|
42
|
+
*/
|
|
43
|
+
warn(msg: string, meta?: unknown): void;
|
|
36
44
|
debug?(msg: string, meta?: unknown): void;
|
|
37
45
|
error?(msg: string, err?: unknown, meta?: unknown): void;
|
|
38
46
|
}
|
|
@@ -129,7 +137,22 @@ declare class AutoEnqueuer {
|
|
|
129
137
|
private readonly subscriptions;
|
|
130
138
|
private readonly subscriptionsObject;
|
|
131
139
|
private readonly refreshIntervalMs;
|
|
132
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Optional, and deliberately NOT defaulted to `{}` (#10556).
|
|
142
|
+
*
|
|
143
|
+
* `OptionalLogger` guarantees a `warn` channel under #9754, so `{}` stopped being a
|
|
144
|
+
* legal value of the type — which is the gate working: an empty object is a
|
|
145
|
+
* sink that declares it can report and then discards everything. The repair
|
|
146
|
+
* is to say what is TRUE — there may be no logger at all — rather than to
|
|
147
|
+
* mint a sink that lies. Runtime behaviour is unchanged in both directions:
|
|
148
|
+
* absent logger and `{}` both printed nothing before, and print nothing now.
|
|
149
|
+
*
|
|
150
|
+
* ⛔ What this deliberately does NOT decide: whether an absent host sink should
|
|
151
|
+
* instead default to a `console`-backed one. That is the open design call the
|
|
152
|
+
* #9754 ledger records against `plugin-security`'s `= {}` field, and it is a
|
|
153
|
+
* maintainer decision — not something to settle here to make a checker green.
|
|
154
|
+
*/
|
|
155
|
+
private readonly logger?;
|
|
133
156
|
private subId;
|
|
134
157
|
private subIdSelfHeal;
|
|
135
158
|
private refreshTimer;
|
|
@@ -404,6 +427,28 @@ declare class WebhookOutboxPlugin implements Plugin {
|
|
|
404
427
|
private boundEngine;
|
|
405
428
|
constructor(options?: WebhookOutboxPluginOptions);
|
|
406
429
|
init(ctx: PluginContext): Promise<void>;
|
|
430
|
+
/**
|
|
431
|
+
* Teardown — the kernel's ONLY teardown hook.
|
|
432
|
+
*
|
|
433
|
+
* [#10772] This body used to be spelled `dispose()`. `Plugin`
|
|
434
|
+
* (`@objectstack/core`'s `types.ts`) declares `init()`, `start?(ctx)` and
|
|
435
|
+
* `destroy?()` and no `dispose()`, and `ObjectKernel.performShutdown()` /
|
|
436
|
+
* `LiteKernel.destroy()` walk the plugins in reverse calling
|
|
437
|
+
* `plugin.destroy()` — so after `await kernel.shutdown()` had RESOLVED the
|
|
438
|
+
* auto-enqueuer was still running and both engine hooks were still bound.
|
|
439
|
+
* Measured on the same revision: `dispose()` had ZERO callers anywhere in
|
|
440
|
+
* the repo, so this teardown had never run in any process at all.
|
|
441
|
+
*
|
|
442
|
+
* Idempotent: `boundEngine` is cleared as it is unbound, so a second
|
|
443
|
+
* teardown is a no-op rather than a second unbind.
|
|
444
|
+
*/
|
|
445
|
+
destroy(): Promise<void>;
|
|
446
|
+
/**
|
|
447
|
+
* Retained alias for {@link destroy}. Kept because it is public API of an
|
|
448
|
+
* exported class: an embedder may have learned to call it directly
|
|
449
|
+
* precisely BECAUSE the kernel never did, and deleting it would break them.
|
|
450
|
+
* Same signature, same return type — a direct caller sees no change.
|
|
451
|
+
*/
|
|
407
452
|
dispose(): Promise<void>;
|
|
408
453
|
private getMessaging;
|
|
409
454
|
/**
|
|
@@ -436,11 +481,35 @@ declare class WebhookOutboxPlugin implements Plugin {
|
|
|
436
481
|
private tryGetService;
|
|
437
482
|
/**
|
|
438
483
|
* Mount POST /api/v1/webhooks/redeliver on the host Hono app, if one is
|
|
439
|
-
* available. Delegates to `messaging.redeliverHttp(deliveryId)`. Auth is
|
|
440
|
-
* better-auth session cookie — every authenticated user counts.
|
|
484
|
+
* available. Delegates to `messaging.redeliverHttp(deliveryId, …)`. Auth is
|
|
485
|
+
* the better-auth session cookie — every authenticated user counts.
|
|
486
|
+
*
|
|
487
|
+
* [#10740] Which is precisely why the caller's ACTIVE ORGANIZATION is
|
|
488
|
+
* resolved here and threaded into the call. `sys_http_delivery` is
|
|
489
|
+
* tenant-scoped, and this is the one door on it a request can reach: an
|
|
490
|
+
* unscoped replay from here is an authenticated user reaching another
|
|
491
|
+
* organization's delivery row on a walled deployment. With the tenant
|
|
492
|
+
* threaded, a row outside the caller's organization is simply not found.
|
|
493
|
+
*
|
|
494
|
+
* ⚠️ A session with no active organization threads `undefined`, and the
|
|
495
|
+
* driver's tenant-audit line then fires for that write. That is deliberate:
|
|
496
|
+
* the deployment could not tell us who is asking, and reporting the gap is
|
|
497
|
+
* the correct outcome. ⛔ It is never repaired with `bypassTenantAudit`,
|
|
498
|
+
* which would silence the report without closing anything.
|
|
441
499
|
*/
|
|
442
500
|
private registerAdminRoutes;
|
|
443
|
-
|
|
501
|
+
/**
|
|
502
|
+
* [#10740] The better-auth session envelope (`{ user, session }`) for this
|
|
503
|
+
* request, or `undefined`.
|
|
504
|
+
*
|
|
505
|
+
* Widened from the previous `resolveSessionUserId` because the route now
|
|
506
|
+
* needs two facts from ONE lookup: who is asking (`user.id`, the
|
|
507
|
+
* authentication gate) and which organization they are asking as
|
|
508
|
+
* (`session.activeOrganizationId`, the tenant threaded into the write).
|
|
509
|
+
* Resolving them separately would mean two `getSession` calls that can
|
|
510
|
+
* disagree.
|
|
511
|
+
*/
|
|
512
|
+
private resolveSession;
|
|
444
513
|
}
|
|
445
514
|
|
|
446
515
|
/**
|
|
@@ -572,6 +641,71 @@ declare const WEBHOOK_SECRET_FIELD = "signing_secret";
|
|
|
572
641
|
/** Column on `sys_webhook` holding the encrypted custom-header map. */
|
|
573
642
|
declare const WEBHOOK_HEADERS_FIELD = "headers_secret";
|
|
574
643
|
|
|
644
|
+
/**
|
|
645
|
+
* ADR-0112 envelope for this refusal. `VALIDATION_ERROR`/400 is the standard
|
|
646
|
+
* catalog member for "the payload is not acceptable" — the SAME pair #8559's
|
|
647
|
+
* `EmptyCredentialWriteError` carries at the same door for the same class of
|
|
648
|
+
* verdict, so a client branching on `code`/`status` handles both malformed
|
|
649
|
+
* credential writes identically. A standard-catalog code needs no ledger entry.
|
|
650
|
+
*/
|
|
651
|
+
declare const WEBHOOK_HEADERS_SHAPE_REFUSAL_CODE = "VALIDATION_ERROR";
|
|
652
|
+
declare const WEBHOOK_HEADERS_SHAPE_REFUSAL_STATUS = 400;
|
|
653
|
+
/**
|
|
654
|
+
* [#8566] Refusal to persist a `headers_secret` plaintext that is not a flat
|
|
655
|
+
* JSON object of string values.
|
|
656
|
+
*
|
|
657
|
+
* Carries the ADR-0112 pair plus the LOCATION (`object`/`field`) as fields, so
|
|
658
|
+
* a consumer branches on `code`/`status` rather than on message text — the same
|
|
659
|
+
* discipline {@link WebhookHeadersUnresolvableError} follows on the read side
|
|
660
|
+
* of this seam, and `EmptyCredentialWriteError` follows on the write side.
|
|
661
|
+
*/
|
|
662
|
+
declare class WebhookHeadersShapeError extends Error {
|
|
663
|
+
readonly code = "VALIDATION_ERROR";
|
|
664
|
+
readonly status = 400;
|
|
665
|
+
readonly object: string;
|
|
666
|
+
readonly field: string;
|
|
667
|
+
constructor(object: string, field: string, diagnosis: string);
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* The verdict, as a pure function of the write payload — exported so the gate
|
|
671
|
+
* can be reasoned about and tested without booting an engine, and so any future
|
|
672
|
+
* caller uses the same one rule rather than restating it.
|
|
673
|
+
*
|
|
674
|
+
* Mutates nothing and returns nothing: it either passes or throws
|
|
675
|
+
* {@link WebhookHeadersShapeError}.
|
|
676
|
+
*/
|
|
677
|
+
declare function assertWritableWebhookHeaders(data: Record<string, unknown> | null | undefined, object?: string, field?: string): void;
|
|
678
|
+
/** Minimal engine surface this binding needs — mirrors `webhook-provenance.ts`. */
|
|
679
|
+
interface MinimalEngine {
|
|
680
|
+
registerHook(event: string, handler: (ctx: any) => any, options?: Record<string, any>): void;
|
|
681
|
+
unregisterHooksByPackage(packageId: string): number;
|
|
682
|
+
}
|
|
683
|
+
interface MinimalLogger {
|
|
684
|
+
info?: (msg: string, meta?: Record<string, any>) => void;
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Bind the shape gate to both write events on `sys_webhook`.
|
|
688
|
+
*
|
|
689
|
+
* ## Deliberately NOT exempt for `isSystem`
|
|
690
|
+
* The provenance stamp next door skips system writes because it is detecting an
|
|
691
|
+
* ADMIN edit; this is a validity verdict on a payload, and a malformed header
|
|
692
|
+
* map is exactly as unusable when a seeder writes it. Ruling item 2 says the
|
|
693
|
+
* plugin's own write paths inherit this validation through the hook, which is
|
|
694
|
+
* only true if system writes are covered. They pass by construction —
|
|
695
|
+
* `bootstrapDeclaredWebhooks` and the migration sweep both write
|
|
696
|
+
* `serializeHeaders(...)` of an already `isHeaderMap`-filtered map — so
|
|
697
|
+
* covering them costs nothing and closes the door for a future write path that
|
|
698
|
+
* is less careful.
|
|
699
|
+
*
|
|
700
|
+
* Registered in CODE rather than from metadata, which also means
|
|
701
|
+
* `session.skipAutomations` (an import run with automations unchecked) cannot
|
|
702
|
+
* suppress it: the engine only skips metadata-bound entries. A validation door
|
|
703
|
+
* that an import could switch off would not be a door.
|
|
704
|
+
*/
|
|
705
|
+
declare function bindWebhookHeadersShapeGate(engine: MinimalEngine, logger?: MinimalLogger): void;
|
|
706
|
+
/** Remove the gate — mirrors `unbindWebhookProvenanceStamp`, for `dispose()`. */
|
|
707
|
+
declare function unbindWebhookHeadersShapeGate(engine: MinimalEngine): void;
|
|
708
|
+
|
|
575
709
|
/**
|
|
576
710
|
* [#7799] One-shot boot sweep that moves already-persisted cleartext signing
|
|
577
711
|
* secrets out of `sys_webhook.definition_json` and into the encrypted
|
|
@@ -625,4 +759,4 @@ interface MigrateWebhookSecretsResult {
|
|
|
625
759
|
*/
|
|
626
760
|
declare function migrateLegacyWebhookSecrets(engine: IDataEngine, logger?: Logger, subscriptionsObject?: string): Promise<MigrateWebhookSecretsResult>;
|
|
627
761
|
|
|
628
|
-
export { AutoEnqueuer, type AutoEnqueuerOptions, type HttpEnqueueFn, type MigrateWebhookSecretsResult, WEBHOOK_HEADERS_FIELD, WEBHOOK_SECRET_FIELD, WebhookOutboxPlugin, type WebhookOutboxPluginOptions, migrateLegacyWebhookSecrets };
|
|
762
|
+
export { AutoEnqueuer, type AutoEnqueuerOptions, type HttpEnqueueFn, type MigrateWebhookSecretsResult, WEBHOOK_HEADERS_FIELD, WEBHOOK_HEADERS_SHAPE_REFUSAL_CODE, WEBHOOK_HEADERS_SHAPE_REFUSAL_STATUS, WEBHOOK_SECRET_FIELD, WebhookHeadersShapeError, WebhookOutboxPlugin, type WebhookOutboxPluginOptions, assertWritableWebhookHeaders, bindWebhookHeadersShapeGate, migrateLegacyWebhookSecrets, unbindWebhookHeadersShapeGate };
|
package/dist/index.d.ts
CHANGED
|
@@ -32,7 +32,15 @@ type HttpEnqueueFn = (input: EnqueueHttpInput) => Promise<string>;
|
|
|
32
32
|
*/
|
|
33
33
|
interface OptionalLogger {
|
|
34
34
|
info?(msg: string, meta?: unknown): void;
|
|
35
|
-
|
|
35
|
+
/**
|
|
36
|
+
* The GUARANTEED fallback channel (#9754). `error` stays optional — hosts do
|
|
37
|
+
* inject reduced sinks — so `warn` is where a durability report lands when
|
|
38
|
+
* `error` is absent, and a fallback that may itself be missing is not a
|
|
39
|
+
* fallback. Call sites keep the `logger?.warn?.(…)` spelling as the backstop
|
|
40
|
+
* for hosts the TYPE cannot reach; `SweepLogger` in plugin-email's
|
|
41
|
+
* `outbox-sweep.ts` carries the full reasoning and the measurement.
|
|
42
|
+
*/
|
|
43
|
+
warn(msg: string, meta?: unknown): void;
|
|
36
44
|
debug?(msg: string, meta?: unknown): void;
|
|
37
45
|
error?(msg: string, err?: unknown, meta?: unknown): void;
|
|
38
46
|
}
|
|
@@ -129,7 +137,22 @@ declare class AutoEnqueuer {
|
|
|
129
137
|
private readonly subscriptions;
|
|
130
138
|
private readonly subscriptionsObject;
|
|
131
139
|
private readonly refreshIntervalMs;
|
|
132
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Optional, and deliberately NOT defaulted to `{}` (#10556).
|
|
142
|
+
*
|
|
143
|
+
* `OptionalLogger` guarantees a `warn` channel under #9754, so `{}` stopped being a
|
|
144
|
+
* legal value of the type — which is the gate working: an empty object is a
|
|
145
|
+
* sink that declares it can report and then discards everything. The repair
|
|
146
|
+
* is to say what is TRUE — there may be no logger at all — rather than to
|
|
147
|
+
* mint a sink that lies. Runtime behaviour is unchanged in both directions:
|
|
148
|
+
* absent logger and `{}` both printed nothing before, and print nothing now.
|
|
149
|
+
*
|
|
150
|
+
* ⛔ What this deliberately does NOT decide: whether an absent host sink should
|
|
151
|
+
* instead default to a `console`-backed one. That is the open design call the
|
|
152
|
+
* #9754 ledger records against `plugin-security`'s `= {}` field, and it is a
|
|
153
|
+
* maintainer decision — not something to settle here to make a checker green.
|
|
154
|
+
*/
|
|
155
|
+
private readonly logger?;
|
|
133
156
|
private subId;
|
|
134
157
|
private subIdSelfHeal;
|
|
135
158
|
private refreshTimer;
|
|
@@ -404,6 +427,28 @@ declare class WebhookOutboxPlugin implements Plugin {
|
|
|
404
427
|
private boundEngine;
|
|
405
428
|
constructor(options?: WebhookOutboxPluginOptions);
|
|
406
429
|
init(ctx: PluginContext): Promise<void>;
|
|
430
|
+
/**
|
|
431
|
+
* Teardown — the kernel's ONLY teardown hook.
|
|
432
|
+
*
|
|
433
|
+
* [#10772] This body used to be spelled `dispose()`. `Plugin`
|
|
434
|
+
* (`@objectstack/core`'s `types.ts`) declares `init()`, `start?(ctx)` and
|
|
435
|
+
* `destroy?()` and no `dispose()`, and `ObjectKernel.performShutdown()` /
|
|
436
|
+
* `LiteKernel.destroy()` walk the plugins in reverse calling
|
|
437
|
+
* `plugin.destroy()` — so after `await kernel.shutdown()` had RESOLVED the
|
|
438
|
+
* auto-enqueuer was still running and both engine hooks were still bound.
|
|
439
|
+
* Measured on the same revision: `dispose()` had ZERO callers anywhere in
|
|
440
|
+
* the repo, so this teardown had never run in any process at all.
|
|
441
|
+
*
|
|
442
|
+
* Idempotent: `boundEngine` is cleared as it is unbound, so a second
|
|
443
|
+
* teardown is a no-op rather than a second unbind.
|
|
444
|
+
*/
|
|
445
|
+
destroy(): Promise<void>;
|
|
446
|
+
/**
|
|
447
|
+
* Retained alias for {@link destroy}. Kept because it is public API of an
|
|
448
|
+
* exported class: an embedder may have learned to call it directly
|
|
449
|
+
* precisely BECAUSE the kernel never did, and deleting it would break them.
|
|
450
|
+
* Same signature, same return type — a direct caller sees no change.
|
|
451
|
+
*/
|
|
407
452
|
dispose(): Promise<void>;
|
|
408
453
|
private getMessaging;
|
|
409
454
|
/**
|
|
@@ -436,11 +481,35 @@ declare class WebhookOutboxPlugin implements Plugin {
|
|
|
436
481
|
private tryGetService;
|
|
437
482
|
/**
|
|
438
483
|
* Mount POST /api/v1/webhooks/redeliver on the host Hono app, if one is
|
|
439
|
-
* available. Delegates to `messaging.redeliverHttp(deliveryId)`. Auth is
|
|
440
|
-
* better-auth session cookie — every authenticated user counts.
|
|
484
|
+
* available. Delegates to `messaging.redeliverHttp(deliveryId, …)`. Auth is
|
|
485
|
+
* the better-auth session cookie — every authenticated user counts.
|
|
486
|
+
*
|
|
487
|
+
* [#10740] Which is precisely why the caller's ACTIVE ORGANIZATION is
|
|
488
|
+
* resolved here and threaded into the call. `sys_http_delivery` is
|
|
489
|
+
* tenant-scoped, and this is the one door on it a request can reach: an
|
|
490
|
+
* unscoped replay from here is an authenticated user reaching another
|
|
491
|
+
* organization's delivery row on a walled deployment. With the tenant
|
|
492
|
+
* threaded, a row outside the caller's organization is simply not found.
|
|
493
|
+
*
|
|
494
|
+
* ⚠️ A session with no active organization threads `undefined`, and the
|
|
495
|
+
* driver's tenant-audit line then fires for that write. That is deliberate:
|
|
496
|
+
* the deployment could not tell us who is asking, and reporting the gap is
|
|
497
|
+
* the correct outcome. ⛔ It is never repaired with `bypassTenantAudit`,
|
|
498
|
+
* which would silence the report without closing anything.
|
|
441
499
|
*/
|
|
442
500
|
private registerAdminRoutes;
|
|
443
|
-
|
|
501
|
+
/**
|
|
502
|
+
* [#10740] The better-auth session envelope (`{ user, session }`) for this
|
|
503
|
+
* request, or `undefined`.
|
|
504
|
+
*
|
|
505
|
+
* Widened from the previous `resolveSessionUserId` because the route now
|
|
506
|
+
* needs two facts from ONE lookup: who is asking (`user.id`, the
|
|
507
|
+
* authentication gate) and which organization they are asking as
|
|
508
|
+
* (`session.activeOrganizationId`, the tenant threaded into the write).
|
|
509
|
+
* Resolving them separately would mean two `getSession` calls that can
|
|
510
|
+
* disagree.
|
|
511
|
+
*/
|
|
512
|
+
private resolveSession;
|
|
444
513
|
}
|
|
445
514
|
|
|
446
515
|
/**
|
|
@@ -572,6 +641,71 @@ declare const WEBHOOK_SECRET_FIELD = "signing_secret";
|
|
|
572
641
|
/** Column on `sys_webhook` holding the encrypted custom-header map. */
|
|
573
642
|
declare const WEBHOOK_HEADERS_FIELD = "headers_secret";
|
|
574
643
|
|
|
644
|
+
/**
|
|
645
|
+
* ADR-0112 envelope for this refusal. `VALIDATION_ERROR`/400 is the standard
|
|
646
|
+
* catalog member for "the payload is not acceptable" — the SAME pair #8559's
|
|
647
|
+
* `EmptyCredentialWriteError` carries at the same door for the same class of
|
|
648
|
+
* verdict, so a client branching on `code`/`status` handles both malformed
|
|
649
|
+
* credential writes identically. A standard-catalog code needs no ledger entry.
|
|
650
|
+
*/
|
|
651
|
+
declare const WEBHOOK_HEADERS_SHAPE_REFUSAL_CODE = "VALIDATION_ERROR";
|
|
652
|
+
declare const WEBHOOK_HEADERS_SHAPE_REFUSAL_STATUS = 400;
|
|
653
|
+
/**
|
|
654
|
+
* [#8566] Refusal to persist a `headers_secret` plaintext that is not a flat
|
|
655
|
+
* JSON object of string values.
|
|
656
|
+
*
|
|
657
|
+
* Carries the ADR-0112 pair plus the LOCATION (`object`/`field`) as fields, so
|
|
658
|
+
* a consumer branches on `code`/`status` rather than on message text — the same
|
|
659
|
+
* discipline {@link WebhookHeadersUnresolvableError} follows on the read side
|
|
660
|
+
* of this seam, and `EmptyCredentialWriteError` follows on the write side.
|
|
661
|
+
*/
|
|
662
|
+
declare class WebhookHeadersShapeError extends Error {
|
|
663
|
+
readonly code = "VALIDATION_ERROR";
|
|
664
|
+
readonly status = 400;
|
|
665
|
+
readonly object: string;
|
|
666
|
+
readonly field: string;
|
|
667
|
+
constructor(object: string, field: string, diagnosis: string);
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* The verdict, as a pure function of the write payload — exported so the gate
|
|
671
|
+
* can be reasoned about and tested without booting an engine, and so any future
|
|
672
|
+
* caller uses the same one rule rather than restating it.
|
|
673
|
+
*
|
|
674
|
+
* Mutates nothing and returns nothing: it either passes or throws
|
|
675
|
+
* {@link WebhookHeadersShapeError}.
|
|
676
|
+
*/
|
|
677
|
+
declare function assertWritableWebhookHeaders(data: Record<string, unknown> | null | undefined, object?: string, field?: string): void;
|
|
678
|
+
/** Minimal engine surface this binding needs — mirrors `webhook-provenance.ts`. */
|
|
679
|
+
interface MinimalEngine {
|
|
680
|
+
registerHook(event: string, handler: (ctx: any) => any, options?: Record<string, any>): void;
|
|
681
|
+
unregisterHooksByPackage(packageId: string): number;
|
|
682
|
+
}
|
|
683
|
+
interface MinimalLogger {
|
|
684
|
+
info?: (msg: string, meta?: Record<string, any>) => void;
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Bind the shape gate to both write events on `sys_webhook`.
|
|
688
|
+
*
|
|
689
|
+
* ## Deliberately NOT exempt for `isSystem`
|
|
690
|
+
* The provenance stamp next door skips system writes because it is detecting an
|
|
691
|
+
* ADMIN edit; this is a validity verdict on a payload, and a malformed header
|
|
692
|
+
* map is exactly as unusable when a seeder writes it. Ruling item 2 says the
|
|
693
|
+
* plugin's own write paths inherit this validation through the hook, which is
|
|
694
|
+
* only true if system writes are covered. They pass by construction —
|
|
695
|
+
* `bootstrapDeclaredWebhooks` and the migration sweep both write
|
|
696
|
+
* `serializeHeaders(...)` of an already `isHeaderMap`-filtered map — so
|
|
697
|
+
* covering them costs nothing and closes the door for a future write path that
|
|
698
|
+
* is less careful.
|
|
699
|
+
*
|
|
700
|
+
* Registered in CODE rather than from metadata, which also means
|
|
701
|
+
* `session.skipAutomations` (an import run with automations unchecked) cannot
|
|
702
|
+
* suppress it: the engine only skips metadata-bound entries. A validation door
|
|
703
|
+
* that an import could switch off would not be a door.
|
|
704
|
+
*/
|
|
705
|
+
declare function bindWebhookHeadersShapeGate(engine: MinimalEngine, logger?: MinimalLogger): void;
|
|
706
|
+
/** Remove the gate — mirrors `unbindWebhookProvenanceStamp`, for `dispose()`. */
|
|
707
|
+
declare function unbindWebhookHeadersShapeGate(engine: MinimalEngine): void;
|
|
708
|
+
|
|
575
709
|
/**
|
|
576
710
|
* [#7799] One-shot boot sweep that moves already-persisted cleartext signing
|
|
577
711
|
* secrets out of `sys_webhook.definition_json` and into the encrypted
|
|
@@ -625,4 +759,4 @@ interface MigrateWebhookSecretsResult {
|
|
|
625
759
|
*/
|
|
626
760
|
declare function migrateLegacyWebhookSecrets(engine: IDataEngine, logger?: Logger, subscriptionsObject?: string): Promise<MigrateWebhookSecretsResult>;
|
|
627
761
|
|
|
628
|
-
export { AutoEnqueuer, type AutoEnqueuerOptions, type HttpEnqueueFn, type MigrateWebhookSecretsResult, WEBHOOK_HEADERS_FIELD, WEBHOOK_SECRET_FIELD, WebhookOutboxPlugin, type WebhookOutboxPluginOptions, migrateLegacyWebhookSecrets };
|
|
762
|
+
export { AutoEnqueuer, type AutoEnqueuerOptions, type HttpEnqueueFn, type MigrateWebhookSecretsResult, WEBHOOK_HEADERS_FIELD, WEBHOOK_HEADERS_SHAPE_REFUSAL_CODE, WEBHOOK_HEADERS_SHAPE_REFUSAL_STATUS, WEBHOOK_SECRET_FIELD, WebhookHeadersShapeError, WebhookOutboxPlugin, type WebhookOutboxPluginOptions, assertWritableWebhookHeaders, bindWebhookHeadersShapeGate, migrateLegacyWebhookSecrets, unbindWebhookHeadersShapeGate };
|