@agentcomms/events-daemon 0.0.0-stage → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,662 @@
1
+ import { canonicalJson, sha256Hex } from "@agentcomms/core";
2
+ //#region src/domain/lifecycle.ts
3
+ /** A content-free domain refusal. No caller should substitute an arbitrary Error message at a control boundary. */
4
+ var EventDomainError = class extends Error {
5
+ code;
6
+ constructor(code, message) {
7
+ super(message);
8
+ this.name = "EventDomainError";
9
+ this.code = code;
10
+ }
11
+ };
12
+ const JUDGE_KINDS = [
13
+ "typesafe",
14
+ "laya",
15
+ "local-endpoint"
16
+ ];
17
+ function isJudgeKind(value) {
18
+ return typeof value === "string" && JUDGE_KINDS.includes(value);
19
+ }
20
+ function assertJudgeKindEnabled(kind, enabled) {
21
+ if (!enabled) throw new EventDomainError("JUDGE_KIND_DISABLED", `the ${kind} judge kind is disabled`);
22
+ }
23
+ //#endregion
24
+ //#region src/network/iana-special-purpose.ts
25
+ /** Checked-in IANA policy; network code must never refresh or consult it at runtime. */
26
+ const IANA_SPECIAL_PURPOSE = {
27
+ snapshotVersion: "iana-special-purpose-2025-10-09",
28
+ ipv4: {
29
+ "title": "IANA IPv4 Special-Purpose Address Registry",
30
+ "url": "https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry-1.csv",
31
+ "lastUpdated": "2025-10-09",
32
+ "rawSourceSha256": "e3e39e76d00b1677335db8e9a805c7b9480ea2f4dc9e33f0b93cd3a905128d73",
33
+ "prefixes": [
34
+ {
35
+ "prefix": "0.0.0.0/8",
36
+ "globallyReachable": false
37
+ },
38
+ {
39
+ "prefix": "0.0.0.0/32",
40
+ "globallyReachable": false
41
+ },
42
+ {
43
+ "prefix": "10.0.0.0/8",
44
+ "globallyReachable": false
45
+ },
46
+ {
47
+ "prefix": "100.64.0.0/10",
48
+ "globallyReachable": false
49
+ },
50
+ {
51
+ "prefix": "127.0.0.0/8",
52
+ "globallyReachable": false
53
+ },
54
+ {
55
+ "prefix": "169.254.0.0/16",
56
+ "globallyReachable": false
57
+ },
58
+ {
59
+ "prefix": "172.16.0.0/12",
60
+ "globallyReachable": false
61
+ },
62
+ {
63
+ "prefix": "192.0.0.0/24",
64
+ "globallyReachable": false
65
+ },
66
+ {
67
+ "prefix": "192.0.0.0/29",
68
+ "globallyReachable": false
69
+ },
70
+ {
71
+ "prefix": "192.0.0.8/32",
72
+ "globallyReachable": false
73
+ },
74
+ {
75
+ "prefix": "192.0.0.9/32",
76
+ "globallyReachable": true
77
+ },
78
+ {
79
+ "prefix": "192.0.0.10/32",
80
+ "globallyReachable": true
81
+ },
82
+ {
83
+ "prefix": "192.0.0.170/32",
84
+ "globallyReachable": false
85
+ },
86
+ {
87
+ "prefix": "192.0.0.171/32",
88
+ "globallyReachable": false
89
+ },
90
+ {
91
+ "prefix": "192.0.2.0/24",
92
+ "globallyReachable": false
93
+ },
94
+ {
95
+ "prefix": "192.31.196.0/24",
96
+ "globallyReachable": true
97
+ },
98
+ {
99
+ "prefix": "192.52.193.0/24",
100
+ "globallyReachable": true
101
+ },
102
+ {
103
+ "prefix": "192.88.99.0/24",
104
+ "globallyReachable": false
105
+ },
106
+ {
107
+ "prefix": "192.88.99.2/32",
108
+ "globallyReachable": false
109
+ },
110
+ {
111
+ "prefix": "192.168.0.0/16",
112
+ "globallyReachable": false
113
+ },
114
+ {
115
+ "prefix": "192.175.48.0/24",
116
+ "globallyReachable": true
117
+ },
118
+ {
119
+ "prefix": "198.18.0.0/15",
120
+ "globallyReachable": false
121
+ },
122
+ {
123
+ "prefix": "198.51.100.0/24",
124
+ "globallyReachable": false
125
+ },
126
+ {
127
+ "prefix": "203.0.113.0/24",
128
+ "globallyReachable": false
129
+ },
130
+ {
131
+ "prefix": "240.0.0.0/4",
132
+ "globallyReachable": false
133
+ },
134
+ {
135
+ "prefix": "255.255.255.255/32",
136
+ "globallyReachable": false
137
+ }
138
+ ]
139
+ },
140
+ ipv6: {
141
+ "title": "IANA IPv6 Special-Purpose Address Registry",
142
+ "url": "https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry-1.csv",
143
+ "lastUpdated": "2025-10-09",
144
+ "rawSourceSha256": "775feea0621dec8735a44fbf30f762e721e8f0a1b3ab7eb341961a88cfce2139",
145
+ "prefixes": [
146
+ {
147
+ "prefix": "::1/128",
148
+ "globallyReachable": false
149
+ },
150
+ {
151
+ "prefix": "::/128",
152
+ "globallyReachable": false
153
+ },
154
+ {
155
+ "prefix": "::ffff:0:0/96",
156
+ "globallyReachable": false
157
+ },
158
+ {
159
+ "prefix": "64:ff9b::/96",
160
+ "globallyReachable": true
161
+ },
162
+ {
163
+ "prefix": "64:ff9b:1::/48",
164
+ "globallyReachable": false
165
+ },
166
+ {
167
+ "prefix": "100::/64",
168
+ "globallyReachable": false
169
+ },
170
+ {
171
+ "prefix": "100:0:0:1::/64",
172
+ "globallyReachable": false
173
+ },
174
+ {
175
+ "prefix": "2001::/23",
176
+ "globallyReachable": false
177
+ },
178
+ {
179
+ "prefix": "2001::/32",
180
+ "globallyReachable": false
181
+ },
182
+ {
183
+ "prefix": "2001:1::1/128",
184
+ "globallyReachable": true
185
+ },
186
+ {
187
+ "prefix": "2001:1::2/128",
188
+ "globallyReachable": true
189
+ },
190
+ {
191
+ "prefix": "2001:1::3/128",
192
+ "globallyReachable": true
193
+ },
194
+ {
195
+ "prefix": "2001:2::/48",
196
+ "globallyReachable": false
197
+ },
198
+ {
199
+ "prefix": "2001:3::/32",
200
+ "globallyReachable": true
201
+ },
202
+ {
203
+ "prefix": "2001:4:112::/48",
204
+ "globallyReachable": true
205
+ },
206
+ {
207
+ "prefix": "2001:10::/28",
208
+ "globallyReachable": false
209
+ },
210
+ {
211
+ "prefix": "2001:20::/28",
212
+ "globallyReachable": true
213
+ },
214
+ {
215
+ "prefix": "2001:30::/28",
216
+ "globallyReachable": true
217
+ },
218
+ {
219
+ "prefix": "2001:db8::/32",
220
+ "globallyReachable": false
221
+ },
222
+ {
223
+ "prefix": "2002::/16",
224
+ "globallyReachable": false
225
+ },
226
+ {
227
+ "prefix": "2620:4f:8000::/48",
228
+ "globallyReachable": true
229
+ },
230
+ {
231
+ "prefix": "3fff::/20",
232
+ "globallyReachable": false
233
+ },
234
+ {
235
+ "prefix": "5f00::/16",
236
+ "globallyReachable": false
237
+ },
238
+ {
239
+ "prefix": "fc00::/7",
240
+ "globallyReachable": false
241
+ },
242
+ {
243
+ "prefix": "fe80::/10",
244
+ "globallyReachable": false
245
+ }
246
+ ]
247
+ }
248
+ };
249
+ var AddressPolicyError = class extends Error {
250
+ code = "NETWORK_ADDRESS_REFUSED";
251
+ constructor(message) {
252
+ super(message);
253
+ this.name = "AddressPolicyError";
254
+ }
255
+ };
256
+ const IPV4_BITS = 32;
257
+ const IPV6_BITS = 128;
258
+ const IPV4_MAX = (1n << 32n) - 1n;
259
+ const WELL_KNOWN_NAT64 = parseCidr("64:ff9b::/96");
260
+ const LOCALLY_ASSIGNED_NAT64 = parseCidr("64:ff9b:1::/48");
261
+ const LINK_LOCAL_V4 = parseCidr("169.254.0.0/16");
262
+ const LINK_LOCAL_V6 = parseCidr("fe80::/10");
263
+ const MULTICAST_V4 = parseCidr("224.0.0.0/4");
264
+ const MULTICAST_V6 = parseCidr("ff00::/8");
265
+ const METADATA_V4 = parseIpLiteral("169.254.169.254");
266
+ const METADATA_V6 = parseIpLiteral("fd00:ec2::254");
267
+ const registryPrefixes = [...IANA_SPECIAL_PURPOSE.ipv4.prefixes.map((entry) => ({
268
+ ...parseCidr(entry.prefix),
269
+ globallyReachable: entry.globallyReachable
270
+ })), ...IANA_SPECIAL_PURPOSE.ipv6.prefixes.map((entry) => ({
271
+ ...parseCidr(entry.prefix),
272
+ globallyReachable: entry.globallyReachable
273
+ }))];
274
+ function bitsFor(family) {
275
+ return family === 4 ? IPV4_BITS : IPV6_BITS;
276
+ }
277
+ function mask(family, prefixLength) {
278
+ const bits = bitsFor(family);
279
+ if (!Number.isInteger(prefixLength) || prefixLength < 0 || prefixLength > bits) throw new AddressPolicyError(`invalid CIDR prefix length ${prefixLength}`);
280
+ if (prefixLength === 0) return 0n;
281
+ return (1n << BigInt(prefixLength)) - 1n << BigInt(bits - prefixLength);
282
+ }
283
+ function stripBrackets(value) {
284
+ if (value.startsWith("[") && value.endsWith("]")) return value.slice(1, -1);
285
+ return value;
286
+ }
287
+ function parseIpv4(value) {
288
+ const parts = value.split(".");
289
+ if (parts.length !== 4) return null;
290
+ let result = 0n;
291
+ for (const part of parts) {
292
+ if (!/^(?:0|[1-9]\d{0,2})$/.test(part)) return null;
293
+ const octet = Number(part);
294
+ if (octet > 255) return null;
295
+ result = result << 8n | BigInt(octet);
296
+ }
297
+ return result;
298
+ }
299
+ function ipv4Text(value) {
300
+ return [
301
+ 24n,
302
+ 16n,
303
+ 8n,
304
+ 0n
305
+ ].map((shift) => Number(value >> shift & 255n)).join(".");
306
+ }
307
+ function parseIpv6(value) {
308
+ if (value.includes("%")) return null;
309
+ const doubleColon = value.indexOf("::");
310
+ if (doubleColon !== -1 && value.indexOf("::", doubleColon + 1) !== -1) return null;
311
+ const left = doubleColon === -1 ? value : value.slice(0, doubleColon);
312
+ const right = doubleColon === -1 ? "" : value.slice(doubleColon + 2);
313
+ const leftParts = left === "" ? [] : left.split(":");
314
+ const rightParts = right === "" ? [] : right.split(":");
315
+ const rawParts = [...leftParts, ...rightParts];
316
+ if (rawParts.some((part) => part === "")) return null;
317
+ const groups = [];
318
+ for (let index = 0; index < rawParts.length; index += 1) {
319
+ const part = rawParts[index];
320
+ if (part === void 0) return null;
321
+ if (part.includes(".")) {
322
+ if (index !== rawParts.length - 1) return null;
323
+ const embedded = parseIpv4(part);
324
+ if (embedded === null) return null;
325
+ groups.push(Number(embedded >> 16n & 65535n), Number(embedded & 65535n));
326
+ continue;
327
+ }
328
+ if (!/^[0-9a-f]{1,4}$/i.test(part)) return null;
329
+ groups.push(Number.parseInt(part, 16));
330
+ }
331
+ if (doubleColon === -1) {
332
+ if (groups.length !== 8) return null;
333
+ } else {
334
+ if (groups.length >= 8) return null;
335
+ const omitted = 8 - groups.length;
336
+ groups.splice(leftParts.reduce((count, part) => count + (part.includes(".") ? 2 : 1), 0), 0, ...Array(omitted).fill(0));
337
+ }
338
+ let result = 0n;
339
+ for (const group of groups) result = result << 16n | BigInt(group);
340
+ return result;
341
+ }
342
+ function ipv6Text(value) {
343
+ const groups = Array.from({ length: 8 }, (_, index) => Number(value >> BigInt((7 - index) * 16) & 65535n));
344
+ let bestStart = -1;
345
+ let bestLength = 0;
346
+ for (let index = 0; index < groups.length;) {
347
+ const group = groups[index];
348
+ if (group === void 0) break;
349
+ if (group !== 0) {
350
+ index += 1;
351
+ continue;
352
+ }
353
+ const start = index;
354
+ while (groups[index] === 0) index += 1;
355
+ const length = index - start;
356
+ if (length > bestLength && length > 1) {
357
+ bestStart = start;
358
+ bestLength = length;
359
+ }
360
+ }
361
+ if (bestStart === 0 && bestLength === 8) return "::";
362
+ const pieces = [];
363
+ for (let index = 0; index < groups.length; index += 1) {
364
+ if (index === bestStart) {
365
+ pieces.push("");
366
+ index += bestLength - 1;
367
+ if (index === groups.length - 1) pieces.push("");
368
+ continue;
369
+ }
370
+ const group = groups[index];
371
+ if (group === void 0) throw new Error("IPv6 formatter lost a parsed group");
372
+ pieces.push(group.toString(16));
373
+ }
374
+ return pieces.join(":").replace(/^:/, "::").replace(/:$/, "::");
375
+ }
376
+ /** Parses only a canonical-literal-shaped IP address, never a hostname or a DNS spelling. */
377
+ function parseIpLiteral(input) {
378
+ const value = stripBrackets(input.trim());
379
+ const ipv4 = parseIpv4(value);
380
+ if (ipv4 !== null) return {
381
+ address: ipv4Text(ipv4),
382
+ family: 4,
383
+ value: ipv4
384
+ };
385
+ const ipv6 = parseIpv6(value);
386
+ if (ipv6 !== null) return {
387
+ address: ipv6Text(ipv6),
388
+ family: 6,
389
+ value: ipv6
390
+ };
391
+ throw new AddressPolicyError(`a literal IP address was required, received ${input}`);
392
+ }
393
+ function parseCidr(input) {
394
+ const slash = input.lastIndexOf("/");
395
+ if (slash <= 0 || slash === input.length - 1) throw new AddressPolicyError(`invalid CIDR ${input}`);
396
+ const address = parseIpLiteral(input.slice(0, slash));
397
+ const prefixLength = Number(input.slice(slash + 1));
398
+ const prefixMask = mask(address.family, prefixLength);
399
+ if ((address.value & prefixMask) !== address.value) throw new AddressPolicyError(`CIDR ${input} has host bits set`);
400
+ return {
401
+ source: `${address.address}/${prefixLength}`,
402
+ family: address.family,
403
+ prefixLength,
404
+ value: address.value
405
+ };
406
+ }
407
+ function inCidr(address, cidr) {
408
+ return address.family === cidr.family && (address.value & mask(cidr.family, cidr.prefixLength)) === cidr.value;
409
+ }
410
+ function parseApprovedAddressSet(entries) {
411
+ if (entries.length === 0) throw new AddressPolicyError("approvedAddressSet must be non-empty");
412
+ const parsed = entries.map((entry) => entry.includes("/") ? parseCidr(entry) : parseCidr(`${parseIpLiteral(entry).address}/${bitsFor(parseIpLiteral(entry).family)}`));
413
+ for (let index = 1; index < entries.length; index += 1) {
414
+ const previous = entries[index - 1];
415
+ const current = entries[index];
416
+ if (previous === void 0 || current === void 0) throw new AddressPolicyError("approvedAddressSet could not be read");
417
+ if (previous.localeCompare(current) >= 0) throw new AddressPolicyError("approvedAddressSet must contain sorted, duplicate-free literals or CIDRs");
418
+ }
419
+ return parsed;
420
+ }
421
+ /** Checks the persisted rule input before a connector can use it to resolve a host. */
422
+ function assertApprovedAddressSet(entries) {
423
+ parseApprovedAddressSet(entries);
424
+ }
425
+ function isUnconditionallyDenied(address) {
426
+ return inCidr(address, LINK_LOCAL_V4) || inCidr(address, LINK_LOCAL_V6) || inCidr(address, MULTICAST_V4) || inCidr(address, MULTICAST_V6) || address.family === METADATA_V4.family && address.value === METADATA_V4.value || address.family === METADATA_V6.family && address.value === METADATA_V6.value;
427
+ }
428
+ function globallyReachable(address) {
429
+ return registryPrefixes.filter((entry) => inCidr(address, entry)).sort((left, right) => right.prefixLength - left.prefixLength)[0]?.globallyReachable ?? true;
430
+ }
431
+ function ipv4From(value) {
432
+ const ipv4 = value & IPV4_MAX;
433
+ return {
434
+ address: ipv4Text(ipv4),
435
+ family: 4,
436
+ value: ipv4
437
+ };
438
+ }
439
+ function configuredNat64(options) {
440
+ const raw = options.activeNat64Prefix ?? null;
441
+ if (raw === null) return null;
442
+ const entries = typeof raw === "string" ? [raw] : raw;
443
+ if (entries.length !== 1) throw new AddressPolicyError("NAT64 needs exactly one trusted /96 prefix");
444
+ const prefixText = entries[0];
445
+ if (prefixText === void 0) throw new AddressPolicyError("NAT64 prefix could not be read");
446
+ const prefix = parseCidr(prefixText);
447
+ if (prefix.family !== 6 || prefix.prefixLength !== 96) throw new AddressPolicyError("NAT64 needs one trusted IPv6 /96 prefix");
448
+ return prefix;
449
+ }
450
+ function embeddedAddresses(address, options) {
451
+ if (address.family !== 6) return [];
452
+ const embedded = [];
453
+ const upper96 = address.value >> 32n;
454
+ const mapped = upper96 === 65535n;
455
+ const compatible = upper96 === 0n && address.value > 1n;
456
+ if (mapped || compatible) embedded.push(ipv4From(address.value));
457
+ const configured = configuredNat64(options);
458
+ if (inCidr(address, WELL_KNOWN_NAT64) || inCidr(address, LOCALLY_ASSIGNED_NAT64) || configured !== null && inCidr(address, configured)) {
459
+ if (configured === null || !inCidr(address, configured)) throw new AddressPolicyError("NAT64 address has no single trusted active /96 prefix");
460
+ embedded.push(ipv4From(address.value));
461
+ }
462
+ if (inCidr(address, parseCidr("2002::/16"))) embedded.push(ipv4From(address.value >> 80n));
463
+ if (inCidr(address, parseCidr("2001:0::/32"))) {
464
+ embedded.push(ipv4From(address.value >> 64n));
465
+ embedded.push(ipv4From((address.value ^ IPV4_MAX) & IPV4_MAX));
466
+ }
467
+ return embedded;
468
+ }
469
+ function assertOne(address, approved, options, seen) {
470
+ if (seen.has(`${address.family}:${address.value}`)) return [];
471
+ seen.add(`${address.family}:${address.value}`);
472
+ if (isUnconditionallyDenied(address)) throw new AddressPolicyError(`address ${address.address} is metadata or link-local and is always refused`);
473
+ const explicitlyApproved = approved.some((entry) => inCidr(address, entry));
474
+ if (!globallyReachable(address) && !explicitlyApproved) throw new AddressPolicyError(`non-global address ${address.address} needs an exact approvedAddressSet entry`);
475
+ if (!explicitlyApproved) throw new AddressPolicyError(`address ${address.address} is not in the approvedAddressSet`);
476
+ const nested = embeddedAddresses(address, options);
477
+ for (const child of nested) assertOne(child, approved, options, seen);
478
+ return nested;
479
+ }
480
+ /** Validates the literal itself and every embedded transition address against the exact approved set. */
481
+ function assertAddressAllowed(input, options) {
482
+ const address = parseIpLiteral(input);
483
+ const embedded = assertOne(address, parseApprovedAddressSet(options.approvedAddressSet), options, /* @__PURE__ */ new Set());
484
+ return {
485
+ address: address.address,
486
+ family: address.family,
487
+ embedded
488
+ };
489
+ }
490
+ //#endregion
491
+ //#region src/network/local-endpoint.ts
492
+ var LocalEndpointError = class extends Error {
493
+ code = "LOCAL_ENDPOINT_REFUSED";
494
+ constructor(message) {
495
+ super(message);
496
+ this.name = "LocalEndpointError";
497
+ }
498
+ };
499
+ /** Validates the deliberately narrow local-only network descriptor. */
500
+ function validateLocalEndpoint(input, approvedAddressSet) {
501
+ const url = typeof input === "string" ? new URL(input) : new URL(input.href);
502
+ const hostname = url.hostname.startsWith("[") && url.hostname.endsWith("]") ? url.hostname.slice(1, -1) : url.hostname;
503
+ if (url.protocol !== "http:") throw new LocalEndpointError("a local endpoint must use HTTP");
504
+ if (url.username !== "" || url.password !== "" || url.hash !== "") throw new LocalEndpointError("a local endpoint URL has unsupported authority data");
505
+ let address;
506
+ try {
507
+ address = parseIpLiteral(hostname).address;
508
+ assertAddressAllowed(address, { approvedAddressSet });
509
+ } catch (error) {
510
+ if (error instanceof AddressPolicyError) throw new LocalEndpointError(error.message);
511
+ throw error;
512
+ }
513
+ if (address !== "127.0.0.1" && address !== "::1") throw new LocalEndpointError("a local endpoint must use literal 127.0.0.1 or ::1");
514
+ if (approvedAddressSet.length !== 1 || approvedAddressSet[0] === void 0 || approvedAddressSet[0].includes("/") || parseIpLiteral(approvedAddressSet[0]).address !== address) throw new LocalEndpointError("a local endpoint requires its one exact approved literal loopback address");
515
+ return {
516
+ url,
517
+ address
518
+ };
519
+ }
520
+ //#endregion
521
+ //#region src/domain/webhook-target.ts
522
+ function fail(message) {
523
+ throw new EventDomainError("VERSION_DOCUMENT_INVALID", message);
524
+ }
525
+ function record(value, name) {
526
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return fail(`${name} is an object`);
527
+ try {
528
+ return JSON.parse(canonicalJson(value));
529
+ } catch {
530
+ return fail(`${name} is canonical JSON`);
531
+ }
532
+ }
533
+ function text(value, name) {
534
+ if (typeof value !== "string" || value.length === 0) return fail(`${name} is a non-empty string`);
535
+ return value;
536
+ }
537
+ function positiveInteger(value, name) {
538
+ if (!Number.isSafeInteger(value) || value < 1) return fail(`${name} is a positive safe integer`);
539
+ return value;
540
+ }
541
+ function canonicalPercentEscapes(value) {
542
+ return value.replace(/%[0-9a-f]{2}/gi, (encoded) => encoded.toUpperCase());
543
+ }
544
+ function canonicalUrl(value, allowQuery) {
545
+ let parsed;
546
+ try {
547
+ parsed = new URL(value);
548
+ } catch {
549
+ return fail("a webhook URL is absolute");
550
+ }
551
+ const scheme = parsed.protocol.slice(0, -1);
552
+ if (scheme !== "http" && scheme !== "https") return fail("a webhook URL uses http or https");
553
+ if (parsed.hash.length !== 0) return fail("a webhook URL has no fragment");
554
+ if (!allowQuery && (parsed.username.length !== 0 || parsed.password.length !== 0 || parsed.search.length !== 0)) return fail("a plain webhook URL has no userinfo or query");
555
+ const port = parsed.port.length === 0 ? scheme === "https" ? 443 : 80 : Number(parsed.port);
556
+ if (!Number.isSafeInteger(port) || port < 1 || port > 65535) return fail("a webhook URL has a valid port");
557
+ const host = parsed.hostname.toLowerCase();
558
+ if (host.length === 0) return fail("a webhook URL has a host");
559
+ return {
560
+ value: `${scheme}://${allowQuery && (parsed.username.length !== 0 || parsed.password.length !== 0) ? `${canonicalPercentEscapes(parsed.username)}${parsed.password.length === 0 ? "" : `:${canonicalPercentEscapes(parsed.password)}`}@` : ""}${host.includes(":") && !host.startsWith("[") ? `[${host}]` : host}:${port}${canonicalPercentEscapes(parsed.pathname)}${allowQuery ? canonicalPercentEscapes(parsed.search) : ""}`,
561
+ authority: {
562
+ scheme,
563
+ host,
564
+ port
565
+ }
566
+ };
567
+ }
568
+ /** Canonicalises the complete hidden URL only for the daemon's secret-store factory. */
569
+ function secretWebhookUrlDescriptor(value) {
570
+ const canonical = canonicalUrl(value, true);
571
+ return {
572
+ kind: "secret",
573
+ ...canonical.authority,
574
+ sha256: sha256Hex(canonical.value)
575
+ };
576
+ }
577
+ /** Produces the private stored URL and its public descriptor together; callers return only the descriptor. */
578
+ function canonicalSecretWebhookUrl(value) {
579
+ const canonical = canonicalUrl(value, true);
580
+ return {
581
+ value: canonical.value,
582
+ descriptor: {
583
+ kind: "secret",
584
+ ...canonical.authority,
585
+ sha256: sha256Hex(canonical.value)
586
+ }
587
+ };
588
+ }
589
+ function canonicalAddressSet(value) {
590
+ if (!Array.isArray(value) || value.length === 0) return fail("a webhook approvedAddressSet is a non-empty array");
591
+ const addresses = value.map((entry) => text(entry, "a webhook approvedAddressSet entry"));
592
+ const sorted = [...addresses].sort();
593
+ if (new Set(sorted).size !== sorted.length || sorted.some((entry, index) => entry !== addresses[index])) return fail("a webhook approvedAddressSet is sorted and duplicate-free");
594
+ return sorted;
595
+ }
596
+ function canonicalUrlDescriptor(value) {
597
+ const url = record(value, "a webhook URL descriptor");
598
+ if (url.kind === "plain") return {
599
+ kind: "plain",
600
+ value: canonicalUrl(text(url.value, "a plain webhook URL"), false).value
601
+ };
602
+ if (url.kind === "secret") {
603
+ const scheme = text(url.scheme, "a secret webhook scheme");
604
+ if (scheme !== "http" && scheme !== "https") return fail("a secret webhook scheme is http or https");
605
+ const host = text(url.host, "a secret webhook host").toLowerCase();
606
+ const port = positiveInteger(url.port, "a secret webhook port");
607
+ if (port > 65535) return fail("a secret webhook port is at most 65535");
608
+ const authority = canonicalUrl(`${scheme}://${host.includes(":") ? `[${host}]` : host}:${port}/`, false).authority;
609
+ if (authority.scheme !== scheme || authority.host !== host || authority.port !== port) return fail("a secret webhook authority is canonical");
610
+ const sha256 = text(url.sha256, "a secret webhook fingerprint");
611
+ if (!/^[a-f0-9]{64}$/.test(sha256)) return fail("a secret webhook fingerprint is lowercase SHA-256");
612
+ return {
613
+ kind: "secret",
614
+ scheme,
615
+ host,
616
+ port,
617
+ sha256
618
+ };
619
+ }
620
+ return fail("a webhook URL descriptor has a known kind");
621
+ }
622
+ /** The canonical immutable webhook target contains public authority only, never a slot or complete secret URL. */
623
+ function canonicalWebhookTarget(value) {
624
+ const target = record(value, "a webhook target document");
625
+ if (target.kind !== "webhook") return fail("a webhook target document has kind webhook");
626
+ if (target.signing !== "standard-webhooks") return fail("a webhook target uses standard-webhooks signing");
627
+ if (target.ordering !== "strict") return fail("a webhook target uses strict delivery ordering");
628
+ const retryLimit = positiveInteger(target.retryLimit, "a webhook retry limit");
629
+ if (retryLimit > 20) return fail("a webhook retry limit is at most 20");
630
+ if (target.representation !== "plain" && target.representation !== "enveloped") return fail("a webhook representation is plain or enveloped");
631
+ const url = canonicalUrlDescriptor(target.url);
632
+ const approvedAddressSet = canonicalAddressSet(target.approvedAddressSet);
633
+ try {
634
+ assertApprovedAddressSet(approvedAddressSet);
635
+ if (url.kind === "plain" && url.value.startsWith("http:")) validateLocalEndpoint(url.value, approvedAddressSet);
636
+ if (url.kind === "secret" && url.scheme === "http") validateLocalEndpoint(`http://${url.host.includes(":") ? `[${url.host}]` : url.host}:${url.port}/`, approvedAddressSet);
637
+ } catch {
638
+ return fail("a webhook target has an approved network authority");
639
+ }
640
+ return {
641
+ targetId: text(target.targetId, "a webhook target id"),
642
+ version: positiveInteger(target.version, "a webhook target version"),
643
+ kind: "webhook",
644
+ url,
645
+ approvedAddressSet,
646
+ signing: "standard-webhooks",
647
+ ordering: "strict",
648
+ retryLimit,
649
+ representation: target.representation
650
+ };
651
+ }
652
+ /** Maps an already canonical document to Task 2's closed delivery union without rebuilding delivery bytes. */
653
+ function webhookDeliveryTarget(document) {
654
+ return {
655
+ kind: "webhook",
656
+ targetId: document.targetId,
657
+ targetVersion: document.version,
658
+ representation: document.representation
659
+ };
660
+ }
661
+ //#endregion
662
+ export { AddressPolicyError as a, parseIpLiteral as c, isJudgeKind as d, webhookDeliveryTarget as i, EventDomainError as l, canonicalWebhookTarget as n, assertAddressAllowed as o, secretWebhookUrlDescriptor as r, assertApprovedAddressSet as s, canonicalSecretWebhookUrl as t, assertJudgeKindEnabled as u };