@fabricorg/webhook-delivery 0.1.2 → 0.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/dist/index.d.cts CHANGED
@@ -1,30 +1,213 @@
1
- import { W as WebhookConfigErrorCode, a as WebhookDeliveryErrorCode, b as WebhookFanOutErrorCode, c as WebhookGovernedActionErrorCode, d as WebhookProjectionErrorCode, e as WebhookStoreErrorCode, f as WebhookDeliveryStore, g as WebhookClock, h as WebhookFanOutJobDraft, i as WebhookCallOptions, j as WebhookInsertResult, k as WebhookClaimRequest, l as WebhookFanOutJob, m as WebhookFencedWrite, n as WebhookDiagnosticCode, o as WebhookScope, p as WebhookRequeueRequest, q as WebhookRequeueResult, r as WebhookActorEnrichmentRequest, s as WebhookActorEnrichmentResult, t as WebhookDeliveryIntent, u as WebhookFanOutJobFence, v as WebhookFencedInsertResult, w as WebhookAdmissionDenialRecord, x as WebhookDeliveryLeaseCheck, y as WebhookDeliveryAttemptRecord, z as WebhookDeliveryAttemptStart, A as WebhookAttemptBeginResult, B as WebhookDeliveryAttemptFinish, C as WebhookDeliveryAttemptLogEntry, D as WebhookRequeueEvidence, E as WebhookTombstone, F as WebhookPurgeResult, G as WebhookSubscriptionStore, H as WebhookSubscriptionWriter, I as WebhookEligibilityRevision, J as WebhookSubscriptionSnapshot, K as WebhookDeliveryConfigurationInput, L as WebhookSubscriptionWriteResult, M as WebhookSubscriptionTransitionInput, N as WebhookConfigurationWriteResult, O as WebhookRandomSource, P as WebhookRetryDelay, Q as WebhookAttemptOutcome, R as WebhookAttemptReasonCode, S as WebhookTransportResult, T as WebhookPayloadPolicy, U as WebhookJobActor, V as WebhookEventMetadata, X as WebhookSourceEnvelope, Y as WebhookJsonObject, Z as WebhookScopePredicate, _ as WebhookDeliveryBodyV1, $ as WebhookAdmissionDecision, a0 as WebhookDeliveryAuthorizationDecision, a1 as WebhookAdmissionDenialCode, a2 as WebhookEventAdmission, a3 as WebhookDeliveryAuthorization, a4 as WebhookSigner, a5 as WebhookDestinationResolver, a6 as WebhookTransport } from './destination-joCBtgvY.cjs';
2
- export { a7 as DEFAULT_WEBHOOK_LOOKUP_TIMEOUT_MS, a8 as WEBHOOK_ACTION_REFUSAL_CODES, a9 as WEBHOOK_ADMISSION_DENIAL_CODES, aa as WEBHOOK_ATTEMPT_OUTCOMES, ab as WEBHOOK_ATTEMPT_REASON_CODES, ac as WEBHOOK_CONFIG_ERROR_CODES, ad as WEBHOOK_DELIVERY_ERROR_CODES, ae as WEBHOOK_DESTINATION_MAX_ADDRESSES, af as WEBHOOK_DESTINATION_MAX_URL_LENGTH, ag as WEBHOOK_DIAGNOSTIC_CODES, ah as WEBHOOK_FAN_OUT_ERROR_CODES, ai as WEBHOOK_GOVERNED_ACTION_ERROR_CODES, aj as WEBHOOK_INPUT_ERROR_CODES, ak as WEBHOOK_PROJECTION_ERROR_CODES, al as WEBHOOK_REVOCATION_CODES, am as WEBHOOK_STORE_ERROR_CODES, an as WebhookActionRefusalCode, ao as WebhookAdmissionInput, ap as WebhookDeliveryAttemptInFlight, aq as WebhookDeliveryAuthorizationInput, ar as WebhookDeliveryConfiguration, as as WebhookDeliveryIntentStatus, at as WebhookDestinationResolution, au as WebhookDestinationResolverOptions, av as WebhookDestinationUrlCheck, aw as WebhookDnsLookup, ax as WebhookEventTypePolicy, ay as WebhookFanOutJobStatus, az as WebhookInputErrorCode, aA as WebhookJsonValue, aB as WebhookLease, aC as WebhookResolvedAddress, aD as WebhookRevocationCode, aE as WebhookScopeInput, aF as WebhookSignRequest, aG as WebhookSignatureScheme, aH as WebhookSubscriptionStatus, aI as WebhookSubscriptionWriteRefusal, aJ as WebhookTransportRequest, aK as WebhookVettedDestination, aL as checkWebhookDestinationUrl, aM as createWebhookDestinationResolver } from './destination-joCBtgvY.cjs';
3
- import { EnterpriseEventPublisher } from '@fabricorg/platform-host';
4
- import { FabricModule } from '@fabricorg/platform';
5
-
1
+ import { EnterpriseEventEnvelope, EnterpriseEventPublisher, EventPayloadClassification } from "@fabricorg/platform-host";
2
+ import { FabricModule } from "@fabricorg/platform";
3
+ //#region src/codes.d.ts
4
+ /**
5
+ * Fixed code sets. Stored diagnostics, denial reasons and attempt outcomes are
6
+ * typed as one of these codes rather than a free string or a port's error
7
+ * message (ADR 0018 decision 2), so a thrown error has no field through which
8
+ * to carry payload, secret or response-body text into a durable record. The
9
+ * arrays are frozen, so no caller can widen a set at runtime.
10
+ */
11
+ /** Reasons an admission port may give for `deny`. */
12
+ export declare const WEBHOOK_ADMISSION_DENIAL_CODES: readonly ["actor_not_permitted", "scope_not_permitted", "event_type_not_permitted", "classification_not_permitted", "policy_denied", "event_erased"];
13
+ /** A reason code carried by an admission `deny` decision and stored on its denial record. */
14
+ type WebhookAdmissionDenialCode = (typeof WEBHOOK_ADMISSION_DENIAL_CODES)[number];
15
+ /** Reasons a delivery authorization port may give for `revoked`. */
16
+ export declare const WEBHOOK_REVOCATION_CODES: readonly ["grant_expired", "scope_revoked", "registration_inactive"];
17
+ /** A reason code carried by a delivery authorization `revoked` decision. */
18
+ type WebhookRevocationCode = (typeof WEBHOOK_REVOCATION_CODES)[number];
19
+ /**
20
+ * Diagnostics stored on a fan-out job or delivery intent when a port failed,
21
+ * timed out or a coordination check refused the work. A port exception is
22
+ * stored as the code for that port, not as its message.
23
+ *
24
+ * Three are written only by the fan-out cycle: `eligibility_unsettled` when
25
+ * it defers a job until its event's eligibility is settled (README,
26
+ * "Fan-out"), `intent_build_failed` when it could not render a body or
27
+ * create a delivery id for an admitted subscription, and `job_unreadable`
28
+ * when it dead-letters a claimed job whose identity and lease it could read
29
+ * but whose body it could not.
30
+ *
31
+ * Three are written only by the delivery cycle (README, "Delivery"):
32
+ * `intent_unreadable` when it dead-letters a claimed intent whose identity
33
+ * and lease it could read but whose body it could not,
34
+ * `subscription_not_in_scope` when the intent's subscription does not exist
35
+ * in the intent's tenant and space, or the store's answer names another
36
+ * one, and `transport_not_sent` when the transport returned `not_sent` for
37
+ * a request it never started.
38
+ */
39
+ export declare const WEBHOOK_DIAGNOSTIC_CODES: readonly ["admission_unavailable", "authorization_unavailable", "subscription_store_unavailable", "delivery_store_unavailable", "signer_unavailable", "resolver_unavailable", "port_timeout", "scope_predicate_failed", "configuration_conflict", "lease_lost", "intent_not_pending", "processing_deadline_exceeded", "eligibility_unsettled", "intent_build_failed", "job_unreadable", "intent_unreadable", "subscription_not_in_scope", "transport_not_sent"];
40
+ /** A stored diagnostic code for a port failure or refused coordination check. */
41
+ type WebhookDiagnosticCode = (typeof WEBHOOK_DIAGNOSTIC_CODES)[number];
42
+ /**
43
+ * What a delivery attempt's observation means for its intent (ADR 0018
44
+ * decision 10). `not_sent` is never a classification: the delivery cycle
45
+ * records it, with the reason `not_sent`, on an attempt it began under the
46
+ * intent's lease and handed to the transport, when the transport answered
47
+ * that it never started the request (its deadline had passed or its signal
48
+ * had aborted while the request was queued). Such an attempt spends no budget.
49
+ */
50
+ export declare const WEBHOOK_ATTEMPT_OUTCOMES: readonly ["delivered", "retryable", "dead_letter", "cancelled", "not_sent"];
51
+ /**
52
+ * The classified result of one attempt. A 401 or 403 is `retryable` like any
53
+ * other row of that kind; its `http_401` / `http_403` reason and the
54
+ * classification's `receiverActionLikely` flag let the caller surface it to
55
+ * operators before the retry budget is exhausted.
56
+ */
57
+ type WebhookAttemptOutcome = (typeof WEBHOOK_ATTEMPT_OUTCOMES)[number];
58
+ /** The row of decision 10's table an observation matched. */
59
+ export declare const WEBHOOK_ATTEMPT_REASON_CODES: readonly ["http_2xx", "network_error", "timeout", "dns_failure", "http_408", "http_425", "http_429", "http_5xx", "signer_unavailable", "http_401", "http_403", "http_1xx", "http_3xx", "http_410", "http_4xx", "invalid_url", "private_address", "authorization_revoked", "subscription_paused", "subscription_deleted", "not_sent"];
60
+ /** The reason code recorded with an attempt outcome. */
61
+ type WebhookAttemptReasonCode = (typeof WEBHOOK_ATTEMPT_REASON_CODES)[number];
62
+ /** Codes on {@link WebhookDeliveryConfigError}. */
63
+ export declare const WEBHOOK_CONFIG_ERROR_CODES: readonly ["limit_not_positive_finite", "limit_not_integer", "limit_out_of_range", "retry_schedule_empty", "limit_unreadable"];
64
+ /** Why a configured limit was rejected. */
65
+ type WebhookConfigErrorCode = (typeof WEBHOOK_CONFIG_ERROR_CODES)[number];
66
+ /** Codes on {@link WebhookProjectionError}. */
67
+ export declare const WEBHOOK_PROJECTION_ERROR_CODES: readonly ["invalid_envelope", "payload_field_not_json", "fact_field_not_json", "accessor_property", "inspection_failed", "scope_mismatch"];
68
+ /** Why an envelope could not be projected. */
69
+ type WebhookProjectionErrorCode = (typeof WEBHOOK_PROJECTION_ERROR_CODES)[number];
70
+ /**
71
+ * Codes on the `TypeError` and `RangeError` instances this package throws
72
+ * for malformed call input (headers, classification, retry, admission).
73
+ * Each code has one constant message; neither repeats the input.
74
+ */
75
+ export declare const WEBHOOK_INPUT_ERROR_CODES: readonly ["input_unreadable", "observation_unknown", "status_out_of_range", "signature_malformed", "timestamp_invalid", "event_id_invalid", "delivery_id_invalid", "attempt_invalid", "raw_body_not_string", "random_source_missing", "random_sample_invalid", "random_source_failed", "denial_code_unknown"];
76
+ /** Why call input was refused. */
77
+ type WebhookInputErrorCode = (typeof WEBHOOK_INPUT_ERROR_CODES)[number];
78
+ /**
79
+ * Codes on {@link WebhookStoreError}: a store call that was malformed, broke
80
+ * a store invariant, or was no longer wanted when the store reached its
81
+ * write. Refusals the caller is expected to handle (a lost lease, an
82
+ * existing key, a replay past the horizon, a requeue of a record that is not
83
+ * dead-lettered) are results, not errors.
84
+ */
85
+ export declare const WEBHOOK_STORE_ERROR_CODES: readonly ["input_invalid", "record_mismatch", "delivery_id_conflict", "call_aborted", "deadline_passed", "clock_invalid", "store_failed"];
86
+ /** Why a store call was refused. */
87
+ type WebhookStoreErrorCode = (typeof WEBHOOK_STORE_ERROR_CODES)[number];
88
+ /**
89
+ * Codes on {@link WebhookFanOutError}: why the fan-out publisher, the
90
+ * composite publisher or the fan-out cycle refused or failed. A publisher
91
+ * rejection makes the Host relay retry and, once its budget is spent,
92
+ * dead-letter the outbox record, so a failed publish stays visible there.
93
+ *
94
+ * - `event_past_replay_horizon`: `insertFanOutJob` answered `past_horizon`.
95
+ * The event is older than the replay horizon (R5), so no job can be stored
96
+ * and the publish fails rather than counting as delivered.
97
+ * - `fan_out_store_failed` / `fan_out_store_timeout`: the publisher's job
98
+ * insert or the cycle's claim threw, or did not settle within
99
+ * `portTimeoutMs`. Store failures later in a cycle reschedule the job
100
+ * with a diagnostic instead.
101
+ * - `publish_aborted`: the caller's signal aborted before the store answered.
102
+ * - `sink_failed`: a required sink of a composite publisher rejected with an
103
+ * error this package did not create.
104
+ * - `sink_timeout`: a required sink of a composite publisher did not settle
105
+ * within the composite's `sinkTimeoutMs`.
106
+ * - `clock_invalid`: the injected clock threw or returned an invalid time.
107
+ * - `options_invalid`: an option is missing or has the wrong shape.
108
+ */
109
+ export declare const WEBHOOK_FAN_OUT_ERROR_CODES: readonly ["event_past_replay_horizon", "fan_out_store_failed", "fan_out_store_timeout", "publish_aborted", "sink_failed", "sink_timeout", "clock_invalid", "options_invalid"];
110
+ /** Why a fan-out publish or cycle failed. */
111
+ type WebhookFanOutErrorCode = (typeof WEBHOOK_FAN_OUT_ERROR_CODES)[number];
112
+ /**
113
+ * Codes on {@link WebhookDeliveryError}: why the delivery cycle could not
114
+ * run. Everything that goes wrong while one claimed intent is processed is
115
+ * settled on that intent with a fixed code instead (README, "Delivery").
116
+ *
117
+ * - `delivery_store_failed` / `delivery_store_timeout`: the claim threw or
118
+ * answered something unreadable, or did not settle within `portTimeoutMs`.
119
+ * - `clock_invalid`: the injected clock threw or returned an invalid time.
120
+ * - `options_invalid`: an option or port is missing or has the wrong shape.
121
+ */
122
+ export declare const WEBHOOK_DELIVERY_ERROR_CODES: readonly ["delivery_store_failed", "delivery_store_timeout", "clock_invalid", "options_invalid"];
123
+ /** Why a delivery cycle failed. */
124
+ type WebhookDeliveryErrorCode = (typeof WEBHOOK_DELIVERY_ERROR_CODES)[number];
125
+ /**
126
+ * Why a governed action (PR 9; README, "Governed actions") refused. At
127
+ * submission, the action's admission check refuses with
128
+ * `webhook_action_refused:<code>` (or `webhook_parameters_refused:<code>:<field>`
129
+ * with a code from `WEBHOOK_ACTION_PARAMETER_CODES`) and Host records nothing.
130
+ * At execution, the action fails with Host's `error` set to
131
+ * `webhook_action_refused:<code>`, writes nothing more and emits no event.
132
+ *
133
+ * - `admission_not_enforced`: the handler ran without Host's record that the
134
+ * submission was admitted (`ActionContext.admittedAt`), as under a Host
135
+ * older than 7.3.0, which ignores `admitSubmission`. Nothing is written.
136
+ * - `scope_invalid` / `actor_invalid`: Host's trusted context names a tenant,
137
+ * space or actor that breaks the store identifier rule.
138
+ * - `secret_ref_invalid`: the application's secret store says the reference
139
+ * names no secret of this tenant. `secret_ref_unverified`: the check threw,
140
+ * timed out or answered something unreadable.
141
+ * - `secret_ref_unchanged`: a rotation named the reference already in use.
142
+ * - `destination_refused`: the resolver refused the URL (`invalid_url`,
143
+ * `private_address`). `destination_unresolved`: it answered `dns_failure`,
144
+ * threw or timed out. Neither writes anything.
145
+ * - `subscription_not_found`, `subscription_exists`, `subscription_deleted`,
146
+ * `invalid_transition`, `revision_conflict`, `version_conflict`: the
147
+ * subscription store refused the write, or the action's own read found
148
+ * the same. `subscription_not_active`: an intent requeue for a paused or
149
+ * deleted subscription. `paused_since_intent`: an intent requeue for an
150
+ * active subscription that was paused and resumed after the intent's
151
+ * eligibility revision; dispatch would cancel it (decision 3).
152
+ * - `not_found`, `stale`, `not_dead_letter`, `past_replay_horizon`: the
153
+ * delivery store refused a requeue, enrichment or replay range (decision 10, R5).
154
+ * - `not_enrichable`, `actor_present`, `actor_suppressed`: enrichment of a
155
+ * completed job, or of a job whose actor is not `legacy` (decision 5).
156
+ * `actor_withheld`, `actor_not_found`, `actor_unavailable`: the
157
+ * application's actor reader withheld the actor, found no such event, or
158
+ * threw, timed out or answered something unreadable.
159
+ * - `store_unavailable`: a read threw, timed out or answered something
160
+ * unreadable; nothing was written. `store_outcome_unknown`: a write threw,
161
+ * timed out or answered something unreadable, so it may have applied
162
+ * (README, "A store write that timed out is unknown"); also a re-run of an
163
+ * intent requeue whose evidence predates `subscriptionId` and whose intent
164
+ * can no longer be read, so the applied requeue has no event (README,
165
+ * "Atomicity and re-runs").
166
+ * - `replay_not_supported`: replay was not explicitly enabled; operator
167
+ * authorization is not asked. `invocation_required`: replay lacks a trusted
168
+ * action invocation ID to key its immutable acceptance receipt.
169
+ * - `replay_range_invalid`: the historical range is empty or reversed.
170
+ * `replay_range_in_future`: its exclusive upper bound exceeds the store clock.
171
+ * - `replay_limit_exceeded`: fixed membership exceeds the configured cap;
172
+ * no receipt or partial work is accepted. `replay_request_conflict`: an
173
+ * invocation ID was reused with different action parameters or action ID.
174
+ * - `action_failed`: anything else, such as a failing clock; nothing it
175
+ * threw is read.
176
+ */
177
+ export declare const WEBHOOK_ACTION_REFUSAL_CODES: readonly ["replay_not_supported", "invocation_required", "replay_range_invalid", "replay_range_in_future", "replay_limit_exceeded", "replay_request_conflict", "admission_not_enforced", "scope_invalid", "actor_invalid", "secret_ref_invalid", "secret_ref_unverified", "secret_ref_unchanged", "destination_refused", "destination_unresolved", "subscription_not_found", "subscription_exists", "subscription_deleted", "subscription_not_active", "paused_since_intent", "invalid_transition", "revision_conflict", "version_conflict", "not_found", "stale", "not_dead_letter", "past_replay_horizon", "not_enrichable", "actor_present", "actor_suppressed", "actor_withheld", "actor_not_found", "actor_unavailable", "store_unavailable", "store_outcome_unknown", "action_failed"];
178
+ /** Why a governed action refused. */
179
+ type WebhookActionRefusalCode = (typeof WEBHOOK_ACTION_REFUSAL_CODES)[number];
180
+ /**
181
+ * Codes on {@link WebhookGovernedActionError}: `options_invalid`, a module
182
+ * option or port is missing or has the wrong shape.
183
+ */
184
+ export declare const WEBHOOK_GOVERNED_ACTION_ERROR_CODES: readonly ["options_invalid"];
185
+ /** Why the governed-action module could not be built. */
186
+ type WebhookGovernedActionErrorCode = (typeof WEBHOOK_GOVERNED_ACTION_ERROR_CODES)[number];
187
+ //#endregion
188
+ //#region src/errors.d.ts
6
189
  /**
7
190
  * A configured limit was not usable: zero, negative, `NaN`, infinite, not an
8
191
  * integer where a count is required, outside its documented range, or not
9
192
  * readable as plain data. The message is constant for the code; `field`
10
193
  * names the limit, for example `deliveryRetryDelaysMs[2]`.
11
194
  */
12
- declare class WebhookDeliveryConfigError extends Error {
13
- readonly code: WebhookConfigErrorCode;
14
- /** The limit that was rejected, for example `deliveryRetryDelaysMs[2]`. */
15
- readonly field: string;
16
- constructor(code: WebhookConfigErrorCode, field: string);
195
+ export declare class WebhookDeliveryConfigError extends Error {
196
+ readonly code: WebhookConfigErrorCode;
197
+ /** The limit that was rejected, for example `deliveryRetryDelaysMs[2]`. */
198
+ readonly field: string;
199
+ constructor(code: WebhookConfigErrorCode, field: string);
17
200
  }
18
201
  /**
19
202
  * An envelope, job or subscription could not be projected. The message is
20
203
  * constant for the code. `field` names an envelope or job field, an option,
21
204
  * or a field name from the caller's own allowlist; never a value.
22
205
  */
23
- declare class WebhookProjectionError extends Error {
24
- readonly code: WebhookProjectionErrorCode;
25
- /** The envelope, job, option or allowlisted payload field at fault. */
26
- readonly field: string;
27
- constructor(code: WebhookProjectionErrorCode, field: string);
206
+ export declare class WebhookProjectionError extends Error {
207
+ readonly code: WebhookProjectionErrorCode;
208
+ /** The envelope, job, option or allowlisted payload field at fault. */
209
+ readonly field: string;
210
+ constructor(code: WebhookProjectionErrorCode, field: string);
28
211
  }
29
212
  /**
30
213
  * A store call was refused: its input was malformed or not plain data
@@ -34,11 +217,11 @@ declare class WebhookProjectionError extends Error {
34
217
  * store's clock failed (`clock_invalid`). Nothing was written. The message
35
218
  * is constant for the code; `field` names an input field, never a value.
36
219
  */
37
- declare class WebhookStoreError extends Error {
38
- readonly code: WebhookStoreErrorCode;
39
- /** The input field at fault, for example `intent.eventId`. */
40
- readonly field: string;
41
- constructor(code: WebhookStoreErrorCode, field: string);
220
+ export declare class WebhookStoreError extends Error {
221
+ readonly code: WebhookStoreErrorCode;
222
+ /** The input field at fault, for example `intent.eventId`. */
223
+ readonly field: string;
224
+ constructor(code: WebhookStoreErrorCode, field: string);
42
225
  }
43
226
  /**
44
227
  * The fan-out publisher, the composite publisher or the fan-out cycle refused
@@ -46,49 +229,1536 @@ declare class WebhookStoreError extends Error {
46
229
  * code; `field` names an option, a store method or a sink name from the
47
230
  * application's own configuration, never an event value.
48
231
  */
49
- declare class WebhookFanOutError extends Error {
50
- readonly code: WebhookFanOutErrorCode;
51
- /** The option, store method or sink at fault, for example `insertFanOutJob`. */
52
- readonly field: string;
53
- constructor(code: WebhookFanOutErrorCode, field: string);
232
+ export declare class WebhookFanOutError extends Error {
233
+ readonly code: WebhookFanOutErrorCode;
234
+ /** The option, store method or sink at fault, for example `insertFanOutJob`. */
235
+ readonly field: string;
236
+ constructor(code: WebhookFanOutErrorCode, field: string);
54
237
  }
55
238
  /**
56
239
  * The delivery cycle could not run (`WEBHOOK_DELIVERY_ERROR_CODES`). The
57
240
  * message is constant for the code; `field` names an option or a store
58
241
  * method, never a value.
59
242
  */
60
- declare class WebhookDeliveryError extends Error {
61
- readonly code: WebhookDeliveryErrorCode;
62
- /** The option or store method at fault, for example `claimDeliveryIntents`. */
63
- readonly field: string;
64
- constructor(code: WebhookDeliveryErrorCode, field: string);
243
+ export declare class WebhookDeliveryError extends Error {
244
+ readonly code: WebhookDeliveryErrorCode;
245
+ /** The option or store method at fault, for example `claimDeliveryIntents`. */
246
+ readonly field: string;
247
+ constructor(code: WebhookDeliveryErrorCode, field: string);
65
248
  }
66
249
  /**
67
250
  * The governed-action module could not be built
68
251
  * (`WEBHOOK_GOVERNED_ACTION_ERROR_CODES`). The message is constant for the
69
252
  * code; `field` names an option, never a value.
70
253
  */
71
- declare class WebhookGovernedActionError extends Error {
72
- readonly code: WebhookGovernedActionErrorCode;
73
- /** The option at fault, for example `secretReferences.verify`. */
74
- readonly field: string;
75
- constructor(code: WebhookGovernedActionErrorCode, field: string);
254
+ export declare class WebhookGovernedActionError extends Error {
255
+ readonly code: WebhookGovernedActionErrorCode;
256
+ /** The option at fault, for example `secretReferences.verify`. */
257
+ readonly field: string;
258
+ constructor(code: WebhookGovernedActionErrorCode, field: string);
259
+ }
260
+ //#endregion
261
+ //#region src/action-parameters.d.ts
262
+ /** The governed actions, by the name after the module namespace (`<namespace>.<name>`). */
263
+ export declare const WEBHOOK_DELIVERY_ACTION_NAMES: readonly ["create_subscription", "update_subscription", "pause_subscription", "resume_subscription", "delete_subscription", "rotate_secret_staged", "rotate_secret_emergency", "requeue_delivery_intent", "requeue_fan_out_job", "enrich_fan_out_job_actor", "replay_events"];
264
+ /** One governed action name. */
265
+ type WebhookDeliveryActionName = (typeof WEBHOOK_DELIVERY_ACTION_NAMES)[number];
266
+ /**
267
+ * Why parameters were refused. `parameters_invalid`: not a plain-data
268
+ * object, a field missing or malformed, or an id breaking the store
269
+ * identifier rule. `unknown_parameter`: a key the action does not declare
270
+ * (named in no message). `secret_ref_invalid`: a `secretRef` that is not a
271
+ * secret reference. `destination_invalid`: a destination URL breaking the
272
+ * URL rules (README, "URL rules"). `event_type_pattern_invalid`: a pattern
273
+ * outside the pattern grammar. `update_ambiguous`: an update naming both or
274
+ * neither of patterns and destination.
275
+ */
276
+ export declare const WEBHOOK_ACTION_PARAMETER_CODES: readonly ["parameters_invalid", "unknown_parameter", "secret_ref_invalid", "destination_invalid", "event_type_pattern_invalid", "update_ambiguous"];
277
+ /** Why an action's parameters were refused. */
278
+ type WebhookActionParameterCode = (typeof WEBHOOK_ACTION_PARAMETER_CODES)[number];
279
+ /** `<namespace>.create_subscription` */
280
+ interface WebhookCreateSubscriptionParameters {
281
+ readonly subscriptionId: string;
282
+ readonly eventTypePatterns: readonly string[];
283
+ readonly destinationUrl: string;
284
+ /** A secret reference, not key material or ciphertext (`isWebhookSecretReference`, then the verifier). */
285
+ readonly secretRef: string;
286
+ /** Only `v1` exists (decision 7); the default. */
287
+ readonly signatureScheme?: "v1";
288
+ }
289
+ /** `<namespace>.update_subscription`: exactly one of the two changes per invocation. */
290
+ type WebhookUpdateSubscriptionParameters = {
291
+ readonly subscriptionId: string;
292
+ readonly eventTypePatterns: readonly string[];
293
+ readonly expectedRevision?: number;
294
+ } | {
295
+ readonly subscriptionId: string;
296
+ readonly destinationUrl: string;
297
+ readonly expectedVersion?: number;
298
+ };
299
+ /** `<namespace>.pause_subscription`, `resume_subscription` and `delete_subscription`. */
300
+ interface WebhookSubscriptionTransitionParameters {
301
+ readonly subscriptionId: string;
302
+ readonly expectedRevision?: number;
303
+ }
304
+ /** `<namespace>.rotate_secret_staged` */
305
+ interface WebhookStagedRotationParameters {
306
+ readonly subscriptionId: string;
307
+ /** The next secret's reference, not key material or ciphertext (`isWebhookSecretReference`, then the verifier). */
308
+ readonly secretRef: string;
309
+ /** The configuration version the operator read; the rotation applies only on it. */
310
+ readonly expectedVersion: number;
311
+ /** The operator's statement that the receiver already accepts the next secret (decision 8). */
312
+ readonly receiverAcceptsNextSecret: true;
313
+ }
314
+ /** `<namespace>.rotate_secret_emergency` */
315
+ interface WebhookEmergencyRotationParameters {
316
+ readonly subscriptionId: string;
317
+ readonly secretRef: string;
318
+ readonly expectedVersion?: number;
76
319
  }
77
-
320
+ /** `<namespace>.requeue_delivery_intent` */
321
+ interface WebhookIntentRequeueParameters {
322
+ readonly deliveryId: string;
323
+ /** The intent's `fence` as the operator read it. */
324
+ readonly expectedFence: number;
325
+ }
326
+ /** `<namespace>.requeue_fan_out_job` and `enrich_fan_out_job_actor`. */
327
+ interface WebhookJobParameters {
328
+ readonly eventId: string;
329
+ /** The job's `fence` as the operator read it. */
330
+ readonly expectedFence: number;
331
+ }
332
+ /** Explicit historical range; from inclusive, to exclusive. */
333
+ interface WebhookReplayEventsParameters {
334
+ readonly subscriptionId: string;
335
+ readonly eventTypePatterns?: readonly string[];
336
+ readonly from: string;
337
+ readonly to: string;
338
+ }
339
+ /** The parameters of each action, by name. */
340
+ interface WebhookDeliveryActionParameters {
341
+ readonly replay_events: WebhookReplayEventsParameters;
342
+ readonly create_subscription: WebhookCreateSubscriptionParameters;
343
+ readonly update_subscription: WebhookUpdateSubscriptionParameters;
344
+ readonly pause_subscription: WebhookSubscriptionTransitionParameters;
345
+ readonly resume_subscription: WebhookSubscriptionTransitionParameters;
346
+ readonly delete_subscription: WebhookSubscriptionTransitionParameters;
347
+ readonly rotate_secret_staged: WebhookStagedRotationParameters;
348
+ readonly rotate_secret_emergency: WebhookEmergencyRotationParameters;
349
+ readonly requeue_delivery_intent: WebhookIntentRequeueParameters;
350
+ readonly requeue_fan_out_job: WebhookJobParameters;
351
+ readonly enrich_fan_out_job_actor: WebhookJobParameters;
352
+ }
353
+ /** What reading one action's parameters found. */
354
+ type WebhookActionParameterRead<N extends WebhookDeliveryActionName> = {
355
+ readonly ok: true;
356
+ readonly value: WebhookDeliveryActionParameters[N];
357
+ } | {
358
+ readonly ok: false;
359
+ readonly code: WebhookActionParameterCode;
360
+ readonly field: string;
361
+ };
362
+ /** The longest secret reference accepted. */
363
+ export declare const WEBHOOK_SECRET_REF_MAX_LENGTH = 256;
364
+ /** The longest run of characters between two separators (`:`, `/`, `@`, `#`) in a secret reference. */
365
+ export declare const WEBHOOK_SECRET_REF_MAX_SEGMENT = 64;
366
+ /**
367
+ * Whether `value` has the shape of a secret reference (ADR 0018 decision 8):
368
+ * a lower-case scheme and `:` (`kms:`, `vault:`, `arn:`, `secret:` …), then
369
+ * only `A–Z a–z 0–9 . _ ~ : / @ # -`, at most
370
+ * {@link WEBHOOK_SECRET_REF_MAX_LENGTH} characters, with no run longer than
371
+ * {@link WEBHOOK_SECRET_REF_MAX_SEGMENT} between separators.
372
+ *
373
+ * That refuses raw key bytes and their usual encodings: hex, base64 and
374
+ * `whsec_…` values (no scheme), padded or standard base64 (`=`, `+`), PEM
375
+ * armour and anything with whitespace or control characters, and long
376
+ * encoded runs such as wrapped ciphertext. It cannot recognise key material
377
+ * deliberately shaped like a reference (`x:` followed by 64 hex digits
378
+ * passes), which is why the actions also require the application's
379
+ * `secretReferences.verify` port to confirm the reference resolves in the
380
+ * tenant's secret store.
381
+ */
382
+ export declare function isWebhookSecretReference(value: unknown): value is string;
383
+ /** The most patterns one subscription takes. */
384
+ export declare const WEBHOOK_MAX_EVENT_TYPE_PATTERNS = 64;
385
+ /**
386
+ * Whether `value` is an event type pattern the matcher understands: `*`,
387
+ * `prefix.*`, or an exact type, built from `A–Z a–z 0–9 _ : -` segments
388
+ * joined by `.`, at most 256 characters. A `*` anywhere else is refused,
389
+ * since the matcher would treat it as a literal.
390
+ */
391
+ export declare function isWebhookEventTypePattern(value: unknown): value is string;
392
+ /**
393
+ * Read one action's parameters. Never throws: the only thrown value whose
394
+ * properties are read is the package's own `Refusal` (recognized by
395
+ * `Refusal.is`, which runs none of a value's code); anything else thrown
396
+ * while the input is read (an accessor, a Proxy trap, a look-alike refusal)
397
+ * is dropped unread and reported as `parameters_invalid`. The code and field
398
+ * returned are each checked against their fixed set (`fixedRefusal`). Tests:
399
+ * "forged refusals: …".
400
+ */
401
+ export declare function readWebhookActionParameters<N extends WebhookDeliveryActionName>(name: N, input: unknown): WebhookActionParameterRead<N>;
402
+ /** The default module namespace. */
403
+ export declare const DEFAULT_WEBHOOK_DELIVERY_NAMESPACE = "webhook-delivery";
404
+ //#endregion
405
+ //#region src/types.d.ts
406
+ /** A JSON value. Every record this package defines is built from these. */
407
+ type WebhookJsonValue = null | boolean | number | string | readonly WebhookJsonValue[] | {
408
+ readonly [key: string]: WebhookJsonValue;
409
+ };
410
+ /** A JSON object. */
411
+ type WebhookJsonObject = {
412
+ readonly [key: string]: WebhookJsonValue;
413
+ };
414
+ /** The Host envelope this package consumes, re-exported for adapters. */
415
+ type WebhookSourceEnvelope = EnterpriseEventEnvelope;
416
+ /**
417
+ * The envelope metadata a fan-out job keeps: everything that identifies,
418
+ * orders and classifies the event, and nothing from its payload, trace
419
+ * context or provenance. Timestamps are ISO-8601 strings so the record is
420
+ * serializable.
421
+ */
422
+ interface WebhookEventMetadata {
423
+ readonly eventId: string;
424
+ readonly eventType: string;
425
+ readonly eventSchemaVersion: number;
426
+ readonly tenantId: string;
427
+ readonly spaceId: string;
428
+ readonly subjectType: string;
429
+ readonly subjectId: string;
430
+ /** The tenant/space ledger cursor. Delivery order is not guaranteed. */
431
+ readonly sequence: number;
432
+ readonly actionInvocationId?: string;
433
+ readonly correlationId: string;
434
+ readonly causationId?: string;
435
+ readonly occurredAt: string;
436
+ /** Stamped by Host's clock; selects the eligibility revision (decision 3). */
437
+ readonly recordedAt: string;
438
+ readonly producerModuleVersion: string;
439
+ readonly payloadClassification: EventPayloadClassification;
440
+ /** Present only when the producing action declared it. */
441
+ readonly consistency?: "authoritative-now" | "provisional-until-reconciled";
442
+ }
443
+ /**
444
+ * The actor a job carries for admission, or why it carries none (decision 5).
445
+ * `suppressed`: Host's `includeActor` hook excluded it. `legacy`: the envelope
446
+ * predates the actor field. Admission that needs an actor treats both
447
+ * absences as `unavailable`. The actor is never rendered into a v1 body.
448
+ */
449
+ type WebhookJobActor = {
450
+ readonly state: "present";
451
+ readonly actorId: string;
452
+ readonly actorType: string;
453
+ } | {
454
+ readonly state: "absent";
455
+ readonly reason: "suppressed" | "legacy";
456
+ };
457
+ /**
458
+ * What the application allows to leave for one event type (decision 9).
459
+ * Field names are top-level payload keys. An event type with no entry goes
460
+ * out metadata-only.
461
+ */
462
+ interface WebhookEventTypePolicy {
463
+ /** Payload fields that may be persisted on the job and rendered into the body. */
464
+ readonly fields: readonly string[];
465
+ /**
466
+ * Payload fields admission and scope predicates need. They are persisted on
467
+ * the job as facts and are never rendered into the body unless also listed
468
+ * in `fields`.
469
+ */
470
+ readonly facts?: readonly string[];
471
+ /** A `restricted` event is metadata-only, and keeps no payload or facts, unless this is `true`. */
472
+ readonly includeRestricted?: boolean;
473
+ /** A scoped type egresses its payload only when the injected scope predicate returns `true`. */
474
+ readonly scoped?: boolean;
475
+ }
476
+ /** Egress policy keyed by event type. Only own properties are consulted. */
477
+ type WebhookPayloadPolicy = {
478
+ readonly [eventType: string]: WebhookEventTypePolicy;
479
+ };
480
+ /**
481
+ * The persistence projection of one envelope (decision 2): what the fan-out
482
+ * publisher inserts, keyed by `eventId`. It holds metadata, the allowlisted
483
+ * payload fields and the declared facts only.
484
+ */
485
+ interface WebhookFanOutJobDraft {
486
+ readonly eventId: string;
487
+ readonly tenantId: string;
488
+ readonly spaceId: string;
489
+ readonly event: WebhookEventMetadata;
490
+ /** Allowlisted payload fields, or `null` when the event is metadata-only. */
491
+ readonly payload: WebhookJsonObject | null;
492
+ /** `true` exactly when `payload` is not `null`. */
493
+ readonly payloadProjected: boolean;
494
+ /** Declared admission and scope facts; empty when none are declared or allowed. */
495
+ readonly facts: WebhookJsonObject;
496
+ /** Store-owned erasure marker; undecided subscriptions must fail closed. */
497
+ readonly erased?: boolean;
498
+ readonly actor: WebhookJobActor;
499
+ }
500
+ /** A fenced lease. A write carrying an older `fence` than the record's is refused. */
501
+ interface WebhookLease {
502
+ readonly owner: string;
503
+ readonly fence: number;
504
+ readonly expiresAt: string;
505
+ }
506
+ /** Fan-out job lifecycle. */
507
+ type WebhookFanOutJobStatus = "pending" | "completed" | "dead_letter";
508
+ /** A stored fan-out job. */
509
+ interface WebhookFanOutJob extends WebhookFanOutJobDraft {
510
+ readonly status: WebhookFanOutJobStatus;
511
+ readonly attemptCount: number;
512
+ readonly availableAt: string;
513
+ /** Persisted at creation; deferrals and reclaims count toward it (R4). */
514
+ readonly processingDeadline: string;
515
+ readonly createdAt: string;
516
+ readonly completedAt?: string;
517
+ readonly lastDiagnostic?: WebhookDiagnosticCode;
518
+ /**
519
+ * The record's fence: the dead-letter generation a requeue must name.
520
+ * Each claim and each applied requeue advances it, so a requeue built
521
+ * from an earlier read is refused as `stale` (decision 10).
522
+ */
523
+ readonly fence: number;
524
+ readonly lease?: WebhookLease;
525
+ }
526
+ /** Subscription lifecycle state recorded on each eligibility revision and configuration read. */
527
+ type WebhookSubscriptionStatus = "active" | "paused" | "deleted";
528
+ /**
529
+ * An immutable eligibility revision (decision 3). Create, pattern change,
530
+ * pause and resume each write one. The interval is half-open:
531
+ * `effectiveFrom <= recordedAt < effectiveUntil`, with `null` meaning open.
532
+ */
533
+ interface WebhookEligibilityRevision {
534
+ readonly subscriptionId: string;
535
+ readonly revision: number;
536
+ readonly tenantId: string;
537
+ readonly spaceId: string;
538
+ readonly status: WebhookSubscriptionStatus;
539
+ readonly eventTypePatterns: readonly string[];
540
+ readonly effectiveFrom: string;
541
+ readonly effectiveUntil: string | null;
542
+ }
543
+ /** The only signature scheme in v1 (decision 7). */
544
+ type WebhookSignatureScheme = "v1";
545
+ /**
546
+ * One version of a subscription's delivery configuration. Intents store no
547
+ * destination; attempts record the version they used. The secret is a
548
+ * reference resolved by the signer within the subscription's tenant, never
549
+ * key material (decision 8).
550
+ */
551
+ interface WebhookDeliveryConfiguration {
552
+ readonly subscriptionId: string;
553
+ readonly tenantId: string;
554
+ readonly spaceId: string;
555
+ readonly version: number;
556
+ readonly destinationUrl: string;
557
+ readonly signatureScheme: WebhookSignatureScheme;
558
+ readonly secretRef: string;
559
+ }
560
+ /** What the dispatch check reads for one subscription. */
561
+ interface WebhookSubscriptionSnapshot {
562
+ readonly subscriptionId: string;
563
+ readonly tenantId: string;
564
+ readonly spaceId: string;
565
+ readonly status: WebhookSubscriptionStatus;
566
+ /**
567
+ * The eligibility revision at which the subscription entered `status`: 1
568
+ * when it has never been paused, otherwise its latest pause, resume or
569
+ * delete revision (a pattern change keeps the status, so it does not
570
+ * move this). Read in the same atomic step as `status`. While the
571
+ * subscription is active, an intent bound to a lower revision belongs to
572
+ * an interval a pause ended, and dispatch cancels it (decision 3;
573
+ * README, "Pause and resume are enforced at dispatch").
574
+ */
575
+ readonly statusSinceRevision: number;
576
+ readonly configuration: WebhookDeliveryConfiguration;
577
+ }
578
+ /** Delivery intent lifecycle. */
579
+ type WebhookDeliveryIntentStatus = "pending" | "delivered" | "dead_letter" | "cancelled";
580
+ /** One intent per (subscriptionId, eventId), holding the rendered body (decision 9). */
581
+ interface WebhookDeliveryIntent {
582
+ readonly deliveryId: string;
583
+ readonly subscriptionId: string;
584
+ readonly eventId: string;
585
+ readonly tenantId: string;
586
+ readonly spaceId: string;
587
+ readonly eligibilityRevision: number;
588
+ /**
589
+ * The event's `recordedAt`, copied by the store from the parent fan-out
590
+ * job when the intent is inserted. It anchors the replay horizon (R5).
591
+ */
592
+ readonly eventRecordedAt: string;
593
+ /** The rendered `fabric.webhook-delivery.v1` body, signed byte-for-byte per attempt. */
594
+ readonly body: string;
595
+ readonly status: WebhookDeliveryIntentStatus;
596
+ readonly attemptCount: number;
597
+ readonly availableAt: string;
598
+ readonly processingDeadline: string;
599
+ readonly createdAt: string;
600
+ readonly lastOutcome?: {
601
+ readonly outcome: WebhookAttemptOutcome;
602
+ readonly reason: WebhookAttemptReasonCode;
603
+ };
604
+ readonly lastDiagnostic?: WebhookDiagnosticCode;
605
+ /**
606
+ * The record's fence: the dead-letter generation a requeue must name.
607
+ * Each claim and each applied requeue advances it, so a requeue built
608
+ * from an earlier read is refused as `stale` (decision 10).
609
+ */
610
+ readonly fence: number;
611
+ readonly lease?: WebhookLease;
612
+ }
613
+ /** A recorded admission denial; it satisfies completeness for its (subscription, event). */
614
+ interface WebhookAdmissionDenialRecord {
615
+ readonly subscriptionId: string;
616
+ readonly eventId: string;
617
+ readonly tenantId: string;
618
+ readonly spaceId: string;
619
+ readonly eligibilityRevision: number;
620
+ readonly code: WebhookAdmissionDenialCode;
621
+ readonly recordedAt: string;
622
+ }
623
+ /**
624
+ * An attempt log entry with its outcome. Response bodies are never
625
+ * persisted. An entry appended whole by `appendAttempt` has no `fence`; one
626
+ * begun by `beginAttempt` and finished by `finishAttempt` carries the
627
+ * intent's lease fence it was begun under.
628
+ */
629
+ interface WebhookDeliveryAttemptRecord {
630
+ readonly deliveryId: string;
631
+ readonly subscriptionId: string;
632
+ readonly eventId: string;
633
+ readonly attempt: number;
634
+ readonly configurationVersion: number;
635
+ readonly startedAt: string;
636
+ readonly finishedAt: string;
637
+ readonly outcome: WebhookAttemptOutcome;
638
+ readonly reason: WebhookAttemptReasonCode;
639
+ readonly httpStatus?: number;
640
+ /** The intent lease fence the attempt was begun under; absent for an entry appended whole. */
641
+ readonly fence?: number;
642
+ }
643
+ /**
644
+ * What `beginAttempt` records, under the intent's lease, immediately before
645
+ * the request is handed to the transport (README, "Delivery": the
646
+ * irrevocable point). The configuration version is the one the request is
647
+ * signed and addressed with.
648
+ */
649
+ interface WebhookDeliveryAttemptStart {
650
+ readonly deliveryId: string;
651
+ readonly subscriptionId: string;
652
+ readonly eventId: string;
653
+ readonly attempt: number;
654
+ readonly configurationVersion: number;
655
+ readonly startedAt: string;
656
+ }
657
+ /** What `finishAttempt` appends to a begun attempt once the transport answered. */
658
+ interface WebhookDeliveryAttemptFinish {
659
+ readonly finishedAt: string;
660
+ readonly outcome: WebhookAttemptOutcome;
661
+ readonly reason: WebhookAttemptReasonCode;
662
+ readonly httpStatus?: number;
663
+ }
664
+ /**
665
+ * A begun attempt with no outcome recorded: it was handed to the transport,
666
+ * or its worker stopped between `beginAttempt` and the handoff, and no
667
+ * outcome has been appended since. The request may have reached the
668
+ * receiver.
669
+ */
670
+ interface WebhookDeliveryAttemptInFlight extends WebhookDeliveryAttemptStart {
671
+ readonly fence: number;
672
+ readonly outcome: "in_flight";
673
+ }
674
+ /** One entry of an intent's attempt log, in the order its first write was made. */
675
+ type WebhookDeliveryAttemptLogEntry = WebhookDeliveryAttemptRecord | WebhookDeliveryAttemptInFlight;
676
+ /**
677
+ * The uniqueness marker for one (subscriptionId, eventId). The store writes
678
+ * it in the same step as the intent or denial it stands for and removes it
679
+ * with that record, when a purge finds the event older than the replay
680
+ * horizon (R5). `retainUntil` is the event's `recordedAt` plus the store's
681
+ * horizon: until then any insert for the pair is `already_present`, and
682
+ * after it every insert for the pair is `past_horizon`, so governed replay
683
+ * cannot create a second intent whether or not a purge has run. Dispatch
684
+ * remains at-least-once (the "cleanup racing" cases in
685
+ * `tests/store-contract.ts`).
686
+ */
687
+ interface WebhookTombstone {
688
+ readonly kind: "intent" | "denial";
689
+ readonly subscriptionId: string;
690
+ readonly eventId: string;
691
+ readonly tenantId: string;
692
+ readonly spaceId: string;
693
+ readonly retainUntil: string;
694
+ }
695
+ /** A governed compare-and-set requeue from dead letter back to pending (decision 10). */
696
+ interface WebhookRequeueRequest {
697
+ readonly expectedStatus: "dead_letter";
698
+ /**
699
+ * The `fence` of the dead-lettered record as the operator read it. The
700
+ * requeue applies only while the record still has that fence; otherwise
701
+ * it is refused as `stale`. Two requeues built from one read therefore
702
+ * apply at most once, and a request read before the record was requeued,
703
+ * re-run and dead-lettered again does not apply to the new dead letter.
704
+ */
705
+ readonly expectedFence: number;
706
+ readonly requestedBy: string;
707
+ readonly requestedAt: Date;
708
+ /**
709
+ * The requeued record's new processing deadline (R4). The record becomes
710
+ * available at `requestedAt` with its attempt budget restored; without a
711
+ * new deadline a requeued record past its old one could not be retried.
712
+ */
713
+ readonly processingDeadline: Date;
714
+ /**
715
+ * The governed invocation that authorized the requeue, for audit. It is
716
+ * also the request's identity: a later request under the same id, for the
717
+ * same target and record in the same scope, is a re-run of this one, and
718
+ * the store answers it with this request's evidence and `replayed: true`
719
+ * instead of applying it (ports, "Re-runs of one invocation").
720
+ */
721
+ readonly actionInvocationId?: string;
722
+ }
723
+ /**
724
+ * A governed actor enrichment of a `legacy` fan-out job (decision 5, PR 9).
725
+ * Like {@link WebhookRequeueRequest} it is a compare-and-set on the job's
726
+ * fence as the operator read it, and it restores the budget with a new
727
+ * processing deadline. `actor` is what the application's reader found on the
728
+ * canonical event for the job's tenant, space and `eventId`.
729
+ */
730
+ interface WebhookActorEnrichmentRequest {
731
+ /** The job's `fence` as the operator read it; a mismatch is refused as `stale`. */
732
+ readonly expectedFence: number;
733
+ readonly actor: {
734
+ readonly actorId: string;
735
+ readonly actorType: string;
736
+ };
737
+ readonly requestedBy: string;
738
+ readonly requestedAt: Date;
739
+ /** The repaired job's new processing deadline (R4). It becomes available at `requestedAt`. */
740
+ readonly processingDeadline: Date;
741
+ /** The governed invocation that authorized the enrichment, for audit, and the request's identity, as for {@link WebhookRequeueRequest.actionInvocationId}. */
742
+ readonly actionInvocationId?: string;
743
+ }
744
+ /**
745
+ * Append-only evidence of one applied requeue or actor enrichment
746
+ * (decisions 5 and 10). The store writes it in the same step as the
747
+ * transition, and no store method updates or removes it, a purge included
748
+ * (the memory store keeps it for its lifetime, the PostgreSQL store until
749
+ * someone deletes rows from `webhook_requeue_evidence` directly);
750
+ * the `prior*` fields keep the state the requeue replaced, so a later
751
+ * requeue cannot erase an earlier failure. Contract cases: "job requeue is a
752
+ * compare-and-set …", "actor enrichment repairs a legacy job …" and "purge
753
+ * removes only terminal records …".
754
+ */
755
+ interface WebhookRequeueEvidence {
756
+ /** `fan_out_job_actor`: an actor enrichment (PR 9), which also requeues the job. */
757
+ readonly target: "fan_out_job" | "delivery_intent" | "fan_out_job_actor";
758
+ readonly eventId: string;
759
+ /** Present when `target` is `delivery_intent`. */
760
+ readonly deliveryId?: string;
761
+ /**
762
+ * Present when `target` is `delivery_intent`: the intent's subscription,
763
+ * so a re-run of the requeue can report it after the intent is purged.
764
+ * Evidence written before this field existed lacks it.
765
+ */
766
+ readonly subscriptionId?: string;
767
+ readonly tenantId: string;
768
+ readonly spaceId: string;
769
+ readonly requestedBy: string;
770
+ readonly requestedAt: string;
771
+ readonly actionInvocationId?: string;
772
+ /** The store's clock when the requeue was applied. */
773
+ readonly appliedAt: string;
774
+ readonly priorAttemptCount: number;
775
+ readonly priorAvailableAt: string;
776
+ readonly priorProcessingDeadline: string;
777
+ /** The record's fence when it was requeued (the request's `expectedFence`); the requeue advances it by one. */
778
+ readonly priorFence: number;
779
+ readonly priorLastDiagnostic?: WebhookDiagnosticCode;
780
+ readonly priorLastOutcome?: {
781
+ readonly outcome: WebhookAttemptOutcome;
782
+ readonly reason: WebhookAttemptReasonCode;
783
+ };
784
+ readonly processingDeadline: string;
785
+ /** Present when `target` is `fan_out_job_actor`: the actor the enrichment set. The prior actor was `legacy`. */
786
+ readonly actor?: {
787
+ readonly actorId: string;
788
+ readonly actorType: string;
789
+ };
790
+ }
791
+ /** A delivery configuration as a writer supplies it; the store assigns `version`. */
792
+ interface WebhookDeliveryConfigurationInput {
793
+ readonly destinationUrl: string;
794
+ readonly signatureScheme: WebhookSignatureScheme;
795
+ /** A reference the signer resolves within the subscription's tenant, never key material (decision 8). */
796
+ readonly secretRef: string;
797
+ }
798
+ /** The v1 wire body (decision 7). Field order is the serialized order. */
799
+ interface WebhookDeliveryBodyV1 {
800
+ readonly format: "fabric.webhook-delivery.v1";
801
+ readonly eventId: string;
802
+ readonly type: string;
803
+ readonly tenantId: string;
804
+ readonly spaceId: string;
805
+ readonly correlationId: string;
806
+ readonly occurredAt: string;
807
+ readonly event: {
808
+ readonly type: string;
809
+ readonly schemaVersion: number;
810
+ readonly subjectType: string;
811
+ readonly subjectId: string;
812
+ readonly sequence: number;
813
+ readonly recordedAt: string;
814
+ readonly actionInvocationId?: string;
815
+ readonly consistency?: "provisional-until-reconciled";
816
+ readonly payload: WebhookJsonObject | null;
817
+ readonly payloadProjected: boolean;
818
+ };
819
+ readonly delivery: {
820
+ readonly deliveryId: string;
821
+ readonly subscriptionId: string;
822
+ };
823
+ }
824
+ //#endregion
825
+ //#region src/ports.d.ts
826
+ /** Bounds every awaited port call. */
827
+ interface WebhookCallOptions {
828
+ /** The absolute time after which the call's result is ignored. */
829
+ readonly deadline: Date;
830
+ /** Aborted when the caller stops waiting; implementations should stop work. */
831
+ readonly signal: AbortSignal;
832
+ }
833
+ /** Tenant and space scope every store read is confined to (decision 9). */
834
+ interface WebhookScope {
835
+ readonly tenantId: string;
836
+ readonly spaceId: string;
837
+ }
838
+ /**
839
+ * Tenant- and space-scoped reads of eligibility revisions and delivery
840
+ * configuration (decision 3). The fan-out and delivery cycles depend on this
841
+ * read port only; writes are on {@link WebhookSubscriptionWriter}.
842
+ */
843
+ interface WebhookSubscriptionStore {
844
+ /**
845
+ * Eligibility revisions in the scope whose half-open interval contains
846
+ * `recordedAt`, at most one per subscription, in `subscriptionId` order.
847
+ * Paused and deleted revisions are returned with their status; the caller
848
+ * decides eligibility.
849
+ */
850
+ listEligibilityRevisions(input: WebhookScope & {
851
+ readonly recordedAt: string;
852
+ }, options: WebhookCallOptions): Promise<readonly WebhookEligibilityRevision[]>;
853
+ /**
854
+ * The subscription's current status, the revision that status began at
855
+ * (`statusSinceRevision`, read in the same atomic step as the status) and
856
+ * its current delivery configuration, or `null` when it does not exist in
857
+ * this scope. The dispatch check calls it as close to transport handoff
858
+ * as it can.
859
+ */
860
+ readSubscription(input: WebhookScope & {
861
+ readonly subscriptionId: string;
862
+ }, options: WebhookCallOptions): Promise<WebhookSubscriptionSnapshot | null>;
863
+ /**
864
+ * Every eligibility revision of one subscription, oldest first, or an
865
+ * empty list when it does not exist in this scope. Revisions are never
866
+ * removed (R5: eligibility history outlives the replay horizon).
867
+ */
868
+ listEligibilityHistory(input: WebhookScope & {
869
+ readonly subscriptionId: string;
870
+ }, options: WebhookCallOptions): Promise<readonly WebhookEligibilityRevision[]>;
871
+ }
872
+ /**
873
+ * Why a subscription write was refused. `not_found`: no such subscription in
874
+ * this scope. `already_exists`: create named an existing one. `deleted`: the
875
+ * subscription is deleted, which is terminal. `invalid_transition`: pause of
876
+ * a paused subscription, or resume of an active one. `revision_conflict` /
877
+ * `version_conflict`: the caller's expected revision or configuration
878
+ * version is not the current one.
879
+ */
880
+ type WebhookSubscriptionWriteRefusal = "not_found" | "already_exists" | "deleted" | "invalid_transition" | "revision_conflict";
881
+ /** The result of a subscription write that appends an eligibility revision. */
882
+ type WebhookSubscriptionWriteResult = {
883
+ readonly applied: true;
884
+ readonly revision: WebhookEligibilityRevision;
885
+ readonly configuration: WebhookDeliveryConfiguration;
886
+ } | {
887
+ readonly applied: false;
888
+ readonly refusal: WebhookSubscriptionWriteRefusal;
889
+ readonly currentRevision?: number;
890
+ };
891
+ /** The result of a delivery configuration replacement. */
892
+ type WebhookConfigurationWriteResult = {
893
+ readonly applied: true;
894
+ readonly configuration: WebhookDeliveryConfiguration;
895
+ } | {
896
+ readonly applied: false;
897
+ readonly refusal: "not_found" | "deleted" | "version_conflict";
898
+ readonly currentVersion?: number;
899
+ };
900
+ /** Names one subscription for a transition, optionally guarded by its current revision. */
901
+ type WebhookSubscriptionTransitionInput = WebhookScope & {
902
+ readonly subscriptionId: string;
903
+ /** When set, the write is refused as `revision_conflict` unless this is the current revision. */
904
+ readonly expectedRevision?: number;
905
+ };
906
+ /**
907
+ * Subscription writes (decision 3), for the governed actions (PR 9). Create,
908
+ * pattern change, pause, resume and delete each append one immutable
909
+ * eligibility revision, effective from the store's clock; configuration
910
+ * replacement appends one delivery configuration version. The store checks
911
+ * each transition against its own current state, not the caller's.
912
+ *
913
+ * Pause and delete do not touch the delivery store; the two stores need not
914
+ * share a database, so nothing here is atomic with intent state. The
915
+ * governed action calls {@link WebhookDeliveryStore.cancelPendingIntents}
916
+ * with the same scope and `subscriptionId` after the revision is written,
917
+ * passing that revision's number as `beforeRevision`, so a cancel delayed
918
+ * past a later resume cannot cancel intents bound to the resumed revision;
919
+ * resume makes the same call with its own revision. These calls are
920
+ * cleanups. What stops delivery, whether they failed, timed out or raced a
921
+ * late fan-out insert, is the delivery cycle's pre-handoff
922
+ * `readSubscription` check: it cancels the intent unsent while the
923
+ * subscription is paused or deleted, and while it is active with a
924
+ * {@link WebhookSubscriptionSnapshot.statusSinceRevision} above the
925
+ * intent's `eligibilityRevision`, i.e. resumed after a pause that ended the
926
+ * intent's interval (decision 3, R2; README, "Pause and resume are enforced
927
+ * at dispatch"). So resume never resurrects an intent of an interval a
928
+ * pause ended: a cancelled intent is terminal, and one still pending, or
929
+ * requeued from dead letter, is cancelled at dispatch.
930
+ */
931
+ interface WebhookSubscriptionWriter {
932
+ /** Create an active subscription with revision 1 and configuration version 1. */
933
+ createSubscription(input: WebhookScope & {
934
+ readonly subscriptionId: string;
935
+ readonly eventTypePatterns: readonly string[];
936
+ readonly configuration: WebhookDeliveryConfigurationInput;
937
+ }, options: WebhookCallOptions): Promise<WebhookSubscriptionWriteResult>;
938
+ /** Append a revision with new patterns and the current status. Refused once deleted. */
939
+ changeEventTypePatterns(input: WebhookSubscriptionTransitionInput & {
940
+ readonly eventTypePatterns: readonly string[];
941
+ }, options: WebhookCallOptions): Promise<WebhookSubscriptionWriteResult>;
942
+ /** Active to paused. See the interface note for cancelling pending intents. */
943
+ pauseSubscription(input: WebhookSubscriptionTransitionInput, options: WebhookCallOptions): Promise<WebhookSubscriptionWriteResult>;
944
+ /** Paused to active. Cancelled intents stay cancelled, and intents bound to revisions before the resume are not delivered (see the interface note). */
945
+ resumeSubscription(input: WebhookSubscriptionTransitionInput, options: WebhookCallOptions): Promise<WebhookSubscriptionWriteResult>;
946
+ /** Active or paused to deleted, which is terminal. History is kept. */
947
+ deleteSubscription(input: WebhookSubscriptionTransitionInput, options: WebhookCallOptions): Promise<WebhookSubscriptionWriteResult>;
948
+ /** Append a delivery configuration version. Refused once deleted. */
949
+ replaceDeliveryConfiguration(input: WebhookScope & {
950
+ readonly subscriptionId: string;
951
+ readonly configuration: WebhookDeliveryConfigurationInput;
952
+ /** When set, the write is refused as `version_conflict` unless this is the current version. */
953
+ readonly expectedVersion?: number;
954
+ }, options: WebhookCallOptions): Promise<WebhookConfigurationWriteResult>;
955
+ }
956
+ /** A claim request for either queue. */
957
+ interface WebhookClaimRequest {
958
+ readonly workerId: string;
959
+ readonly leaseDurationMs: number;
960
+ readonly limit: number;
961
+ readonly now: Date;
962
+ }
963
+ /** Identifies a leased record; a write whose fence is stale is refused. */
964
+ interface WebhookFencedWrite {
965
+ readonly owner: string;
966
+ readonly fence: number;
967
+ readonly now: Date;
968
+ }
969
+ /**
970
+ * The result of an insert-if-absent. `already_present`: a job for the
971
+ * `eventId` exists. `past_horizon`: the event's `recordedAt` is older than
972
+ * the replay horizon (R5), so nothing was written, whether or not a job for
973
+ * it was ever stored or has been purged.
974
+ */
975
+ interface WebhookInsertResult {
976
+ readonly outcome: "inserted" | "already_present" | "past_horizon";
977
+ }
978
+ /**
979
+ * The fan-out job lease an intent or denial insert is made under. The job is
980
+ * keyed by `eventId`; `fence` is the job's lease token, which a reclaim and
981
+ * actor enrichment both advance.
982
+ */
983
+ interface WebhookFanOutJobFence {
984
+ readonly eventId: string;
985
+ readonly owner: string;
986
+ readonly fence: number;
987
+ }
988
+ /**
989
+ * The result of an insert made under a fan-out job's lease. `lease_lost`:
990
+ * the job's current owner or fence no longer matches, and nothing was
991
+ * written. `past_horizon`: the event is older than the replay horizon (R5),
992
+ * and nothing was written. `already_present`: an intent, denial or tombstone
993
+ * already exists for (subscriptionId, eventId). Checked in that order.
994
+ */
995
+ interface WebhookFencedInsertResult {
996
+ readonly outcome: "inserted" | "already_present" | "lease_lost" | "past_horizon";
997
+ }
998
+ /**
999
+ * The result of a compare-and-set requeue, checked in this order.
1000
+ * `not_found`: no such record in this scope. `stale`: the record's fence is
1001
+ * no longer the request's `expectedFence`, because it was claimed or
1002
+ * requeued since the operator read it (including when it has since been
1003
+ * dead-lettered again). `not_dead_letter`: the record is not dead-lettered
1004
+ * (a pending, completed, delivered or cancelled record is never requeued).
1005
+ * `past_horizon`: the event is older than the replay horizon (decision 10).
1006
+ */
1007
+ type WebhookRequeueResult = {
1008
+ readonly applied: true;
1009
+ readonly evidence: WebhookRequeueEvidence;
1010
+ /** Present when the request's `actionInvocationId` had already applied it: `evidence` is that application's, and nothing changed now. */
1011
+ readonly replayed?: true;
1012
+ } | {
1013
+ readonly applied: false;
1014
+ readonly refusal: "not_found" | "stale" | "not_dead_letter" | "past_horizon";
1015
+ };
1016
+ /**
1017
+ * The result of an actor enrichment (decision 5), checked in this order.
1018
+ * `not_found`: no such job in this scope. `stale`: the job's fence is no
1019
+ * longer the request's `expectedFence`. `not_enrichable`: the job is
1020
+ * `completed`, which remains final for ordinary fan-out's historical selection
1021
+ * (README, "Completeness boundary"); a governed replay request does not reopen
1022
+ * or enrich that completed source job.
1023
+ * `actor_present`: the job already carries an actor. `actor_suppressed`:
1024
+ * Host's `includeActor` hook withheld the actor, which enrichment never
1025
+ * overrides. `past_horizon`: the event is older than the replay horizon.
1026
+ */
1027
+ type WebhookActorEnrichmentResult = {
1028
+ readonly applied: true;
1029
+ readonly evidence: WebhookRequeueEvidence;
1030
+ /** As on {@link WebhookRequeueResult}. */
1031
+ readonly replayed?: true;
1032
+ } | {
1033
+ readonly applied: false;
1034
+ readonly refusal: "not_found" | "stale" | "not_enrichable" | "actor_present" | "actor_suppressed" | "past_horizon";
1035
+ };
1036
+ /**
1037
+ * What a purge removed. `cutoff` is the store's clock minus the horizon;
1038
+ * only records whose event `recordedAt` is earlier were removed. `more` is
1039
+ * `true` when the limit stopped the purge before every such record went.
1040
+ */
1041
+ interface WebhookPurgeResult {
1042
+ readonly cutoff: string;
1043
+ readonly removed: {
1044
+ readonly fanOutJobs: number;
1045
+ readonly deliveryIntents: number;
1046
+ readonly admissionDenials: number;
1047
+ };
1048
+ readonly more: boolean;
1049
+ }
1050
+ /**
1051
+ * What `beginAttempt` answered. `receipt` is the only proof that the caller
1052
+ * began the attempt; it is needed to record the attempt's outcome.
1053
+ */
1054
+ type WebhookAttemptBeginResult = {
1055
+ readonly begun: true;
1056
+ readonly receipt: string;
1057
+ } | {
1058
+ readonly begun: false;
1059
+ };
1060
+ /**
1061
+ * What a pre-handoff lease check found. `valid` only when the intent is
1062
+ * still `pending`, leased to this owner at this fence, in this scope, and
1063
+ * the lease has not expired at `now`.
1064
+ */
1065
+ type WebhookDeliveryLeaseCheck = {
1066
+ readonly valid: true;
1067
+ } | {
1068
+ readonly valid: false;
1069
+ readonly reason: "lease_lost" | "intent_not_pending" | "not_found";
1070
+ };
1071
+ interface WebhookRedactionResult {
1072
+ readonly jobsRedacted: number;
1073
+ readonly intentsRedacted: number;
1074
+ readonly markersRecorded: number;
1075
+ }
1076
+ /**
1077
+ * Durable state for both stages (decisions 2, 3 and 10): fan-out jobs keyed
1078
+ * by `eventId`, intents keyed by (subscriptionId, eventId), fenced leases,
1079
+ * retry scheduling, dead letter, compare-and-set requeue, an append-only
1080
+ * attempt log and tombstones. Fenced writes (job completion, reschedule,
1081
+ * dead letter and lease renewal; intent and denial inserts under a parent
1082
+ * job's fence; `appendAttempt`, `beginAttempt`, `settleDeliveryIntent` and
1083
+ * `renewDeliveryIntentLease`) resolve
1084
+ * `false`, or `lease_lost` for an insert, without changing anything, when
1085
+ * the caller no longer holds the lease: the record is not `pending`, its
1086
+ * lease owner or fence differs, or its lease has expired. Expiry is judged
1087
+ * on the store's own clock, read inside the atomic step (after every lock
1088
+ * is held, in PostgreSQL), never on the caller's `now`, so a write delayed
1089
+ * past its lease is refused even before another worker reclaims the record
1090
+ * (contract case "every fenced write decides lease expiry on the store's
1091
+ * clock …"; changed in PR 8, before which the fence alone decided).
1092
+ * `finishAttempt` is not fenced. Memory and PostgreSQL implementations
1093
+ * share one contract suite (`tests/store-contract.ts`).
1094
+ *
1095
+ * **Replay horizon (R5).** The store is built with a horizon `H` and reads
1096
+ * its own clock. Every record keyed by an event is anchored at that event's
1097
+ * `recordedAt`. Let `floor` be the later of `now - H` and the highest
1098
+ * `cutoff` any purge has used. A fan-out job, intent or denial insert, a
1099
+ * job or intent requeue, and an actor enrichment (PR 9), is refused as
1100
+ * `past_horizon` when its anchor is
1101
+ * earlier than `floor`; {@link WebhookDeliveryStore.purgeExpiredRecords}
1102
+ * removes only terminal records whose anchor is earlier than its `cutoff`.
1103
+ * The comparison and the write are one atomic step (one transaction, or one
1104
+ * synchronous step in memory), so a purge and a replay of the same key
1105
+ * cannot interleave between them. Whichever runs first, a replay of a
1106
+ * purged key is refused and a record inside the horizon is never removed.
1107
+ * The floor can fall when the store's clock steps backwards, but never below
1108
+ * the highest cutoff a purge has used, which is all a purged key needs: it
1109
+ * stays refused even if the clock steps backwards after the purge. The
1110
+ * race and floor cases in `tests/store-contract.ts` check the outcome of
1111
+ * each whole-operation order, forced through the `beforeAtomicStep` seam;
1112
+ * they cannot interpose inside the atomic step, so each implementation
1113
+ * proves its check-to-write atomicity separately (README, "Contract suite").
1114
+ */
1115
+ interface WebhookDeliveryStore {
1116
+ /**
1117
+ * In one atomic step, scrub jobs and intents scoped to tenant and space for
1118
+ * 1–100 distinct event identifiers. Durable markers scrub late inserts.
1119
+ * Repeated calls repair rows written by older versions, counting and fencing
1120
+ * only rows whose content changes; a no-op leaves fences and leases alone.
1121
+ * Erased jobs deny undecided subscriptions with `event_erased`.
1122
+ * Changed jobs and pending intents advance fences and clear leases.
1123
+ * An attempt begun before this step can still send its old body afterward;
1124
+ * its settlement loses the advanced fence and metadata-only delivery retries.
1125
+ */
1126
+ redactEventRecords(input: WebhookScope & {
1127
+ readonly eventIds: readonly string[];
1128
+ }, options: WebhookCallOptions): Promise<WebhookRedactionResult>;
1129
+ /**
1130
+ * Insert a pending job, available at `createdAt`, unless one exists for
1131
+ * `eventId` or the event is past the replay horizon.
1132
+ */
1133
+ insertFanOutJob(job: WebhookFanOutJobDraft & {
1134
+ readonly processingDeadline: string;
1135
+ readonly createdAt: string;
1136
+ }, options: WebhookCallOptions): Promise<WebhookInsertResult>;
1137
+ /**
1138
+ * Lease up to `limit` pending jobs whose `availableAt` has passed and that
1139
+ * are unleased or whose lease has expired, oldest `availableAt` first.
1140
+ * Each claim advances the job's fence. Claiming spends no attempt.
1141
+ */
1142
+ claimFanOutJobs(request: WebhookClaimRequest, options: WebhookCallOptions): Promise<readonly WebhookFanOutJob[]>;
1143
+ /** Mark a job complete once each eligible revision has an intent or recorded denial. */
1144
+ completeFanOutJob(input: WebhookFencedWrite & {
1145
+ readonly eventId: string;
1146
+ }, options: WebhookCallOptions): Promise<boolean>;
1147
+ /** Return a job to pending at `availableAt`, spending one attempt. */
1148
+ rescheduleFanOutJob(input: WebhookFencedWrite & {
1149
+ readonly eventId: string;
1150
+ readonly availableAt: Date;
1151
+ readonly diagnostic: WebhookDiagnosticCode;
1152
+ }, options: WebhookCallOptions): Promise<boolean>;
1153
+ /**
1154
+ * Extend the lease the caller holds on a job, so a worker that is still
1155
+ * making progress keeps it (PR 8). Fenced like the other job writes:
1156
+ * resolves `true` only when the job is `pending`, leased to `owner` at
1157
+ * `fence`, and the lease has not expired on the store's clock read after
1158
+ * its locks; otherwise `false`, writing nothing. On `true` the lease's
1159
+ * expiry becomes the later of its current expiry and the earlier of
1160
+ * `now + leaseDurationMs` and the job's processing deadline: a renewal
1161
+ * never shortens a lease and never extends it past the processing
1162
+ * deadline (contract case "a lease renewal extends only a lease the
1163
+ * caller holds …"). The fence, attempt count and availability are
1164
+ * unchanged.
1165
+ */
1166
+ renewFanOutJobLease(input: WebhookFencedWrite & {
1167
+ readonly eventId: string;
1168
+ readonly leaseDurationMs: number;
1169
+ }, options: WebhookCallOptions): Promise<boolean>;
1170
+ /** Move an exhausted job to dead letter. */
1171
+ deadLetterFanOutJob(input: WebhookFencedWrite & {
1172
+ readonly eventId: string;
1173
+ readonly diagnostic: WebhookDiagnosticCode;
1174
+ }, options: WebhookCallOptions): Promise<boolean>;
1175
+ /**
1176
+ * Compare-and-set a dead-lettered job back to pending with a restored
1177
+ * budget and a new processing deadline, appending requeue evidence in the
1178
+ * same step. The compare is on the fence (`request.expectedFence`) as
1179
+ * well as the status, and the requeue advances the fence, so one read
1180
+ * yields at most one applied requeue. The fence check, the status and
1181
+ * horizon checks, the transition and the evidence are one atomic step.
1182
+ * Refused past the replay horizon.
1183
+ *
1184
+ * **Re-runs of one invocation.** When the request carries an
1185
+ * `actionInvocationId`, the atomic step first looks for evidence this
1186
+ * scope holds under that id for the same target and record. If there is
1187
+ * some, the call answers `{ applied: true, replayed: true, evidence }`
1188
+ * with that evidence and changes nothing, before any other check: the
1189
+ * record may since have moved on, been claimed or been purged. So
1190
+ * requests of one governed invocation that reach this step, overlapping
1191
+ * or one after the other, apply once and each reports that application,
1192
+ * while a request under another id is checked as usual. An attempt that
1193
+ * a check before this step refused never reaches it (README, "Atomicity
1194
+ * and re-runs"). Contract cases "a request repeated
1195
+ * under its actionInvocationId …" (three).
1196
+ */
1197
+ requeueFanOutJob(input: WebhookScope & {
1198
+ readonly eventId: string;
1199
+ readonly request: WebhookRequeueRequest;
1200
+ }, options: WebhookCallOptions): Promise<WebhookRequeueResult>;
1201
+ /**
1202
+ * Repair a `legacy` job's actor (decision 5, PR 9): set the actor the
1203
+ * application read from the canonical event, return the job to pending
1204
+ * with a restored budget and a new processing deadline, advance its fence
1205
+ * and clear its lease, and append evidence with `target:
1206
+ * "fan_out_job_actor"`. The job may be `pending` (leased or not) or
1207
+ * `dead_letter`; a `completed` job is refused. Advancing the fence is what
1208
+ * stops a worker that leased the job before the repair: its later fenced
1209
+ * writes (intent and denial inserts, completion, reschedule, dead letter,
1210
+ * renewal) answer `lease_lost` or `false`. The scope, fence, status,
1211
+ * actor-state and horizon checks, the transition and the evidence are one
1212
+ * atomic step, so a `suppressed` or `present` actor is refused by the store
1213
+ * itself, whatever the caller read before. A request of an invocation that
1214
+ * already applied is answered as for {@link WebhookDeliveryStore.requeueFanOutJob}
1215
+ * ("Re-runs of one invocation").
1216
+ */
1217
+ enrichFanOutJobActor(input: WebhookScope & {
1218
+ readonly eventId: string;
1219
+ readonly request: WebhookActorEnrichmentRequest;
1220
+ }, options: WebhookCallOptions): Promise<WebhookActorEnrichmentResult>;
1221
+ /** The job for `eventId` in this scope, or `null`. */
1222
+ readFanOutJob(input: WebhookScope & {
1223
+ readonly eventId: string;
1224
+ }, options: WebhookCallOptions): Promise<WebhookFanOutJob | null>;
1225
+ /**
1226
+ * Insert an intent unless an intent, denial or tombstone exists for
1227
+ * (subscriptionId, eventId), on behalf of the fan-out job `parent`. The
1228
+ * store copies the parent job's event `recordedAt` onto the intent.
1229
+ *
1230
+ * Atomicity requirement: the check that `parent.eventId`'s job is still
1231
+ * leased to `parent.owner` at `parent.fence`, the replay-horizon check,
1232
+ * the uniqueness check and the insert are one atomic step (one
1233
+ * transaction, or one conditional write). A worker whose lease was
1234
+ * reclaimed, or whose job fence actor enrichment advanced, gets
1235
+ * `lease_lost` and writes nothing, even if it lost the lease between its
1236
+ * last read and this call. The intent's `eventId`, `tenantId` and
1237
+ * `spaceId` must match the job's (`record_mismatch` otherwise), and its
1238
+ * `deliveryId` must not belong to another pair (`delivery_id_conflict`).
1239
+ */
1240
+ insertDeliveryIntent(intent: Omit<WebhookDeliveryIntent, "status" | "attemptCount" | "lastOutcome" | "lastDiagnostic" | "lease" | "eventRecordedAt" | "fence">, parent: WebhookFanOutJobFence, options: WebhookCallOptions): Promise<WebhookFencedInsertResult>;
1241
+ /**
1242
+ * Record an admission denial unless an intent, denial or tombstone exists
1243
+ * for (subscriptionId, eventId), on behalf of the fan-out job `parent`.
1244
+ * Same atomicity requirement as {@link WebhookDeliveryStore.insertDeliveryIntent}:
1245
+ * the parent fence check, the horizon check and the insert are one atomic step.
1246
+ */
1247
+ recordAdmissionDenial(record: WebhookAdmissionDenialRecord, parent: WebhookFanOutJobFence, options: WebhookCallOptions): Promise<WebhookFencedInsertResult>;
1248
+ /**
1249
+ * Lease up to `limit` pending intents whose `availableAt` has passed and
1250
+ * that are unleased or whose lease has expired, oldest `availableAt` first.
1251
+ * Each claim advances the intent's fence. Claiming spends no attempt.
1252
+ */
1253
+ claimDeliveryIntents(request: WebhookClaimRequest, options: WebhookCallOptions): Promise<readonly WebhookDeliveryIntent[]>;
1254
+ /**
1255
+ * Extend the lease the caller holds on an intent (PR 8), with the rule
1256
+ * of {@link WebhookDeliveryStore.renewFanOutJobLease}: fenced, decided
1257
+ * on the store's clock after its locks, never shortening the lease and
1258
+ * never extending it past the intent's processing deadline. The fence,
1259
+ * attempt count and availability are unchanged.
1260
+ */
1261
+ renewDeliveryIntentLease(input: WebhookFencedWrite & {
1262
+ readonly deliveryId: string;
1263
+ readonly leaseDurationMs: number;
1264
+ }, options: WebhookCallOptions): Promise<boolean>;
1265
+ /**
1266
+ * Read-only check that the intent is still `pending` and leased to
1267
+ * `owner` at `fence` in this tenant and space, with a lease that has not
1268
+ * expired at `now`, so a caller learns that its intent was cancelled,
1269
+ * settled or re-leased even when the subscription itself is active
1270
+ * again. The delivery cycle does not call it: its last check before the
1271
+ * handoff is {@link WebhookDeliveryStore.beginAttempt}, which makes the
1272
+ * same check and records the attempt in one atomic step.
1273
+ *
1274
+ * Requirements: it changes nothing (no settlement, no lease renewal); it
1275
+ * reads the committed current row, not a cached or replica copy, so it
1276
+ * observes every settle, cancel, requeue or reclaim that committed before
1277
+ * it ran; it is bounded by `options`. A change committed after it returns
1278
+ * may still race with the handoff (R2).
1279
+ */
1280
+ checkDeliveryLease(input: WebhookScope & {
1281
+ readonly deliveryId: string;
1282
+ readonly owner: string;
1283
+ readonly fence: number;
1284
+ readonly now: Date;
1285
+ }, options: WebhookCallOptions): Promise<WebhookDeliveryLeaseCheck>;
1286
+ /**
1287
+ * Append one complete attempt to the log, fenced by the intent's lease.
1288
+ * The delivery cycle uses it for attempts that end before the transport
1289
+ * is reached (a signer failure, a refused destination). Never updates or
1290
+ * removes an earlier entry; a purge keeps the log. The attempt's
1291
+ * `subscriptionId` and `eventId` must match the intent's.
1292
+ */
1293
+ appendAttempt(input: WebhookFencedWrite & {
1294
+ readonly attempt: WebhookDeliveryAttemptRecord;
1295
+ }, options: WebhookCallOptions): Promise<boolean>;
1296
+ /**
1297
+ * Record, as one atomic step, that the lease holder is about to hand
1298
+ * `attempt` to the transport: the delivery cycle's last check before the
1299
+ * irrevocable point (R2). Resolves `{ begun: true, receipt }` and appends
1300
+ * an in-flight entry keyed by (`deliveryId`, `fence`) only when the
1301
+ * intent is `pending`, leased to `owner` at `fence`, its lease has not
1302
+ * expired on the store's clock, and no attempt has been begun under this
1303
+ * fence yet. Otherwise it resolves `{ begun: false }` and writes nothing.
1304
+ * It does not change the intent. The attempt's `subscriptionId` and
1305
+ * `eventId` must match the intent's (`record_mismatch` otherwise).
1306
+ *
1307
+ * `receipt` is an unguessable token (32 random bytes) created for this
1308
+ * attempt and returned only here. The store keeps its SHA-256 digest,
1309
+ * never the token, and no read returns either; only the worker that
1310
+ * began the attempt can record its outcome ({@link WebhookDeliveryStore.finishAttempt}).
1311
+ * The delivery cycle keeps it in a local variable only.
1312
+ *
1313
+ * The delivery cycle calls it before every handoff and hands off only on
1314
+ * a well-formed `begun: true` answer (src/delivery-cycle.ts), so every
1315
+ * request it hands to the transport has an attempt entry, even when its
1316
+ * worker loses the lease before it can settle the intent (README, "Delivery").
1317
+ */
1318
+ beginAttempt(input: WebhookFencedWrite & {
1319
+ readonly attempt: WebhookDeliveryAttemptStart;
1320
+ }, options: WebhookCallOptions): Promise<WebhookAttemptBeginResult>;
1321
+ /**
1322
+ * Append the outcome of the attempt begun under (`deliveryId`, `fence`)
1323
+ * in this scope, presenting the `receipt` its `beginAttempt` returned.
1324
+ * Not fenced: a worker whose lease was reclaimed after the handoff can
1325
+ * still record what its request observed. Bound to the receipt instead:
1326
+ * a worker that did not begin that attempt cannot record its outcome,
1327
+ * whether or not it holds the lease now. It reads and changes nothing of
1328
+ * the intent, so no worker can move the intent's state through it
1329
+ * (contract cases "a worker reclaimed after its handoff still finishes
1330
+ * its attempt …" and "only the worker that began an attempt can record
1331
+ * its outcome …"). Resolves `true` when it appended the outcome; `false`,
1332
+ * writing nothing, when no attempt was begun under that key in this
1333
+ * scope, the receipt does not match, or its outcome is already recorded
1334
+ * (the first outcome stands).
1335
+ */
1336
+ finishAttempt(input: WebhookScope & {
1337
+ readonly deliveryId: string;
1338
+ readonly fence: number;
1339
+ readonly receipt: string;
1340
+ readonly finish: WebhookDeliveryAttemptFinish;
1341
+ }, options: WebhookCallOptions): Promise<boolean>;
1342
+ /**
1343
+ * Settle an intent after an attempt or a refused check, clearing its
1344
+ * lease. `release` returns it to pending unsent without spending budget;
1345
+ * `retry` spends one attempt; `delivered`, `dead_letter` and `cancelled`
1346
+ * are terminal and leave the attempt count as it was.
1347
+ */
1348
+ settleDeliveryIntent(input: WebhookFencedWrite & {
1349
+ readonly deliveryId: string;
1350
+ readonly settlement: {
1351
+ readonly kind: "delivered";
1352
+ } | {
1353
+ readonly kind: "retry";
1354
+ readonly availableAt: Date;
1355
+ readonly outcome: WebhookAttemptOutcome;
1356
+ readonly reason: WebhookAttemptReasonCode;
1357
+ } | {
1358
+ readonly kind: "release";
1359
+ readonly availableAt: Date;
1360
+ readonly diagnostic: WebhookDiagnosticCode;
1361
+ } | {
1362
+ readonly kind: "dead_letter";
1363
+ readonly reason: WebhookAttemptReasonCode | WebhookDiagnosticCode;
1364
+ } | {
1365
+ readonly kind: "cancelled";
1366
+ readonly reason: WebhookAttemptReasonCode;
1367
+ };
1368
+ }, options: WebhookCallOptions): Promise<boolean>;
1369
+ /**
1370
+ * Cancel the pending intents of a subscription in this scope, leased or
1371
+ * not, that are bound to an eligibility revision earlier than
1372
+ * `beforeRevision`, as a pause or delete does. Returns the count. Pass the
1373
+ * revision the pause or delete wrote: then only intents for events the
1374
+ * pause ended are cancelled, and an intent bound to a later revision (one
1375
+ * written after a resume) is left alone even when this call is delayed
1376
+ * past the resume. A cancelled intent is terminal; its lease holder's
1377
+ * later fenced writes and lease checks fail. The governed actions call
1378
+ * it as a cleanup; dispatch does not depend on it (see
1379
+ * {@link WebhookSubscriptionWriter}).
1380
+ */
1381
+ cancelPendingIntents(input: WebhookScope & {
1382
+ readonly subscriptionId: string;
1383
+ readonly reason: "subscription_paused" | "subscription_deleted";
1384
+ /** Only intents whose `eligibilityRevision` is lower than this are cancelled. */
1385
+ readonly beforeRevision: number;
1386
+ readonly now: Date;
1387
+ }, options: WebhookCallOptions): Promise<number>;
1388
+ /**
1389
+ * Compare-and-set a dead-lettered intent back to pending, with the same
1390
+ * fence compare-and-set, atomicity and re-run recognition as
1391
+ * {@link WebhookDeliveryStore.requeueFanOutJob}; the evidence names the
1392
+ * intent's `subscriptionId`. Refused past the replay horizon.
1393
+ */
1394
+ requeueDeliveryIntent(input: WebhookScope & {
1395
+ readonly deliveryId: string;
1396
+ readonly request: WebhookRequeueRequest;
1397
+ }, options: WebhookCallOptions): Promise<WebhookRequeueResult>;
1398
+ /** The intent in this scope, or `null`. */
1399
+ readDeliveryIntent(input: WebhookScope & {
1400
+ readonly deliveryId: string;
1401
+ }, options: WebhookCallOptions): Promise<WebhookDeliveryIntent | null>;
1402
+ /**
1403
+ * The attempt log of one intent in this scope, in the order each entry
1404
+ * was first written: entries appended whole, begun attempts with their
1405
+ * outcome once one is recorded, and begun attempts still `in_flight`.
1406
+ */
1407
+ listDeliveryAttempts(input: WebhookScope & {
1408
+ readonly deliveryId: string;
1409
+ }, options: WebhookCallOptions): Promise<readonly WebhookDeliveryAttemptLogEntry[]>;
1410
+ /** Requeue evidence for one event's job and intents in this scope, in append order. */
1411
+ listRequeueEvidence(input: WebhookScope & {
1412
+ readonly eventId: string;
1413
+ }, options: WebhookCallOptions): Promise<readonly WebhookRequeueEvidence[]>;
1414
+ /**
1415
+ * Requeue and enrichment evidence appended under one governed
1416
+ * `actionInvocationId` in this scope, in append order, whether or not the
1417
+ * records it names still exist. The governed actions read it before they
1418
+ * read the record, so a re-run reports what its invocation applied.
1419
+ */
1420
+ listInvocationRequeueEvidence(input: WebhookScope & {
1421
+ readonly actionInvocationId: string;
1422
+ }, options: WebhookCallOptions): Promise<readonly WebhookRequeueEvidence[]>;
1423
+ /** Read the tombstone for (subscriptionId, eventId), if one is retained. */
1424
+ readTombstone(input: WebhookScope & {
1425
+ readonly subscriptionId: string;
1426
+ readonly eventId: string;
1427
+ }, options: WebhookCallOptions): Promise<WebhookTombstone | null>;
1428
+ /**
1429
+ * Remove up to `limit` terminal records whose event is older than the
1430
+ * replay horizon at the store's clock: completed and dead-lettered jobs,
1431
+ * delivered, dead-lettered and cancelled intents, and denials, each with
1432
+ * its tombstone. Pending records are never removed, so live work is not
1433
+ * lost; the cycles are to move them to a terminal state through their
1434
+ * processing deadlines (R4). The attempt log and requeue evidence are audit records
1435
+ * and are kept. The purge's cutoff raises the store's horizon floor in
1436
+ * the same step, so no later insert or requeue can recreate what it
1437
+ * removed. Oldest anchor first.
1438
+ */
1439
+ purgeExpiredRecords(input: {
1440
+ readonly limit: number;
1441
+ }, options: WebhookCallOptions): Promise<WebhookPurgeResult>;
1442
+ }
1443
+ /** What a signer signs: the timestamp and the exact body bytes (decision 7). */
1444
+ interface WebhookSignRequest {
1445
+ readonly scheme: WebhookSignatureScheme;
1446
+ /** Resolved by the signer within `tenantId`; the package never holds key material. */
1447
+ readonly secretRef: string;
1448
+ readonly tenantId: string;
1449
+ readonly spaceId: string;
1450
+ readonly timestampSeconds: number;
1451
+ readonly rawBody: string;
1452
+ }
1453
+ /**
1454
+ * Signs one attempt at send time. For `v1` it returns the `X-Fabric-Signature`
1455
+ * value `v1=<hex hmac-sha256(secret, "<timestampSeconds>.<rawBody>")>`. A
1456
+ * throw is classified `signer_unavailable` and retried.
1457
+ */
1458
+ interface WebhookSigner {
1459
+ sign(request: WebhookSignRequest, options: WebhookCallOptions): Promise<string>;
1460
+ }
1461
+ /** One resolved address of a destination host. */
1462
+ interface WebhookResolvedAddress {
1463
+ readonly address: string;
1464
+ readonly family: 4 | 6;
1465
+ }
1466
+ /** A destination that passed the URL rules and whose every resolved answer is public. */
1467
+ interface WebhookVettedDestination {
1468
+ readonly url: string;
1469
+ /** Kept for SNI and certificate hostname verification. */
1470
+ readonly hostname: string;
1471
+ readonly port: 443;
1472
+ /** The vetted addresses the transport posts to without re-resolving. */
1473
+ readonly addresses: readonly WebhookResolvedAddress[];
1474
+ }
1475
+ /** The outcome of resolving a destination. Rejections carry a fixed code, never the URL's parts. */
1476
+ type WebhookDestinationResolution = {
1477
+ readonly ok: true;
1478
+ readonly destination: WebhookVettedDestination;
1479
+ } | {
1480
+ readonly ok: false;
1481
+ readonly code: "invalid_url" | "private_address" | "dns_failure";
1482
+ };
1483
+ /**
1484
+ * Validates a destination (`https:`, port 443 only, no userinfo) and resolves
1485
+ * it, requiring every answer to be public. Called at configuration change,
1486
+ * requeue and dispatch.
1487
+ */
1488
+ interface WebhookDestinationResolver {
1489
+ resolve(url: string, options: WebhookCallOptions): Promise<WebhookDestinationResolution>;
1490
+ }
1491
+ /** One HTTP request handed to the transport. */
1492
+ interface WebhookTransportRequest {
1493
+ readonly destination: WebhookVettedDestination;
1494
+ readonly method: "POST";
1495
+ readonly headers: Readonly<Record<string, string>>;
1496
+ readonly body: string;
1497
+ }
1498
+ /**
1499
+ * What the transport observed. `not_sent` means the request never started
1500
+ * (deadline already passed, or the signal aborted while queued) and the
1501
+ * dispatcher releases the intent without spending budget. `timeout` covers a
1502
+ * deadline or abort after the request started. Response bodies are not
1503
+ * returned.
1504
+ */
1505
+ type WebhookTransportResult = {
1506
+ readonly kind: "response";
1507
+ readonly status: number;
1508
+ readonly retryAfter?: string | null;
1509
+ } | {
1510
+ readonly kind: "network_error" | "timeout" | "dns_failure" | "invalid_url" | "private_address";
1511
+ } | {
1512
+ readonly kind: "not_sent";
1513
+ readonly reason: "deadline_expired" | "aborted";
1514
+ };
1515
+ /**
1516
+ * Posts to the vetted address without re-resolving, verifies certificate and
1517
+ * hostname, refuses redirects and bounds the response. It must reject a
1518
+ * handoff whose deadline has passed and never start a request whose signal
1519
+ * aborted while queued (R3).
1520
+ */
1521
+ interface WebhookTransport {
1522
+ send(request: WebhookTransportRequest, options: WebhookCallOptions): Promise<WebhookTransportResult>;
1523
+ }
1524
+ /** What admission decides about one (subscription revision, event). */
1525
+ interface WebhookAdmissionInput {
1526
+ readonly subscriptionId: string;
1527
+ readonly eligibilityRevision: number;
1528
+ readonly event: WebhookEventMetadata;
1529
+ readonly facts: WebhookJsonObject;
1530
+ readonly actor: WebhookJobActor;
1531
+ }
1532
+ /**
1533
+ * Tri-state admission (decision 4). `unavailable` retries the job without
1534
+ * writing an intent; so does a throw or a timeout.
1535
+ */
1536
+ type WebhookAdmissionDecision = {
1537
+ readonly decision: "allow";
1538
+ } | {
1539
+ readonly decision: "deny";
1540
+ readonly code: WebhookAdmissionDenialCode;
1541
+ } | {
1542
+ readonly decision: "unavailable";
1543
+ };
1544
+ /** Decides whether an event may be delivered to a subscription at fan-out. */
1545
+ interface WebhookEventAdmission {
1546
+ admit(input: WebhookAdmissionInput, options: WebhookCallOptions): Promise<WebhookAdmissionDecision>;
1547
+ }
1548
+ /** The attempt a delivery authorization check is about. */
1549
+ interface WebhookDeliveryAuthorizationInput {
1550
+ readonly subscriptionId: string;
1551
+ readonly tenantId: string;
1552
+ readonly spaceId: string;
1553
+ readonly eventId: string;
1554
+ readonly deliveryId: string;
1555
+ readonly configurationVersion: number;
1556
+ readonly attempt: number;
1557
+ }
1558
+ /**
1559
+ * Per-attempt authorization (decision 4). `revoked` cancels the intent;
1560
+ * `unavailable`, a throw or a timeout defers it without spending budget.
1561
+ */
1562
+ type WebhookDeliveryAuthorizationDecision = {
1563
+ readonly decision: "authorized";
1564
+ } | {
1565
+ readonly decision: "revoked";
1566
+ readonly code: WebhookRevocationCode;
1567
+ } | {
1568
+ readonly decision: "unavailable";
1569
+ };
1570
+ /** Re-checks grant expiry, scope revocation and registration status before each attempt. */
1571
+ interface WebhookDeliveryAuthorization {
1572
+ authorize(input: WebhookDeliveryAuthorizationInput, options: WebhookCallOptions): Promise<WebhookDeliveryAuthorizationDecision>;
1573
+ }
1574
+ /** The injected clock. */
1575
+ interface WebhookClock {
1576
+ now(): Date;
1577
+ }
1578
+ /**
1579
+ * Delay in milliseconds before the next attempt, given the 1-based number of
1580
+ * the attempt that just failed. See `createWebhookRetryDelay`.
1581
+ */
1582
+ type WebhookRetryDelay = (attempt: number) => number;
1583
+ /** A random source returning a number in `[0, 1)`, injected so jitter is testable. */
1584
+ type WebhookRandomSource = () => number;
1585
+ /** What a scope predicate sees when deciding whether a scoped type may egress its payload. */
1586
+ interface WebhookScopeInput {
1587
+ readonly subscriptionId: string;
1588
+ readonly tenantId: string;
1589
+ readonly spaceId: string;
1590
+ readonly event: WebhookEventMetadata;
1591
+ readonly facts: WebhookJsonObject;
1592
+ }
1593
+ /**
1594
+ * Synchronous scope predicate for scoped event types (decision 9). Only a
1595
+ * literal `true` lets the payload out; `false`, any other value, a throw or a
1596
+ * missing predicate renders metadata-only.
1597
+ */
1598
+ type WebhookScopePredicate = (input: WebhookScopeInput) => boolean;
1599
+ //#endregion
1600
+ //#region src/replay.d.ts
1601
+ /** Opt-in current-head read; revision and active interval must share one snapshot. */
1602
+ interface WebhookReplaySubscriptionReader {
1603
+ readReplaySubscription(input: WebhookScope & {
1604
+ readonly subscriptionId: string;
1605
+ }, options: WebhookCallOptions): Promise<{
1606
+ readonly revision: WebhookEligibilityRevision;
1607
+ readonly statusSinceRevision: number;
1608
+ } | null>;
1609
+ }
1610
+ interface WebhookReplayKey extends WebhookScope {
1611
+ readonly actionInvocationId: string;
1612
+ }
1613
+ interface WebhookReplayJobKey extends WebhookReplayKey {
1614
+ readonly eventId: string;
1615
+ }
1616
+ interface WebhookReplayRequestInput extends WebhookReplayKey {
1617
+ readonly actionId: string;
1618
+ readonly requestedBy: {
1619
+ readonly actorId: string;
1620
+ readonly actorType: string;
1621
+ };
1622
+ readonly parameters: WebhookReplayEventsParameters;
1623
+ readonly observedRevision: WebhookEligibilityRevision;
1624
+ readonly observedStatusSinceRevision: number;
1625
+ readonly maxEvents: number;
1626
+ readonly processingLifetimeMs: number;
1627
+ }
1628
+ interface WebhookReplayReceipt extends WebhookReplayKey {
1629
+ readonly actionId: string;
1630
+ readonly requestedBy: {
1631
+ readonly actorId: string;
1632
+ readonly actorType: string;
1633
+ };
1634
+ readonly parameters: WebhookReplayEventsParameters;
1635
+ readonly observedRevision: WebhookEligibilityRevision;
1636
+ readonly observedStatusSinceRevision: number;
1637
+ readonly acceptedAt: string;
1638
+ readonly processingDeadline: string;
1639
+ readonly selected: readonly {
1640
+ readonly eventId: string;
1641
+ readonly recordedAt: string;
1642
+ }[];
1643
+ readonly count: number;
1644
+ }
1645
+ type WebhookReplayRefusal = "replay_range_invalid" | "replay_range_in_future" | "replay_limit_exceeded" | "replay_request_conflict" | "past_replay_horizon";
1646
+ type WebhookReplayRequestResult = {
1647
+ readonly applied: true;
1648
+ readonly receipt: WebhookReplayReceipt;
1649
+ readonly replayed?: true;
1650
+ } | {
1651
+ readonly applied: false;
1652
+ readonly code: WebhookReplayRefusal;
1653
+ };
1654
+ type WebhookReplaySkipReason = "subscription_not_found" | "subscription_not_active" | "active_interval_ended" | "patterns_no_longer_match";
1655
+ type WebhookReplayOutcome = {
1656
+ readonly kind: "intent";
1657
+ readonly deliveryId: string;
1658
+ } | {
1659
+ readonly kind: "denial";
1660
+ readonly code: WebhookAdmissionDenialCode;
1661
+ } | {
1662
+ readonly kind: "already_decided";
1663
+ } | {
1664
+ readonly kind: "skipped";
1665
+ readonly reason: WebhookReplaySkipReason;
1666
+ } | {
1667
+ readonly kind: "past_replay_horizon";
1668
+ } | {
1669
+ readonly kind: "dead_lettered";
1670
+ readonly diagnostic: WebhookDiagnosticCode | "source_missing";
1671
+ };
1672
+ interface WebhookReplayItem extends WebhookReplayJobKey {
1673
+ /** Fixed acceptance-time active interval and bounded pattern lists, independent of membership size. */
1674
+ readonly observedStatusSinceRevision: number;
1675
+ readonly eventTypePatterns: readonly string[];
1676
+ readonly observedEventTypePatterns: readonly string[];
1677
+ readonly subscriptionId: string;
1678
+ readonly recordedAt: string;
1679
+ readonly processingDeadline: string;
1680
+ readonly availableAt: string;
1681
+ readonly attemptCount: number;
1682
+ readonly fence: number;
1683
+ readonly lease?: WebhookLease;
1684
+ readonly outcome?: WebhookReplayOutcome;
1685
+ readonly evaluatedRevision?: number;
1686
+ readonly settledAt?: string;
1687
+ }
1688
+ type WebhookReplaySettlement = {
1689
+ readonly kind: "intent";
1690
+ readonly sourceFence: number;
1691
+ readonly evaluatedRevision: number;
1692
+ readonly deliveryId: string;
1693
+ readonly body: string;
1694
+ readonly deliveryLifetimeMs: number;
1695
+ } | {
1696
+ readonly kind: "denial";
1697
+ readonly sourceFence: number;
1698
+ readonly evaluatedRevision: number;
1699
+ readonly code: WebhookAdmissionDenialCode;
1700
+ } | {
1701
+ readonly kind: "already_decided";
1702
+ } | {
1703
+ readonly kind: "skip";
1704
+ readonly reason: WebhookReplaySkipReason;
1705
+ readonly evaluatedRevision?: number;
1706
+ } | {
1707
+ readonly kind: "retry";
1708
+ readonly availableAt: string;
1709
+ readonly diagnostic: WebhookDiagnosticCode;
1710
+ } | {
1711
+ readonly kind: "dead_letter";
1712
+ readonly diagnostic: WebhookDiagnosticCode | "source_missing";
1713
+ };
1714
+ type WebhookReplaySettlementResult = "settled" | "rescheduled" | "lease_lost" | "source_changed" | "pair_not_decided";
1715
+ /** Separate capability: existing custom delivery ports do not implement replay implicitly. */
1716
+ interface WebhookReplayStore {
1717
+ readReplayRequest(input: WebhookReplayKey, options: WebhookCallOptions): Promise<WebhookReplayReceipt | null>;
1718
+ createReplayRequest(input: WebhookReplayRequestInput, options: WebhookCallOptions): Promise<WebhookReplayRequestResult>;
1719
+ claimReplayJobs(request: WebhookClaimRequest, options: WebhookCallOptions): Promise<readonly WebhookReplayItem[]>;
1720
+ renewReplayJobLease(input: WebhookReplayJobKey & WebhookFencedWrite & {
1721
+ readonly leaseDurationMs: number;
1722
+ }, options: WebhookCallOptions): Promise<boolean>;
1723
+ settleReplayJob(input: WebhookReplayJobKey & WebhookFencedWrite & {
1724
+ readonly settlement: WebhookReplaySettlement;
1725
+ }, options: WebhookCallOptions): Promise<WebhookReplaySettlementResult>;
1726
+ listReplayItems(input: WebhookReplayKey & {
1727
+ readonly afterEventId?: string;
1728
+ readonly limit: number;
1729
+ }, options: WebhookCallOptions): Promise<{
1730
+ readonly items: readonly WebhookReplayItem[];
1731
+ readonly more: boolean;
1732
+ }>;
1733
+ }
1734
+ interface WebhookReplayOptions {
1735
+ readonly store: WebhookReplayStore;
1736
+ readonly subscriptions: WebhookReplaySubscriptionReader;
1737
+ /** Default 10,000. Requests above the cap are refused atomically. */
1738
+ readonly maxEvents?: number;
1739
+ /** Default one hour; persisted at acceptance and unchanged on reclaim. */
1740
+ readonly processingLifetimeMs?: number;
1741
+ }
1742
+ export declare const DEFAULT_WEBHOOK_REPLAY: Readonly<{
1743
+ maxEvents: 10000;
1744
+ processingLifetimeMs: 3600000;
1745
+ }>;
1746
+ //#endregion
1747
+ //#region src/memory-delivery-store.d.ts
78
1748
  /** Options for {@link MemoryWebhookDeliveryStore}. */
79
1749
  interface MemoryWebhookDeliveryStoreOptions {
80
- /** The store's clock: it timestamps requeue evidence and decides the replay horizon. */
81
- readonly clock: WebhookClock;
82
- /** The replay horizon in milliseconds; see `WebhookDeliveryLimits.replayHorizonMs`. */
83
- readonly replayHorizonMs: number;
84
- /**
85
- * Test seam. Awaited at each call's I/O boundary, after its input is read
86
- * and before its atomic step, with the method name. A contract test uses
87
- * it to hold one call there while another runs to completion, forcing an
88
- * interleaving. A throw or rejection fails the call as `store_failed`,
89
- * with nothing written. Leave it unset outside tests.
90
- */
91
- readonly beforeAtomicStep?: (operation: string) => Promise<void> | void;
1750
+ /** The store's clock: it timestamps requeue evidence and decides the replay horizon. */
1751
+ readonly clock: WebhookClock;
1752
+ /** The replay horizon in milliseconds; see `WebhookDeliveryLimits.replayHorizonMs`. */
1753
+ readonly replayHorizonMs: number;
1754
+ /**
1755
+ * Test seam. Awaited at each call's I/O boundary, after its input is read
1756
+ * and before its atomic step, with the method name. A contract test uses
1757
+ * it to hold one call there while another runs to completion, forcing an
1758
+ * interleaving. A throw or rejection fails the call as `store_failed`,
1759
+ * with nothing written. Leave it unset outside tests.
1760
+ */
1761
+ readonly beforeAtomicStep?: (operation: string) => Promise<void> | void;
92
1762
  }
93
1763
  /**
94
1764
  * An in-memory {@link WebhookDeliveryStore}. Construct one per test or local
@@ -98,107 +1768,127 @@ interface MemoryWebhookDeliveryStoreOptions {
98
1768
  * positive whole number of milliseconds within `WEBHOOK_MAX_REPLAY_HORIZON_MS`.
99
1769
  * @throws {WebhookStoreError} `clock_invalid` when `clock` has no `now` function.
100
1770
  */
101
- declare class MemoryWebhookDeliveryStore implements WebhookDeliveryStore {
102
- #private;
103
- constructor(options: MemoryWebhookDeliveryStoreOptions);
104
- /** The replay horizon this store enforces, in milliseconds. */
105
- get replayHorizonMs(): number;
106
- insertFanOutJob(job: WebhookFanOutJobDraft & {
107
- readonly processingDeadline: string;
108
- readonly createdAt: string;
109
- }, options: WebhookCallOptions): Promise<WebhookInsertResult>;
110
- claimFanOutJobs(request: WebhookClaimRequest, options: WebhookCallOptions): Promise<readonly WebhookFanOutJob[]>;
111
- completeFanOutJob(input: WebhookFencedWrite & {
112
- readonly eventId: string;
113
- }, options: WebhookCallOptions): Promise<boolean>;
114
- rescheduleFanOutJob(input: WebhookFencedWrite & {
115
- readonly eventId: string;
116
- readonly availableAt: Date;
117
- readonly diagnostic: WebhookDiagnosticCode;
118
- }, options: WebhookCallOptions): Promise<boolean>;
119
- renewFanOutJobLease(input: WebhookFencedWrite & {
120
- readonly eventId: string;
121
- readonly leaseDurationMs: number;
122
- }, options: WebhookCallOptions): Promise<boolean>;
123
- deadLetterFanOutJob(input: WebhookFencedWrite & {
124
- readonly eventId: string;
125
- readonly diagnostic: WebhookDiagnosticCode;
126
- }, options: WebhookCallOptions): Promise<boolean>;
127
- requeueFanOutJob(input: WebhookScope & {
128
- readonly eventId: string;
129
- readonly request: WebhookRequeueRequest;
130
- }, options: WebhookCallOptions): Promise<WebhookRequeueResult>;
131
- enrichFanOutJobActor(input: WebhookScope & {
132
- readonly eventId: string;
133
- readonly request: WebhookActorEnrichmentRequest;
134
- }, options: WebhookCallOptions): Promise<WebhookActorEnrichmentResult>;
135
- readFanOutJob(input: WebhookScope & {
136
- readonly eventId: string;
137
- }, options: WebhookCallOptions): Promise<WebhookFanOutJob | null>;
138
- insertDeliveryIntent(intent: Omit<WebhookDeliveryIntent, "status" | "attemptCount" | "lastOutcome" | "lastDiagnostic" | "lease" | "eventRecordedAt" | "fence">, parent: WebhookFanOutJobFence, options: WebhookCallOptions): Promise<WebhookFencedInsertResult>;
139
- recordAdmissionDenial(record: WebhookAdmissionDenialRecord, parent: WebhookFanOutJobFence, options: WebhookCallOptions): Promise<WebhookFencedInsertResult>;
140
- claimDeliveryIntents(request: WebhookClaimRequest, options: WebhookCallOptions): Promise<readonly WebhookDeliveryIntent[]>;
141
- renewDeliveryIntentLease(input: WebhookFencedWrite & {
142
- readonly deliveryId: string;
143
- readonly leaseDurationMs: number;
144
- }, options: WebhookCallOptions): Promise<boolean>;
145
- checkDeliveryLease(input: WebhookScope & {
146
- readonly deliveryId: string;
147
- readonly owner: string;
148
- readonly fence: number;
149
- readonly now: Date;
150
- }, options: WebhookCallOptions): Promise<WebhookDeliveryLeaseCheck>;
151
- appendAttempt(input: WebhookFencedWrite & {
152
- readonly attempt: WebhookDeliveryAttemptRecord;
153
- }, options: WebhookCallOptions): Promise<boolean>;
154
- beginAttempt(input: WebhookFencedWrite & {
155
- readonly attempt: WebhookDeliveryAttemptStart;
156
- }, options: WebhookCallOptions): Promise<WebhookAttemptBeginResult>;
157
- finishAttempt(input: WebhookScope & {
158
- readonly deliveryId: string;
159
- readonly fence: number;
160
- readonly receipt: string;
161
- readonly finish: WebhookDeliveryAttemptFinish;
162
- }, options: WebhookCallOptions): Promise<boolean>;
163
- settleDeliveryIntent(input: Parameters<WebhookDeliveryStore["settleDeliveryIntent"]>[0], options: WebhookCallOptions): Promise<boolean>;
164
- cancelPendingIntents(input: Parameters<WebhookDeliveryStore["cancelPendingIntents"]>[0], options: WebhookCallOptions): Promise<number>;
165
- requeueDeliveryIntent(input: WebhookScope & {
166
- readonly deliveryId: string;
167
- readonly request: WebhookRequeueRequest;
168
- }, options: WebhookCallOptions): Promise<WebhookRequeueResult>;
169
- readDeliveryIntent(input: WebhookScope & {
170
- readonly deliveryId: string;
171
- }, options: WebhookCallOptions): Promise<WebhookDeliveryIntent | null>;
172
- listDeliveryAttempts(input: WebhookScope & {
173
- readonly deliveryId: string;
174
- }, options: WebhookCallOptions): Promise<readonly WebhookDeliveryAttemptLogEntry[]>;
175
- listRequeueEvidence(input: WebhookScope & {
176
- readonly eventId: string;
177
- }, options: WebhookCallOptions): Promise<readonly WebhookRequeueEvidence[]>;
178
- listInvocationRequeueEvidence(input: WebhookScope & {
179
- readonly actionInvocationId: string;
180
- }, options: WebhookCallOptions): Promise<readonly WebhookRequeueEvidence[]>;
181
- readTombstone(input: WebhookScope & {
182
- readonly subscriptionId: string;
183
- readonly eventId: string;
184
- }, options: WebhookCallOptions): Promise<WebhookTombstone | null>;
185
- purgeExpiredRecords(input: {
186
- readonly limit: number;
187
- }, options: WebhookCallOptions): Promise<WebhookPurgeResult>;
188
- }
189
-
1771
+ export declare class MemoryWebhookDeliveryStore implements WebhookDeliveryStore, WebhookReplayStore {
1772
+ #private;
1773
+ constructor(options: MemoryWebhookDeliveryStoreOptions);
1774
+ /** The replay horizon this store enforces, in milliseconds. */
1775
+ get replayHorizonMs(): number;
1776
+ redactEventRecords(input: WebhookScope & {
1777
+ readonly eventIds: readonly string[];
1778
+ }, options: WebhookCallOptions): Promise<WebhookRedactionResult>;
1779
+ insertFanOutJob(job: WebhookFanOutJobDraft & {
1780
+ readonly processingDeadline: string;
1781
+ readonly createdAt: string;
1782
+ }, options: WebhookCallOptions): Promise<WebhookInsertResult>;
1783
+ claimFanOutJobs(request: WebhookClaimRequest, options: WebhookCallOptions): Promise<readonly WebhookFanOutJob[]>;
1784
+ completeFanOutJob(input: WebhookFencedWrite & {
1785
+ readonly eventId: string;
1786
+ }, options: WebhookCallOptions): Promise<boolean>;
1787
+ rescheduleFanOutJob(input: WebhookFencedWrite & {
1788
+ readonly eventId: string;
1789
+ readonly availableAt: Date;
1790
+ readonly diagnostic: WebhookDiagnosticCode;
1791
+ }, options: WebhookCallOptions): Promise<boolean>;
1792
+ renewFanOutJobLease(input: WebhookFencedWrite & {
1793
+ readonly eventId: string;
1794
+ readonly leaseDurationMs: number;
1795
+ }, options: WebhookCallOptions): Promise<boolean>;
1796
+ deadLetterFanOutJob(input: WebhookFencedWrite & {
1797
+ readonly eventId: string;
1798
+ readonly diagnostic: WebhookDiagnosticCode;
1799
+ }, options: WebhookCallOptions): Promise<boolean>;
1800
+ requeueFanOutJob(input: WebhookScope & {
1801
+ readonly eventId: string;
1802
+ readonly request: WebhookRequeueRequest;
1803
+ }, options: WebhookCallOptions): Promise<WebhookRequeueResult>;
1804
+ enrichFanOutJobActor(input: WebhookScope & {
1805
+ readonly eventId: string;
1806
+ readonly request: WebhookActorEnrichmentRequest;
1807
+ }, options: WebhookCallOptions): Promise<WebhookActorEnrichmentResult>;
1808
+ readFanOutJob(input: WebhookScope & {
1809
+ readonly eventId: string;
1810
+ }, options: WebhookCallOptions): Promise<WebhookFanOutJob | null>;
1811
+ insertDeliveryIntent(intent: Omit<WebhookDeliveryIntent, "status" | "attemptCount" | "lastOutcome" | "lastDiagnostic" | "lease" | "eventRecordedAt" | "fence">, parent: WebhookFanOutJobFence, options: WebhookCallOptions): Promise<WebhookFencedInsertResult>;
1812
+ recordAdmissionDenial(record: WebhookAdmissionDenialRecord, parent: WebhookFanOutJobFence, options: WebhookCallOptions): Promise<WebhookFencedInsertResult>;
1813
+ claimDeliveryIntents(request: WebhookClaimRequest, options: WebhookCallOptions): Promise<readonly WebhookDeliveryIntent[]>;
1814
+ renewDeliveryIntentLease(input: WebhookFencedWrite & {
1815
+ readonly deliveryId: string;
1816
+ readonly leaseDurationMs: number;
1817
+ }, options: WebhookCallOptions): Promise<boolean>;
1818
+ checkDeliveryLease(input: WebhookScope & {
1819
+ readonly deliveryId: string;
1820
+ readonly owner: string;
1821
+ readonly fence: number;
1822
+ readonly now: Date;
1823
+ }, options: WebhookCallOptions): Promise<WebhookDeliveryLeaseCheck>;
1824
+ appendAttempt(input: WebhookFencedWrite & {
1825
+ readonly attempt: WebhookDeliveryAttemptRecord;
1826
+ }, options: WebhookCallOptions): Promise<boolean>;
1827
+ beginAttempt(input: WebhookFencedWrite & {
1828
+ readonly attempt: WebhookDeliveryAttemptStart;
1829
+ }, options: WebhookCallOptions): Promise<WebhookAttemptBeginResult>;
1830
+ finishAttempt(input: WebhookScope & {
1831
+ readonly deliveryId: string;
1832
+ readonly fence: number;
1833
+ readonly receipt: string;
1834
+ readonly finish: WebhookDeliveryAttemptFinish;
1835
+ }, options: WebhookCallOptions): Promise<boolean>;
1836
+ settleDeliveryIntent(input: Parameters<WebhookDeliveryStore["settleDeliveryIntent"]>[0], options: WebhookCallOptions): Promise<boolean>;
1837
+ cancelPendingIntents(input: Parameters<WebhookDeliveryStore["cancelPendingIntents"]>[0], options: WebhookCallOptions): Promise<number>;
1838
+ requeueDeliveryIntent(input: WebhookScope & {
1839
+ readonly deliveryId: string;
1840
+ readonly request: WebhookRequeueRequest;
1841
+ }, options: WebhookCallOptions): Promise<WebhookRequeueResult>;
1842
+ readDeliveryIntent(input: WebhookScope & {
1843
+ readonly deliveryId: string;
1844
+ }, options: WebhookCallOptions): Promise<WebhookDeliveryIntent | null>;
1845
+ listDeliveryAttempts(input: WebhookScope & {
1846
+ readonly deliveryId: string;
1847
+ }, options: WebhookCallOptions): Promise<readonly WebhookDeliveryAttemptLogEntry[]>;
1848
+ listRequeueEvidence(input: WebhookScope & {
1849
+ readonly eventId: string;
1850
+ }, options: WebhookCallOptions): Promise<readonly WebhookRequeueEvidence[]>;
1851
+ listInvocationRequeueEvidence(input: WebhookScope & {
1852
+ readonly actionInvocationId: string;
1853
+ }, options: WebhookCallOptions): Promise<readonly WebhookRequeueEvidence[]>;
1854
+ readTombstone(input: WebhookScope & {
1855
+ readonly subscriptionId: string;
1856
+ readonly eventId: string;
1857
+ }, options: WebhookCallOptions): Promise<WebhookTombstone | null>;
1858
+ readReplayRequest(input: WebhookReplayKey, options: WebhookCallOptions): Promise<WebhookReplayReceipt | null>;
1859
+ createReplayRequest(input: WebhookReplayRequestInput, options: WebhookCallOptions): Promise<WebhookReplayRequestResult>;
1860
+ claimReplayJobs(request: WebhookClaimRequest, options: WebhookCallOptions): Promise<readonly WebhookReplayItem[]>;
1861
+ renewReplayJobLease(input: WebhookReplayJobKey & WebhookFencedWrite & {
1862
+ readonly leaseDurationMs: number;
1863
+ }, options: WebhookCallOptions): Promise<boolean>;
1864
+ settleReplayJob(input: WebhookReplayJobKey & WebhookFencedWrite & {
1865
+ readonly settlement: WebhookReplaySettlement;
1866
+ }, options: WebhookCallOptions): Promise<WebhookReplaySettlementResult>;
1867
+ listReplayItems(input: WebhookReplayKey & {
1868
+ readonly afterEventId?: string;
1869
+ readonly limit: number;
1870
+ }, options: WebhookCallOptions): Promise<{
1871
+ readonly items: readonly WebhookReplayItem[];
1872
+ readonly more: boolean;
1873
+ }>;
1874
+ purgeExpiredRecords(input: {
1875
+ readonly limit: number;
1876
+ }, options: WebhookCallOptions): Promise<WebhookPurgeResult>;
1877
+ }
1878
+ //#endregion
1879
+ //#region src/memory-subscription-store.d.ts
190
1880
  /** Options for {@link MemoryWebhookSubscriptionStore}. */
191
1881
  interface MemoryWebhookSubscriptionStoreOptions {
192
- /** The store's clock: every revision takes effect at its time (decision 3). */
193
- readonly clock: WebhookClock;
194
- /**
195
- * Test seam. Awaited at each call's I/O boundary, after its input is read
196
- * and before its atomic step, with the method name. A contract test uses
197
- * it to hold one call there while another runs to completion, forcing an
198
- * interleaving. A throw or rejection fails the call as `store_failed`,
199
- * with nothing written. Leave it unset outside tests.
200
- */
201
- readonly beforeAtomicStep?: (operation: string) => Promise<void> | void;
1882
+ /** The store's clock: every revision takes effect at its time (decision 3). */
1883
+ readonly clock: WebhookClock;
1884
+ /**
1885
+ * Test seam. Awaited at each call's I/O boundary, after its input is read
1886
+ * and before its atomic step, with the method name. A contract test uses
1887
+ * it to hold one call there while another runs to completion, forcing an
1888
+ * interleaving. A throw or rejection fails the call as `store_failed`,
1889
+ * with nothing written. Leave it unset outside tests.
1890
+ */
1891
+ readonly beforeAtomicStep?: (operation: string) => Promise<void> | void;
202
1892
  }
203
1893
  /**
204
1894
  * An in-memory {@link WebhookSubscriptionStore} and {@link WebhookSubscriptionWriter}.
@@ -207,85 +1897,93 @@ interface MemoryWebhookSubscriptionStoreOptions {
207
1897
  *
208
1898
  * @throws {WebhookStoreError} `clock_invalid` when `clock` has no `now` function.
209
1899
  */
210
- declare class MemoryWebhookSubscriptionStore implements WebhookSubscriptionStore, WebhookSubscriptionWriter {
211
- #private;
212
- constructor(options: MemoryWebhookSubscriptionStoreOptions);
213
- listEligibilityRevisions(input: WebhookScope & {
214
- readonly recordedAt: string;
215
- }, options: WebhookCallOptions): Promise<readonly WebhookEligibilityRevision[]>;
216
- readSubscription(input: WebhookScope & {
217
- readonly subscriptionId: string;
218
- }, options: WebhookCallOptions): Promise<WebhookSubscriptionSnapshot | null>;
219
- listEligibilityHistory(input: WebhookScope & {
220
- readonly subscriptionId: string;
221
- }, options: WebhookCallOptions): Promise<readonly WebhookEligibilityRevision[]>;
222
- createSubscription(input: WebhookScope & {
223
- readonly subscriptionId: string;
224
- readonly eventTypePatterns: readonly string[];
225
- readonly configuration: WebhookDeliveryConfigurationInput;
226
- }, options: WebhookCallOptions): Promise<WebhookSubscriptionWriteResult>;
227
- changeEventTypePatterns(input: WebhookSubscriptionTransitionInput & {
228
- readonly eventTypePatterns: readonly string[];
229
- }, options: WebhookCallOptions): Promise<WebhookSubscriptionWriteResult>;
230
- pauseSubscription(input: WebhookSubscriptionTransitionInput, options: WebhookCallOptions): Promise<WebhookSubscriptionWriteResult>;
231
- resumeSubscription(input: WebhookSubscriptionTransitionInput, options: WebhookCallOptions): Promise<WebhookSubscriptionWriteResult>;
232
- deleteSubscription(input: WebhookSubscriptionTransitionInput, options: WebhookCallOptions): Promise<WebhookSubscriptionWriteResult>;
233
- replaceDeliveryConfiguration(input: WebhookScope & {
234
- readonly subscriptionId: string;
235
- readonly configuration: WebhookDeliveryConfigurationInput;
236
- readonly expectedVersion?: number;
237
- }, options: WebhookCallOptions): Promise<WebhookConfigurationWriteResult>;
238
- }
239
-
1900
+ export declare class MemoryWebhookSubscriptionStore implements WebhookSubscriptionStore, WebhookSubscriptionWriter {
1901
+ #private;
1902
+ constructor(options: MemoryWebhookSubscriptionStoreOptions);
1903
+ listEligibilityRevisions(input: WebhookScope & {
1904
+ readonly recordedAt: string;
1905
+ }, options: WebhookCallOptions): Promise<readonly WebhookEligibilityRevision[]>;
1906
+ readSubscription(input: WebhookScope & {
1907
+ readonly subscriptionId: string;
1908
+ }, options: WebhookCallOptions): Promise<WebhookSubscriptionSnapshot | null>;
1909
+ readReplaySubscription(input: WebhookScope & {
1910
+ readonly subscriptionId: string;
1911
+ }, options: WebhookCallOptions): Promise<{
1912
+ readonly revision: WebhookEligibilityRevision;
1913
+ readonly statusSinceRevision: number;
1914
+ } | null>;
1915
+ listEligibilityHistory(input: WebhookScope & {
1916
+ readonly subscriptionId: string;
1917
+ }, options: WebhookCallOptions): Promise<readonly WebhookEligibilityRevision[]>;
1918
+ createSubscription(input: WebhookScope & {
1919
+ readonly subscriptionId: string;
1920
+ readonly eventTypePatterns: readonly string[];
1921
+ readonly configuration: WebhookDeliveryConfigurationInput;
1922
+ }, options: WebhookCallOptions): Promise<WebhookSubscriptionWriteResult>;
1923
+ changeEventTypePatterns(input: WebhookSubscriptionTransitionInput & {
1924
+ readonly eventTypePatterns: readonly string[];
1925
+ }, options: WebhookCallOptions): Promise<WebhookSubscriptionWriteResult>;
1926
+ pauseSubscription(input: WebhookSubscriptionTransitionInput, options: WebhookCallOptions): Promise<WebhookSubscriptionWriteResult>;
1927
+ resumeSubscription(input: WebhookSubscriptionTransitionInput, options: WebhookCallOptions): Promise<WebhookSubscriptionWriteResult>;
1928
+ deleteSubscription(input: WebhookSubscriptionTransitionInput, options: WebhookCallOptions): Promise<WebhookSubscriptionWriteResult>;
1929
+ replaceDeliveryConfiguration(input: WebhookScope & {
1930
+ readonly subscriptionId: string;
1931
+ readonly configuration: WebhookDeliveryConfigurationInput;
1932
+ readonly expectedVersion?: number;
1933
+ }, options: WebhookCallOptions): Promise<WebhookConfigurationWriteResult>;
1934
+ }
1935
+ //#endregion
1936
+ //#region src/postgres-sql.d.ts
240
1937
  /** The rows a query returned. */
241
1938
  interface WebhookPostgresResult<Row = Record<string, unknown>> {
242
- rows: Row[];
1939
+ rows: Row[];
243
1940
  }
244
1941
  /** Anything that runs one parameterized statement. */
245
1942
  interface WebhookPostgresQueryable {
246
- query<Row = Record<string, unknown>>(sql: string, values?: unknown[]): Promise<WebhookPostgresResult<Row>>;
1943
+ query<Row = Record<string, unknown>>(sql: string, values?: unknown[]): Promise<WebhookPostgresResult<Row>>;
247
1944
  }
248
1945
  /** One checked-out connection. `release(true)` discards it instead of returning it to the pool. */
249
1946
  interface WebhookPostgresConnection extends WebhookPostgresQueryable {
250
- release(destroy?: boolean): void;
1947
+ release(destroy?: boolean): void;
251
1948
  }
252
1949
  /** A connection pool: `pg.Pool`, or a Lakebase-compatible client with the same two methods. */
253
1950
  interface WebhookPostgresPool extends WebhookPostgresQueryable {
254
- connect(): Promise<WebhookPostgresConnection>;
1951
+ connect(): Promise<WebhookPostgresConnection>;
255
1952
  }
256
1953
  /** Where the store pauses inside a transaction, for the serialization tests only. */
257
1954
  type WebhookTransactionCheckpointStage = "locked" | "written";
258
-
1955
+ //#endregion
1956
+ //#region src/postgres-delivery-store.d.ts
259
1957
  /** Options for {@link PostgresWebhookDeliveryStore}. */
260
1958
  interface PostgresWebhookDeliveryStoreOptions {
261
- /** The connection pool, for example a `pg.Pool`. The store never ends it. */
262
- readonly pool: WebhookPostgresPool;
263
- /** The store's clock: it timestamps requeue evidence and decides the replay horizon. */
264
- readonly clock: WebhookClock;
265
- /** The replay horizon in milliseconds; see `WebhookDeliveryLimits.replayHorizonMs`. */
266
- readonly replayHorizonMs: number;
267
- /**
268
- * The schema the tables live in, default `fabric_webhook_delivery`.
269
- * Lower-case letters, digits and underscores only. The horizon floor is
270
- * per schema: two stores on one schema share one floor.
271
- */
272
- readonly schema?: string;
273
- /**
274
- * Test seam, with the memory store's meaning: awaited once per call,
275
- * with the method name, after the input is read and before a connection
276
- * is taken. Leave it unset outside tests.
277
- */
278
- readonly beforeAtomicStep?: (operation: string) => Promise<void> | void;
279
- /**
280
- * Test seam for the serialization tests. Awaited inside the transaction
281
- * of each horizon-checked method and of the purge: at `locked`, after the
282
- * horizon lock, the purge-cutoff read and the record locks, and before
283
- * the clock is read or anything is written; and at `written`, after the
284
- * write and before the commit (a call that returns a refusal does not
285
- * reach it). A throw or rejection rolls the transaction back and fails
286
- * the call as `store_failed`. Leave it unset outside tests.
287
- */
288
- readonly transactionCheckpoint?: (operation: string, stage: WebhookTransactionCheckpointStage) => Promise<void> | void;
1959
+ /** The connection pool, for example a `pg.Pool`. The store never ends it. */
1960
+ readonly pool: WebhookPostgresPool;
1961
+ /** The store's clock: it timestamps requeue evidence and decides the replay horizon. */
1962
+ readonly clock: WebhookClock;
1963
+ /** The replay horizon in milliseconds; see `WebhookDeliveryLimits.replayHorizonMs`. */
1964
+ readonly replayHorizonMs: number;
1965
+ /**
1966
+ * The schema the tables live in, default `fabric_webhook_delivery`.
1967
+ * Lower-case letters, digits and underscores only. The horizon floor is
1968
+ * per schema: two stores on one schema share one floor.
1969
+ */
1970
+ readonly schema?: string;
1971
+ /**
1972
+ * Test seam, with the memory store's meaning: awaited once per call,
1973
+ * with the method name, after the input is read and before a connection
1974
+ * is taken. Leave it unset outside tests.
1975
+ */
1976
+ readonly beforeAtomicStep?: (operation: string) => Promise<void> | void;
1977
+ /**
1978
+ * Test seam for the serialization tests. Awaited inside the transaction
1979
+ * of each horizon-checked method and of the purge: at `locked`, after the
1980
+ * horizon lock, the purge-cutoff read and the record locks, and before
1981
+ * the clock is read or anything is written; and at `written`, after the
1982
+ * write and before the commit (a call that returns a refusal does not
1983
+ * reach it). A throw or rejection rolls the transaction back and fails
1984
+ * the call as `store_failed`. Leave it unset outside tests.
1985
+ */
1986
+ readonly transactionCheckpoint?: (operation: string, stage: WebhookTransactionCheckpointStage) => Promise<void> | void;
289
1987
  }
290
1988
  /**
291
1989
  * A PostgreSQL {@link WebhookDeliveryStore}. Call {@link ensureSchema} once
@@ -296,112 +1994,132 @@ interface PostgresWebhookDeliveryStoreOptions {
296
1994
  * @throws {WebhookStoreError} `clock_invalid` when `clock` has no `now`
297
1995
  * function; `input_invalid` when `pool`, `schema` or a test seam is not usable.
298
1996
  */
299
- declare class PostgresWebhookDeliveryStore implements WebhookDeliveryStore {
300
- #private;
301
- constructor(options: PostgresWebhookDeliveryStoreOptions);
302
- /** The replay horizon this store enforces, in milliseconds. */
303
- get replayHorizonMs(): number;
304
- /**
305
- * Create the schema and tables, or check that the applied migrations are
306
- * unchanged. Safe to call concurrently and repeatedly: an advisory lock
307
- * serializes callers and each migration is recorded once
308
- * (`applyWebhookMigrations` in src/postgres-sql.ts).
309
- */
310
- ensureSchema(): Promise<void>;
311
- insertFanOutJob(job: WebhookFanOutJobDraft & {
312
- readonly processingDeadline: string;
313
- readonly createdAt: string;
314
- }, options: WebhookCallOptions): Promise<WebhookInsertResult>;
315
- claimFanOutJobs(request: WebhookClaimRequest, options: WebhookCallOptions): Promise<readonly WebhookFanOutJob[]>;
316
- completeFanOutJob(input: WebhookFencedWrite & {
317
- readonly eventId: string;
318
- }, options: WebhookCallOptions): Promise<boolean>;
319
- rescheduleFanOutJob(input: WebhookFencedWrite & {
320
- readonly eventId: string;
321
- readonly availableAt: Date;
322
- readonly diagnostic: WebhookDiagnosticCode;
323
- }, options: WebhookCallOptions): Promise<boolean>;
324
- renewFanOutJobLease(input: WebhookFencedWrite & {
325
- readonly eventId: string;
326
- readonly leaseDurationMs: number;
327
- }, options: WebhookCallOptions): Promise<boolean>;
328
- deadLetterFanOutJob(input: WebhookFencedWrite & {
329
- readonly eventId: string;
330
- readonly diagnostic: WebhookDiagnosticCode;
331
- }, options: WebhookCallOptions): Promise<boolean>;
332
- requeueFanOutJob(input: WebhookScope & {
333
- readonly eventId: string;
334
- readonly request: WebhookRequeueRequest;
335
- }, options: WebhookCallOptions): Promise<WebhookRequeueResult>;
336
- enrichFanOutJobActor(input: WebhookScope & {
337
- readonly eventId: string;
338
- readonly request: WebhookActorEnrichmentRequest;
339
- }, options: WebhookCallOptions): Promise<WebhookActorEnrichmentResult>;
340
- readFanOutJob(input: WebhookScope & {
341
- readonly eventId: string;
342
- }, options: WebhookCallOptions): Promise<WebhookFanOutJob | null>;
343
- insertDeliveryIntent(intent: Omit<WebhookDeliveryIntent, "status" | "attemptCount" | "lastOutcome" | "lastDiagnostic" | "lease" | "eventRecordedAt" | "fence">, parent: WebhookFanOutJobFence, options: WebhookCallOptions): Promise<WebhookFencedInsertResult>;
344
- recordAdmissionDenial(record: WebhookAdmissionDenialRecord, parent: WebhookFanOutJobFence, options: WebhookCallOptions): Promise<WebhookFencedInsertResult>;
345
- claimDeliveryIntents(request: WebhookClaimRequest, options: WebhookCallOptions): Promise<readonly WebhookDeliveryIntent[]>;
346
- renewDeliveryIntentLease(input: WebhookFencedWrite & {
347
- readonly deliveryId: string;
348
- readonly leaseDurationMs: number;
349
- }, options: WebhookCallOptions): Promise<boolean>;
350
- checkDeliveryLease(input: WebhookScope & {
351
- readonly deliveryId: string;
352
- readonly owner: string;
353
- readonly fence: number;
354
- readonly now: Date;
355
- }, options: WebhookCallOptions): Promise<WebhookDeliveryLeaseCheck>;
356
- appendAttempt(input: WebhookFencedWrite & {
357
- readonly attempt: WebhookDeliveryAttemptRecord;
358
- }, options: WebhookCallOptions): Promise<boolean>;
359
- beginAttempt(input: WebhookFencedWrite & {
360
- readonly attempt: WebhookDeliveryAttemptStart;
361
- }, options: WebhookCallOptions): Promise<WebhookAttemptBeginResult>;
362
- finishAttempt(input: WebhookScope & {
363
- readonly deliveryId: string;
364
- readonly fence: number;
365
- readonly receipt: string;
366
- readonly finish: WebhookDeliveryAttemptFinish;
367
- }, options: WebhookCallOptions): Promise<boolean>;
368
- settleDeliveryIntent(input: Parameters<WebhookDeliveryStore["settleDeliveryIntent"]>[0], options: WebhookCallOptions): Promise<boolean>;
369
- cancelPendingIntents(input: Parameters<WebhookDeliveryStore["cancelPendingIntents"]>[0], options: WebhookCallOptions): Promise<number>;
370
- requeueDeliveryIntent(input: WebhookScope & {
371
- readonly deliveryId: string;
372
- readonly request: WebhookRequeueRequest;
373
- }, options: WebhookCallOptions): Promise<WebhookRequeueResult>;
374
- readDeliveryIntent(input: WebhookScope & {
375
- readonly deliveryId: string;
376
- }, options: WebhookCallOptions): Promise<WebhookDeliveryIntent | null>;
377
- listDeliveryAttempts(input: WebhookScope & {
378
- readonly deliveryId: string;
379
- }, options: WebhookCallOptions): Promise<readonly WebhookDeliveryAttemptLogEntry[]>;
380
- listRequeueEvidence(input: WebhookScope & {
381
- readonly eventId: string;
382
- }, options: WebhookCallOptions): Promise<readonly WebhookRequeueEvidence[]>;
383
- listInvocationRequeueEvidence(input: WebhookScope & {
384
- readonly actionInvocationId: string;
385
- }, options: WebhookCallOptions): Promise<readonly WebhookRequeueEvidence[]>;
386
- readTombstone(input: WebhookScope & {
387
- readonly subscriptionId: string;
388
- readonly eventId: string;
389
- }, options: WebhookCallOptions): Promise<WebhookTombstone | null>;
390
- purgeExpiredRecords(input: {
391
- readonly limit: number;
392
- }, options: WebhookCallOptions): Promise<WebhookPurgeResult>;
393
- }
394
-
1997
+ export declare class PostgresWebhookDeliveryStore implements WebhookDeliveryStore, WebhookReplayStore {
1998
+ #private;
1999
+ constructor(options: PostgresWebhookDeliveryStoreOptions);
2000
+ /** The replay horizon this store enforces, in milliseconds. */
2001
+ get replayHorizonMs(): number;
2002
+ /**
2003
+ * Create the schema and tables, or check that the applied migrations are
2004
+ * unchanged. Safe to call concurrently and repeatedly: an advisory lock
2005
+ * serializes callers and each migration is recorded once
2006
+ * (`applyWebhookMigrations` in src/postgres-sql.ts).
2007
+ */
2008
+ ensureSchema(): Promise<void>;
2009
+ redactEventRecords(input: WebhookScope & {
2010
+ readonly eventIds: readonly string[];
2011
+ }, options: WebhookCallOptions): Promise<WebhookRedactionResult>;
2012
+ insertFanOutJob(job: WebhookFanOutJobDraft & {
2013
+ readonly processingDeadline: string;
2014
+ readonly createdAt: string;
2015
+ }, options: WebhookCallOptions): Promise<WebhookInsertResult>;
2016
+ claimFanOutJobs(request: WebhookClaimRequest, options: WebhookCallOptions): Promise<readonly WebhookFanOutJob[]>;
2017
+ completeFanOutJob(input: WebhookFencedWrite & {
2018
+ readonly eventId: string;
2019
+ }, options: WebhookCallOptions): Promise<boolean>;
2020
+ rescheduleFanOutJob(input: WebhookFencedWrite & {
2021
+ readonly eventId: string;
2022
+ readonly availableAt: Date;
2023
+ readonly diagnostic: WebhookDiagnosticCode;
2024
+ }, options: WebhookCallOptions): Promise<boolean>;
2025
+ renewFanOutJobLease(input: WebhookFencedWrite & {
2026
+ readonly eventId: string;
2027
+ readonly leaseDurationMs: number;
2028
+ }, options: WebhookCallOptions): Promise<boolean>;
2029
+ deadLetterFanOutJob(input: WebhookFencedWrite & {
2030
+ readonly eventId: string;
2031
+ readonly diagnostic: WebhookDiagnosticCode;
2032
+ }, options: WebhookCallOptions): Promise<boolean>;
2033
+ requeueFanOutJob(input: WebhookScope & {
2034
+ readonly eventId: string;
2035
+ readonly request: WebhookRequeueRequest;
2036
+ }, options: WebhookCallOptions): Promise<WebhookRequeueResult>;
2037
+ enrichFanOutJobActor(input: WebhookScope & {
2038
+ readonly eventId: string;
2039
+ readonly request: WebhookActorEnrichmentRequest;
2040
+ }, options: WebhookCallOptions): Promise<WebhookActorEnrichmentResult>;
2041
+ readFanOutJob(input: WebhookScope & {
2042
+ readonly eventId: string;
2043
+ }, options: WebhookCallOptions): Promise<WebhookFanOutJob | null>;
2044
+ insertDeliveryIntent(intent: Omit<WebhookDeliveryIntent, "status" | "attemptCount" | "lastOutcome" | "lastDiagnostic" | "lease" | "eventRecordedAt" | "fence">, parent: WebhookFanOutJobFence, options: WebhookCallOptions): Promise<WebhookFencedInsertResult>;
2045
+ recordAdmissionDenial(record: WebhookAdmissionDenialRecord, parent: WebhookFanOutJobFence, options: WebhookCallOptions): Promise<WebhookFencedInsertResult>;
2046
+ claimDeliveryIntents(request: WebhookClaimRequest, options: WebhookCallOptions): Promise<readonly WebhookDeliveryIntent[]>;
2047
+ renewDeliveryIntentLease(input: WebhookFencedWrite & {
2048
+ readonly deliveryId: string;
2049
+ readonly leaseDurationMs: number;
2050
+ }, options: WebhookCallOptions): Promise<boolean>;
2051
+ checkDeliveryLease(input: WebhookScope & {
2052
+ readonly deliveryId: string;
2053
+ readonly owner: string;
2054
+ readonly fence: number;
2055
+ readonly now: Date;
2056
+ }, options: WebhookCallOptions): Promise<WebhookDeliveryLeaseCheck>;
2057
+ appendAttempt(input: WebhookFencedWrite & {
2058
+ readonly attempt: WebhookDeliveryAttemptRecord;
2059
+ }, options: WebhookCallOptions): Promise<boolean>;
2060
+ beginAttempt(input: WebhookFencedWrite & {
2061
+ readonly attempt: WebhookDeliveryAttemptStart;
2062
+ }, options: WebhookCallOptions): Promise<WebhookAttemptBeginResult>;
2063
+ finishAttempt(input: WebhookScope & {
2064
+ readonly deliveryId: string;
2065
+ readonly fence: number;
2066
+ readonly receipt: string;
2067
+ readonly finish: WebhookDeliveryAttemptFinish;
2068
+ }, options: WebhookCallOptions): Promise<boolean>;
2069
+ settleDeliveryIntent(input: Parameters<WebhookDeliveryStore["settleDeliveryIntent"]>[0], options: WebhookCallOptions): Promise<boolean>;
2070
+ cancelPendingIntents(input: Parameters<WebhookDeliveryStore["cancelPendingIntents"]>[0], options: WebhookCallOptions): Promise<number>;
2071
+ requeueDeliveryIntent(input: WebhookScope & {
2072
+ readonly deliveryId: string;
2073
+ readonly request: WebhookRequeueRequest;
2074
+ }, options: WebhookCallOptions): Promise<WebhookRequeueResult>;
2075
+ readDeliveryIntent(input: WebhookScope & {
2076
+ readonly deliveryId: string;
2077
+ }, options: WebhookCallOptions): Promise<WebhookDeliveryIntent | null>;
2078
+ listDeliveryAttempts(input: WebhookScope & {
2079
+ readonly deliveryId: string;
2080
+ }, options: WebhookCallOptions): Promise<readonly WebhookDeliveryAttemptLogEntry[]>;
2081
+ listRequeueEvidence(input: WebhookScope & {
2082
+ readonly eventId: string;
2083
+ }, options: WebhookCallOptions): Promise<readonly WebhookRequeueEvidence[]>;
2084
+ listInvocationRequeueEvidence(input: WebhookScope & {
2085
+ readonly actionInvocationId: string;
2086
+ }, options: WebhookCallOptions): Promise<readonly WebhookRequeueEvidence[]>;
2087
+ readTombstone(input: WebhookScope & {
2088
+ readonly subscriptionId: string;
2089
+ readonly eventId: string;
2090
+ }, options: WebhookCallOptions): Promise<WebhookTombstone | null>;
2091
+ readReplayRequest(input: WebhookReplayKey, options: WebhookCallOptions): Promise<WebhookReplayReceipt | null>;
2092
+ createReplayRequest(input: WebhookReplayRequestInput, options: WebhookCallOptions): Promise<WebhookReplayRequestResult>;
2093
+ claimReplayJobs(request: WebhookClaimRequest, options: WebhookCallOptions): Promise<readonly WebhookReplayItem[]>;
2094
+ renewReplayJobLease(input: WebhookReplayJobKey & WebhookFencedWrite & {
2095
+ readonly leaseDurationMs: number;
2096
+ }, options: WebhookCallOptions): Promise<boolean>;
2097
+ settleReplayJob(input: WebhookReplayJobKey & WebhookFencedWrite & {
2098
+ readonly settlement: WebhookReplaySettlement;
2099
+ }, options: WebhookCallOptions): Promise<WebhookReplaySettlementResult>;
2100
+ listReplayItems(input: WebhookReplayKey & {
2101
+ readonly afterEventId?: string;
2102
+ readonly limit: number;
2103
+ }, options: WebhookCallOptions): Promise<{
2104
+ readonly items: readonly WebhookReplayItem[];
2105
+ readonly more: boolean;
2106
+ }>;
2107
+ purgeExpiredRecords(input: {
2108
+ readonly limit: number;
2109
+ }, options: WebhookCallOptions): Promise<WebhookPurgeResult>;
2110
+ }
2111
+ //#endregion
2112
+ //#region src/postgres-subscription-store.d.ts
395
2113
  /** Options for {@link PostgresWebhookSubscriptionStore}. */
396
2114
  interface PostgresWebhookSubscriptionStoreOptions {
397
- /** The connection pool, for example a `pg.Pool`. The store never ends it. */
398
- readonly pool: WebhookPostgresPool;
399
- /** The store's clock: every revision takes effect at its time (decision 3). */
400
- readonly clock: WebhookClock;
401
- /** The schema the tables live in, default `fabric_webhook_delivery`. It may be the delivery store's schema. */
402
- readonly schema?: string;
403
- /** Test seam, with the memory store's meaning. Leave it unset outside tests. */
404
- readonly beforeAtomicStep?: (operation: string) => Promise<void> | void;
2115
+ /** The connection pool, for example a `pg.Pool`. The store never ends it. */
2116
+ readonly pool: WebhookPostgresPool;
2117
+ /** The store's clock: every revision takes effect at its time (decision 3). */
2118
+ readonly clock: WebhookClock;
2119
+ /** The schema the tables live in, default `fabric_webhook_delivery`. It may be the delivery store's schema. */
2120
+ readonly schema?: string;
2121
+ /** Test seam, with the memory store's meaning. Leave it unset outside tests. */
2122
+ readonly beforeAtomicStep?: (operation: string) => Promise<void> | void;
405
2123
  }
406
2124
  /**
407
2125
  * A PostgreSQL {@link WebhookSubscriptionStore} and {@link WebhookSubscriptionWriter}.
@@ -412,86 +2130,93 @@ interface PostgresWebhookSubscriptionStoreOptions {
412
2130
  * @throws {WebhookStoreError} `clock_invalid` when `clock` has no `now`
413
2131
  * function; `input_invalid` when `pool`, `schema` or the test seam is not usable.
414
2132
  */
415
- declare class PostgresWebhookSubscriptionStore implements WebhookSubscriptionStore, WebhookSubscriptionWriter {
416
- #private;
417
- constructor(options: PostgresWebhookSubscriptionStoreOptions);
418
- /** Create the schema and tables, or check the applied migrations. Safe to call concurrently and repeatedly. */
419
- ensureSchema(): Promise<void>;
420
- listEligibilityRevisions(input: WebhookScope & {
421
- readonly recordedAt: string;
422
- }, options: WebhookCallOptions): Promise<readonly WebhookEligibilityRevision[]>;
423
- readSubscription(input: WebhookScope & {
424
- readonly subscriptionId: string;
425
- }, options: WebhookCallOptions): Promise<WebhookSubscriptionSnapshot | null>;
426
- listEligibilityHistory(input: WebhookScope & {
427
- readonly subscriptionId: string;
428
- }, options: WebhookCallOptions): Promise<readonly WebhookEligibilityRevision[]>;
429
- createSubscription(input: WebhookScope & {
430
- readonly subscriptionId: string;
431
- readonly eventTypePatterns: readonly string[];
432
- readonly configuration: WebhookDeliveryConfigurationInput;
433
- }, options: WebhookCallOptions): Promise<WebhookSubscriptionWriteResult>;
434
- changeEventTypePatterns(input: WebhookSubscriptionTransitionInput & {
435
- readonly eventTypePatterns: readonly string[];
436
- }, options: WebhookCallOptions): Promise<WebhookSubscriptionWriteResult>;
437
- pauseSubscription(input: WebhookSubscriptionTransitionInput, options: WebhookCallOptions): Promise<WebhookSubscriptionWriteResult>;
438
- resumeSubscription(input: WebhookSubscriptionTransitionInput, options: WebhookCallOptions): Promise<WebhookSubscriptionWriteResult>;
439
- deleteSubscription(input: WebhookSubscriptionTransitionInput, options: WebhookCallOptions): Promise<WebhookSubscriptionWriteResult>;
440
- replaceDeliveryConfiguration(input: WebhookScope & {
441
- readonly subscriptionId: string;
442
- readonly configuration: WebhookDeliveryConfigurationInput;
443
- readonly expectedVersion?: number;
444
- }, options: WebhookCallOptions): Promise<WebhookConfigurationWriteResult>;
445
- }
446
-
2133
+ export declare class PostgresWebhookSubscriptionStore implements WebhookSubscriptionStore, WebhookSubscriptionWriter {
2134
+ #private;
2135
+ constructor(options: PostgresWebhookSubscriptionStoreOptions);
2136
+ /** Create the schema and tables, or check the applied migrations. Safe to call concurrently and repeatedly. */
2137
+ ensureSchema(): Promise<void>;
2138
+ listEligibilityRevisions(input: WebhookScope & {
2139
+ readonly recordedAt: string;
2140
+ }, options: WebhookCallOptions): Promise<readonly WebhookEligibilityRevision[]>;
2141
+ readSubscription(input: WebhookScope & {
2142
+ readonly subscriptionId: string;
2143
+ }, options: WebhookCallOptions): Promise<WebhookSubscriptionSnapshot | null>;
2144
+ readReplaySubscription(input: WebhookScope & {
2145
+ readonly subscriptionId: string;
2146
+ }, options: WebhookCallOptions): Promise<{
2147
+ readonly revision: WebhookEligibilityRevision;
2148
+ readonly statusSinceRevision: number;
2149
+ } | null>;
2150
+ listEligibilityHistory(input: WebhookScope & {
2151
+ readonly subscriptionId: string;
2152
+ }, options: WebhookCallOptions): Promise<readonly WebhookEligibilityRevision[]>;
2153
+ createSubscription(input: WebhookScope & {
2154
+ readonly subscriptionId: string;
2155
+ readonly eventTypePatterns: readonly string[];
2156
+ readonly configuration: WebhookDeliveryConfigurationInput;
2157
+ }, options: WebhookCallOptions): Promise<WebhookSubscriptionWriteResult>;
2158
+ changeEventTypePatterns(input: WebhookSubscriptionTransitionInput & {
2159
+ readonly eventTypePatterns: readonly string[];
2160
+ }, options: WebhookCallOptions): Promise<WebhookSubscriptionWriteResult>;
2161
+ pauseSubscription(input: WebhookSubscriptionTransitionInput, options: WebhookCallOptions): Promise<WebhookSubscriptionWriteResult>;
2162
+ resumeSubscription(input: WebhookSubscriptionTransitionInput, options: WebhookCallOptions): Promise<WebhookSubscriptionWriteResult>;
2163
+ deleteSubscription(input: WebhookSubscriptionTransitionInput, options: WebhookCallOptions): Promise<WebhookSubscriptionWriteResult>;
2164
+ replaceDeliveryConfiguration(input: WebhookScope & {
2165
+ readonly subscriptionId: string;
2166
+ readonly configuration: WebhookDeliveryConfigurationInput;
2167
+ readonly expectedVersion?: number;
2168
+ }, options: WebhookCallOptions): Promise<WebhookConfigurationWriteResult>;
2169
+ }
2170
+ //#endregion
2171
+ //#region src/limits.d.ts
447
2172
  /**
448
2173
  * Every bound the delivery lifecycle relies on (R4). All values are
449
2174
  * milliseconds or counts and must be positive and finite;
450
2175
  * {@link resolveWebhookDeliveryLimits} enforces that.
451
2176
  */
452
2177
  interface WebhookDeliveryLimits {
453
- /** Attempts per delivery intent before dead letter, including the first. */
454
- readonly maxDeliveryAttempts: number;
455
- /** Delays before attempts 2, 3, ...; the last entry repeats if attempts outnumber it. */
456
- readonly deliveryRetryDelaysMs: readonly number[];
457
- /** Processing deadline persisted on an intent at creation. */
458
- readonly deliveryDeadlineMs: number;
459
- /** Attempts per fan-out job before dead letter. */
460
- readonly maxFanOutAttempts: number;
461
- /** Fan-out retry delays; the last entry repeats. */
462
- readonly fanOutRetryDelaysMs: readonly number[];
463
- /** Processing deadline persisted on a fan-out job at creation. */
464
- readonly fanOutDeadlineMs: number;
465
- /** Bound on one transport call, from handoff to settled response. */
466
- readonly attemptTimeoutMs: number;
467
- /** Bound on every other awaited port call, stores included. */
468
- readonly portTimeoutMs: number;
469
- /** Jitter added to a retry delay, as a fraction of it, in `(0, 1]`. */
470
- readonly retryJitterRatio: number;
471
- /** The longest `Retry-After` honoured; longer requests are clamped to it. */
472
- readonly maxRetryAfterMs: number;
473
- /**
474
- * The replay horizon (R5): how long after an event's `recordedAt` the
475
- * delivery store accepts a fan-out job, intent or denial for it, accepts a
476
- * requeue of one, and keeps its records and tombstones. A replay older
477
- * than this is rejected as `past_horizon`. It must be a whole number of
478
- * milliseconds no shorter than `fanOutDeadlineMs + deliveryDeadlineMs`,
479
- * and should be much longer: it also has to cover the Host relay budget,
480
- * how long a Host outbox dead letter may wait for requeue, and how long an
481
- * operator may take to requeue a webhook dead letter. Pass the same value
482
- * to the delivery store.
483
- */
484
- readonly replayHorizonMs: number;
2178
+ /** Attempts per delivery intent before dead letter, including the first. */
2179
+ readonly maxDeliveryAttempts: number;
2180
+ /** Delays before attempts 2, 3, ...; the last entry repeats if attempts outnumber it. */
2181
+ readonly deliveryRetryDelaysMs: readonly number[];
2182
+ /** Processing deadline persisted on an intent at creation. */
2183
+ readonly deliveryDeadlineMs: number;
2184
+ /** Attempts per fan-out job before dead letter. */
2185
+ readonly maxFanOutAttempts: number;
2186
+ /** Fan-out retry delays; the last entry repeats. */
2187
+ readonly fanOutRetryDelaysMs: readonly number[];
2188
+ /** Processing deadline persisted on a fan-out job at creation. */
2189
+ readonly fanOutDeadlineMs: number;
2190
+ /** Bound on one transport call, from handoff to settled response. */
2191
+ readonly attemptTimeoutMs: number;
2192
+ /** Bound on every other awaited port call, stores included. */
2193
+ readonly portTimeoutMs: number;
2194
+ /** Jitter added to a retry delay, as a fraction of it, in `(0, 1]`. */
2195
+ readonly retryJitterRatio: number;
2196
+ /** The longest `Retry-After` honoured; longer requests are clamped to it. */
2197
+ readonly maxRetryAfterMs: number;
2198
+ /**
2199
+ * The replay horizon (R5): how long after an event's `recordedAt` the
2200
+ * delivery store accepts a fan-out job, intent or denial for it, accepts a
2201
+ * requeue of one, and keeps its records and tombstones. A replay older
2202
+ * than this is rejected as `past_horizon`. It must be a whole number of
2203
+ * milliseconds no shorter than `fanOutDeadlineMs + deliveryDeadlineMs`,
2204
+ * and should be much longer: it also has to cover the Host relay budget,
2205
+ * how long a Host outbox dead letter may wait for requeue, and how long an
2206
+ * operator may take to requeue a webhook dead letter. Pass the same value
2207
+ * to the delivery store.
2208
+ */
2209
+ readonly replayHorizonMs: number;
485
2210
  }
486
2211
  /** The largest millisecond offset a JavaScript `Date` represents, so `now - horizon` stays a valid time. */
487
- declare const WEBHOOK_MAX_REPLAY_HORIZON_MS = 8640000000000000;
2212
+ export declare const WEBHOOK_MAX_REPLAY_HORIZON_MS = 8640000000000000;
488
2213
  /** The largest delay a JavaScript timer accepts without overflowing. */
489
- declare const WEBHOOK_MAX_TIMER_MS = 2147483647;
2214
+ export declare const WEBHOOK_MAX_TIMER_MS = 2147483647;
490
2215
  /**
491
2216
  * Defaults. Delivery: eight attempts with AutoRefi's first seven delays
492
2217
  * (1m, 5m, 15m, 1h, 6h, 12h, 24h), per ADR 0018 decision 10.
493
2218
  */
494
- declare const DEFAULT_WEBHOOK_DELIVERY_LIMITS: WebhookDeliveryLimits;
2219
+ export declare const DEFAULT_WEBHOOK_DELIVERY_LIMITS: WebhookDeliveryLimits;
495
2220
  /**
496
2221
  * Merge overrides onto {@link DEFAULT_WEBHOOK_DELIVERY_LIMITS} and validate
497
2222
  * every limit. An override left `undefined` keeps the default. The
@@ -504,16 +2229,17 @@ declare const DEFAULT_WEBHOOK_DELIVERY_LIMITS: WebhookDeliveryLimits;
504
2229
  * `NaN`, infinite, fractional where a count is required, out of range, or
505
2230
  * cannot be read as plain data (`limit_unreadable`).
506
2231
  */
507
- declare function resolveWebhookDeliveryLimits(overrides?: Partial<WebhookDeliveryLimits>): WebhookDeliveryLimits;
508
-
2232
+ export declare function resolveWebhookDeliveryLimits(overrides?: Partial<WebhookDeliveryLimits>): WebhookDeliveryLimits;
2233
+ //#endregion
2234
+ //#region src/retry.d.ts
509
2235
  /** Options for {@link createWebhookRetryDelay}. */
510
2236
  interface WebhookRetryDelayOptions {
511
- /** Base delays before attempts 2, 3, ...; defaults to the delivery schedule. The last entry repeats. */
512
- readonly delaysMs?: readonly number[];
513
- /** Jitter as a fraction of the base delay, in `(0, 1]`; defaults to 0.1. */
514
- readonly jitterRatio?: number;
515
- /** Random source in `[0, 1)`. Required so jitter is never hidden process state. */
516
- readonly random: WebhookRandomSource;
2237
+ /** Base delays before attempts 2, 3, ...; defaults to the delivery schedule. The last entry repeats. */
2238
+ readonly delaysMs?: readonly number[];
2239
+ /** Jitter as a fraction of the base delay, in `(0, 1]`; defaults to 0.1. */
2240
+ readonly jitterRatio?: number;
2241
+ /** Random source in `[0, 1)`. Required so jitter is never hidden process state. */
2242
+ readonly random: WebhookRandomSource;
517
2243
  }
518
2244
  /**
519
2245
  * Build the retry delay port. For the 1-based number of the attempt that just
@@ -531,8 +2257,9 @@ interface WebhookRetryDelayOptions {
531
2257
  * and `TypeError` `random_source_failed` when the random source throws; the
532
2258
  * source's exception is dropped unread.
533
2259
  */
534
- declare function createWebhookRetryDelay(options: WebhookRetryDelayOptions): WebhookRetryDelay;
535
-
2260
+ export declare function createWebhookRetryDelay(options: WebhookRetryDelayOptions): WebhookRetryDelay;
2261
+ //#endregion
2262
+ //#region src/classify.d.ts
536
2263
  /**
537
2264
  * Everything decision 10's table classifies: a transport observation that
538
2265
  * reached the network, or a check that stopped the attempt. A transport
@@ -540,35 +2267,35 @@ declare function createWebhookRetryDelay(options: WebhookRetryDelayOptions): Web
540
2267
  * without spending budget.
541
2268
  */
542
2269
  type WebhookDeliveryObservation = Exclude<WebhookTransportResult, {
543
- readonly kind: "not_sent";
2270
+ readonly kind: "not_sent";
544
2271
  }> | {
545
- readonly kind: "signer_unavailable" | "authorization_revoked" | "subscription_paused" | "subscription_deleted";
2272
+ readonly kind: "signer_unavailable" | "authorization_revoked" | "subscription_paused" | "subscription_deleted";
546
2273
  };
547
2274
  /** The classified attempt. */
548
2275
  interface WebhookDeliveryClassification {
549
- readonly outcome: WebhookAttemptOutcome;
550
- readonly reason: WebhookAttemptReasonCode;
551
- /** The HTTP status, when a response was received. */
552
- readonly httpStatus?: number;
553
- /**
554
- * For a 429 with a parseable `Retry-After`: the requested delay, clamped
555
- * to `[0, maxRetryAfterMs]`. The dispatcher is expected to schedule the
556
- * later of this and the retry schedule's delay.
557
- */
558
- readonly retryAfterMs?: number;
559
- /**
560
- * `true` for 401 and 403: still `retryable`, but usually needing the
561
- * receiver to act, so the caller surfaces it to operators before the retry
562
- * budget is exhausted.
563
- */
564
- readonly receiverActionLikely?: true;
2276
+ readonly outcome: WebhookAttemptOutcome;
2277
+ readonly reason: WebhookAttemptReasonCode;
2278
+ /** The HTTP status, when a response was received. */
2279
+ readonly httpStatus?: number;
2280
+ /**
2281
+ * For a 429 with a parseable `Retry-After`: the requested delay, clamped
2282
+ * to `[0, maxRetryAfterMs]`. The dispatcher is expected to schedule the
2283
+ * later of this and the retry schedule's delay.
2284
+ */
2285
+ readonly retryAfterMs?: number;
2286
+ /**
2287
+ * `true` for 401 and 403: still `retryable`, but usually needing the
2288
+ * receiver to act, so the caller surfaces it to operators before the retry
2289
+ * budget is exhausted.
2290
+ */
2291
+ readonly receiverActionLikely?: true;
565
2292
  }
566
2293
  /** Inputs the classifier needs beyond the observation. */
567
2294
  interface WebhookClassifyOptions {
568
- /** Used to turn an HTTP-date `Retry-After` into a delay. */
569
- readonly now: Date;
570
- /** Upper bound on an honoured `Retry-After`. */
571
- readonly maxRetryAfterMs: number;
2295
+ /** Used to turn an HTTP-date `Retry-After` into a delay. */
2296
+ readonly now: Date;
2297
+ /** Upper bound on an honoured `Retry-After`. */
2298
+ readonly maxRetryAfterMs: number;
572
2299
  }
573
2300
  /**
574
2301
  * Classify one attempt by ADR 0018 decision 10:
@@ -591,7 +2318,7 @@ interface WebhookClassifyOptions {
591
2318
  * message is constant and never repeats the input.
592
2319
  * @throws {WebhookDeliveryConfigError} when `maxRetryAfterMs` is not positive and finite.
593
2320
  */
594
- declare function classifyWebhookDeliveryObservation(observation: WebhookDeliveryObservation, options: WebhookClassifyOptions): WebhookDeliveryClassification;
2321
+ export declare function classifyWebhookDeliveryObservation(observation: WebhookDeliveryObservation, options: WebhookClassifyOptions): WebhookDeliveryClassification;
595
2322
  /**
596
2323
  * Parse `Retry-After` as delta-seconds or an IMF-fixdate HTTP-date and clamp
597
2324
  * it to `[0, maxRetryAfterMs]`. An absent or unparseable value returns
@@ -601,17 +2328,18 @@ declare function classifyWebhookDeliveryObservation(observation: WebhookDelivery
601
2328
  * @throws {WebhookDeliveryConfigError} when `maxRetryAfterMs` is not positive and finite.
602
2329
  * @throws {TypeError} `input_unreadable` for options that cannot be read.
603
2330
  */
604
- declare function parseRetryAfterMs(value: string | null | undefined, options: WebhookClassifyOptions): number | undefined;
605
-
2331
+ export declare function parseRetryAfterMs(value: string | null | undefined, options: WebhookClassifyOptions): number | undefined;
2332
+ //#endregion
2333
+ //#region src/persistence-projection.d.ts
606
2334
  /** Inputs to {@link projectWebhookFanOutJob}. */
607
2335
  interface WebhookPersistenceProjectionOptions {
608
- /** The application's per-event-type egress allowlist and declared facts. */
609
- readonly policy: WebhookPayloadPolicy;
610
- /**
611
- * The actor Host passed with the envelope, or why it is absent. Defaults to
612
- * `{ state: "absent", reason: "legacy" }`.
613
- */
614
- readonly actor?: WebhookJobActor;
2336
+ /** The application's per-event-type egress allowlist and declared facts. */
2337
+ readonly policy: WebhookPayloadPolicy;
2338
+ /**
2339
+ * The actor Host passed with the envelope, or why it is absent. Defaults to
2340
+ * `{ state: "absent", reason: "legacy" }`.
2341
+ */
2342
+ readonly actor?: WebhookJobActor;
615
2343
  }
616
2344
  /**
617
2345
  * The persistence projection (ADR 0018 decision 2). Host copies
@@ -634,7 +2362,7 @@ interface WebhookPersistenceProjectionOptions {
634
2362
  * inspected. The message is constant for the code; no error keeps an input
635
2363
  * value or an input-raised error.
636
2364
  */
637
- declare function projectWebhookFanOutJob(envelope: WebhookSourceEnvelope, options: WebhookPersistenceProjectionOptions): WebhookFanOutJobDraft;
2365
+ export declare function projectWebhookFanOutJob(envelope: WebhookSourceEnvelope, options: WebhookPersistenceProjectionOptions): WebhookFanOutJobDraft;
638
2366
  /**
639
2367
  * Envelope metadata as a serializable record: identifiers, ordering,
640
2368
  * classification and consistency. Dates become ISO-8601 strings computed
@@ -644,36 +2372,37 @@ declare function projectWebhookFanOutJob(envelope: WebhookSourceEnvelope, option
644
2372
  * @throws {WebhookProjectionError} for a missing identifier, an invalid
645
2373
  * date, an accessor property or a value that cannot be inspected.
646
2374
  */
647
- declare function projectWebhookEventMetadata(envelope: unknown): WebhookEventMetadata;
648
-
2375
+ export declare function projectWebhookEventMetadata(envelope: unknown): WebhookEventMetadata;
2376
+ //#endregion
2377
+ //#region src/egress.d.ts
649
2378
  /** The v1 body's `format` value. Receivers read it from the raw body. */
650
- declare const WEBHOOK_DELIVERY_FORMAT_V1: "fabric.webhook-delivery.v1";
2379
+ export declare const WEBHOOK_DELIVERY_FORMAT_V1: "fabric.webhook-delivery.v1";
651
2380
  /** The subscription a body is rendered for. */
652
2381
  interface WebhookEgressSubscription {
653
- readonly subscriptionId: string;
654
- readonly tenantId: string;
655
- readonly spaceId: string;
2382
+ readonly subscriptionId: string;
2383
+ readonly tenantId: string;
2384
+ readonly spaceId: string;
656
2385
  }
657
2386
  /** Inputs to {@link projectWebhookEgressPayload}. */
658
2387
  interface WebhookEgressProjectionInput {
659
- /** The fan-out job; only what the persistence projection kept can leave. */
660
- readonly job: WebhookFanOutJobDraft;
661
- readonly subscription: WebhookEgressSubscription;
662
- /** The current allowlist; it is re-applied, so a narrowed entry takes effect. */
663
- readonly policy: WebhookPayloadPolicy;
664
- /** Consulted only for event types whose entry sets `scoped`. */
665
- readonly scopePredicate?: WebhookScopePredicate;
2388
+ /** The fan-out job; only what the persistence projection kept can leave. */
2389
+ readonly job: WebhookFanOutJobDraft;
2390
+ readonly subscription: WebhookEgressSubscription;
2391
+ /** The current allowlist; it is re-applied, so a narrowed entry takes effect. */
2392
+ readonly policy: WebhookPayloadPolicy;
2393
+ /** Consulted only for event types whose entry sets `scoped`. */
2394
+ readonly scopePredicate?: WebhookScopePredicate;
666
2395
  }
667
2396
  /** The payload that may leave for one subscription, and why it is metadata-only when it is. */
668
2397
  interface WebhookEgressPayload {
669
- readonly payload: WebhookJsonObject | null;
670
- readonly payloadProjected: boolean;
671
- /**
672
- * Why the payload was withheld: no allowlist entry, nothing persisted, a
673
- * restricted event without opt-in, or a scoped type whose predicate was
674
- * missing, threw, or did not return `true`.
675
- */
676
- readonly withheld?: "no_allowlist_entry" | "not_persisted" | "restricted" | "scope_predicate_missing" | "scope_predicate_failed" | "scope_predicate_false";
2398
+ readonly payload: WebhookJsonObject | null;
2399
+ readonly payloadProjected: boolean;
2400
+ /**
2401
+ * Why the payload was withheld: no allowlist entry, nothing persisted, a
2402
+ * restricted event without opt-in, or a scoped type whose predicate was
2403
+ * missing, threw, or did not return `true`.
2404
+ */
2405
+ readonly withheld?: "no_allowlist_entry" | "not_persisted" | "restricted" | "scope_predicate_missing" | "scope_predicate_failed" | "scope_predicate_false";
677
2406
  }
678
2407
  /**
679
2408
  * The egress projection (ADR 0018 decision 9). The payload leaves only when
@@ -690,16 +2419,16 @@ interface WebhookEgressPayload {
690
2419
  * names another tenant or space than the job; other codes when the job or
691
2420
  * subscription cannot be read safely.
692
2421
  */
693
- declare function projectWebhookEgressPayload(input: WebhookEgressProjectionInput): WebhookEgressPayload;
2422
+ export declare function projectWebhookEgressPayload(input: WebhookEgressProjectionInput): WebhookEgressPayload;
694
2423
  /** Inputs to {@link renderWebhookDeliveryBodyV1}. */
695
2424
  interface WebhookRenderInput extends WebhookEgressProjectionInput {
696
- readonly deliveryId: string;
2425
+ readonly deliveryId: string;
697
2426
  }
698
2427
  /** A rendered v1 body: the object and the exact string that is stored, signed and sent. */
699
2428
  interface WebhookRenderedBody {
700
- readonly body: WebhookDeliveryBodyV1;
701
- readonly rawBody: string;
702
- readonly withheld?: WebhookEgressPayload["withheld"];
2429
+ readonly body: WebhookDeliveryBodyV1;
2430
+ readonly rawBody: string;
2431
+ readonly withheld?: WebhookEgressPayload["withheld"];
703
2432
  }
704
2433
  /**
705
2434
  * Render the `fabric.webhook-delivery.v1` body (ADR 0018 decision 7) in the
@@ -710,16 +2439,17 @@ interface WebhookRenderedBody {
710
2439
  * and `rawBody` is serialized by the package from that snapshot, so no
711
2440
  * `toJSON`, getter or prototype member of any object takes part.
712
2441
  */
713
- declare function renderWebhookDeliveryBodyV1(input: WebhookRenderInput): WebhookRenderedBody;
714
-
2442
+ export declare function renderWebhookDeliveryBodyV1(input: WebhookRenderInput): WebhookRenderedBody;
2443
+ //#endregion
2444
+ //#region src/headers.d.ts
715
2445
  /** Header names of a v1 delivery (ADR 0018 decision 7). Only the signature and timestamp are signed. */
716
- declare const WEBHOOK_HEADERS: Readonly<{
717
- readonly signature: "X-Fabric-Signature";
718
- readonly timestamp: "X-Fabric-Timestamp";
719
- readonly eventId: "X-Fabric-Event-Id";
720
- readonly deliveryId: "X-Fabric-Delivery-Id";
721
- readonly attempt: "X-Fabric-Delivery-Attempt";
722
- readonly contentType: "Content-Type";
2446
+ export declare const WEBHOOK_HEADERS: Readonly<{
2447
+ readonly signature: "X-Fabric-Signature";
2448
+ readonly timestamp: "X-Fabric-Timestamp";
2449
+ readonly eventId: "X-Fabric-Event-Id";
2450
+ readonly deliveryId: "X-Fabric-Delivery-Id";
2451
+ readonly attempt: "X-Fabric-Delivery-Attempt";
2452
+ readonly contentType: "Content-Type";
723
2453
  }>;
724
2454
  /**
725
2455
  * The exact string a `v1` signer MACs: `"<timestampSeconds>.<rawBody>"`. The
@@ -729,16 +2459,16 @@ declare const WEBHOOK_HEADERS: Readonly<{
729
2459
  * non-negative whole number of seconds.
730
2460
  * @throws {TypeError} `raw_body_not_string` when the body is not a string.
731
2461
  */
732
- declare function webhookSignedContentV1(timestampSeconds: number, rawBody: string): string;
2462
+ export declare function webhookSignedContentV1(timestampSeconds: number, rawBody: string): string;
733
2463
  /** Inputs to {@link buildWebhookRequestHeaders}. */
734
2464
  interface WebhookRequestHeaderInput {
735
- /** The signer's `v1=<64 lowercase hex>` value. */
736
- readonly signature: string;
737
- readonly timestampSeconds: number;
738
- readonly eventId: string;
739
- readonly deliveryId: string;
740
- /** 1-based attempt number. */
741
- readonly attempt: number;
2465
+ /** The signer's `v1=<64 lowercase hex>` value. */
2466
+ readonly signature: string;
2467
+ readonly timestampSeconds: number;
2468
+ readonly eventId: string;
2469
+ readonly deliveryId: string;
2470
+ /** 1-based attempt number. */
2471
+ readonly attempt: number;
742
2472
  }
743
2473
  /**
744
2474
  * Build the headers of one attempt. A signature that is not a well-formed
@@ -752,8 +2482,9 @@ interface WebhookRequestHeaderInput {
752
2482
  * `WEBHOOK_INPUT_ERROR_CODES` for a malformed signature, timestamp,
753
2483
  * identifier or attempt, or an input that cannot be read.
754
2484
  */
755
- declare function buildWebhookRequestHeaders(input: WebhookRequestHeaderInput): Readonly<Record<string, string>>;
756
-
2485
+ export declare function buildWebhookRequestHeaders(input: WebhookRequestHeaderInput): Readonly<Record<string, string>>;
2486
+ //#endregion
2487
+ //#region src/address.d.ts
757
2488
  /**
758
2489
  * `true` when `address` is a well-formed IPv4 (canonical dotted-quad) or
759
2490
  * IPv6 address that a webhook may be sent to: not loopback, private,
@@ -764,18 +2495,101 @@ declare function buildWebhookRequestHeaders(input: WebhookRequestHeaderInput): R
764
2495
  * every embedded IPv4 address is public. Anything that does not parse is
765
2496
  * `false`. The README lists every refused block.
766
2497
  */
767
- declare function isPublicWebhookAddress(address: string): boolean;
768
-
2498
+ export declare function isPublicWebhookAddress(address: string): boolean;
2499
+ //#endregion
2500
+ //#region src/destination.d.ts
2501
+ /** The longest destination URL accepted, in UTF-16 code units. */
2502
+ export declare const WEBHOOK_DESTINATION_MAX_URL_LENGTH = 2048;
2503
+ /** The most addresses one lookup may return; a longer answer is refused as `dns_failure`. */
2504
+ export declare const WEBHOOK_DESTINATION_MAX_ADDRESSES = 32;
2505
+ /** The default bound on one DNS lookup. */
2506
+ export declare const DEFAULT_WEBHOOK_LOOKUP_TIMEOUT_MS = 5000;
2507
+ /** The result of checking a destination URL's rules, before any DNS lookup. */
2508
+ type WebhookDestinationUrlCheck = {
2509
+ readonly ok: true;
2510
+ /** `https://<host><path><query>`: normalized by WHATWG URL parsing, fragment dropped. */
2511
+ readonly url: string;
2512
+ /** The host without IPv6 brackets: the SNI name and certificate identity. */
2513
+ readonly hostname: string;
2514
+ /** Set when the host is an IP literal; it is the only address the destination may use. */
2515
+ readonly literal?: WebhookResolvedAddress;
2516
+ } | {
2517
+ readonly ok: false;
2518
+ readonly code: "invalid_url" | "private_address";
2519
+ };
2520
+ /**
2521
+ * Check a destination URL against decision 6's rules without resolving it.
2522
+ * `invalid_url` when it is not a string of at most
2523
+ * {@link WEBHOOK_DESTINATION_MAX_URL_LENGTH} characters with no ASCII
2524
+ * control character or space, does not start with `https://` and a host
2525
+ * character (WHATWG would find a host in `https:host` or `https:///host`),
2526
+ * does not parse, is not `https:`, names a port
2527
+ * other than 443 (an explicit `:443` is the default and accepted), carries
2528
+ * userinfo, or has an empty host or one ending in `.`. `private_address` when
2529
+ * the host is `localhost` or a `.localhost` name, or an IP literal that is not
2530
+ * public ({@link isPublicWebhookAddress}). A public IP literal passes and is
2531
+ * returned as `literal`. IPv4 literals in decimal, hex, octal or shorthand
2532
+ * form are normalized to dotted-quad by URL parsing before they are checked.
2533
+ * Never throws.
2534
+ */
2535
+ export declare function checkWebhookDestinationUrl(url: string): WebhookDestinationUrlCheck;
2536
+ /**
2537
+ * Looks up a hostname's addresses. It is passed `{ deadline, signal }`; the
2538
+ * resolver aborts `signal` when it stops waiting, and ignores any answer that
2539
+ * arrives after that. Each answer is `{ address, family }` with a textual IPv4
2540
+ * or IPv6 address. `createNodeWebhookDnsLookup()` at
2541
+ * `@fabricorg/webhook-delivery/node` is one implementation.
2542
+ */
2543
+ type WebhookDnsLookup = (hostname: string, options: WebhookCallOptions) => Promise<readonly WebhookResolvedAddress[]>;
2544
+ /** Options for {@link createWebhookDestinationResolver}. */
2545
+ interface WebhookDestinationResolverOptions {
2546
+ readonly lookup: WebhookDnsLookup;
2547
+ /** The longest one lookup may take; default {@link DEFAULT_WEBHOOK_LOOKUP_TIMEOUT_MS}. The call's own deadline also applies. */
2548
+ readonly lookupTimeoutMs?: number;
2549
+ /** The clock the call's deadline is compared with; the system clock by default. */
2550
+ readonly clock?: WebhookClock;
2551
+ }
2552
+ /**
2553
+ * A {@link WebhookDestinationResolver} that applies
2554
+ * {@link checkWebhookDestinationUrl}, then resolves the host through the
2555
+ * injected lookup and requires every answer to be public. The vetted
2556
+ * destination it returns carries the addresses the transport must connect
2557
+ * to; the transport never resolves the name again, so a later answer (a DNS
2558
+ * rebinding) cannot change where the request goes.
2559
+ *
2560
+ * A URL that passes the rules resolves to `dns_failure`, never a pass, when
2561
+ * the call's signal has aborted, its deadline has passed or the clock cannot
2562
+ * be read (no lookup is made; this applies to IP literals too), when the
2563
+ * lookup throws, rejects, or has not answered within `lookupTimeoutMs` or by
2564
+ * the deadline (its signal is then aborted and a late answer ignored), when
2565
+ * an answer arrives but, once read, the signal has aborted, the clock cannot
2566
+ * be read or that lookup deadline has passed, or when its answer is empty,
2567
+ * longer than {@link WEBHOOK_DESTINATION_MAX_ADDRESSES}, or not a list of
2568
+ * `{ address, family }` records whose family matches the address. It is
2569
+ * `private_address` when any answer is a well-formed address that is not
2570
+ * public, whatever the others are. A public IP-literal host is returned
2571
+ * without a lookup.
2572
+ *
2573
+ * @throws {TypeError} `input_unreadable` at construction when `lookup` is not
2574
+ * a function or `clock` has no `now` function, and from `resolve` (as a
2575
+ * rejected promise) when `{ deadline, signal }` is not a real `Date` and
2576
+ * `AbortSignal`.
2577
+ * @throws {WebhookDeliveryConfigError} when `lookupTimeoutMs` is not positive,
2578
+ * finite and at most `WEBHOOK_MAX_TIMER_MS`.
2579
+ */
2580
+ export declare function createWebhookDestinationResolver(options: WebhookDestinationResolverOptions): WebhookDestinationResolver;
2581
+ //#endregion
2582
+ //#region src/decisions.d.ts
769
2583
  /** Admission allows the event. */
770
- declare function webhookAdmissionAllow(): WebhookAdmissionDecision;
2584
+ export declare function webhookAdmissionAllow(): WebhookAdmissionDecision;
771
2585
  /**
772
2586
  * Admission denies the event with a fixed reason code; the denial is recorded.
773
2587
  *
774
2588
  * @throws {TypeError} `denial_code_unknown` for a code outside `WEBHOOK_ADMISSION_DENIAL_CODES`.
775
2589
  */
776
- declare function webhookAdmissionDeny(code: WebhookAdmissionDenialCode): WebhookAdmissionDecision;
2590
+ export declare function webhookAdmissionDeny(code: WebhookAdmissionDenialCode): WebhookAdmissionDecision;
777
2591
  /** Admission cannot decide now; the job retries without an intent. */
778
- declare function webhookAdmissionUnavailable(): WebhookAdmissionDecision;
2592
+ export declare function webhookAdmissionUnavailable(): WebhookAdmissionDecision;
779
2593
  /**
780
2594
  * Read an admission port's answer fail-closed. Anything other than a
781
2595
  * well-formed `allow`, `deny` with a known code, or `unavailable`, including
@@ -783,32 +2597,33 @@ declare function webhookAdmissionUnavailable(): WebhookAdmissionDecision;
783
2597
  * answer whose inspection throws, becomes `unavailable`: it never becomes
784
2598
  * `allow`, an unknown code is never stored, and this function never throws.
785
2599
  */
786
- declare function normalizeWebhookAdmissionDecision(value: unknown): WebhookAdmissionDecision;
2600
+ export declare function normalizeWebhookAdmissionDecision(value: unknown): WebhookAdmissionDecision;
787
2601
  /**
788
2602
  * Read a delivery authorization port's answer fail-closed. Anything other
789
2603
  * than a well-formed `authorized`, `revoked` with a known code, or
790
2604
  * `unavailable`, including an unreadable answer, becomes `unavailable`,
791
2605
  * which defers the intent unsent. This function never throws.
792
2606
  */
793
- declare function normalizeWebhookDeliveryAuthorizationDecision(value: unknown): WebhookDeliveryAuthorizationDecision;
794
-
2607
+ export declare function normalizeWebhookDeliveryAuthorizationDecision(value: unknown): WebhookDeliveryAuthorizationDecision;
2608
+ //#endregion
2609
+ //#region src/fan-out-publisher.d.ts
795
2610
  /** Options for {@link createWebhookFanOutPublisher}. */
796
2611
  interface WebhookFanOutPublisherOptions {
797
- /** Where jobs are inserted. Only `insertFanOutJob` is called. */
798
- readonly store: Pick<WebhookDeliveryStore, "insertFanOutJob">;
799
- /** Stamps the job's `createdAt` and processing deadline, and bounds the store call. */
800
- readonly clock: {
801
- now(): Date;
802
- };
803
- /** The persistence projection's allowlist and declared facts (decision 9). */
804
- readonly policy: WebhookPayloadPolicy;
805
- /**
806
- * Overrides of {@link WebhookDeliveryLimits}; every limit is validated as
807
- * positive and finite at construction (R4). The publisher uses
808
- * `fanOutDeadlineMs` (the job's processing deadline) and `portTimeoutMs`
809
- * (the bound on the insert).
810
- */
811
- readonly limits?: Partial<WebhookDeliveryLimits>;
2612
+ /** Where jobs are inserted. Only `insertFanOutJob` is called. */
2613
+ readonly store: Pick<WebhookDeliveryStore, "insertFanOutJob">;
2614
+ /** Stamps the job's `createdAt` and processing deadline, and bounds the store call. */
2615
+ readonly clock: {
2616
+ now(): Date;
2617
+ };
2618
+ /** The persistence projection's allowlist and declared facts (decision 9). */
2619
+ readonly policy: WebhookPayloadPolicy;
2620
+ /**
2621
+ * Overrides of {@link WebhookDeliveryLimits}; every limit is validated as
2622
+ * positive and finite at construction (R4). The publisher uses
2623
+ * `fanOutDeadlineMs` (the job's processing deadline) and `portTimeoutMs`
2624
+ * (the bound on the insert).
2625
+ */
2626
+ readonly limits?: Partial<WebhookDeliveryLimits>;
812
2627
  }
813
2628
  /**
814
2629
  * Build the stage-one publisher: an `EnterpriseEventPublisher` to hand to
@@ -822,7 +2637,7 @@ interface WebhookFanOutPublisherOptions {
822
2637
  * 2. inserts the job with `createdAt` now and a processing deadline
823
2638
  * `fanOutDeadlineMs` later, bounded by `portTimeoutMs` and the caller's
824
2639
  * signal;
825
- * 3. resolves on `inserted` or `already_present` (a replay or a bridge
2640
+ * 3. resolves on `inserted` or `already_present` (Host outbox re-publication or a bridge
826
2641
  * overlap: the first insert wins, and the stored job is not changed).
827
2642
  *
828
2643
  * It rejects, so the Host relay retries the record and eventually
@@ -840,37 +2655,37 @@ interface WebhookFanOutPublisherOptions {
840
2655
  * @throws {WebhookFanOutError} `options_invalid` at construction, for a
841
2656
  * missing or unreadable store, clock or options object.
842
2657
  */
843
- declare function createWebhookFanOutPublisher(options: WebhookFanOutPublisherOptions): EnterpriseEventPublisher;
2658
+ export declare function createWebhookFanOutPublisher(options: WebhookFanOutPublisherOptions): EnterpriseEventPublisher;
844
2659
  /** One sink of a composite publisher. */
845
2660
  interface WebhookCompositeSink {
846
- /** Names the sink in a `sink_failed` or `sink_timeout` error: 1 to 64 printable ASCII characters, unique in the composite. */
847
- readonly name: string;
848
- readonly publisher: EnterpriseEventPublisher;
849
- /**
850
- * Only `true` passes `actorId` and `actorType` to this sink, with the
851
- * relay's `actorDisclosure` unchanged. Any other value gives the sink a
852
- * copy of the envelope without those keys, and reports an `included`
853
- * disclosure to it as `suppressed` (decision 5).
854
- */
855
- readonly includeActor?: boolean;
856
- /**
857
- * Defaults to `true`. The composite resolves only after every required
858
- * sink resolves. A sink with `required: false` is started with the others
859
- * but never waited for: its outcome, timeout included, is ignored, so an
860
- * event it fails is not retried for it.
861
- */
862
- readonly required?: boolean;
2661
+ /** Names the sink in a `sink_failed` or `sink_timeout` error: 1 to 64 printable ASCII characters, unique in the composite. */
2662
+ readonly name: string;
2663
+ readonly publisher: EnterpriseEventPublisher;
2664
+ /**
2665
+ * Only `true` passes `actorId` and `actorType` to this sink, with the
2666
+ * relay's `actorDisclosure` unchanged. Any other value gives the sink a
2667
+ * copy of the envelope without those keys, and reports an `included`
2668
+ * disclosure to it as `suppressed` (decision 5).
2669
+ */
2670
+ readonly includeActor?: boolean;
2671
+ /**
2672
+ * Defaults to `true`. The composite resolves only after every required
2673
+ * sink resolves. A sink with `required: false` is started with the others
2674
+ * but never waited for: its outcome, timeout included, is ignored, so an
2675
+ * event it fails is not retried for it.
2676
+ */
2677
+ readonly required?: boolean;
863
2678
  }
864
2679
  /** Options for {@link createWebhookCompositePublisher}. */
865
2680
  interface WebhookCompositePublisherOptions {
866
- /** At least one sink, at most 16, called concurrently. */
867
- readonly sinks: readonly WebhookCompositeSink[];
868
- /**
869
- * How long each sink is given, in ms, before its signal is aborted and
870
- * it counts as timed out. A positive whole number up to
871
- * `WEBHOOK_MAX_TIMER_MS`; default 10 000, Host's default publish timeout.
872
- */
873
- readonly sinkTimeoutMs?: number;
2681
+ /** At least one sink, at most 16, called concurrently. */
2682
+ readonly sinks: readonly WebhookCompositeSink[];
2683
+ /**
2684
+ * How long each sink is given, in ms, before its signal is aborted and
2685
+ * it counts as timed out. A positive whole number up to
2686
+ * `WEBHOOK_MAX_TIMER_MS`; default 10 000, Host's default publish timeout.
2687
+ */
2688
+ readonly sinkTimeoutMs?: number;
874
2689
  }
875
2690
  /**
876
2691
  * Build a publisher that hands each event to several sinks, for example the
@@ -913,105 +2728,108 @@ interface WebhookCompositePublisherOptions {
913
2728
  * @throws {WebhookDeliveryConfigError} at construction for a `sinkTimeoutMs`
914
2729
  * that is not a positive whole number within the timer range.
915
2730
  */
916
- declare function createWebhookCompositePublisher(options: WebhookCompositePublisherOptions): EnterpriseEventPublisher;
917
-
2731
+ export declare function createWebhookCompositePublisher(options: WebhookCompositePublisherOptions): EnterpriseEventPublisher;
2732
+ //#endregion
2733
+ //#region src/cycle-options.d.ts
2734
+ /** Defaults for the cycle's own options. */
2735
+ export declare const DEFAULT_WEBHOOK_FAN_OUT_CYCLE: Readonly<{
2736
+ batchSize: 25;
2737
+ leaseDurationMs: 60000;
2738
+ eligibilitySettleMs: 5000;
2739
+ }>;
2740
+ //#endregion
2741
+ //#region src/fan-out-cycle.d.ts
918
2742
  /** What decides whether a subscription revision needs the event's actor identity. */
919
2743
  interface WebhookActorRequirementInput {
920
- readonly subscriptionId: string;
921
- readonly eligibilityRevision: number;
922
- readonly tenantId: string;
923
- readonly spaceId: string;
924
- readonly event: WebhookEventMetadata;
2744
+ readonly subscriptionId: string;
2745
+ readonly eligibilityRevision: number;
2746
+ readonly tenantId: string;
2747
+ readonly spaceId: string;
2748
+ readonly event: WebhookEventMetadata;
925
2749
  }
926
2750
  /** Options for {@link runWebhookFanOutCycle}. */
927
2751
  interface WebhookFanOutCycleOptions {
928
- readonly deliveries: WebhookDeliveryStore;
929
- readonly subscriptions: WebhookSubscriptionStore;
930
- readonly admission: WebhookEventAdmission;
931
- readonly clock: {
932
- now(): Date;
933
- };
934
- /** The lease owner this worker claims jobs as. */
935
- readonly workerId: string;
936
- /** The egress allowlist the intent bodies are rendered with (decision 9). */
937
- readonly policy: WebhookPayloadPolicy;
938
- /** Consulted for scoped event types when rendering a body. */
939
- readonly scopePredicate?: WebhookScopePredicate;
940
- /**
941
- * Whether admission for a revision needs actor identity (decision 5).
942
- * Required, so the choice is explicit. `true`, or a function returning
943
- * anything but a literal `false` (a throw included), makes a job whose
944
- * actor is `suppressed` or `legacy` `unavailable` for that revision
945
- * without calling admission: the job retries and finally dead-letters,
946
- * and actor enrichment (PR 9) repairs a legacy one.
947
- */
948
- readonly actorRequired: boolean | ((input: WebhookActorRequirementInput) => boolean);
949
- /**
950
- * Overrides of {@link WebhookDeliveryLimits}, all validated positive and
951
- * finite (R4). Used: `maxFanOutAttempts`, `fanOutRetryDelaysMs`,
952
- * `retryJitterRatio`, `portTimeoutMs`, `deliveryDeadlineMs` (each new
953
- * intent's processing deadline).
954
- */
955
- readonly limits?: Partial<WebhookDeliveryLimits>;
956
- /** Delay before the next attempt; defaults to `fanOutRetryDelaysMs` with jitter from `random`. */
957
- readonly retryDelay?: WebhookRetryDelay;
958
- /** Random source for the default retry delay's jitter; required when `retryDelay` is absent. */
959
- readonly random?: WebhookRandomSource;
960
- /** Jobs claimed per cycle. A positive whole number; default 25. */
961
- readonly batchSize?: number;
962
- /**
963
- * Lease on each claimed job, in ms; renewed while the job is processed
964
- * (see "Lease renewal" above). A positive whole number above
965
- * `3 * portTimeoutMs`; default 60 000.
966
- */
967
- readonly leaseDurationMs?: number;
968
- /**
969
- * How long after `recordedAt`, on this worker's clock, an event's
970
- * eligibility is treated as settled (see the completeness boundary
971
- * above). It must exceed the skew between this worker's clock and the
972
- * subscription store's, plus the longest revision write. A positive whole
973
- * number of ms below `fanOutDeadlineMs`; default 5 000.
974
- */
975
- readonly eligibilitySettleMs?: number;
976
- /** Creates each intent's `deliveryId`; defaults to `dlv_` plus a random UUID. */
977
- readonly createDeliveryId?: (input: {
978
- readonly subscriptionId: string;
979
- readonly eventId: string;
980
- }) => string;
981
- /** Stops the cycle between jobs; the rest of the claimed jobs are reclaimed after their leases expire. */
982
- readonly signal?: AbortSignal;
2752
+ readonly deliveries: WebhookDeliveryStore;
2753
+ readonly subscriptions: WebhookSubscriptionStore;
2754
+ readonly admission: WebhookEventAdmission;
2755
+ readonly clock: {
2756
+ now(): Date;
2757
+ };
2758
+ /** The lease owner this worker claims jobs as. */
2759
+ readonly workerId: string;
2760
+ /** The egress allowlist the intent bodies are rendered with (decision 9). */
2761
+ readonly policy: WebhookPayloadPolicy;
2762
+ /** Consulted for scoped event types when rendering a body. */
2763
+ readonly scopePredicate?: WebhookScopePredicate;
2764
+ /**
2765
+ * Whether admission for a revision needs actor identity (decision 5).
2766
+ * Required, so the choice is explicit. `true`, or a function returning
2767
+ * anything but a literal `false` (a throw included), makes a job whose
2768
+ * actor is `suppressed` or `legacy` `unavailable` for that revision
2769
+ * without calling admission: the job retries and finally dead-letters,
2770
+ * and actor enrichment (PR 9) repairs a legacy one.
2771
+ */
2772
+ readonly actorRequired: boolean | ((input: WebhookActorRequirementInput) => boolean);
2773
+ /**
2774
+ * Overrides of {@link WebhookDeliveryLimits}, all validated positive and
2775
+ * finite (R4). Used: `maxFanOutAttempts`, `fanOutRetryDelaysMs`,
2776
+ * `retryJitterRatio`, `portTimeoutMs`, `deliveryDeadlineMs` (each new
2777
+ * intent's processing deadline).
2778
+ */
2779
+ readonly limits?: Partial<WebhookDeliveryLimits>;
2780
+ /** Delay before the next attempt; defaults to `fanOutRetryDelaysMs` with jitter from `random`. */
2781
+ readonly retryDelay?: WebhookRetryDelay;
2782
+ /** Random source for the default retry delay's jitter; required when `retryDelay` is absent. */
2783
+ readonly random?: WebhookRandomSource;
2784
+ /** Jobs claimed per cycle. A positive whole number; default 25. */
2785
+ readonly batchSize?: number;
2786
+ /**
2787
+ * Lease on each claimed job, in ms; renewed while the job is processed
2788
+ * (see "Lease renewal" above). A positive whole number above
2789
+ * `3 * portTimeoutMs`; default 60 000.
2790
+ */
2791
+ readonly leaseDurationMs?: number;
2792
+ /**
2793
+ * How long after `recordedAt`, on this worker's clock, an event's
2794
+ * eligibility is treated as settled (see the completeness boundary
2795
+ * above). It must exceed the skew between this worker's clock and the
2796
+ * subscription store's, plus the longest revision write. A positive whole
2797
+ * number of ms below `fanOutDeadlineMs`; default 5 000.
2798
+ */
2799
+ readonly eligibilitySettleMs?: number;
2800
+ /** Creates each intent's `deliveryId`; defaults to `dlv_` plus a random UUID. */
2801
+ readonly createDeliveryId?: (input: {
2802
+ readonly subscriptionId: string;
2803
+ readonly eventId: string;
2804
+ }) => string;
2805
+ /** Stops the cycle between jobs; the rest of the claimed jobs are reclaimed after their leases expire. */
2806
+ readonly signal?: AbortSignal;
983
2807
  }
984
2808
  /** What one cycle did. */
985
2809
  interface WebhookFanOutCycleResult {
986
- readonly claimed: number;
987
- /** Jobs marked complete. */
988
- readonly completed: number;
989
- /** Jobs deferred unprocessed because their eligibility was not yet settled. */
990
- readonly deferred: number;
991
- /** Jobs returned to pending after an unavailable admission or a failed port. */
992
- readonly rescheduled: number;
993
- readonly deadLettered: number;
994
- /** Jobs this worker stopped on because a fenced write or a lease renewal found its lease gone. */
995
- readonly leaseLost: number;
996
- /**
997
- * Jobs left for lease expiry, with nothing more written: a record whose
998
- * identity or lease could not be read, one leased to another owner, or
999
- * one whose final fenced write threw or timed out. A final write that
1000
- * timed out may have committed (README, "Bounds and late results"); the
1001
- * job is then completed, rescheduled or dead-lettered as that write said.
1002
- */
1003
- readonly abandoned: number;
1004
- /** Intent inserts the store confirmed. One that timed out may have committed and is not counted. */
1005
- readonly intentsInserted: number;
1006
- /** Denial inserts the store confirmed, counted like `intentsInserted`. */
1007
- readonly denialsRecorded: number;
2810
+ readonly claimed: number;
2811
+ /** Jobs marked complete. */
2812
+ readonly completed: number;
2813
+ /** Jobs deferred unprocessed because their eligibility was not yet settled. */
2814
+ readonly deferred: number;
2815
+ /** Jobs returned to pending after an unavailable admission or a failed port. */
2816
+ readonly rescheduled: number;
2817
+ readonly deadLettered: number;
2818
+ /** Jobs this worker stopped on because a fenced write or a lease renewal found its lease gone. */
2819
+ readonly leaseLost: number;
2820
+ /**
2821
+ * Jobs left for lease expiry, with nothing more written: a record whose
2822
+ * identity or lease could not be read, one leased to another owner, or
2823
+ * one whose final fenced write threw or timed out. A final write that
2824
+ * timed out may have committed (README, "Bounds and late results"); the
2825
+ * job is then completed, rescheduled or dead-lettered as that write said.
2826
+ */
2827
+ readonly abandoned: number;
2828
+ /** Intent inserts the store confirmed. One that timed out may have committed and is not counted. */
2829
+ readonly intentsInserted: number;
2830
+ /** Denial inserts the store confirmed, counted like `intentsInserted`. */
2831
+ readonly denialsRecorded: number;
1008
2832
  }
1009
- /** Defaults for the cycle's own options. */
1010
- declare const DEFAULT_WEBHOOK_FAN_OUT_CYCLE: Readonly<{
1011
- batchSize: 25;
1012
- leaseDurationMs: 60000;
1013
- eligibilitySettleMs: 5000;
1014
- }>;
1015
2833
  /**
1016
2834
  * Whether an event type matches one subscription pattern. `*` matches every
1017
2835
  * type; a pattern ending in `.*` matches every type that starts with the
@@ -1025,7 +2843,7 @@ declare const DEFAULT_WEBHOOK_FAN_OUT_CYCLE: Readonly<{
1025
2843
  * an event type (`isEligible` below calls it), so the exclusion holds for
1026
2844
  * every subscription whatever its patterns.
1027
2845
  */
1028
- declare function matchesWebhookEventTypePattern(pattern: string, eventType: string): boolean;
2846
+ export declare function matchesWebhookEventTypePattern(pattern: string, eventType: string): boolean;
1029
2847
  /**
1030
2848
  * Run one fan-out cycle: claim up to `batchSize` pending jobs under a fenced
1031
2849
  * lease and process each in turn. A claimed record whose `eventId`,
@@ -1080,8 +2898,9 @@ declare function matchesWebhookEventTypePattern(pattern: string, eventType: stri
1080
2898
  * option; `fan_out_store_failed` or `fan_out_store_timeout` when the claim
1081
2899
  * fails; `clock_invalid` for a failing clock.
1082
2900
  */
1083
- declare function runWebhookFanOutCycle(options: WebhookFanOutCycleOptions): Promise<WebhookFanOutCycleResult>;
1084
-
2901
+ export declare function runWebhookFanOutCycle(options: WebhookFanOutCycleOptions): Promise<WebhookFanOutCycleResult>;
2902
+ //#endregion
2903
+ //#region src/delivery-cycle.d.ts
1085
2904
  /** One intent's outcome, as reported to {@link WebhookDeliveryCycleOptions.observe}. */
1086
2905
  type WebhookDeliveryCycleResultKind = "delivered" | "retry_scheduled" | "dead_lettered" | "cancelled" | "released" | "lease_lost" | "abandoned";
1087
2906
  /**
@@ -1090,79 +2909,79 @@ type WebhookDeliveryCycleResultKind = "delivered" | "retry_scheduled" | "dead_le
1090
2909
  * destination, secret reference, signature, header, body or port error.
1091
2910
  */
1092
2911
  interface WebhookDeliveryCycleEvent {
1093
- readonly deliveryId: string;
1094
- readonly tenantId: string;
1095
- readonly spaceId: string;
1096
- readonly subscriptionId?: string;
1097
- readonly eventId?: string;
1098
- /** The 1-based attempt number this processing was for. */
1099
- readonly attempt?: number;
1100
- /** The configuration version the attempt was prepared with, once read. */
1101
- readonly configurationVersion?: number;
1102
- readonly result: WebhookDeliveryCycleResultKind;
1103
- /** `true` once the request was handed to the transport. */
1104
- readonly handedOff: boolean;
1105
- readonly reason?: WebhookAttemptReasonCode;
1106
- readonly diagnostic?: WebhookDiagnosticCode;
1107
- readonly httpStatus?: number;
1108
- /** 401 or 403: still retried, but usually needing the receiver to act (decision 10). */
1109
- readonly receiverActionLikely?: true;
2912
+ readonly deliveryId: string;
2913
+ readonly tenantId: string;
2914
+ readonly spaceId: string;
2915
+ readonly subscriptionId?: string;
2916
+ readonly eventId?: string;
2917
+ /** The 1-based attempt number this processing was for. */
2918
+ readonly attempt?: number;
2919
+ /** The configuration version the attempt was prepared with, once read. */
2920
+ readonly configurationVersion?: number;
2921
+ readonly result: WebhookDeliveryCycleResultKind;
2922
+ /** `true` once the request was handed to the transport. */
2923
+ readonly handedOff: boolean;
2924
+ readonly reason?: WebhookAttemptReasonCode;
2925
+ readonly diagnostic?: WebhookDiagnosticCode;
2926
+ readonly httpStatus?: number;
2927
+ /** 401 or 403: still retried, but usually needing the receiver to act (decision 10). */
2928
+ readonly receiverActionLikely?: true;
1110
2929
  }
1111
2930
  /** Options for {@link runWebhookDeliveryCycle}. */
1112
2931
  interface WebhookDeliveryCycleOptions {
1113
- readonly deliveries: WebhookDeliveryStore;
1114
- readonly subscriptions: WebhookSubscriptionStore;
1115
- readonly authorization: WebhookDeliveryAuthorization;
1116
- readonly signer: WebhookSigner;
1117
- readonly resolver: WebhookDestinationResolver;
1118
- readonly transport: WebhookTransport;
1119
- readonly clock: WebhookClock;
1120
- /** The lease owner this worker claims intents as. */
1121
- readonly workerId: string;
1122
- /**
1123
- * Overrides of {@link WebhookDeliveryLimits}, all validated positive and
1124
- * finite (R4). Used: `maxDeliveryAttempts`, `deliveryRetryDelaysMs`,
1125
- * `retryJitterRatio`, `attemptTimeoutMs`, `portTimeoutMs`, `maxRetryAfterMs`.
1126
- */
1127
- readonly limits?: Partial<WebhookDeliveryLimits>;
1128
- /** Delay before the next attempt; defaults to `deliveryRetryDelaysMs` with jitter from `random`. */
1129
- readonly retryDelay?: WebhookRetryDelay;
1130
- /** Random source for the default retry delay's jitter; required when `retryDelay` is absent. */
1131
- readonly random?: WebhookRandomSource;
1132
- /** Intents claimed per cycle. A positive whole number; default 10. */
1133
- readonly batchSize?: number;
1134
- /**
1135
- * Lease on each claimed intent, in ms. A positive whole number above
1136
- * `attemptTimeoutMs + 10 * portTimeoutMs` (one whole attempt and the
1137
- * renewal before it; see `ATTEMPT_PORT_CALLS`); default 120 000.
1138
- */
1139
- readonly leaseDurationMs?: number;
1140
- /** Called once per claimed intent with fixed-code telemetry. A throw is ignored. */
1141
- readonly observe?: (event: WebhookDeliveryCycleEvent) => void;
1142
- /** Stops the cycle; intents not yet settled are reclaimed after their leases expire. */
1143
- readonly signal?: AbortSignal;
2932
+ readonly deliveries: WebhookDeliveryStore;
2933
+ readonly subscriptions: WebhookSubscriptionStore;
2934
+ readonly authorization: WebhookDeliveryAuthorization;
2935
+ readonly signer: WebhookSigner;
2936
+ readonly resolver: WebhookDestinationResolver;
2937
+ readonly transport: WebhookTransport;
2938
+ readonly clock: WebhookClock;
2939
+ /** The lease owner this worker claims intents as. */
2940
+ readonly workerId: string;
2941
+ /**
2942
+ * Overrides of {@link WebhookDeliveryLimits}, all validated positive and
2943
+ * finite (R4). Used: `maxDeliveryAttempts`, `deliveryRetryDelaysMs`,
2944
+ * `retryJitterRatio`, `attemptTimeoutMs`, `portTimeoutMs`, `maxRetryAfterMs`.
2945
+ */
2946
+ readonly limits?: Partial<WebhookDeliveryLimits>;
2947
+ /** Delay before the next attempt; defaults to `deliveryRetryDelaysMs` with jitter from `random`. */
2948
+ readonly retryDelay?: WebhookRetryDelay;
2949
+ /** Random source for the default retry delay's jitter; required when `retryDelay` is absent. */
2950
+ readonly random?: WebhookRandomSource;
2951
+ /** Intents claimed per cycle. A positive whole number; default 10. */
2952
+ readonly batchSize?: number;
2953
+ /**
2954
+ * Lease on each claimed intent, in ms. A positive whole number above
2955
+ * `attemptTimeoutMs + 10 * portTimeoutMs` (one whole attempt and the
2956
+ * renewal before it; see `ATTEMPT_PORT_CALLS`); default 120 000.
2957
+ */
2958
+ readonly leaseDurationMs?: number;
2959
+ /** Called once per claimed intent with fixed-code telemetry. A throw is ignored. */
2960
+ readonly observe?: (event: WebhookDeliveryCycleEvent) => void;
2961
+ /** Stops the cycle; intents not yet settled are reclaimed after their leases expire. */
2962
+ readonly signal?: AbortSignal;
1144
2963
  }
1145
2964
  /** What one cycle did. */
1146
2965
  interface WebhookDeliveryCycleResult {
1147
- readonly claimed: number;
1148
- readonly delivered: number;
1149
- /** Settled for a later attempt, spending one. */
1150
- readonly retried: number;
1151
- readonly deadLettered: number;
1152
- readonly cancelled: number;
1153
- /** Returned to pending unsent, spending no attempt. */
1154
- readonly released: number;
1155
- /** Intents this worker stopped on because a fenced write found its lease gone. */
1156
- readonly leaseLost: number;
1157
- /** Intents left for lease expiry: unreadable, another owner's, or whose final write failed. */
1158
- readonly abandoned: number;
1159
- /** Requests handed to the transport. */
1160
- readonly handedOff: number;
2966
+ readonly claimed: number;
2967
+ readonly delivered: number;
2968
+ /** Settled for a later attempt, spending one. */
2969
+ readonly retried: number;
2970
+ readonly deadLettered: number;
2971
+ readonly cancelled: number;
2972
+ /** Returned to pending unsent, spending no attempt. */
2973
+ readonly released: number;
2974
+ /** Intents this worker stopped on because a fenced write found its lease gone. */
2975
+ readonly leaseLost: number;
2976
+ /** Intents left for lease expiry: unreadable, another owner's, or whose final write failed. */
2977
+ readonly abandoned: number;
2978
+ /** Requests handed to the transport. */
2979
+ readonly handedOff: number;
1161
2980
  }
1162
2981
  /** Defaults for the cycle's own options. */
1163
- declare const DEFAULT_WEBHOOK_DELIVERY_CYCLE: Readonly<{
1164
- batchSize: 10;
1165
- leaseDurationMs: 120000;
2982
+ export declare const DEFAULT_WEBHOOK_DELIVERY_CYCLE: Readonly<{
2983
+ batchSize: 10;
2984
+ leaseDurationMs: 120000;
1166
2985
  }>;
1167
2986
  /**
1168
2987
  * Run one delivery cycle: claim up to `batchSize` pending intents under a
@@ -1178,164 +2997,37 @@ declare const DEFAULT_WEBHOOK_DELIVERY_CYCLE: Readonly<{
1178
2997
  * option; `delivery_store_failed` or `delivery_store_timeout` when the claim
1179
2998
  * fails; `clock_invalid` for a failing clock.
1180
2999
  */
1181
- declare function runWebhookDeliveryCycle(options: WebhookDeliveryCycleOptions): Promise<WebhookDeliveryCycleResult>;
1182
-
1183
- declare const WEBHOOK_DELIVERY_ACTION_EVENT_TYPES: readonly ["WebhookSubscriptionCreated", "WebhookSubscriptionUpdated", "WebhookSubscriptionPaused", "WebhookSubscriptionResumed", "WebhookSubscriptionDeleted", "WebhookSecretRotated", "WebhookDeliveryIntentRequeued", "WebhookFanOutJobRequeued", "WebhookFanOutJobActorEnriched"];
3000
+ export declare function runWebhookDeliveryCycle(options: WebhookDeliveryCycleOptions): Promise<WebhookDeliveryCycleResult>;
3001
+ //#endregion
3002
+ //#region src/action-events.d.ts
3003
+ export declare const WEBHOOK_DELIVERY_ACTION_EVENT_TYPES: readonly ["WebhookReplayRequested", "WebhookSubscriptionCreated", "WebhookSubscriptionUpdated", "WebhookSubscriptionPaused", "WebhookSubscriptionResumed", "WebhookSubscriptionDeleted", "WebhookSecretRotated", "WebhookDeliveryIntentRequeued", "WebhookFanOutJobRequeued", "WebhookFanOutJobActorEnriched"];
1184
3004
  /** An event type one of the package's governed actions emits. */
1185
3005
  type WebhookDeliveryActionEventType = (typeof WEBHOOK_DELIVERY_ACTION_EVENT_TYPES)[number];
1186
3006
  /** `true` when `eventType` is emitted by one of the package's governed actions. */
1187
- declare function isWebhookDeliveryActionEventType(eventType: unknown): eventType is WebhookDeliveryActionEventType;
1188
-
1189
- /** The governed actions, by the name after the module namespace (`<namespace>.<name>`). */
1190
- declare const WEBHOOK_DELIVERY_ACTION_NAMES: readonly ["create_subscription", "update_subscription", "pause_subscription", "resume_subscription", "delete_subscription", "rotate_secret_staged", "rotate_secret_emergency", "requeue_delivery_intent", "requeue_fan_out_job", "enrich_fan_out_job_actor"];
1191
- /** One governed action name. */
1192
- type WebhookDeliveryActionName = (typeof WEBHOOK_DELIVERY_ACTION_NAMES)[number];
1193
- /**
1194
- * Why parameters were refused. `parameters_invalid`: not a plain-data
1195
- * object, a field missing or malformed, or an id breaking the store
1196
- * identifier rule. `unknown_parameter`: a key the action does not declare
1197
- * (named in no message). `secret_ref_invalid`: a `secretRef` that is not a
1198
- * secret reference. `destination_invalid`: a destination URL breaking the
1199
- * URL rules (README, "URL rules"). `event_type_pattern_invalid`: a pattern
1200
- * outside the pattern grammar. `update_ambiguous`: an update naming both or
1201
- * neither of patterns and destination.
1202
- */
1203
- declare const WEBHOOK_ACTION_PARAMETER_CODES: readonly ["parameters_invalid", "unknown_parameter", "secret_ref_invalid", "destination_invalid", "event_type_pattern_invalid", "update_ambiguous"];
1204
- /** Why an action's parameters were refused. */
1205
- type WebhookActionParameterCode = (typeof WEBHOOK_ACTION_PARAMETER_CODES)[number];
1206
- /** `<namespace>.create_subscription` */
1207
- interface WebhookCreateSubscriptionParameters {
1208
- readonly subscriptionId: string;
1209
- readonly eventTypePatterns: readonly string[];
1210
- readonly destinationUrl: string;
1211
- /** A secret reference, not key material or ciphertext (`isWebhookSecretReference`, then the verifier). */
1212
- readonly secretRef: string;
1213
- /** Only `v1` exists (decision 7); the default. */
1214
- readonly signatureScheme?: "v1";
1215
- }
1216
- /** `<namespace>.update_subscription`: exactly one of the two changes per invocation. */
1217
- type WebhookUpdateSubscriptionParameters = {
1218
- readonly subscriptionId: string;
1219
- readonly eventTypePatterns: readonly string[];
1220
- readonly expectedRevision?: number;
1221
- } | {
1222
- readonly subscriptionId: string;
1223
- readonly destinationUrl: string;
1224
- readonly expectedVersion?: number;
1225
- };
1226
- /** `<namespace>.pause_subscription`, `resume_subscription` and `delete_subscription`. */
1227
- interface WebhookSubscriptionTransitionParameters {
1228
- readonly subscriptionId: string;
1229
- readonly expectedRevision?: number;
1230
- }
1231
- /** `<namespace>.rotate_secret_staged` */
1232
- interface WebhookStagedRotationParameters {
1233
- readonly subscriptionId: string;
1234
- /** The next secret's reference, not key material or ciphertext (`isWebhookSecretReference`, then the verifier). */
1235
- readonly secretRef: string;
1236
- /** The configuration version the operator read; the rotation applies only on it. */
1237
- readonly expectedVersion: number;
1238
- /** The operator's statement that the receiver already accepts the next secret (decision 8). */
1239
- readonly receiverAcceptsNextSecret: true;
1240
- }
1241
- /** `<namespace>.rotate_secret_emergency` */
1242
- interface WebhookEmergencyRotationParameters {
1243
- readonly subscriptionId: string;
1244
- readonly secretRef: string;
1245
- readonly expectedVersion?: number;
1246
- }
1247
- /** `<namespace>.requeue_delivery_intent` */
1248
- interface WebhookIntentRequeueParameters {
1249
- readonly deliveryId: string;
1250
- /** The intent's `fence` as the operator read it. */
1251
- readonly expectedFence: number;
1252
- }
1253
- /** `<namespace>.requeue_fan_out_job` and `enrich_fan_out_job_actor`. */
1254
- interface WebhookJobParameters {
1255
- readonly eventId: string;
1256
- /** The job's `fence` as the operator read it. */
1257
- readonly expectedFence: number;
1258
- }
1259
- /** The parameters of each action, by name. */
1260
- interface WebhookDeliveryActionParameters {
1261
- readonly create_subscription: WebhookCreateSubscriptionParameters;
1262
- readonly update_subscription: WebhookUpdateSubscriptionParameters;
1263
- readonly pause_subscription: WebhookSubscriptionTransitionParameters;
1264
- readonly resume_subscription: WebhookSubscriptionTransitionParameters;
1265
- readonly delete_subscription: WebhookSubscriptionTransitionParameters;
1266
- readonly rotate_secret_staged: WebhookStagedRotationParameters;
1267
- readonly rotate_secret_emergency: WebhookEmergencyRotationParameters;
1268
- readonly requeue_delivery_intent: WebhookIntentRequeueParameters;
1269
- readonly requeue_fan_out_job: WebhookJobParameters;
1270
- readonly enrich_fan_out_job_actor: WebhookJobParameters;
1271
- }
1272
- /** What reading one action's parameters found. */
1273
- type WebhookActionParameterRead<N extends WebhookDeliveryActionName> = {
1274
- readonly ok: true;
1275
- readonly value: WebhookDeliveryActionParameters[N];
1276
- } | {
1277
- readonly ok: false;
1278
- readonly code: WebhookActionParameterCode;
1279
- readonly field: string;
1280
- };
1281
- /** The longest secret reference accepted. */
1282
- declare const WEBHOOK_SECRET_REF_MAX_LENGTH = 256;
1283
- /** The longest run of characters between two separators (`:`, `/`, `@`, `#`) in a secret reference. */
1284
- declare const WEBHOOK_SECRET_REF_MAX_SEGMENT = 64;
1285
- /**
1286
- * Whether `value` has the shape of a secret reference (ADR 0018 decision 8):
1287
- * a lower-case scheme and `:` (`kms:`, `vault:`, `arn:`, `secret:` …), then
1288
- * only `A–Z a–z 0–9 . _ ~ : / @ # -`, at most
1289
- * {@link WEBHOOK_SECRET_REF_MAX_LENGTH} characters, with no run longer than
1290
- * {@link WEBHOOK_SECRET_REF_MAX_SEGMENT} between separators.
1291
- *
1292
- * That refuses raw key bytes and their usual encodings: hex, base64 and
1293
- * `whsec_…` values (no scheme), padded or standard base64 (`=`, `+`), PEM
1294
- * armour and anything with whitespace or control characters, and long
1295
- * encoded runs such as wrapped ciphertext. It cannot recognise key material
1296
- * deliberately shaped like a reference (`x:` followed by 64 hex digits
1297
- * passes), which is why the actions also require the application's
1298
- * `secretReferences.verify` port to confirm the reference resolves in the
1299
- * tenant's secret store.
1300
- */
1301
- declare function isWebhookSecretReference(value: unknown): value is string;
1302
- /** The most patterns one subscription takes. */
1303
- declare const WEBHOOK_MAX_EVENT_TYPE_PATTERNS = 64;
1304
- /**
1305
- * Whether `value` is an event type pattern the matcher understands: `*`,
1306
- * `prefix.*`, or an exact type, built from `A–Z a–z 0–9 _ : -` segments
1307
- * joined by `.`, at most 256 characters. A `*` anywhere else is refused,
1308
- * since the matcher would treat it as a literal.
1309
- */
1310
- declare function isWebhookEventTypePattern(value: unknown): value is string;
1311
- /**
1312
- * Read one action's parameters. Never throws: the only thrown value whose
1313
- * properties are read is the package's own `Refusal` (recognized by
1314
- * `Refusal.is`, which runs none of a value's code); anything else thrown
1315
- * while the input is read (an accessor, a Proxy trap, a look-alike refusal)
1316
- * is dropped unread and reported as `parameters_invalid`. The code and field
1317
- * returned are each checked against their fixed set (`fixedRefusal`). Tests:
1318
- * "forged refusals: …".
1319
- */
1320
- declare function readWebhookActionParameters<N extends WebhookDeliveryActionName>(name: N, input: unknown): WebhookActionParameterRead<N>;
1321
- /** The default module namespace. */
1322
- declare const DEFAULT_WEBHOOK_DELIVERY_NAMESPACE = "webhook-delivery";
1323
-
3007
+ export declare function isWebhookDeliveryActionEventType(eventType: unknown): eventType is WebhookDeliveryActionEventType;
3008
+ //#endregion
3009
+ //#region src/governed-actions.d.ts
1324
3010
  /** What the module's operator policy asks the application about one invocation. */
1325
3011
  interface WebhookOperatorAuthorizationInput {
1326
- /** `<namespace>.<action name>`. */
1327
- readonly actionId: string;
1328
- readonly action: WebhookDeliveryActionName;
1329
- readonly tenantId: string;
1330
- readonly spaceId: string;
1331
- readonly actorId: string;
1332
- readonly actorType: string;
1333
- /** The ids the action names, copied from three id fields by `operatorPolicy`; no URL or secret reference. */
1334
- readonly target: {
1335
- readonly subscriptionId?: string;
1336
- readonly deliveryId?: string;
1337
- readonly eventId?: string;
1338
- };
3012
+ /** `<namespace>.<action name>`. */
3013
+ readonly actionId: string;
3014
+ readonly action: WebhookDeliveryActionName;
3015
+ readonly tenantId: string;
3016
+ readonly spaceId: string;
3017
+ readonly actorId: string;
3018
+ readonly actorType: string;
3019
+ /** Normalized historical range/filter metadata for replay authorization; no event data or secrets. */
3020
+ readonly replay?: {
3021
+ readonly from: string;
3022
+ readonly to: string;
3023
+ readonly eventTypePatterns?: readonly string[];
3024
+ };
3025
+ /** The ids the action names, copied from three id fields by `operatorPolicy`; no URL or secret reference. */
3026
+ readonly target: {
3027
+ readonly subscriptionId?: string;
3028
+ readonly deliveryId?: string;
3029
+ readonly eventId?: string;
3030
+ };
1339
3031
  }
1340
3032
  /**
1341
3033
  * The module policy's question (`<namespace>.operator_authorized.v1`). Only a
@@ -1352,9 +3044,9 @@ type WebhookOperatorAuthorization = (input: WebhookOperatorAuthorizationInput, o
1352
3044
  * `options.signal`, which Host aborts at its admission deadline.
1353
3045
  */
1354
3046
  interface WebhookSecretReferenceVerifier {
1355
- verify(input: WebhookScope & {
1356
- readonly secretRef: string;
1357
- }, options: WebhookCallOptions): Promise<"valid" | "invalid">;
3047
+ verify(input: WebhookScope & {
3048
+ readonly secretRef: string;
3049
+ }, options: WebhookCallOptions): Promise<"valid" | "invalid">;
1358
3050
  }
1359
3051
  /**
1360
3052
  * What the application's actor reader found on the canonical event
@@ -1364,41 +3056,42 @@ interface WebhookSecretReferenceVerifier {
1364
3056
  * tenant and space.
1365
3057
  */
1366
3058
  type WebhookEventActorReading = {
1367
- readonly state: "found";
1368
- readonly actorId: string;
1369
- readonly actorType: string;
3059
+ readonly state: "found";
3060
+ readonly actorId: string;
3061
+ readonly actorType: string;
1370
3062
  } | {
1371
- readonly state: "withheld";
3063
+ readonly state: "withheld";
1372
3064
  } | {
1373
- readonly state: "not_found";
3065
+ readonly state: "not_found";
1374
3066
  };
1375
3067
  /** Reads the actor of the canonical event for a legacy job's tenant, space and `eventId` (decision 5). */
1376
3068
  interface WebhookEventActorReader {
1377
- readEventActor(input: WebhookScope & {
1378
- readonly eventId: string;
1379
- readonly eventType: string;
1380
- readonly payloadClassification: string;
1381
- }, options: WebhookCallOptions): Promise<WebhookEventActorReading>;
3069
+ readEventActor(input: WebhookScope & {
3070
+ readonly eventId: string;
3071
+ readonly eventType: string;
3072
+ readonly payloadClassification: string;
3073
+ }, options: WebhookCallOptions): Promise<WebhookEventActorReading>;
1382
3074
  }
1383
3075
  /** Options for {@link createWebhookDeliveryModule}. */
1384
3076
  interface WebhookDeliveryModuleOptions {
1385
- /** The module namespace; actions are `<namespace>.<name>`. Default `webhook-delivery`. */
1386
- readonly namespace?: string;
1387
- readonly subscriptions: WebhookSubscriptionStore & WebhookSubscriptionWriter;
1388
- readonly deliveries: WebhookDeliveryStore;
1389
- /** Re-validates a destination at configuration change and at intent requeue (decision 6). */
1390
- readonly resolver: WebhookDestinationResolver;
1391
- readonly secretReferences: WebhookSecretReferenceVerifier;
1392
- readonly actorReader: WebhookEventActorReader;
1393
- readonly authorizeOperatorAction: WebhookOperatorAuthorization;
1394
- readonly clock: WebhookClock;
1395
- /** `portTimeoutMs` bounds every port call; `fanOutDeadlineMs` and `deliveryDeadlineMs` set requeued deadlines; `attemptTimeoutMs` enters the staged rotation's retention. */
1396
- readonly limits?: Partial<WebhookDeliveryLimits>;
1397
- /** The receivers' signature timestamp tolerance, for a staged rotation's `previousSecretRetainUntil`. Default 300 000 (R6). */
1398
- readonly verifierToleranceMs?: number;
3077
+ readonly replay?: WebhookReplayOptions;
3078
+ /** The module namespace; actions are `<namespace>.<name>`. Default `webhook-delivery`. */
3079
+ readonly namespace?: string;
3080
+ readonly subscriptions: WebhookSubscriptionStore & WebhookSubscriptionWriter;
3081
+ readonly deliveries: WebhookDeliveryStore;
3082
+ /** Re-validates a destination at configuration change and at intent requeue (decision 6). */
3083
+ readonly resolver: WebhookDestinationResolver;
3084
+ readonly secretReferences: WebhookSecretReferenceVerifier;
3085
+ readonly actorReader: WebhookEventActorReader;
3086
+ readonly authorizeOperatorAction: WebhookOperatorAuthorization;
3087
+ readonly clock: WebhookClock;
3088
+ /** `portTimeoutMs` bounds every port call; `fanOutDeadlineMs` and `deliveryDeadlineMs` set requeued deadlines; `attemptTimeoutMs` enters the staged rotation's retention. */
3089
+ readonly limits?: Partial<WebhookDeliveryLimits>;
3090
+ /** The receivers' signature timestamp tolerance, for a staged rotation's `previousSecretRetainUntil`. Default 300 000 (R6). */
3091
+ readonly verifierToleranceMs?: number;
1399
3092
  }
1400
3093
  /** The module policy's id for a namespace. */
1401
- declare function webhookOperatorPolicyId(namespace?: string): `${string}.v${number}`;
3094
+ export declare function webhookOperatorPolicyId(namespace?: string): `${string}.v${number}`;
1402
3095
  /**
1403
3096
  * Build the package's governed operator actions as one `FabricModule`
1404
3097
  * (README, "Governed actions"). Register it with `createModuleRegistry()`
@@ -1413,6 +3106,18 @@ declare function webhookOperatorPolicyId(namespace?: string): `${string}.v${numb
1413
3106
  * @throws {WebhookDeliveryConfigError} when a limit or `verifierToleranceMs`
1414
3107
  * is not a positive whole number.
1415
3108
  */
1416
- declare function createWebhookDeliveryModule<TDb = unknown>(options: WebhookDeliveryModuleOptions): FabricModule<TDb>;
1417
-
1418
- export { DEFAULT_WEBHOOK_DELIVERY_CYCLE, DEFAULT_WEBHOOK_DELIVERY_LIMITS, DEFAULT_WEBHOOK_DELIVERY_NAMESPACE, DEFAULT_WEBHOOK_FAN_OUT_CYCLE, MemoryWebhookDeliveryStore, type MemoryWebhookDeliveryStoreOptions, MemoryWebhookSubscriptionStore, type MemoryWebhookSubscriptionStoreOptions, PostgresWebhookDeliveryStore, type PostgresWebhookDeliveryStoreOptions, PostgresWebhookSubscriptionStore, type PostgresWebhookSubscriptionStoreOptions, WEBHOOK_ACTION_PARAMETER_CODES, WEBHOOK_DELIVERY_ACTION_EVENT_TYPES, WEBHOOK_DELIVERY_ACTION_NAMES, WEBHOOK_DELIVERY_FORMAT_V1, WEBHOOK_HEADERS, WEBHOOK_MAX_EVENT_TYPE_PATTERNS, WEBHOOK_MAX_REPLAY_HORIZON_MS, WEBHOOK_MAX_TIMER_MS, WEBHOOK_SECRET_REF_MAX_LENGTH, WEBHOOK_SECRET_REF_MAX_SEGMENT, type WebhookActionParameterCode, type WebhookActionParameterRead, WebhookActorEnrichmentRequest, WebhookActorEnrichmentResult, type WebhookActorRequirementInput, WebhookAdmissionDecision, WebhookAdmissionDenialCode, WebhookAdmissionDenialRecord, WebhookAttemptBeginResult, WebhookAttemptOutcome, WebhookAttemptReasonCode, WebhookCallOptions, WebhookClaimRequest, type WebhookClassifyOptions, WebhookClock, type WebhookCompositePublisherOptions, type WebhookCompositeSink, WebhookConfigErrorCode, WebhookConfigurationWriteResult, type WebhookCreateSubscriptionParameters, type WebhookDeliveryActionEventType, type WebhookDeliveryActionName, type WebhookDeliveryActionParameters, WebhookDeliveryAttemptFinish, WebhookDeliveryAttemptLogEntry, WebhookDeliveryAttemptRecord, WebhookDeliveryAttemptStart, WebhookDeliveryAuthorization, WebhookDeliveryAuthorizationDecision, WebhookDeliveryBodyV1, type WebhookDeliveryClassification, WebhookDeliveryConfigError, WebhookDeliveryConfigurationInput, type WebhookDeliveryCycleEvent, type WebhookDeliveryCycleOptions, type WebhookDeliveryCycleResult, type WebhookDeliveryCycleResultKind, WebhookDeliveryError, WebhookDeliveryErrorCode, WebhookDeliveryIntent, WebhookDeliveryLeaseCheck, type WebhookDeliveryLimits, type WebhookDeliveryModuleOptions, type WebhookDeliveryObservation, WebhookDeliveryStore, WebhookDestinationResolver, WebhookDiagnosticCode, type WebhookEgressPayload, type WebhookEgressProjectionInput, type WebhookEgressSubscription, WebhookEligibilityRevision, type WebhookEmergencyRotationParameters, type WebhookEventActorReader, type WebhookEventActorReading, WebhookEventAdmission, WebhookEventMetadata, type WebhookFanOutCycleOptions, type WebhookFanOutCycleResult, WebhookFanOutError, WebhookFanOutErrorCode, WebhookFanOutJob, WebhookFanOutJobDraft, WebhookFanOutJobFence, type WebhookFanOutPublisherOptions, WebhookFencedInsertResult, WebhookFencedWrite, WebhookGovernedActionError, WebhookGovernedActionErrorCode, WebhookInsertResult, type WebhookIntentRequeueParameters, WebhookJobActor, type WebhookJobParameters, WebhookJsonObject, type WebhookOperatorAuthorization, type WebhookOperatorAuthorizationInput, WebhookPayloadPolicy, type WebhookPersistenceProjectionOptions, type WebhookPostgresConnection, type WebhookPostgresPool, type WebhookPostgresQueryable, type WebhookPostgresResult, WebhookProjectionError, WebhookProjectionErrorCode, WebhookPurgeResult, WebhookRandomSource, type WebhookRenderInput, type WebhookRenderedBody, type WebhookRequestHeaderInput, WebhookRequeueEvidence, WebhookRequeueRequest, WebhookRequeueResult, WebhookRetryDelay, type WebhookRetryDelayOptions, WebhookScope, WebhookScopePredicate, type WebhookSecretReferenceVerifier, WebhookSigner, WebhookSourceEnvelope, type WebhookStagedRotationParameters, WebhookStoreError, WebhookStoreErrorCode, WebhookSubscriptionSnapshot, WebhookSubscriptionStore, WebhookSubscriptionTransitionInput, type WebhookSubscriptionTransitionParameters, WebhookSubscriptionWriteResult, WebhookSubscriptionWriter, WebhookTombstone, type WebhookTransactionCheckpointStage, WebhookTransport, WebhookTransportResult, type WebhookUpdateSubscriptionParameters, buildWebhookRequestHeaders, classifyWebhookDeliveryObservation, createWebhookCompositePublisher, createWebhookDeliveryModule, createWebhookFanOutPublisher, createWebhookRetryDelay, isPublicWebhookAddress, isWebhookDeliveryActionEventType, isWebhookEventTypePattern, isWebhookSecretReference, matchesWebhookEventTypePattern, normalizeWebhookAdmissionDecision, normalizeWebhookDeliveryAuthorizationDecision, parseRetryAfterMs, projectWebhookEgressPayload, projectWebhookEventMetadata, projectWebhookFanOutJob, readWebhookActionParameters, renderWebhookDeliveryBodyV1, resolveWebhookDeliveryLimits, runWebhookDeliveryCycle, runWebhookFanOutCycle, webhookAdmissionAllow, webhookAdmissionDeny, webhookAdmissionUnavailable, webhookOperatorPolicyId, webhookSignedContentV1 };
3109
+ export declare function createWebhookDeliveryModule<TDb = unknown>(options: WebhookDeliveryModuleOptions): FabricModule<TDb>;
3110
+ //#endregion
3111
+ //#region src/replay-cycle.d.ts
3112
+ interface WebhookReplayCycleOptions extends Omit<WebhookFanOutCycleOptions, "subscriptions" | "eligibilitySettleMs"> {
3113
+ readonly replay: WebhookReplayStore;
3114
+ readonly subscriptions: WebhookReplaySubscriptionReader;
3115
+ }
3116
+ /** Processes fixed replay membership; dispatch uses the ordinary delivery worker. */
3117
+ export declare function runWebhookReplayCycle(options: WebhookReplayCycleOptions): Promise<{
3118
+ readonly claimed: number;
3119
+ readonly settled: number;
3120
+ readonly abandoned: number;
3121
+ }>;
3122
+ //#endregion
3123
+ export type { MemoryWebhookDeliveryStoreOptions, MemoryWebhookSubscriptionStoreOptions, PostgresWebhookDeliveryStoreOptions, PostgresWebhookSubscriptionStoreOptions, WebhookActionParameterCode, WebhookActionParameterRead, WebhookActionRefusalCode, WebhookActorEnrichmentRequest, WebhookActorEnrichmentResult, WebhookActorRequirementInput, WebhookAdmissionDecision, WebhookAdmissionDenialCode, WebhookAdmissionDenialRecord, WebhookAdmissionInput, WebhookAttemptBeginResult, WebhookAttemptOutcome, WebhookAttemptReasonCode, WebhookCallOptions, WebhookClaimRequest, WebhookClassifyOptions, WebhookClock, WebhookCompositePublisherOptions, WebhookCompositeSink, WebhookConfigErrorCode, WebhookConfigurationWriteResult, WebhookCreateSubscriptionParameters, WebhookDeliveryActionEventType, WebhookDeliveryActionName, WebhookDeliveryActionParameters, WebhookDeliveryAttemptFinish, WebhookDeliveryAttemptInFlight, WebhookDeliveryAttemptLogEntry, WebhookDeliveryAttemptRecord, WebhookDeliveryAttemptStart, WebhookDeliveryAuthorization, WebhookDeliveryAuthorizationDecision, WebhookDeliveryAuthorizationInput, WebhookDeliveryBodyV1, WebhookDeliveryClassification, WebhookDeliveryConfiguration, WebhookDeliveryConfigurationInput, WebhookDeliveryCycleEvent, WebhookDeliveryCycleOptions, WebhookDeliveryCycleResult, WebhookDeliveryCycleResultKind, WebhookDeliveryErrorCode, WebhookDeliveryIntent, WebhookDeliveryIntentStatus, WebhookDeliveryLeaseCheck, WebhookDeliveryLimits, WebhookDeliveryModuleOptions, WebhookDeliveryObservation, WebhookDeliveryStore, WebhookDestinationResolution, WebhookDestinationResolver, WebhookDestinationResolverOptions, WebhookDestinationUrlCheck, WebhookDiagnosticCode, WebhookDnsLookup, WebhookEgressPayload, WebhookEgressProjectionInput, WebhookEgressSubscription, WebhookEligibilityRevision, WebhookEmergencyRotationParameters, WebhookEventActorReader, WebhookEventActorReading, WebhookEventAdmission, WebhookEventMetadata, WebhookEventTypePolicy, WebhookFanOutCycleOptions, WebhookFanOutCycleResult, WebhookFanOutErrorCode, WebhookFanOutJob, WebhookFanOutJobDraft, WebhookFanOutJobFence, WebhookFanOutJobStatus, WebhookFanOutPublisherOptions, WebhookFencedInsertResult, WebhookFencedWrite, WebhookGovernedActionErrorCode, WebhookInputErrorCode, WebhookInsertResult, WebhookIntentRequeueParameters, WebhookJobActor, WebhookJobParameters, WebhookJsonObject, WebhookJsonValue, WebhookLease, WebhookOperatorAuthorization, WebhookOperatorAuthorizationInput, WebhookPayloadPolicy, WebhookPersistenceProjectionOptions, WebhookPostgresConnection, WebhookPostgresPool, WebhookPostgresQueryable, WebhookPostgresResult, WebhookProjectionErrorCode, WebhookPurgeResult, WebhookRandomSource, WebhookRedactionResult, WebhookRenderInput, WebhookRenderedBody, WebhookReplayCycleOptions, WebhookReplayEventsParameters, WebhookReplayItem, WebhookReplayJobKey, WebhookReplayKey, WebhookReplayOptions, WebhookReplayOutcome, WebhookReplayReceipt, WebhookReplayRefusal, WebhookReplayRequestInput, WebhookReplayRequestResult, WebhookReplaySettlement, WebhookReplaySettlementResult, WebhookReplaySkipReason, WebhookReplayStore, WebhookReplaySubscriptionReader, WebhookRequestHeaderInput, WebhookRequeueEvidence, WebhookRequeueRequest, WebhookRequeueResult, WebhookResolvedAddress, WebhookRetryDelay, WebhookRetryDelayOptions, WebhookRevocationCode, WebhookScope, WebhookScopeInput, WebhookScopePredicate, WebhookSecretReferenceVerifier, WebhookSignRequest, WebhookSignatureScheme, WebhookSigner, WebhookSourceEnvelope, WebhookStagedRotationParameters, WebhookStoreErrorCode, WebhookSubscriptionSnapshot, WebhookSubscriptionStatus, WebhookSubscriptionStore, WebhookSubscriptionTransitionInput, WebhookSubscriptionTransitionParameters, WebhookSubscriptionWriteRefusal, WebhookSubscriptionWriteResult, WebhookSubscriptionWriter, WebhookTombstone, WebhookTransactionCheckpointStage, WebhookTransport, WebhookTransportRequest, WebhookTransportResult, WebhookUpdateSubscriptionParameters, WebhookVettedDestination };