@fabricorg/webhook-delivery 0.1.1 → 0.1.3

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