@tinycloud/sdk-core 2.6.3-beta.0 → 2.7.0-beta.2

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,2912 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/requester/index.ts
21
+ var requester_exports = {};
22
+ __export(requester_exports, {
23
+ HOLDER_KEY_BINDING_PRESENTATION_SCHEMA: () => HOLDER_KEY_BINDING_PRESENTATION_SCHEMA,
24
+ LISTEN_SQL_STATEMENT_CATALOG: () => LISTEN_SQL_STATEMENT_CATALOG,
25
+ POLICY_ENGINE_CHALLENGE_REQUEST_SCHEMA: () => POLICY_ENGINE_CHALLENGE_REQUEST_SCHEMA,
26
+ POLICY_ENGINE_CHALLENGE_RESPONSE_SCHEMA: () => POLICY_ENGINE_CHALLENGE_RESPONSE_SCHEMA,
27
+ POLICY_ENGINE_DENIAL_SCHEMA: () => POLICY_ENGINE_DENIAL_SCHEMA,
28
+ POLICY_ENGINE_GRANT_PRESENTATION_DENIAL_CODES: () => POLICY_ENGINE_GRANT_PRESENTATION_DENIAL_CODES,
29
+ POLICY_ENGINE_RESOLVE_REQUEST_SCHEMA: () => POLICY_ENGINE_RESOLVE_REQUEST_SCHEMA,
30
+ PORTABLE_DELEGATION_SCHEMA: () => PORTABLE_DELEGATION_SCHEMA,
31
+ REQUESTER_ENGINE_RETRY_ATTEMPTS: () => REQUESTER_ENGINE_RETRY_ATTEMPTS,
32
+ REQUESTER_ENGINE_RETRY_MAX_DELAY_MS: () => REQUESTER_ENGINE_RETRY_MAX_DELAY_MS,
33
+ REQUESTER_NEAR_EXPIRY_SECONDS: () => REQUESTER_NEAR_EXPIRY_SECONDS,
34
+ TranscriptRequester: () => TranscriptRequester,
35
+ TranscriptRequesterError: () => TranscriptRequesterError,
36
+ createTranscriptRequester: () => createTranscriptRequester,
37
+ deriveDelegationCid: () => deriveDelegationCid,
38
+ listenTranscriptScopedStatementName: () => listenTranscriptScopedStatementName,
39
+ parseNodeNativePortableDelegation: () => parseNodeNativePortableDelegation
40
+ });
41
+ module.exports = __toCommonJS(requester_exports);
42
+ var import_viem4 = require("viem");
43
+ var import_blake3 = require("@noble/hashes/blake3");
44
+ var import_cid = require("multiformats/cid");
45
+ var import_digest = require("multiformats/hashes/digest");
46
+ var import_zod = require("zod");
47
+
48
+ // src/policy/errors.ts
49
+ var SignedObjectProfileError = class extends Error {
50
+ constructor(code, message) {
51
+ super(message);
52
+ this.name = "SignedObjectProfileError";
53
+ this.code = code;
54
+ }
55
+ };
56
+ var SignedObjectSchemaError = class extends SignedObjectProfileError {
57
+ constructor(message) {
58
+ super("schema-invalid", message);
59
+ this.name = "SignedObjectSchemaError";
60
+ }
61
+ };
62
+ var SignedObjectCanonicalizationError = class extends SignedObjectProfileError {
63
+ constructor(message) {
64
+ super("canonicalization-mismatch", message);
65
+ this.name = "SignedObjectCanonicalizationError";
66
+ }
67
+ };
68
+ var SignedObjectDigestError = class extends SignedObjectProfileError {
69
+ constructor(message) {
70
+ super("digest-mismatch", message);
71
+ this.name = "SignedObjectDigestError";
72
+ }
73
+ };
74
+ var SignedObjectIdError = class extends SignedObjectProfileError {
75
+ constructor(message) {
76
+ super("id-mismatch", message);
77
+ this.name = "SignedObjectIdError";
78
+ }
79
+ };
80
+ var SigningKeyBindingError = class extends SignedObjectProfileError {
81
+ constructor(message) {
82
+ super("signing-key-binding", message);
83
+ this.name = "SigningKeyBindingError";
84
+ }
85
+ };
86
+ var SignatureMaterialError = class extends SignedObjectProfileError {
87
+ constructor(message) {
88
+ super("signature-material-invalid", message);
89
+ this.name = "SignatureMaterialError";
90
+ }
91
+ };
92
+ var UnsupportedSignatureSuiteError = class extends SignedObjectProfileError {
93
+ constructor(message) {
94
+ super("signature-suite-unsupported", message);
95
+ this.name = "UnsupportedSignatureSuiteError";
96
+ }
97
+ };
98
+ var SignatureVerificationError = class extends SignedObjectProfileError {
99
+ constructor(message) {
100
+ super("signature-invalid", message);
101
+ this.name = "SignatureVerificationError";
102
+ }
103
+ };
104
+
105
+ // src/policy/jcs.ts
106
+ var objectHasOwn = Object.hasOwn ?? Object.prototype.hasOwnProperty.call.bind(
107
+ Object.prototype.hasOwnProperty
108
+ );
109
+ function jcsCanonicalize(input) {
110
+ return serialize(normalizeJson(input, "$"));
111
+ }
112
+ function normalizeJson(input, path = "$") {
113
+ if (input === null) {
114
+ return null;
115
+ }
116
+ switch (typeof input) {
117
+ case "boolean":
118
+ return input;
119
+ case "number":
120
+ if (!Number.isFinite(input)) {
121
+ throw new SignedObjectCanonicalizationError(
122
+ `${path} must be a finite JSON number`
123
+ );
124
+ }
125
+ return input;
126
+ case "string":
127
+ assertUnicodeScalarString(input, path);
128
+ return input;
129
+ case "object":
130
+ return normalizeJsonObjectOrArray(input, path);
131
+ case "bigint":
132
+ case "function":
133
+ case "symbol":
134
+ case "undefined":
135
+ default:
136
+ throw new SignedObjectCanonicalizationError(
137
+ `${path} is not a JSON value`
138
+ );
139
+ }
140
+ }
141
+ function serialize(value) {
142
+ if (value === null) {
143
+ return "null";
144
+ }
145
+ switch (typeof value) {
146
+ case "boolean":
147
+ return value ? "true" : "false";
148
+ case "number": {
149
+ const encoded = JSON.stringify(value);
150
+ if (encoded === void 0) {
151
+ throw new SignedObjectCanonicalizationError(
152
+ "number could not be serialized as JSON"
153
+ );
154
+ }
155
+ return encoded;
156
+ }
157
+ case "string":
158
+ return JSON.stringify(value);
159
+ case "object":
160
+ if (Array.isArray(value)) {
161
+ return `[${value.map((item) => serialize(item)).join(",")}]`;
162
+ }
163
+ return serializeObject(value);
164
+ default:
165
+ throw new SignedObjectCanonicalizationError(
166
+ `unsupported JSON value type ${typeof value}`
167
+ );
168
+ }
169
+ }
170
+ function normalizeJsonObjectOrArray(input, path) {
171
+ assertNoSymbolKeys(input, path);
172
+ if (Array.isArray(input)) {
173
+ for (const key of Object.getOwnPropertyNames(input)) {
174
+ if (key === "length") {
175
+ continue;
176
+ }
177
+ if (!isArrayIndexKey(key, input.length)) {
178
+ throw new SignedObjectCanonicalizationError(
179
+ `${path}.${key} is not allowed on a JSON array`
180
+ );
181
+ }
182
+ const descriptor = Object.getOwnPropertyDescriptor(input, key);
183
+ if (descriptor?.enumerable !== true) {
184
+ throw new SignedObjectCanonicalizationError(
185
+ `${path}[${key}] must be an enumerable JSON array item`
186
+ );
187
+ }
188
+ if (!("value" in descriptor)) {
189
+ throw new SignedObjectCanonicalizationError(
190
+ `${path}[${key}] must be a JSON data property`
191
+ );
192
+ }
193
+ }
194
+ const output2 = [];
195
+ for (let index = 0; index < input.length; index++) {
196
+ if (!objectHasOwn(input, index)) {
197
+ throw new SignedObjectCanonicalizationError(
198
+ `${path}[${index}] must not be a sparse array hole`
199
+ );
200
+ }
201
+ output2.push(normalizeJson(input[index], `${path}[${index}]`));
202
+ }
203
+ return output2;
204
+ }
205
+ const proto = Object.getPrototypeOf(input);
206
+ if (proto !== Object.prototype && proto !== null) {
207
+ throw new SignedObjectCanonicalizationError(
208
+ `${path} must be a plain JSON object`
209
+ );
210
+ }
211
+ const output = /* @__PURE__ */ Object.create(null);
212
+ for (const key of Object.getOwnPropertyNames(input)) {
213
+ assertUnicodeScalarString(key, `${path} key`);
214
+ if (key === "__proto__" || key === "constructor") {
215
+ throw new SignedObjectCanonicalizationError(
216
+ `${path}.${key} is not allowed`
217
+ );
218
+ }
219
+ const descriptor = Object.getOwnPropertyDescriptor(input, key);
220
+ if (descriptor?.enumerable !== true) {
221
+ throw new SignedObjectCanonicalizationError(
222
+ `${path}.${key} must be an enumerable JSON object property`
223
+ );
224
+ }
225
+ if (!("value" in descriptor)) {
226
+ throw new SignedObjectCanonicalizationError(
227
+ `${path}.${key} must be a JSON data property`
228
+ );
229
+ }
230
+ const value = descriptor.value;
231
+ output[key] = normalizeJson(value, `${path}.${key}`);
232
+ }
233
+ return output;
234
+ }
235
+ function assertNoSymbolKeys(input, path) {
236
+ if (Object.getOwnPropertySymbols(input).length > 0) {
237
+ throw new SignedObjectCanonicalizationError(
238
+ `${path} must not have symbol properties`
239
+ );
240
+ }
241
+ }
242
+ function isArrayIndexKey(key, length) {
243
+ if (!/^(0|[1-9]\d*)$/.test(key)) {
244
+ return false;
245
+ }
246
+ const index = Number(key);
247
+ return Number.isSafeInteger(index) && index >= 0 && index < length;
248
+ }
249
+ function serializeObject(value) {
250
+ const keys = Object.keys(value).sort(compareCodePoints);
251
+ const parts = keys.map((key) => `${JSON.stringify(key)}:${serialize(value[key])}`);
252
+ return `{${parts.join(",")}}`;
253
+ }
254
+ function compareCodePoints(a, b) {
255
+ const left = Array.from(a);
256
+ const right = Array.from(b);
257
+ const max = Math.min(left.length, right.length);
258
+ for (let index = 0; index < max; index++) {
259
+ const leftPoint = left[index].codePointAt(0) ?? 0;
260
+ const rightPoint = right[index].codePointAt(0) ?? 0;
261
+ if (leftPoint !== rightPoint) {
262
+ return leftPoint - rightPoint;
263
+ }
264
+ }
265
+ return left.length - right.length;
266
+ }
267
+ function assertUnicodeScalarString(value, path) {
268
+ for (let index = 0; index < value.length; index++) {
269
+ const code = value.charCodeAt(index);
270
+ if (code >= 55296 && code <= 56319) {
271
+ const next = value.charCodeAt(index + 1);
272
+ if (Number.isNaN(next) || next < 56320 || next > 57343) {
273
+ throw new SignedObjectCanonicalizationError(
274
+ `${path} contains a lone high surrogate`
275
+ );
276
+ }
277
+ index++;
278
+ continue;
279
+ }
280
+ if (code >= 56320 && code <= 57343) {
281
+ throw new SignedObjectCanonicalizationError(
282
+ `${path} contains a lone low surrogate`
283
+ );
284
+ }
285
+ }
286
+ }
287
+
288
+ // src/policy/capability.ts
289
+ var import_bootstrap = require("@tinycloud/bootstrap");
290
+ var import_viem = require("viem");
291
+ var textEncoder = new TextEncoder();
292
+ var PolicyCapabilityError = class extends Error {
293
+ constructor(code, message) {
294
+ super(message);
295
+ this.name = "PolicyCapabilityError";
296
+ this.code = code;
297
+ }
298
+ };
299
+ var objectHasOwn2 = Object.hasOwn ?? Object.prototype.hasOwnProperty.call.bind(
300
+ Object.prototype.hasOwnProperty
301
+ );
302
+ var CEILING_SERVICES = /* @__PURE__ */ new Set(["tinycloud.kv", "tinycloud.sql", "tinycloud.vfs"]);
303
+ var GRANTABLE_ACTIONS = /* @__PURE__ */ new Map();
304
+ for (const entry of import_bootstrap.CAPABILITY_REGISTRY) {
305
+ if (!CEILING_SERVICES.has(entry.service)) {
306
+ continue;
307
+ }
308
+ if (entry.aliasOf !== void 0 || entry.implies !== void 0 || entry.urn.endsWith("/*")) {
309
+ continue;
310
+ }
311
+ const existing = GRANTABLE_ACTIONS.get(entry.service);
312
+ if (existing === void 0) {
313
+ GRANTABLE_ACTIONS.set(entry.service, /* @__PURE__ */ new Set([entry.urn]));
314
+ continue;
315
+ }
316
+ existing.add(entry.urn);
317
+ }
318
+ function normalizePolicyCapability(input) {
319
+ return normalizePolicyCapabilityWithOptions(input, { requireCanonical: true });
320
+ }
321
+ function canonicalizePolicyCapability(input) {
322
+ return normalizePolicyCapabilityWithOptions(input, { allowPrefixPaths: true });
323
+ }
324
+ function policyCapabilityContains(authority, request) {
325
+ let auth;
326
+ let req;
327
+ try {
328
+ auth = canonicalizePolicyCapability(authority);
329
+ req = canonicalizePolicyCapability(request);
330
+ } catch {
331
+ return false;
332
+ }
333
+ if (auth.service !== req.service || auth.space !== req.space) {
334
+ return false;
335
+ }
336
+ if (!pathContains(auth.path, req.path)) {
337
+ return false;
338
+ }
339
+ const authActions = new Set(auth.actions);
340
+ for (const action of req.actions) {
341
+ if (!authActions.has(action)) {
342
+ return false;
343
+ }
344
+ }
345
+ return caveatsContain(auth.caveats, req.caveats);
346
+ }
347
+ function normalizePolicyCapabilityWithOptions(input, options) {
348
+ const object = expectObject(input, "$");
349
+ assertExactKeys(object, ["service", "space", "path", "actions", "caveats"], "$");
350
+ const service = requiredString(object, "service", "$", "policy-capability-malformed-service");
351
+ if (!CEILING_SERVICES.has(service)) {
352
+ throw new PolicyCapabilityError(
353
+ "policy-capability-malformed-service",
354
+ "$.service is outside the frozen permissionsCeiling vocabulary"
355
+ );
356
+ }
357
+ const space = requiredString(object, "space", "$", "policy-capability-malformed-space");
358
+ validateConcreteSpace(space);
359
+ const canonicalSpace = space.normalize("NFC");
360
+ if (options.requireCanonical && canonicalSpace !== space) {
361
+ throw new PolicyCapabilityError(
362
+ "policy-capability-malformed-space",
363
+ "$.space must already be canonical NFC"
364
+ );
365
+ }
366
+ const rawPath = requiredString(object, "path", "$", "policy-capability-malformed-path");
367
+ validateRawPath(rawPath, options);
368
+ const path = normalizePath(rawPath);
369
+ if (options.requireCanonical && path !== rawPath) {
370
+ throw new PolicyCapabilityError(
371
+ "policy-capability-malformed-path",
372
+ "$.path must already be canonical"
373
+ );
374
+ }
375
+ validateRawPath(path, options);
376
+ const actions = normalizeActions(
377
+ requiredArray(object, "actions", "$", "policy-capability-malformed-action"),
378
+ service,
379
+ options
380
+ );
381
+ const output = /* @__PURE__ */ Object.create(null);
382
+ output.service = service;
383
+ output.space = canonicalSpace;
384
+ output.path = path;
385
+ output.actions = actions;
386
+ if (hasOwn(object, "caveats")) {
387
+ output.caveats = validateCaveats(
388
+ requiredValue(object, "caveats", "$", "policy-capability-malformed-caveats"),
389
+ output.service
390
+ );
391
+ }
392
+ return output;
393
+ }
394
+ function expectObject(input, path) {
395
+ try {
396
+ const normalized = normalizeJson(input);
397
+ if (normalized === null || typeof normalized !== "object" || Array.isArray(normalized)) {
398
+ throw new PolicyCapabilityError("policy-capability-malformed", `${path} must be an object`);
399
+ }
400
+ return normalized;
401
+ } catch (error) {
402
+ if (error instanceof PolicyCapabilityError) {
403
+ throw error;
404
+ }
405
+ throw new PolicyCapabilityError(
406
+ "policy-capability-malformed",
407
+ error instanceof Error ? error.message : String(error)
408
+ );
409
+ }
410
+ }
411
+ function assertExactKeys(object, allowed, path) {
412
+ const allowedSet = new Set(allowed);
413
+ for (const key of Object.keys(object)) {
414
+ if (!allowedSet.has(key)) {
415
+ throw new PolicyCapabilityError(
416
+ key === "id" || key === "scope" ? "policy-capability-malformed" : "policy-capability-unknown-key",
417
+ `${path} has unknown field ${key}`
418
+ );
419
+ }
420
+ }
421
+ }
422
+ function requiredValue(object, key, path, code) {
423
+ if (!hasOwn(object, key)) {
424
+ throw new PolicyCapabilityError(code, `${path}.${key} is required`);
425
+ }
426
+ return object[key];
427
+ }
428
+ function requiredString(object, key, path, code) {
429
+ const value = requiredValue(object, key, path, code);
430
+ if (typeof value !== "string" || value.length === 0) {
431
+ throw new PolicyCapabilityError(code, `${path}.${key} must be a non-empty string`);
432
+ }
433
+ return value;
434
+ }
435
+ function requiredArray(object, key, path, code) {
436
+ const value = requiredValue(object, key, path, code);
437
+ if (!Array.isArray(value)) {
438
+ throw new PolicyCapabilityError(code, `${path}.${key} must be an array`);
439
+ }
440
+ return value;
441
+ }
442
+ function validateConcreteSpace(space) {
443
+ if (space === "*" || space.includes("*") || space.includes("?") || space.includes("/")) {
444
+ throw new PolicyCapabilityError(
445
+ "policy-capability-malformed-space",
446
+ "$.space must be concrete"
447
+ );
448
+ }
449
+ }
450
+ function validateRawPath(path, options) {
451
+ if (path.length === 0) {
452
+ throw new PolicyCapabilityError("policy-capability-malformed-path", "$.path is empty");
453
+ }
454
+ if (!options.allowPrefixPaths && path.endsWith("/")) {
455
+ throw new PolicyCapabilityError(
456
+ "policy-capability-malformed-path",
457
+ "$.path must be an exact concrete path, not a prefix"
458
+ );
459
+ }
460
+ if (path.endsWith("/*") || path.includes("**")) {
461
+ throw new PolicyCapabilityError("policy-capability-malformed-path", "$.path is a prefix form");
462
+ }
463
+ const segments = path.split("/");
464
+ for (let index = 0; index < segments.length; index++) {
465
+ const segment = segments[index];
466
+ const isTrailingPrefixSegment = options.allowPrefixPaths && index === segments.length - 1 && segment.length === 0;
467
+ if (segment.length === 0 && !isTrailingPrefixSegment) {
468
+ throw new PolicyCapabilityError("policy-capability-malformed-path", "$.path has an empty segment");
469
+ }
470
+ if (segment === "." || segment === "..") {
471
+ throw new PolicyCapabilityError(
472
+ "policy-capability-malformed-path",
473
+ "$.path has a traversal segment"
474
+ );
475
+ }
476
+ if (segment === "*" || segment === "?" || segment.includes("*") || segment.includes("?")) {
477
+ throw new PolicyCapabilityError(
478
+ "policy-capability-malformed-path",
479
+ "$.path has a wildcard segment"
480
+ );
481
+ }
482
+ }
483
+ }
484
+ function normalizePath(path) {
485
+ return decodeUnreserved(path).normalize("NFC");
486
+ }
487
+ function decodeUnreserved(path) {
488
+ return path.replace(/%[0-9A-Fa-f]{2}/g, (encoded) => {
489
+ const char = String.fromCharCode(Number.parseInt(encoded.slice(1), 16));
490
+ return /^[A-Za-z0-9._~-]$/.test(char) ? char : encoded.toUpperCase();
491
+ });
492
+ }
493
+ function normalizeActions(actions, service, options) {
494
+ const accepted = GRANTABLE_ACTIONS.get(service);
495
+ if (accepted === void 0) {
496
+ throw new PolicyCapabilityError(
497
+ "policy-capability-malformed-service",
498
+ "$.service is unsupported"
499
+ );
500
+ }
501
+ const unique = /* @__PURE__ */ new Set();
502
+ const rawActions = [];
503
+ for (let index = 0; index < actions.length; index++) {
504
+ const action = actions[index];
505
+ if (typeof action !== "string" || action.length === 0) {
506
+ throw new PolicyCapabilityError(
507
+ "policy-capability-malformed-action",
508
+ `$.actions[${index}] must be a non-empty action URN`
509
+ );
510
+ }
511
+ if (!action.startsWith(`${service}/`) || action.includes("*") || !accepted.has(action)) {
512
+ throw new PolicyCapabilityError(
513
+ "policy-capability-malformed-action",
514
+ `$.actions[${index}] is not a grantable action URN`
515
+ );
516
+ }
517
+ rawActions.push(action);
518
+ unique.add(action);
519
+ }
520
+ if (unique.size === 0) {
521
+ throw new PolicyCapabilityError(
522
+ "policy-capability-empty-actions",
523
+ "$.actions must not be empty"
524
+ );
525
+ }
526
+ const normalizedActions = [...unique].sort();
527
+ if (options.requireCanonical && (rawActions.length !== normalizedActions.length || rawActions.some((action, index) => action !== normalizedActions[index]))) {
528
+ throw new PolicyCapabilityError(
529
+ "policy-capability-malformed-action",
530
+ "$.actions must already be sorted, deduplicated canonical action URNs"
531
+ );
532
+ }
533
+ return normalizedActions;
534
+ }
535
+ function validateCaveats(input, service) {
536
+ if (input === null || typeof input !== "object" || Array.isArray(input)) {
537
+ throw new PolicyCapabilityError(
538
+ "policy-capability-malformed-caveats",
539
+ "$.caveats must be an object"
540
+ );
541
+ }
542
+ if (service === "tinycloud.sql") {
543
+ return validateSqlCaveats(input);
544
+ }
545
+ throw new PolicyCapabilityError(
546
+ "policy-capability-malformed-caveats",
547
+ "$.caveats are not defined for this service"
548
+ );
549
+ }
550
+ function validateSqlCaveats(input) {
551
+ assertCaveatKeys(input, ["mode", "readOnly", "statements"], "$.caveats");
552
+ const mode = caveatString(input, "mode", "$.caveats");
553
+ if (mode !== "constrained-statements") {
554
+ throw new PolicyCapabilityError(
555
+ "policy-capability-malformed-caveats",
556
+ "$.caveats.mode is unsupported"
557
+ );
558
+ }
559
+ if (requiredCaveat(input, "readOnly", "$.caveats") !== true) {
560
+ throw new PolicyCapabilityError(
561
+ "policy-capability-malformed-caveats",
562
+ "$.caveats.readOnly must be true"
563
+ );
564
+ }
565
+ const statements = requiredCaveat(input, "statements", "$.caveats");
566
+ if (!Array.isArray(statements) || statements.length === 0) {
567
+ throw new PolicyCapabilityError(
568
+ "policy-capability-malformed-caveats",
569
+ "$.caveats.statements must be a non-empty array"
570
+ );
571
+ }
572
+ for (let index = 0; index < statements.length; index++) {
573
+ validateSqlStatement(statements[index], `$.caveats.statements[${index}]`);
574
+ }
575
+ return input;
576
+ }
577
+ function validateSqlStatement(input, path) {
578
+ if (input === null || typeof input !== "object" || Array.isArray(input)) {
579
+ throw new PolicyCapabilityError("policy-capability-malformed-caveats", `${path} must be an object`);
580
+ }
581
+ const object = input;
582
+ assertCaveatKeys(object, ["name", "sql", "fixedParams"], path);
583
+ caveatString(object, "name", path);
584
+ caveatString(object, "sql", path);
585
+ const fixedParams = requiredCaveat(object, "fixedParams", path);
586
+ if (!Array.isArray(fixedParams)) {
587
+ throw new PolicyCapabilityError(
588
+ "policy-capability-malformed-caveats",
589
+ `${path}.fixedParams must be an array`
590
+ );
591
+ }
592
+ for (let index = 0; index < fixedParams.length; index++) {
593
+ validateFixedParam(fixedParams[index], `${path}.fixedParams[${index}]`);
594
+ }
595
+ }
596
+ function validateFixedParam(input, path) {
597
+ if (input === null || typeof input !== "object" || Array.isArray(input)) {
598
+ throw new PolicyCapabilityError("policy-capability-malformed-caveats", `${path} must be an object`);
599
+ }
600
+ const object = input;
601
+ assertCaveatKeys(object, ["index", "value"], path);
602
+ const index = requiredCaveat(object, "index", path);
603
+ if (typeof index !== "number" || !Number.isSafeInteger(index) || index < 0) {
604
+ throw new PolicyCapabilityError(
605
+ "policy-capability-malformed-caveats",
606
+ `${path}.index must be a non-negative integer`
607
+ );
608
+ }
609
+ requiredCaveat(object, "value", path);
610
+ }
611
+ function assertCaveatKeys(object, allowed, path) {
612
+ const allowedSet = new Set(allowed);
613
+ for (const key of Object.keys(object)) {
614
+ if (!allowedSet.has(key)) {
615
+ throw new PolicyCapabilityError(
616
+ "policy-capability-malformed-caveats",
617
+ `${path} has unknown field ${key}`
618
+ );
619
+ }
620
+ }
621
+ }
622
+ function requiredCaveat(object, key, path) {
623
+ if (!hasOwn(object, key)) {
624
+ throw new PolicyCapabilityError(
625
+ "policy-capability-malformed-caveats",
626
+ `${path}.${key} is required`
627
+ );
628
+ }
629
+ return object[key];
630
+ }
631
+ function caveatString(object, key, path) {
632
+ const value = requiredCaveat(object, key, path);
633
+ if (typeof value !== "string" || value.length === 0) {
634
+ throw new PolicyCapabilityError(
635
+ "policy-capability-malformed-caveats",
636
+ `${path}.${key} must be a non-empty string`
637
+ );
638
+ }
639
+ return value;
640
+ }
641
+ function pathContains(authority, request) {
642
+ if (authority === request) {
643
+ return true;
644
+ }
645
+ return authority.endsWith("/") && request.startsWith(authority);
646
+ }
647
+ function caveatsContain(authority, request) {
648
+ if (authority === void 0) {
649
+ return request === void 0;
650
+ }
651
+ if (request === void 0) {
652
+ return Object.keys(authority).length === 0;
653
+ }
654
+ if (authority.mode !== "constrained-statements" || request.mode !== "constrained-statements") {
655
+ return false;
656
+ }
657
+ const authorityStatements = Array.isArray(authority.statements) ? authority.statements : [];
658
+ const requestStatements = Array.isArray(request.statements) ? request.statements : [];
659
+ return requestStatements.every(
660
+ (statement) => authorityStatements.some((candidate) => jcsCanonicalize(candidate) === jcsCanonicalize(statement))
661
+ );
662
+ }
663
+ function hasOwn(object, key) {
664
+ return objectHasOwn2(object, key);
665
+ }
666
+
667
+ // src/policy/signed-object.ts
668
+ var import_ed25519 = require("@noble/curves/ed25519");
669
+ var import_basics = require("multiformats/basics");
670
+ var import_viem3 = require("viem");
671
+
672
+ // src/identity.ts
673
+ var import_viem2 = require("viem");
674
+ var IdentityParseError = class extends Error {
675
+ constructor(message) {
676
+ super(message);
677
+ this.name = "IdentityParseError";
678
+ }
679
+ };
680
+ var PKH_DID_RE = /^did:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40})$/;
681
+ function assertValidChainId(chainId) {
682
+ if (!Number.isSafeInteger(chainId) || chainId <= 0) {
683
+ throw new IdentityParseError(`Invalid EIP-155 chain ID: ${chainId}`);
684
+ }
685
+ }
686
+ function isEvmAddress(input) {
687
+ return (0, import_viem2.isAddress)(input, { strict: false });
688
+ }
689
+ function canonicalizeAddress(address) {
690
+ if (!isEvmAddress(address)) {
691
+ throw new IdentityParseError(`Invalid EVM address: ${address}`);
692
+ }
693
+ return (0, import_viem2.getAddress)(address);
694
+ }
695
+ function parsePkhDid(did) {
696
+ const match = did.match(PKH_DID_RE);
697
+ if (!match) return null;
698
+ const chainId = Number(match[1]);
699
+ assertValidChainId(chainId);
700
+ return {
701
+ method: "pkh",
702
+ namespace: "eip155",
703
+ chainId,
704
+ address: canonicalizeAddress(match[2])
705
+ };
706
+ }
707
+
708
+ // src/policy/signed-object.ts
709
+ var POLICY_SCHEMA = "xyz.tinycloud.policy/policy/v0";
710
+ var POLICY_STATUS_SCHEMA = "xyz.tinycloud.policy/status/v0";
711
+ var POLICY_ENGINE_RECORD_SCHEMA = "xyz.tinycloud.policy/engine-record/v0";
712
+ var ED25519_JCS_SIGNATURE_SUITE = "eddsa-ed25519-sha256-jcs-v1";
713
+ var EIP191_JCS_SIGNATURE_SUITE = "eip191-secp256k1-sha256-jcs-v1";
714
+ var DESCRIPTORS = {
715
+ Policy: {
716
+ kind: "Policy",
717
+ schema: POLICY_SCHEMA,
718
+ idField: "policyId",
719
+ idPrefix: "pol_",
720
+ domain: POLICY_SCHEMA
721
+ },
722
+ PolicyStatus: {
723
+ kind: "PolicyStatus",
724
+ schema: POLICY_STATUS_SCHEMA,
725
+ idField: "statusId",
726
+ idPrefix: "polst_",
727
+ domain: POLICY_STATUS_SCHEMA
728
+ },
729
+ PolicyEngineRecord: {
730
+ kind: "PolicyEngineRecord",
731
+ schema: POLICY_ENGINE_RECORD_SCHEMA,
732
+ idField: "engineRecordId",
733
+ idPrefix: "peng_",
734
+ domain: POLICY_ENGINE_RECORD_SCHEMA
735
+ }
736
+ };
737
+ var textEncoder2 = new TextEncoder();
738
+ var BASE32_ALPHABET = "abcdefghijklmnopqrstuvwxyz234567";
739
+ var BASE64URL_RE = /^[A-Za-z0-9_-]+$/;
740
+ var objectHasOwn3 = Object.hasOwn ?? Object.prototype.hasOwnProperty.call.bind(
741
+ Object.prototype.hasOwnProperty
742
+ );
743
+ async function verifySignedObject(input) {
744
+ const signed = validateSignedObjectShape(input);
745
+ const signedJson = signed;
746
+ const descriptor = descriptorForSchema(signed.schema);
747
+ const signature = validateSignature(signed.signature);
748
+ assertSigningKeyBindingForVerify(signedJson, signature);
749
+ const unsigned = stripOwnIdAndSignature(signedJson, descriptor);
750
+ const material = materialForUnsigned(unsigned, descriptor);
751
+ assertIdMatches(signedJson, material, descriptor);
752
+ if (!await verifySignature(signature, material.digest)) {
753
+ throw new SignatureVerificationError("signature verification failed");
754
+ }
755
+ return { object: signed, material };
756
+ }
757
+ async function verifyPolicyEngineRecord(input) {
758
+ const result = await verifySignedObject(input);
759
+ return {
760
+ object: validatePolicyEngineRecordSignedShape(result.object),
761
+ material: result.material
762
+ };
763
+ }
764
+ function validatePolicySignedShape(input) {
765
+ return validatePolicyShape(input, true);
766
+ }
767
+ function validatePolicyStatusSignedShape(input) {
768
+ return validatePolicyStatusShape(input, true);
769
+ }
770
+ function validatePolicyEngineRecordSignedShape(input) {
771
+ return validatePolicyEngineRecordShape(input, true);
772
+ }
773
+ function validateSignedObjectShape(input) {
774
+ const normalized = expectJsonObject(normalizeJson(input), "$");
775
+ const schema = requiredString2(normalized, "schema", "$");
776
+ const descriptor = descriptorForSchema(schema);
777
+ switch (descriptor.kind) {
778
+ case "Policy":
779
+ return validatePolicySignedShape(normalized);
780
+ case "PolicyStatus":
781
+ return validatePolicyStatusSignedShape(normalized);
782
+ case "PolicyEngineRecord":
783
+ return validatePolicyEngineRecordSignedShape(normalized);
784
+ }
785
+ }
786
+ function validatePolicyShape(input, signed) {
787
+ const object = expectJsonObject(normalizeJson(input), "$");
788
+ assertExactKeys2(
789
+ object,
790
+ signed ? [
791
+ "schema",
792
+ "policyId",
793
+ "ownerDid",
794
+ "signingKeyDid",
795
+ "createdAt",
796
+ "expiresAt",
797
+ "resource",
798
+ "when",
799
+ "grant",
800
+ "disclosure",
801
+ "audit",
802
+ "signature"
803
+ ] : [
804
+ "schema",
805
+ "ownerDid",
806
+ "signingKeyDid",
807
+ "createdAt",
808
+ "expiresAt",
809
+ "resource",
810
+ "when",
811
+ "grant",
812
+ "disclosure",
813
+ "audit"
814
+ ],
815
+ "$"
816
+ );
817
+ expectConst(requiredString2(object, "schema", "$"), POLICY_SCHEMA, "$.schema");
818
+ if (signed) {
819
+ requiredString2(object, "policyId", "$");
820
+ validateSignature(requiredValue2(object, "signature", "$"));
821
+ }
822
+ requiredString2(object, "ownerDid", "$");
823
+ requiredString2(object, "signingKeyDid", "$");
824
+ requiredDateString(object, "createdAt", "$");
825
+ optionalDateString(object, "expiresAt", "$");
826
+ validatePolicyResource(requiredValue2(object, "resource", "$"), "$.resource");
827
+ validateExpression(requiredValue2(object, "when", "$"), "$.when");
828
+ validateGrant(requiredValue2(object, "grant", "$"), "$.grant");
829
+ if (hasOwn2(object, "disclosure")) {
830
+ validateDisclosure(requiredValue2(object, "disclosure", "$"), "$.disclosure");
831
+ }
832
+ if (hasOwn2(object, "audit")) {
833
+ validateAudit(requiredValue2(object, "audit", "$"), "$.audit");
834
+ }
835
+ return object;
836
+ }
837
+ function validatePolicyStatusShape(input, signed) {
838
+ const object = expectJsonObject(normalizeJson(input), "$");
839
+ assertExactKeys2(
840
+ object,
841
+ signed ? [
842
+ "schema",
843
+ "statusId",
844
+ "policyId",
845
+ "ownerDid",
846
+ "sequence",
847
+ "disposition",
848
+ "effectiveAt",
849
+ "reasonCode",
850
+ "signingKeyDid",
851
+ "signature"
852
+ ] : [
853
+ "schema",
854
+ "policyId",
855
+ "ownerDid",
856
+ "sequence",
857
+ "disposition",
858
+ "effectiveAt",
859
+ "reasonCode",
860
+ "signingKeyDid"
861
+ ],
862
+ "$"
863
+ );
864
+ expectConst(requiredString2(object, "schema", "$"), POLICY_STATUS_SCHEMA, "$.schema");
865
+ if (signed) {
866
+ requiredString2(object, "statusId", "$");
867
+ validateSignature(requiredValue2(object, "signature", "$"));
868
+ }
869
+ requiredString2(object, "policyId", "$");
870
+ requiredString2(object, "ownerDid", "$");
871
+ requiredInteger(object, "sequence", "$", 0);
872
+ expectOneOf(requiredString2(object, "disposition", "$"), [
873
+ "active",
874
+ "suspended",
875
+ "revoked"
876
+ ], "$.disposition");
877
+ requiredDateString(object, "effectiveAt", "$");
878
+ optionalString(object, "reasonCode", "$");
879
+ requiredString2(object, "signingKeyDid", "$");
880
+ return object;
881
+ }
882
+ function validatePolicyEngineRecordShape(input, signed) {
883
+ const object = expectJsonObject(normalizeJson(input), "$");
884
+ assertExactKeys2(
885
+ object,
886
+ signed ? [
887
+ "schema",
888
+ "engineRecordId",
889
+ "ownerDid",
890
+ "endpoint",
891
+ "audience",
892
+ "supportedPolicyVersions",
893
+ "supportedEvidenceVerifiers",
894
+ "grantIssuerDid",
895
+ "expiresAt",
896
+ "signature"
897
+ ] : [
898
+ "schema",
899
+ "ownerDid",
900
+ "endpoint",
901
+ "audience",
902
+ "supportedPolicyVersions",
903
+ "supportedEvidenceVerifiers",
904
+ "grantIssuerDid",
905
+ "expiresAt"
906
+ ],
907
+ "$"
908
+ );
909
+ expectConst(
910
+ requiredString2(object, "schema", "$"),
911
+ POLICY_ENGINE_RECORD_SCHEMA,
912
+ "$.schema"
913
+ );
914
+ if (signed) {
915
+ requiredString2(object, "engineRecordId", "$");
916
+ validateSignature(requiredValue2(object, "signature", "$"));
917
+ }
918
+ requiredString2(object, "ownerDid", "$");
919
+ requiredString2(object, "endpoint", "$");
920
+ requiredString2(object, "audience", "$");
921
+ requiredStringArray(
922
+ object,
923
+ "supportedPolicyVersions",
924
+ "$",
925
+ (value, path) => expectConst(value, "v0", path)
926
+ );
927
+ requiredStringArray(object, "supportedEvidenceVerifiers", "$");
928
+ requiredString2(object, "grantIssuerDid", "$");
929
+ requiredDateString(object, "expiresAt", "$");
930
+ return object;
931
+ }
932
+ function validateSignature(input) {
933
+ const object = expectJsonObject(input, "$.signature");
934
+ assertExactKeys2(object, ["suite", "signerDid", "value"], "$.signature");
935
+ const suite = requiredString2(object, "suite", "$.signature");
936
+ assertSupportedSignatureSuite(suite);
937
+ const signerDid = requiredString2(object, "signerDid", "$.signature");
938
+ const value = requiredString2(object, "value", "$.signature");
939
+ decodeSignatureValue(value, suite);
940
+ return { suite, signerDid, value };
941
+ }
942
+ function validatePolicyResource(input, path) {
943
+ const object = expectJsonObject(input, path);
944
+ assertExactKeys2(object, ["resourceType", "resourceId", "permissionsCeiling"], path);
945
+ requiredString2(object, "resourceType", path);
946
+ requiredString2(object, "resourceId", path);
947
+ const ceiling = requiredArray2(object, "permissionsCeiling", path, 1);
948
+ for (let index = 0; index < ceiling.length; index++) {
949
+ validatePolicyCapability(ceiling[index], `${path}.permissionsCeiling[${index}]`);
950
+ }
951
+ }
952
+ function validatePolicyCapability(input, path) {
953
+ const object = expectJsonObject(input, path);
954
+ try {
955
+ const canonical = canonicalizePolicyCapability(object);
956
+ if (jcsCanonicalize(object) !== jcsCanonicalize(canonical)) {
957
+ throw new SignedObjectSchemaError(`${path} must be canonical PolicyCapability JSON`);
958
+ }
959
+ } catch (error) {
960
+ if (error instanceof SignedObjectSchemaError) {
961
+ throw error;
962
+ }
963
+ throw new SignedObjectSchemaError(
964
+ error instanceof Error ? error.message : String(error)
965
+ );
966
+ }
967
+ }
968
+ function validateExpression(input, path) {
969
+ const object = expectJsonObject(input, path);
970
+ const keys = Object.keys(object);
971
+ if (keys.length !== 1) {
972
+ throw new SignedObjectSchemaError(`${path} must have exactly one expression key`);
973
+ }
974
+ const key = keys[0];
975
+ if (key === "allOf" || key === "anyOf") {
976
+ const values = requiredArray2(object, key, path, 1);
977
+ for (let index = 0; index < values.length; index++) {
978
+ validateExpression(values[index], `${path}.${key}[${index}]`);
979
+ }
980
+ return;
981
+ }
982
+ if (key === "subject") {
983
+ const subject = expectJsonObject(requiredValue2(object, "subject", path), `${path}.subject`);
984
+ assertExactKeys2(subject, ["did"], `${path}.subject`);
985
+ requiredString2(subject, "did", `${path}.subject`);
986
+ return;
987
+ }
988
+ if (key === "evidence") {
989
+ validateEvidenceRequirement(requiredValue2(object, "evidence", path), `${path}.evidence`);
990
+ return;
991
+ }
992
+ throw new SignedObjectSchemaError(`${path} has unknown expression key ${key}`);
993
+ }
994
+ function validateEvidenceRequirement(input, path) {
995
+ const object = expectJsonObject(input, path);
996
+ assertExactKeys2(
997
+ object,
998
+ ["requirementId", "verifier", "requirements", "authority", "freshness"],
999
+ path
1000
+ );
1001
+ requiredString2(object, "requirementId", path);
1002
+ requiredString2(object, "verifier", path);
1003
+ requiredValue2(object, "requirements", path);
1004
+ if (hasOwn2(object, "authority")) {
1005
+ const authority = expectJsonObject(requiredValue2(object, "authority", path), `${path}.authority`);
1006
+ assertExactKeys2(
1007
+ authority,
1008
+ ["profile", "acceptedIssuers", "allowOwnerAuthorizedIssuer"],
1009
+ `${path}.authority`
1010
+ );
1011
+ optionalString(authority, "profile", `${path}.authority`);
1012
+ if (hasOwn2(authority, "acceptedIssuers")) {
1013
+ requiredStringArray(authority, "acceptedIssuers", `${path}.authority`);
1014
+ }
1015
+ optionalBoolean(authority, "allowOwnerAuthorizedIssuer", `${path}.authority`);
1016
+ }
1017
+ if (hasOwn2(object, "freshness")) {
1018
+ const freshness = expectJsonObject(requiredValue2(object, "freshness", path), `${path}.freshness`);
1019
+ assertExactKeys2(freshness, ["maxStatusAgeSeconds"], `${path}.freshness`);
1020
+ requiredInteger(freshness, "maxStatusAgeSeconds", `${path}.freshness`, 0);
1021
+ }
1022
+ }
1023
+ function validateGrant(input, path) {
1024
+ const object = expectJsonObject(input, path);
1025
+ assertExactKeys2(object, ["output", "maxTtlSeconds", "delegationMode", "revocation"], path);
1026
+ expectConst(requiredString2(object, "output", path), "portable-delegation", `${path}.output`);
1027
+ requiredInteger(object, "maxTtlSeconds", path, 1);
1028
+ expectOneOf(requiredString2(object, "delegationMode", path), [
1029
+ "terminal",
1030
+ "attenuable"
1031
+ ], `${path}.delegationMode`);
1032
+ expectOneOf(requiredString2(object, "revocation", path), [
1033
+ "refresh_only",
1034
+ "active_cutoff"
1035
+ ], `${path}.revocation`);
1036
+ }
1037
+ function validateDisclosure(input, path) {
1038
+ const object = expectJsonObject(input, path);
1039
+ assertExactKeys2(object, ["denial"], path);
1040
+ expectOneOf(requiredString2(object, "denial", path), ["none", "code", "debug"], `${path}.denial`);
1041
+ }
1042
+ function validateAudit(input, path) {
1043
+ const object = expectJsonObject(input, path);
1044
+ assertExactKeys2(object, ["issuance"], path);
1045
+ expectOneOf(requiredString2(object, "issuance", path), ["off", "security", "full"], `${path}.issuance`);
1046
+ }
1047
+ function materialForUnsigned(unsigned, descriptor) {
1048
+ const jcs = jcsCanonicalize(unsigned);
1049
+ const jcsBytes = textEncoder2.encode(jcs);
1050
+ const digest = sha256Bytes(concatBytes(textEncoder2.encode(`${descriptor.domain}\0`), jcsBytes));
1051
+ const id = `${descriptor.idPrefix}${base32LowerNoPad(digest)}`;
1052
+ return {
1053
+ kind: descriptor.kind,
1054
+ idField: descriptor.idField,
1055
+ id,
1056
+ domain: descriptor.domain,
1057
+ unsigned,
1058
+ jcs,
1059
+ jcsBytes,
1060
+ digest,
1061
+ digestHex: (0, import_viem3.bytesToHex)(digest).slice(2)
1062
+ };
1063
+ }
1064
+ function assertIdMatches(signed, material, descriptor) {
1065
+ const actual = requiredString2(signed, descriptor.idField, "$");
1066
+ if (actual === material.id) {
1067
+ return;
1068
+ }
1069
+ if (new RegExp(`^${descriptor.idPrefix}[a-z2-7]{52}$`).test(actual)) {
1070
+ throw new SignedObjectDigestError(
1071
+ `${descriptor.idField} was not derived from the signed object digest`
1072
+ );
1073
+ }
1074
+ throw new SignedObjectIdError(`${descriptor.idField} does not match ${descriptor.idPrefix}`);
1075
+ }
1076
+ function assertSigningKeyBindingForVerify(signed, signature) {
1077
+ if (!hasOwn2(signed, "signingKeyDid")) {
1078
+ return;
1079
+ }
1080
+ const signingKeyDid = requiredString2(signed, "signingKeyDid", "$");
1081
+ if (signature.signerDid !== signingKeyDid) {
1082
+ throw new SigningKeyBindingError(
1083
+ `signature signerDid ${signature.signerDid} does not match signingKeyDid ${signingKeyDid}`
1084
+ );
1085
+ }
1086
+ }
1087
+ async function verifySignature(signature, digest) {
1088
+ if (signature.suite === ED25519_JCS_SIGNATURE_SUITE) {
1089
+ const publicKey = ed25519PublicKeyFromDidKey(signature.signerDid);
1090
+ const signatureBytes = decodeSignatureValue(signature.value, signature.suite);
1091
+ try {
1092
+ return import_ed25519.ed25519.verify(signatureBytes, digest, publicKey);
1093
+ } catch {
1094
+ throw new SignatureVerificationError("Ed25519 signature verification failed");
1095
+ }
1096
+ }
1097
+ if (signature.suite === EIP191_JCS_SIGNATURE_SUITE) {
1098
+ const pkh = parseDidPkh(signature.signerDid);
1099
+ const signatureBytes = decodeSignatureValue(signature.value, signature.suite);
1100
+ try {
1101
+ return (0, import_viem3.verifyMessage)({
1102
+ address: pkh.address,
1103
+ message: { raw: digest },
1104
+ signature: (0, import_viem3.bytesToHex)(signatureBytes)
1105
+ });
1106
+ } catch {
1107
+ throw new SignatureVerificationError("EIP-191 signature verification failed");
1108
+ }
1109
+ }
1110
+ throw new UnsupportedSignatureSuiteError(`unsupported signature suite: ${signature.suite}`);
1111
+ }
1112
+ function ed25519PublicKeyFromDidKey(did) {
1113
+ if (!did.startsWith("did:key:")) {
1114
+ throw new SignatureMaterialError("Ed25519 signerDid must be did:key");
1115
+ }
1116
+ const identifier = did.slice("did:key:".length);
1117
+ if (!identifier.startsWith("z")) {
1118
+ throw new SignatureMaterialError("did:key must use base58btc multibase");
1119
+ }
1120
+ let bytes;
1121
+ try {
1122
+ bytes = import_basics.bases.base58btc.decode(identifier);
1123
+ } catch {
1124
+ throw new SignatureMaterialError("did:key signerDid is undecodable");
1125
+ }
1126
+ if (bytes.length === 34 && bytes[0] === 237 && bytes[1] === 1) {
1127
+ return bytes.slice(2);
1128
+ }
1129
+ throw new SignatureMaterialError("did:key signerDid is not an Ed25519 key");
1130
+ }
1131
+ function parseDidPkh(did) {
1132
+ let parsed;
1133
+ try {
1134
+ parsed = parsePkhDid(did);
1135
+ } catch {
1136
+ throw new SignatureMaterialError("did:pkh signerDid is undecodable");
1137
+ }
1138
+ if (!parsed) {
1139
+ throw new SignatureMaterialError("EIP-191 signerDid must be did:pkh");
1140
+ }
1141
+ return { address: parsed.address };
1142
+ }
1143
+ function decodeSignatureValue(value, suite) {
1144
+ if (value.length === 0 || !BASE64URL_RE.test(value) || value.includes("=")) {
1145
+ throw new SignatureMaterialError("signature value must be base64url without padding");
1146
+ }
1147
+ const bytes = base64UrlDecode(value);
1148
+ if (suite === ED25519_JCS_SIGNATURE_SUITE && bytes.length !== 64) {
1149
+ throw new SignatureMaterialError("Ed25519 signature must be 64 bytes");
1150
+ }
1151
+ if (suite === EIP191_JCS_SIGNATURE_SUITE) {
1152
+ if (bytes.length !== 65) {
1153
+ throw new SignatureMaterialError("EIP-191 signature must be 65 bytes");
1154
+ }
1155
+ const v = bytes[64];
1156
+ if (v !== 27 && v !== 28) {
1157
+ throw new SignatureMaterialError("EIP-191 signature recovery id must be 27 or 28");
1158
+ }
1159
+ }
1160
+ return bytes;
1161
+ }
1162
+ function stripOwnIdAndSignature(object, descriptor) {
1163
+ const output = /* @__PURE__ */ Object.create(null);
1164
+ for (const [key, value] of Object.entries(object)) {
1165
+ if (key === descriptor.idField || key === "signature") {
1166
+ continue;
1167
+ }
1168
+ output[key] = value;
1169
+ }
1170
+ return output;
1171
+ }
1172
+ function descriptorForSchema(schema) {
1173
+ if (schema === POLICY_SCHEMA) return DESCRIPTORS.Policy;
1174
+ if (schema === POLICY_STATUS_SCHEMA) return DESCRIPTORS.PolicyStatus;
1175
+ if (schema === POLICY_ENGINE_RECORD_SCHEMA) return DESCRIPTORS.PolicyEngineRecord;
1176
+ throw new SignedObjectSchemaError(`unsupported signed-object schema: ${schema}`);
1177
+ }
1178
+ function expectJsonObject(input, path) {
1179
+ if (input === null || typeof input !== "object" || Array.isArray(input)) {
1180
+ throw new SignedObjectSchemaError(`${path} must be an object`);
1181
+ }
1182
+ return input;
1183
+ }
1184
+ function assertExactKeys2(object, allowed, path) {
1185
+ const allowedSet = new Set(allowed);
1186
+ for (const key of Object.keys(object)) {
1187
+ if (!allowedSet.has(key)) {
1188
+ throw new SignedObjectSchemaError(`${path} has unknown field ${key}`);
1189
+ }
1190
+ }
1191
+ }
1192
+ function requiredValue2(object, key, path) {
1193
+ if (!hasOwn2(object, key)) {
1194
+ throw new SignedObjectSchemaError(`${path}.${key} is required`);
1195
+ }
1196
+ return object[key];
1197
+ }
1198
+ function requiredString2(object, key, path) {
1199
+ return requireStringType(requiredValue2(object, key, path), `${path}.${key}`);
1200
+ }
1201
+ function optionalString(object, key, path) {
1202
+ if (!hasOwn2(object, key)) {
1203
+ return;
1204
+ }
1205
+ requireStringType(requiredValue2(object, key, path), `${path}.${key}`);
1206
+ }
1207
+ function requireStringType(value, path) {
1208
+ if (typeof value !== "string" || value.length === 0) {
1209
+ throw new SignedObjectSchemaError(`${path} must be a non-empty string`);
1210
+ }
1211
+ return value;
1212
+ }
1213
+ function optionalBoolean(object, key, path) {
1214
+ if (!hasOwn2(object, key)) {
1215
+ return;
1216
+ }
1217
+ if (typeof requiredValue2(object, key, path) !== "boolean") {
1218
+ throw new SignedObjectSchemaError(`${path}.${key} must be a boolean`);
1219
+ }
1220
+ }
1221
+ function requiredInteger(object, key, path, minimum) {
1222
+ const value = requiredValue2(object, key, path);
1223
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < minimum) {
1224
+ throw new SignedObjectSchemaError(
1225
+ `${path}.${key} must be an integer >= ${minimum}`
1226
+ );
1227
+ }
1228
+ return value;
1229
+ }
1230
+ function requiredArray2(object, key, path, minimum) {
1231
+ const value = requiredValue2(object, key, path);
1232
+ if (!Array.isArray(value) || value.length < minimum) {
1233
+ throw new SignedObjectSchemaError(
1234
+ `${path}.${key} must be an array with at least ${minimum} item(s)`
1235
+ );
1236
+ }
1237
+ return value;
1238
+ }
1239
+ function requiredStringArray(object, key, path, check) {
1240
+ const values = requiredArray2(object, key, path, 0);
1241
+ for (let index = 0; index < values.length; index++) {
1242
+ const itemPath = `${path}.${key}[${index}]`;
1243
+ const value = requireStringType(values[index], itemPath);
1244
+ check?.(value, itemPath);
1245
+ }
1246
+ }
1247
+ function requiredDateString(object, key, path) {
1248
+ assertRfc3339(requiredString2(object, key, path), `${path}.${key}`);
1249
+ }
1250
+ function optionalDateString(object, key, path) {
1251
+ if (!hasOwn2(object, key)) {
1252
+ return;
1253
+ }
1254
+ assertRfc3339(requiredString2(object, key, path), `${path}.${key}`);
1255
+ }
1256
+ function assertRfc3339(value, path) {
1257
+ const match = value.match(
1258
+ /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d+)?(Z|[+-]\d{2}:\d{2})$/
1259
+ );
1260
+ if (!match) {
1261
+ throw new SignedObjectSchemaError(`${path} must be strict RFC 3339`);
1262
+ }
1263
+ const parsed = Date.parse(value);
1264
+ if (Number.isNaN(parsed)) {
1265
+ throw new SignedObjectSchemaError(`${path} must be a parseable RFC 3339 timestamp`);
1266
+ }
1267
+ const canonical = new Date(parsed).toISOString().replace(".000Z", "Z");
1268
+ if (canonical !== value) {
1269
+ throw new SignedObjectSchemaError(`${path} must be a normalizable RFC 3339 timestamp`);
1270
+ }
1271
+ }
1272
+ function expectConst(actual, expected, path) {
1273
+ if (actual !== expected) {
1274
+ throw new SignedObjectSchemaError(`${path} must be ${expected}`);
1275
+ }
1276
+ return expected;
1277
+ }
1278
+ function expectOneOf(actual, allowed, path) {
1279
+ for (const value of allowed) {
1280
+ if (actual === value) {
1281
+ return value;
1282
+ }
1283
+ }
1284
+ throw new SignedObjectSchemaError(`${path} has unsupported value ${actual}`);
1285
+ }
1286
+ function hasOwn2(object, key) {
1287
+ return objectHasOwn3(object, key);
1288
+ }
1289
+ function assertSupportedSignatureSuite(suite) {
1290
+ if (suite !== ED25519_JCS_SIGNATURE_SUITE && suite !== EIP191_JCS_SIGNATURE_SUITE) {
1291
+ throw new UnsupportedSignatureSuiteError(`unsupported signature suite: ${suite}`);
1292
+ }
1293
+ }
1294
+ function sha256Bytes(bytes) {
1295
+ return (0, import_viem3.sha256)(bytes, "bytes");
1296
+ }
1297
+ function concatBytes(left, right) {
1298
+ const output = new Uint8Array(left.length + right.length);
1299
+ output.set(left, 0);
1300
+ output.set(right, left.length);
1301
+ return output;
1302
+ }
1303
+ function base32LowerNoPad(bytes) {
1304
+ let output = "";
1305
+ let buffer = 0;
1306
+ let bits = 0;
1307
+ for (const byte of bytes) {
1308
+ buffer = buffer << 8 | byte;
1309
+ bits += 8;
1310
+ while (bits >= 5) {
1311
+ bits -= 5;
1312
+ output += BASE32_ALPHABET[buffer >> bits & 31];
1313
+ }
1314
+ }
1315
+ if (bits > 0) {
1316
+ output += BASE32_ALPHABET[buffer << 5 - bits & 31];
1317
+ }
1318
+ return output;
1319
+ }
1320
+ function base64UrlEncode(bytes) {
1321
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
1322
+ let output = "";
1323
+ for (let index = 0; index < bytes.length; index += 3) {
1324
+ const a = bytes[index];
1325
+ const b = bytes[index + 1];
1326
+ const c = bytes[index + 2];
1327
+ const triplet = a << 16 | (b ?? 0) << 8 | (c ?? 0);
1328
+ output += alphabet[triplet >> 18 & 63];
1329
+ output += alphabet[triplet >> 12 & 63];
1330
+ if (index + 1 < bytes.length) {
1331
+ output += alphabet[triplet >> 6 & 63];
1332
+ }
1333
+ if (index + 2 < bytes.length) {
1334
+ output += alphabet[triplet & 63];
1335
+ }
1336
+ }
1337
+ return output;
1338
+ }
1339
+ function base64UrlDecode(value) {
1340
+ if (value.length % 4 === 1) {
1341
+ throw new SignatureMaterialError("signature value is not canonical base64url");
1342
+ }
1343
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
1344
+ const bytes = [];
1345
+ let buffer = 0;
1346
+ let bits = 0;
1347
+ for (const char of value) {
1348
+ const index = alphabet.indexOf(char);
1349
+ if (index < 0) {
1350
+ throw new SignatureMaterialError("signature value is not base64url");
1351
+ }
1352
+ buffer = buffer << 6 | index;
1353
+ bits += 6;
1354
+ if (bits >= 8) {
1355
+ bits -= 8;
1356
+ bytes.push(buffer >> bits & 255);
1357
+ }
1358
+ }
1359
+ const decoded = Uint8Array.from(bytes);
1360
+ if (base64UrlEncode(decoded) !== value) {
1361
+ throw new SignatureMaterialError("signature value is not canonical base64url");
1362
+ }
1363
+ return decoded;
1364
+ }
1365
+
1366
+ // src/policy/authoring.ts
1367
+ var TRANSCRIPT_SHARE_BOOTSTRAP_SCHEMA = "xyz.tinycloud.exchange/transcript-bootstrap/v0";
1368
+ var OWNER_NODE_ENDPOINT_SCHEMA = "xyz.tinycloud.exchange/owner-node-endpoint/v1";
1369
+ var POLICY_VERSION_V0 = "v0";
1370
+ var W3C_VC_CREDENTIAL_VERIFIER = "w3c.vc/credential/v1";
1371
+ var PolicyAuthoringError = class extends Error {
1372
+ constructor(code, message) {
1373
+ super(message);
1374
+ this.name = "PolicyAuthoringError";
1375
+ this.code = code;
1376
+ }
1377
+ };
1378
+ async function verifyPolicyEngineRecordForRequester(options) {
1379
+ const normalized = expectObject2(options, "$", "policy-authoring-malformed");
1380
+ assertExactKeys3(
1381
+ normalized,
1382
+ [
1383
+ "signedRecord",
1384
+ "ownerDid",
1385
+ "audience",
1386
+ "grantIssuerDid",
1387
+ "now",
1388
+ "requiredPolicyVersion",
1389
+ "requiredEvidenceVerifier"
1390
+ ],
1391
+ "$"
1392
+ );
1393
+ if (!hasOwn3(normalized, "signedRecord")) {
1394
+ throw new PolicyAuthoringError(
1395
+ "policy-engine-record-absent",
1396
+ "$.signedRecord must be present"
1397
+ );
1398
+ }
1399
+ const signedRecord = normalized["signedRecord"];
1400
+ if (signedRecord === null || typeof signedRecord !== "object" || Array.isArray(signedRecord)) {
1401
+ throw new PolicyAuthoringError(
1402
+ "policy-engine-record-absent",
1403
+ "$.signedRecord must be present"
1404
+ );
1405
+ }
1406
+ const recordObject = signedRecord;
1407
+ const expiresAt = fieldString(
1408
+ recordObject,
1409
+ "expiresAt",
1410
+ "$.signedRecord",
1411
+ "policy-engine-record-date-invalid"
1412
+ );
1413
+ const now = fieldString(
1414
+ normalized,
1415
+ "now",
1416
+ "$",
1417
+ "policy-engine-record-date-invalid"
1418
+ );
1419
+ const expiresMs = parseStrictRfc3339(expiresAt, "$.signedRecord.expiresAt");
1420
+ const nowMs = parseStrictRfc3339(now, "$.now");
1421
+ if (expiresMs <= nowMs) {
1422
+ throw new PolicyAuthoringError(
1423
+ "policy-engine-record-expired",
1424
+ "$.signedRecord.expiresAt is expired"
1425
+ );
1426
+ }
1427
+ let verified;
1428
+ try {
1429
+ verified = (await verifyPolicyEngineRecord(recordObject)).object;
1430
+ } catch (error) {
1431
+ throw new PolicyAuthoringError(
1432
+ "policy-engine-record-signature-invalid",
1433
+ error instanceof Error ? error.message : String(error)
1434
+ );
1435
+ }
1436
+ if (verified.signature.signerDid !== verified.ownerDid) {
1437
+ throw new PolicyAuthoringError(
1438
+ "policy-engine-record-signature-invalid",
1439
+ "$.signedRecord.signature.signerDid must match ownerDid for the direct-owner requester profile"
1440
+ );
1441
+ }
1442
+ const expectedOwnerDid = requiredString3(normalized, "ownerDid", "$");
1443
+ if (verified.ownerDid !== expectedOwnerDid) {
1444
+ throw new PolicyAuthoringError(
1445
+ "policy-engine-record-owner-mismatch",
1446
+ "$.signedRecord.ownerDid does not match"
1447
+ );
1448
+ }
1449
+ const expectedAudience = requiredString3(normalized, "audience", "$");
1450
+ if (verified.audience !== expectedAudience) {
1451
+ throw new PolicyAuthoringError(
1452
+ "policy-engine-record-audience-mismatch",
1453
+ "$.signedRecord.audience does not match"
1454
+ );
1455
+ }
1456
+ const expectedGrantIssuerDid = requiredString3(
1457
+ normalized,
1458
+ "grantIssuerDid",
1459
+ "$"
1460
+ );
1461
+ if (verified.grantIssuerDid !== expectedGrantIssuerDid) {
1462
+ throw new PolicyAuthoringError(
1463
+ "policy-engine-record-grant-issuer-mismatch",
1464
+ "$.signedRecord.grantIssuerDid does not match"
1465
+ );
1466
+ }
1467
+ const requiredPolicyVersion = hasOwn3(normalized, "requiredPolicyVersion") ? requiredString3(normalized, "requiredPolicyVersion", "$") : POLICY_VERSION_V0;
1468
+ if (!verified.supportedPolicyVersions.includes(requiredPolicyVersion)) {
1469
+ throw new PolicyAuthoringError(
1470
+ "policy-engine-record-policy-version-unsupported",
1471
+ "$.signedRecord.supportedPolicyVersions does not include the required version"
1472
+ );
1473
+ }
1474
+ const requiredEvidenceVerifier = hasOwn3(
1475
+ normalized,
1476
+ "requiredEvidenceVerifier"
1477
+ ) ? requiredString3(normalized, "requiredEvidenceVerifier", "$") : W3C_VC_CREDENTIAL_VERIFIER;
1478
+ if (!verified.supportedEvidenceVerifiers.includes(requiredEvidenceVerifier)) {
1479
+ throw new PolicyAuthoringError(
1480
+ "policy-engine-record-evidence-verifier-unsupported",
1481
+ "$.signedRecord.supportedEvidenceVerifiers does not include the required verifier"
1482
+ );
1483
+ }
1484
+ return verified;
1485
+ }
1486
+ function expectObject2(input, path, code) {
1487
+ try {
1488
+ const normalized = normalizeJson(input);
1489
+ if (normalized === null || typeof normalized !== "object" || Array.isArray(normalized)) {
1490
+ throw new PolicyAuthoringError(code, `${path} must be an object`);
1491
+ }
1492
+ return normalized;
1493
+ } catch (error) {
1494
+ if (error instanceof PolicyAuthoringError) {
1495
+ throw error;
1496
+ }
1497
+ throw new PolicyAuthoringError(
1498
+ code,
1499
+ error instanceof Error ? error.message : String(error)
1500
+ );
1501
+ }
1502
+ }
1503
+ function assertExactKeys3(object, allowed, path) {
1504
+ const allowedSet = new Set(allowed);
1505
+ for (const key of Object.keys(object)) {
1506
+ if (!allowedSet.has(key)) {
1507
+ throw new PolicyAuthoringError(
1508
+ "policy-authoring-unknown-key",
1509
+ `${path} has unknown field ${key}`
1510
+ );
1511
+ }
1512
+ }
1513
+ }
1514
+ function requiredValue3(object, key, path) {
1515
+ if (!hasOwn3(object, key)) {
1516
+ throw new PolicyAuthoringError(
1517
+ "policy-authoring-malformed",
1518
+ `${path}.${key} is required`
1519
+ );
1520
+ }
1521
+ return object[key];
1522
+ }
1523
+ function requiredString3(object, key, path) {
1524
+ const value = requiredValue3(object, key, path);
1525
+ if (typeof value !== "string" || value.length === 0) {
1526
+ throw new PolicyAuthoringError(
1527
+ "policy-authoring-malformed",
1528
+ `${path}.${key} must be a non-empty string`
1529
+ );
1530
+ }
1531
+ return value;
1532
+ }
1533
+ function fieldString(object, key, path, code) {
1534
+ if (!hasOwn3(object, key)) {
1535
+ throw new PolicyAuthoringError(code, `${path}.${key} is required`);
1536
+ }
1537
+ const value = object[key];
1538
+ if (typeof value !== "string" || value.length === 0) {
1539
+ throw new PolicyAuthoringError(
1540
+ code,
1541
+ `${path}.${key} must be a non-empty string`
1542
+ );
1543
+ }
1544
+ return value;
1545
+ }
1546
+ function parseStrictRfc3339(value, path) {
1547
+ const match = value.match(
1548
+ /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d+)?Z$/
1549
+ );
1550
+ if (!match) {
1551
+ throw new PolicyAuthoringError(
1552
+ "policy-engine-record-date-invalid",
1553
+ `${path} must be strict RFC 3339`
1554
+ );
1555
+ }
1556
+ const parsed = Date.parse(value);
1557
+ if (Number.isNaN(parsed)) {
1558
+ throw new PolicyAuthoringError(
1559
+ "policy-engine-record-date-invalid",
1560
+ `${path} must be parseable`
1561
+ );
1562
+ }
1563
+ const canonical = new Date(parsed).toISOString().replace(".000Z", "Z");
1564
+ if (canonical !== value) {
1565
+ throw new PolicyAuthoringError(
1566
+ "policy-engine-record-date-invalid",
1567
+ `${path} must be normalizable`
1568
+ );
1569
+ }
1570
+ return parsed;
1571
+ }
1572
+ var objectHasOwn4 = Object.hasOwn ?? Object.prototype.hasOwnProperty.call.bind(
1573
+ Object.prototype.hasOwnProperty
1574
+ );
1575
+ function hasOwn3(object, key) {
1576
+ return objectHasOwn4(object, key);
1577
+ }
1578
+
1579
+ // src/requester/index.ts
1580
+ var REQUESTER_NEAR_EXPIRY_SECONDS = 30;
1581
+ var REQUESTER_ENGINE_RETRY_ATTEMPTS = 3;
1582
+ var REQUESTER_ENGINE_RETRY_MAX_DELAY_MS = 250;
1583
+ var POLICY_ENGINE_CHALLENGE_REQUEST_SCHEMA = "xyz.tinycloud.policy-engine/challenge-request/v0";
1584
+ var POLICY_ENGINE_CHALLENGE_RESPONSE_SCHEMA = "xyz.tinycloud.policy/challenge/v0";
1585
+ var POLICY_ENGINE_RESOLVE_REQUEST_SCHEMA = "xyz.tinycloud.policy/presentation/v0";
1586
+ var POLICY_ENGINE_DENIAL_SCHEMA = "xyz.tinycloud.policy-engine/denial/v0";
1587
+ var HOLDER_KEY_BINDING_PRESENTATION_SCHEMA = "xyz.tinycloud.policy/presentation/v0";
1588
+ var PORTABLE_DELEGATION_SCHEMA = "xyz.tinycloud.policy/portable-delegation/v0";
1589
+ var POLICY_ENGINE_GRANT_PRESENTATION_DENIAL_CODES = [
1590
+ "schema-invalid",
1591
+ "challenge-not-found",
1592
+ "challenge-expired",
1593
+ "challenge-nonce-consumed",
1594
+ "presentation-expired",
1595
+ "presentation-audience-mismatch",
1596
+ "presentation-evidence-missing",
1597
+ "digest-mismatch",
1598
+ "evidence-requirement-unknown",
1599
+ "evidence-requirement-duplicate",
1600
+ "holder-signature-invalid",
1601
+ "holder-signature-signer-mismatch",
1602
+ "id-mismatch",
1603
+ "requested-capabilities-exceeded",
1604
+ "requested-capabilities-hash-mismatch",
1605
+ "evidence-authority-missing",
1606
+ "evidence-credential-invalid",
1607
+ "evidence-domain-invalid",
1608
+ "evidence-domain-missing",
1609
+ "evidence-freshness-expired",
1610
+ "evidence-freshness-unestablishable",
1611
+ "evidence-issuer-missing",
1612
+ "evidence-issuer-untrusted",
1613
+ "evidence-presentation-invalid",
1614
+ "evidence-requirements-invalid",
1615
+ "evidence-verifier-unsupported",
1616
+ "enrollment-binding-mismatch",
1617
+ "enrollment-expired",
1618
+ "enrollment-not-yet-valid",
1619
+ "enrollment-out-of-scope",
1620
+ "enrollment-revoked",
1621
+ "enrollment-revoked-irreversible",
1622
+ "enrollment-status-rollback",
1623
+ "signature-invalid",
1624
+ "signer-not-authorized",
1625
+ "audience-mismatch",
1626
+ "capability-not-contained",
1627
+ "evidence-invalid",
1628
+ "evidence-missing",
1629
+ "evidence-stale",
1630
+ "evidence-subject-mismatch",
1631
+ "evidence-untrusted",
1632
+ "grant-ttl-exceeds-policy",
1633
+ "holder-did-mismatch",
1634
+ "holder-key-not-permitted",
1635
+ "holder-signature-invalid",
1636
+ "owner-mismatch",
1637
+ "policy-expired",
1638
+ "policy-inactive",
1639
+ "policy-not-found",
1640
+ "policy-not-satisfied",
1641
+ "policy-revoked",
1642
+ "policy-status-rollback",
1643
+ "rate-limited"
1644
+ ];
1645
+ var TranscriptRequesterError = class extends Error {
1646
+ constructor(code, message, state = "invalid", denialCode, status) {
1647
+ super(message);
1648
+ this.name = "TranscriptRequesterError";
1649
+ this.code = code;
1650
+ this.state = state;
1651
+ this.denialCode = denialCode;
1652
+ this.status = status;
1653
+ }
1654
+ };
1655
+ var JsonValueSchema = import_zod.z.lazy(
1656
+ () => import_zod.z.union([
1657
+ import_zod.z.string(),
1658
+ import_zod.z.number().finite(),
1659
+ import_zod.z.boolean(),
1660
+ import_zod.z.null(),
1661
+ import_zod.z.array(JsonValueSchema),
1662
+ import_zod.z.record(JsonValueSchema)
1663
+ ])
1664
+ );
1665
+ var Rfc3339Schema = import_zod.z.string().refine((value) => parseStrictRfc33392(value) !== void 0, {
1666
+ message: "must be strict RFC 3339 date-time with timezone"
1667
+ });
1668
+ var SignedRecordSchema = import_zod.z.object({
1669
+ schema: import_zod.z.string(),
1670
+ engineRecordId: import_zod.z.string(),
1671
+ ownerDid: import_zod.z.string(),
1672
+ endpoint: import_zod.z.string(),
1673
+ audience: import_zod.z.string(),
1674
+ supportedPolicyVersions: import_zod.z.array(import_zod.z.string()),
1675
+ supportedEvidenceVerifiers: import_zod.z.array(import_zod.z.string()),
1676
+ grantIssuerDid: import_zod.z.string(),
1677
+ expiresAt: Rfc3339Schema,
1678
+ signature: import_zod.z.object({
1679
+ suite: import_zod.z.string(),
1680
+ signerDid: import_zod.z.string(),
1681
+ value: import_zod.z.string()
1682
+ }).strict()
1683
+ }).strict();
1684
+ var PolicyEngineSchema = import_zod.z.object({
1685
+ endpoint: import_zod.z.string().url(),
1686
+ audience: import_zod.z.string(),
1687
+ supportedEvidenceVerifiers: import_zod.z.tuple([
1688
+ import_zod.z.literal(W3C_VC_CREDENTIAL_VERIFIER)
1689
+ ]),
1690
+ signedRecord: SignedRecordSchema
1691
+ }).strict();
1692
+ var OwnerNodeSchema = import_zod.z.object({
1693
+ schema: import_zod.z.literal(OWNER_NODE_ENDPOINT_SCHEMA),
1694
+ endpoint: import_zod.z.string().url(),
1695
+ spaceId: import_zod.z.string().min(1)
1696
+ }).strict();
1697
+ var ResourceHintSchema = import_zod.z.object({
1698
+ resourceType: import_zod.z.string(),
1699
+ resourceId: import_zod.z.string(),
1700
+ requestedCapabilities: import_zod.z.array(JsonValueSchema).min(1)
1701
+ }).strict();
1702
+ var BootstrapSchema = import_zod.z.object({
1703
+ schema: import_zod.z.literal(TRANSCRIPT_SHARE_BOOTSTRAP_SCHEMA),
1704
+ policyId: import_zod.z.string(),
1705
+ policyEngine: PolicyEngineSchema,
1706
+ ownerNode: OwnerNodeSchema,
1707
+ resourceHint: ResourceHintSchema
1708
+ }).strict();
1709
+ var SignatureSchema = import_zod.z.object({
1710
+ suite: import_zod.z.string(),
1711
+ signerDid: import_zod.z.string(),
1712
+ value: import_zod.z.string()
1713
+ }).strict();
1714
+ var ChallengeSchema = import_zod.z.object({
1715
+ schema: import_zod.z.literal(POLICY_ENGINE_CHALLENGE_RESPONSE_SCHEMA),
1716
+ challengeId: import_zod.z.string(),
1717
+ policyId: import_zod.z.string(),
1718
+ audience: import_zod.z.string(),
1719
+ nonce: import_zod.z.string().min(16),
1720
+ challengeExpiresAt: Rfc3339Schema,
1721
+ acceptedSuites: import_zod.z.array(import_zod.z.string()).min(1),
1722
+ requestedCapabilitiesTemplate: import_zod.z.array(JsonValueSchema).optional(),
1723
+ signature: SignatureSchema
1724
+ }).strict();
1725
+ var ChallengeResponseSchema = import_zod.z.object({ challenge: ChallengeSchema }).strict();
1726
+ var DenialSchema = import_zod.z.object({
1727
+ schema: import_zod.z.literal(POLICY_ENGINE_DENIAL_SCHEMA),
1728
+ code: import_zod.z.enum(POLICY_ENGINE_GRANT_PRESENTATION_DENIAL_CODES),
1729
+ message: import_zod.z.string().optional()
1730
+ }).strict();
1731
+ var ErrorEnvelopeDenialSchema = import_zod.z.object({
1732
+ error: import_zod.z.object({
1733
+ code: import_zod.z.enum(POLICY_ENGINE_GRANT_PRESENTATION_DENIAL_CODES),
1734
+ message: import_zod.z.string().optional()
1735
+ }).strict()
1736
+ }).strict();
1737
+ var WireDelegationSchema = import_zod.z.object({
1738
+ delegationId: import_zod.z.string(),
1739
+ issuanceId: import_zod.z.string().min(1),
1740
+ issuerDid: import_zod.z.string(),
1741
+ holderDid: import_zod.z.string(),
1742
+ policyId: import_zod.z.string(),
1743
+ capabilityHashHex: import_zod.z.string().regex(/^[0-9a-f]{64}$/),
1744
+ revocationMode: import_zod.z.literal("refresh_only"),
1745
+ issuedAt: Rfc3339Schema,
1746
+ expiresAt: Rfc3339Schema,
1747
+ terminal: import_zod.z.boolean(),
1748
+ encoded: import_zod.z.string()
1749
+ }).strict();
1750
+ var ResolveResponseSchema = import_zod.z.object({ delegation: WireDelegationSchema }).strict();
1751
+ var DelegateReceiptSchema = import_zod.z.object({
1752
+ cid: import_zod.z.string().min(1),
1753
+ activated: import_zod.z.array(import_zod.z.string()),
1754
+ skipped: import_zod.z.array(import_zod.z.string())
1755
+ }).strict();
1756
+ var SqlReadResponseSchema = import_zod.z.object({ rows: import_zod.z.array(JsonValueSchema) }).strict();
1757
+ var KvReadResponseSchema = import_zod.z.object({ value: JsonValueSchema }).strict();
1758
+ var LISTEN_SQL_STATEMENT_CATALOG = [
1759
+ {
1760
+ name: "listen.getConversation",
1761
+ sql: "SELECT id, title, source, source_id, source_url, started_at, ended_at, duration_secs, summary, metadata, transcript_json, transcript_text, created_at, updated_at FROM conversation WHERE id = ?",
1762
+ fixedParams: [{ index: 0, value: "{conversationId}" }]
1763
+ },
1764
+ {
1765
+ name: "listen.listParticipants",
1766
+ sql: "SELECT id, name, email, speaker_label FROM participant WHERE conversation_id = ? ORDER BY COALESCE(speaker_label, name), id",
1767
+ fixedParams: [{ index: 0, value: "{conversationId}" }]
1768
+ }
1769
+ ];
1770
+ function listenTranscriptScopedStatementName(statementName, conversationId) {
1771
+ if (conversationId.length === 0) {
1772
+ throw new TranscriptRequesterError(
1773
+ "requester-access-not-contained",
1774
+ "conversation ID must be non-empty for a scoped Listen SQL read",
1775
+ "not-contained"
1776
+ );
1777
+ }
1778
+ return `${statementName}@${encodeURIComponent(conversationId)}`;
1779
+ }
1780
+ var LISTEN_SQL_STATEMENT_BY_NAME = new Map(
1781
+ LISTEN_SQL_STATEMENT_CATALOG.map((statement) => [
1782
+ statement.name,
1783
+ statement
1784
+ ])
1785
+ );
1786
+ var TranscriptRequester = class _TranscriptRequester {
1787
+ constructor(bootstrap, requestedCapabilities, nativeAuthorityCeiling, requestedCapabilitiesHash2, policyEngineRecordExpiresAtMs, policyEngineAddresses, ownerNodeAddresses, options) {
1788
+ this.usedChallengeNonces = /* @__PURE__ */ new Set();
1789
+ this.accessEnded = false;
1790
+ this.bootstrap = bootstrap;
1791
+ this.requestedCapabilities = requestedCapabilities;
1792
+ this.nativeAuthorityCeiling = nativeAuthorityCeiling;
1793
+ this.requestedCapabilitiesHash = requestedCapabilitiesHash2;
1794
+ this.requesterDid = options.requesterDid;
1795
+ this.ownerDid = options.ownerDid;
1796
+ this.audience = options.audience;
1797
+ this.grantIssuerDid = options.grantIssuerDid;
1798
+ this.transport = options.transport;
1799
+ this.signingCapability = options.signingCapability;
1800
+ this.invocationCapability = options.invocationCapability;
1801
+ this.policyEngineRecordExpiresAtMs = policyEngineRecordExpiresAtMs;
1802
+ this.policyEngineAddresses = policyEngineAddresses;
1803
+ this.ownerNodeAddresses = ownerNodeAddresses;
1804
+ this.eligibleSubjectDid = options.eligibleSubjectDid ?? options.signingCapability?.eligibleSubjectDid ?? options.requesterDid;
1805
+ this.holderBinding = options.holderBinding ?? options.signingCapability?.holderBinding ?? {
1806
+ type: "enrolled-agent",
1807
+ enrollment: {
1808
+ schema: "xyz.tinycloud.policy/holder-enrollment/v0",
1809
+ holderDid: options.requesterDid
1810
+ }
1811
+ };
1812
+ this.evidence = options.evidence ?? options.signingCapability?.evidence;
1813
+ this.presentationTtlSeconds = Math.min(
1814
+ Math.max(options.presentationTtlSeconds ?? 60, 1),
1815
+ 300
1816
+ );
1817
+ this.now = options.now ?? (() => /* @__PURE__ */ new Date());
1818
+ this.sleep = options.sleep ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
1819
+ this.random = options.random ?? Math.random;
1820
+ this.engineRetryAttempts = options.engineRetryAttempts ?? REQUESTER_ENGINE_RETRY_ATTEMPTS;
1821
+ }
1822
+ static async create(options) {
1823
+ const bootstrap = parseBootstrap(options.bootstrap);
1824
+ if (bootstrap.ownerNode.endpoint !== options.trustedOwnerNode.endpoint || bootstrap.ownerNode.spaceId !== options.trustedOwnerNode.spaceId) {
1825
+ throw endpointError(
1826
+ "owner-node",
1827
+ "bootstrap owner-node binding does not match trusted discovery/config"
1828
+ );
1829
+ }
1830
+ const record = await verifyRecordBeforeEgress(bootstrap, options);
1831
+ if (record.endpoint !== bootstrap.policyEngine.endpoint) {
1832
+ throw new TranscriptRequesterError(
1833
+ "requester-engine-record-endpoint-mismatch",
1834
+ "policy engine record endpoint does not match bootstrap endpoint",
1835
+ "bootstrap-invalid"
1836
+ );
1837
+ }
1838
+ if (record.audience !== bootstrap.policyEngine.audience) {
1839
+ throw new TranscriptRequesterError(
1840
+ "requester-engine-record-audience-mismatch",
1841
+ "policy engine record audience does not match bootstrap audience",
1842
+ "bootstrap-invalid"
1843
+ );
1844
+ }
1845
+ const policyEngineAddresses = await validateAndResolvePublicEndpoint(
1846
+ bootstrap.policyEngine.endpoint,
1847
+ options.transport,
1848
+ "policy-engine"
1849
+ );
1850
+ const ownerNodeAddresses = await validateAndResolveOwnerNode(
1851
+ bootstrap.ownerNode.endpoint,
1852
+ options.transport
1853
+ );
1854
+ const requestedCapabilities = bootstrap.resourceHint.requestedCapabilities.map(
1855
+ (capability, index) => parsePolicyCapability(
1856
+ capability,
1857
+ `$.resourceHint.requestedCapabilities[${index}]`
1858
+ )
1859
+ );
1860
+ const nativeAuthorityCeiling = requestedCapabilities.map((capability) => ({
1861
+ ...capability,
1862
+ space: bootstrap.ownerNode.spaceId
1863
+ }));
1864
+ return new _TranscriptRequester(
1865
+ bootstrap,
1866
+ requestedCapabilities,
1867
+ nativeAuthorityCeiling,
1868
+ requestedCapabilitiesHash(requestedCapabilities),
1869
+ parseStrictRfc33392(record.expiresAt),
1870
+ policyEngineAddresses,
1871
+ ownerNodeAddresses,
1872
+ options
1873
+ );
1874
+ }
1875
+ get accessState() {
1876
+ if (this.accessEnded) {
1877
+ return "access-ended";
1878
+ }
1879
+ if (this.importedDelegation === void 0) {
1880
+ return "needs-renewal";
1881
+ }
1882
+ if (this.requiresRenewal(this.importedDelegation)) {
1883
+ return "needs-renewal";
1884
+ }
1885
+ return "active";
1886
+ }
1887
+ async readSql(statementName, conversationId) {
1888
+ try {
1889
+ const catalogStatement = listenSqlStatementFromCatalog(statementName);
1890
+ const selectedConversationId = conversationId ?? this.bootstrap.resourceHint.resourceId;
1891
+ const statement = {
1892
+ ...catalogStatement,
1893
+ name: conversationId === void 0 ? catalogStatement.name : listenTranscriptScopedStatementName(
1894
+ statementName,
1895
+ conversationId
1896
+ ),
1897
+ fixedParams: catalogStatement.fixedParams.map((item) => ({
1898
+ ...item,
1899
+ value: item.value === "{conversationId}" ? selectedConversationId : item.value
1900
+ }))
1901
+ };
1902
+ const delegation = await this.ensureFreshDelegation();
1903
+ const requested = this.sqlAccessCapabilityForDelegation(
1904
+ delegation,
1905
+ statement
1906
+ );
1907
+ this.assertContainedByDelegation(delegation, requested);
1908
+ const response = await this.nativeInvoke(
1909
+ "sql",
1910
+ requested.path,
1911
+ "tinycloud.sql/read",
1912
+ {
1913
+ action: "executeStatement",
1914
+ name: statement.name,
1915
+ // The native constrained-SQL route injects caveat-pinned values and
1916
+ // rejects callers that supply those indices. Uncaveated reads must
1917
+ // resolve the catalog placeholder themselves.
1918
+ params: requested.caveats === void 0 ? statement.fixedParams.map((item) => item.value) : []
1919
+ },
1920
+ requested.caveats
1921
+ );
1922
+ return parseNodeDataResponse(
1923
+ response,
1924
+ SqlReadResponseSchema,
1925
+ "SQL read"
1926
+ );
1927
+ } catch (error) {
1928
+ this.recordAccessEnded(error);
1929
+ throw error;
1930
+ }
1931
+ }
1932
+ async readKv(path) {
1933
+ try {
1934
+ const delegation = await this.ensureFreshDelegation();
1935
+ const grantedKv = delegation.capabilities.find(
1936
+ (capability) => capability.service === "tinycloud.kv" && capability.path === path && capability.actions.includes("tinycloud.kv/get")
1937
+ );
1938
+ const requested = parsePolicyCapability(
1939
+ {
1940
+ service: "tinycloud.kv",
1941
+ space: grantedKv?.space ?? "applications",
1942
+ path,
1943
+ actions: ["tinycloud.kv/get"]
1944
+ },
1945
+ "$.kvRead"
1946
+ );
1947
+ this.assertContainedByDelegation(delegation, requested);
1948
+ const response = await this.nativeInvoke("kv", path, "tinycloud.kv/get");
1949
+ return parseNodeDataResponse(
1950
+ response,
1951
+ KvReadResponseSchema,
1952
+ "KV read"
1953
+ );
1954
+ } catch (error) {
1955
+ this.recordAccessEnded(error);
1956
+ throw error;
1957
+ }
1958
+ }
1959
+ async nativeInvoke(service, path, action, body, caveats) {
1960
+ const capability = this.invocationCapability;
1961
+ if (capability === void 0) {
1962
+ throw new TranscriptRequesterError(
1963
+ "requester-invocation-signer-required",
1964
+ "holder invocation capability is required for native reads"
1965
+ );
1966
+ }
1967
+ if (capability.holderDid !== this.requesterDid || capability.holderDid !== this.signingCapability?.holderDid) {
1968
+ throw new TranscriptRequesterError(
1969
+ "requester-invocation-signer-mismatch",
1970
+ "invocation signer must be the presentation key-binding holder"
1971
+ );
1972
+ }
1973
+ if (this.importedDelegationCid === void 0 || this.importedDelegation === void 0) {
1974
+ throw new TranscriptRequesterError(
1975
+ "requester-delegation-import-failed",
1976
+ "native read requires a confirmed delegation import"
1977
+ );
1978
+ }
1979
+ const session = {
1980
+ delegationHeader: { Authorization: this.importedDelegation.encoded },
1981
+ delegationCid: this.importedDelegationCid,
1982
+ spaceId: this.bootstrap.ownerNode.spaceId,
1983
+ verificationMethod: capability.verificationMethod,
1984
+ jwk: capability.jwk
1985
+ };
1986
+ let invocationHeaders;
1987
+ if (caveats === void 0) {
1988
+ invocationHeaders = capability.invoke(session, service, path, action);
1989
+ } else {
1990
+ if (capability.invokeAny === void 0) {
1991
+ throw new TranscriptRequesterError(
1992
+ "requester-invocation-signer-required",
1993
+ "holder invocation capability must support caveated native reads"
1994
+ );
1995
+ }
1996
+ invocationHeaders = capability.invokeAny(session, [
1997
+ {
1998
+ spaceId: this.bootstrap.ownerNode.spaceId,
1999
+ service,
2000
+ path,
2001
+ action,
2002
+ caveats: [caveats]
2003
+ }
2004
+ ]);
2005
+ }
2006
+ const headers = headersRecord(invocationHeaders);
2007
+ const response = await this.transport.request({
2008
+ method: "POST",
2009
+ url: `${trimTrailingSlash(this.bootstrap.ownerNode.endpoint)}/invoke`,
2010
+ headers,
2011
+ redirect: "error",
2012
+ allowedResolvedAddresses: [...this.ownerNodeAddresses],
2013
+ ...body === void 0 ? {} : { body }
2014
+ });
2015
+ this.assertOwnerNodeResponse(response, "/invoke");
2016
+ return response;
2017
+ }
2018
+ async ensureFreshDelegation() {
2019
+ if (this.accessEnded) {
2020
+ throw new TranscriptRequesterError(
2021
+ "requester-access-ended",
2022
+ "requester access has ended",
2023
+ "access-ended"
2024
+ );
2025
+ }
2026
+ if (this.importedDelegation !== void 0 && !this.requiresRenewal(this.importedDelegation)) {
2027
+ return this.importedDelegation;
2028
+ }
2029
+ if (this.signingCapability === void 0 || this.signingCapability.holderDid !== this.requesterDid) {
2030
+ throw new TranscriptRequesterError(
2031
+ "requester-renewal-required",
2032
+ "a permitted requester signing capability is required for access-triggered renewal",
2033
+ "renewal-required"
2034
+ );
2035
+ }
2036
+ try {
2037
+ let lastError;
2038
+ const attempts = Math.max(1, this.engineRetryAttempts);
2039
+ for (let attempt = 0; attempt < attempts; attempt++) {
2040
+ const challenge = await this.obtainChallenge();
2041
+ const presentation = await this.mintPresentation(challenge);
2042
+ try {
2043
+ const delegation = await this.resolveOnce(challenge, presentation);
2044
+ this.importedDelegation = delegation;
2045
+ return delegation;
2046
+ } catch (error) {
2047
+ if (!(error instanceof TranscriptRequesterError) || error.state !== "unreachable") {
2048
+ throw error;
2049
+ }
2050
+ lastError = error;
2051
+ }
2052
+ if (attempt + 1 < attempts) {
2053
+ await this.sleep(retryDelay(attempt, this.random()));
2054
+ }
2055
+ }
2056
+ throw new TranscriptRequesterError(
2057
+ "requester-engine-unreachable",
2058
+ lastError instanceof Error ? lastError.message : "policy engine unreachable",
2059
+ "unreachable"
2060
+ );
2061
+ } catch (error) {
2062
+ this.recordAccessEnded(error);
2063
+ throw error;
2064
+ }
2065
+ }
2066
+ recordAccessEnded(error) {
2067
+ if (error instanceof TranscriptRequesterError && error.state === "access-ended") {
2068
+ this.accessEnded = true;
2069
+ }
2070
+ }
2071
+ async obtainChallenge() {
2072
+ this.assertPolicyEngineRecordCurrent();
2073
+ const response = await this.challengeRequestWithRetry({
2074
+ method: "POST",
2075
+ url: `${trimTrailingSlash(this.bootstrap.policyEngine.endpoint)}/policy/v0/challenge`,
2076
+ redirect: "error",
2077
+ allowedResolvedAddresses: [...this.policyEngineAddresses],
2078
+ body: {
2079
+ policyId: this.bootstrap.policyId
2080
+ }
2081
+ });
2082
+ return parseEngineSuccess(
2083
+ response.body,
2084
+ ChallengeResponseSchema,
2085
+ "challenge response"
2086
+ ).challenge;
2087
+ }
2088
+ async mintPresentation(challenge) {
2089
+ if (challenge.policyId !== this.bootstrap.policyId || challenge.audience !== this.bootstrap.policyEngine.audience || challenge.audience !== this.audience) {
2090
+ throw new TranscriptRequesterError(
2091
+ "requester-engine-response-invalid",
2092
+ "challenge response binding does not match requester context"
2093
+ );
2094
+ }
2095
+ if (this.usedChallengeNonces.has(challenge.nonce)) {
2096
+ throw new TranscriptRequesterError(
2097
+ "requester-challenge-reused",
2098
+ "challenge nonce was already used by this requester"
2099
+ );
2100
+ }
2101
+ this.usedChallengeNonces.add(challenge.nonce);
2102
+ const expiresAt = new Date(
2103
+ this.now().getTime() + this.presentationTtlSeconds * 1e3
2104
+ ).toISOString().replace(".000Z", "Z");
2105
+ const input = {
2106
+ schema: HOLDER_KEY_BINDING_PRESENTATION_SCHEMA,
2107
+ policyId: this.bootstrap.policyId,
2108
+ eligibleSubjectDid: this.eligibleSubjectDid,
2109
+ holderDid: this.requesterDid,
2110
+ holderBinding: this.holderBinding,
2111
+ requestedCapabilities: this.requestedCapabilities,
2112
+ requestedCapabilitiesHash: this.requestedCapabilitiesHash,
2113
+ audience: this.audience,
2114
+ nonce: challenge.nonce,
2115
+ expiresAt,
2116
+ ...this.evidence === void 0 ? {} : { evidence: this.evidence }
2117
+ };
2118
+ const signature = this.signingCapability.signGrantPresentation === void 0 ? await this.signingCapability.signKeyBinding({
2119
+ ...input,
2120
+ challengeId: challenge.challengeId,
2121
+ issuedAt: expiresAt,
2122
+ keyId: this.signingCapability.keyId
2123
+ }) : await this.signingCapability.signGrantPresentation(input);
2124
+ if (typeof signature !== "string" || signature.length === 0) {
2125
+ throw new TranscriptRequesterError(
2126
+ "requester-presentation-invalid",
2127
+ "signing capability returned an invalid holder key-binding signature"
2128
+ );
2129
+ }
2130
+ return {
2131
+ ...input,
2132
+ holderSignature: {
2133
+ suite: this.signingCapability.suite ?? challenge.acceptedSuites[0],
2134
+ signerDid: this.requesterDid,
2135
+ value: signature
2136
+ }
2137
+ };
2138
+ }
2139
+ async resolveOnce(challenge, presentation) {
2140
+ void challenge;
2141
+ this.assertPolicyEngineRecordCurrent();
2142
+ const response = await this.resolveRequestOnce({
2143
+ method: "POST",
2144
+ url: `${trimTrailingSlash(this.bootstrap.policyEngine.endpoint)}/policy/v0/resolve`,
2145
+ redirect: "error",
2146
+ allowedResolvedAddresses: [...this.policyEngineAddresses],
2147
+ body: { presentation }
2148
+ });
2149
+ const parsed = parseEngineSuccess(
2150
+ response.body,
2151
+ ResolveResponseSchema,
2152
+ "resolve response"
2153
+ );
2154
+ return this.importPortableDelegation(parsed.delegation);
2155
+ }
2156
+ async importPortableDelegation(input) {
2157
+ const parsed = parseNodeNativePortableDelegation(input);
2158
+ if (parsed.policyId !== this.bootstrap.policyId) {
2159
+ throw new TranscriptRequesterError(
2160
+ "requester-delegation-invalid",
2161
+ "portable delegation policy id does not match bootstrap"
2162
+ );
2163
+ }
2164
+ if (parsed.holderDid !== this.requesterDid) {
2165
+ throw new TranscriptRequesterError(
2166
+ "requester-delegation-wrong-holder",
2167
+ "portable delegation is not targeted at the requester DID"
2168
+ );
2169
+ }
2170
+ if (parsed.issuerDid !== this.grantIssuerDid) {
2171
+ throw new TranscriptRequesterError(
2172
+ "requester-delegation-invalid",
2173
+ "portable delegation issuer does not match the verified grant issuer DID"
2174
+ );
2175
+ }
2176
+ if (parsed.maxTtlSeconds > 300) {
2177
+ throw new TranscriptRequesterError(
2178
+ "requester-delegation-ttl-excessive",
2179
+ "portable delegation TTL exceeds 300 seconds"
2180
+ );
2181
+ }
2182
+ const issuedAt = parseStrictRfc33392(parsed.issuedAt);
2183
+ const expiresAt = parseStrictRfc33392(parsed.expiresAt);
2184
+ if (expiresAt <= issuedAt || expiresAt - issuedAt > parsed.maxTtlSeconds * 1e3) {
2185
+ throw new TranscriptRequesterError(
2186
+ "requester-delegation-ttl-excessive",
2187
+ "portable delegation expires outside its maxTtlSeconds bound"
2188
+ );
2189
+ }
2190
+ const capabilities = parsed.capabilities.map(
2191
+ (capability, index) => parsePolicyCapability(capability, `$.delegation.capabilities[${index}]`)
2192
+ );
2193
+ for (const granted of capabilities) {
2194
+ if (!this.nativeAuthorityCeiling.some(
2195
+ (requested) => policyCapabilityContains(requested, granted)
2196
+ )) {
2197
+ throw new TranscriptRequesterError(
2198
+ "requester-delegation-capability-wider",
2199
+ "portable delegation grants a capability outside the bootstrap requested set"
2200
+ );
2201
+ }
2202
+ }
2203
+ if (typeof parsed.encoded !== "string" || parsed.encoded.split(".").length !== 3) {
2204
+ throw new TranscriptRequesterError(
2205
+ "requester-delegation-invalid",
2206
+ "portable delegation is not a compact-JWS UCAN"
2207
+ );
2208
+ }
2209
+ const delegation = { ...parsed, capabilities };
2210
+ const response = await this.transport.request({
2211
+ method: "POST",
2212
+ url: `${trimTrailingSlash(this.bootstrap.ownerNode.endpoint)}/delegate`,
2213
+ headers: { Authorization: parsed.encoded },
2214
+ redirect: "error",
2215
+ allowedResolvedAddresses: [...this.ownerNodeAddresses]
2216
+ });
2217
+ this.assertOwnerNodeResponse(response, "/delegate");
2218
+ const receipt = parseDelegateReceipt(response);
2219
+ const target = this.bootstrap.ownerNode.spaceId;
2220
+ if (!receipt.activated.includes(target) || receipt.skipped.includes(target)) {
2221
+ throw new TranscriptRequesterError(
2222
+ "requester-delegation-import-failed",
2223
+ receipt.activated.includes(target) && receipt.skipped.includes(target) ? "owner node returned a contradictory delegation receipt" : "owner node did not activate the target owner space"
2224
+ );
2225
+ }
2226
+ this.importedDelegationCid = deriveDelegationCid(parsed.encoded);
2227
+ return delegation;
2228
+ }
2229
+ assertOwnerNodeResponse(response, path) {
2230
+ this.assertEndpointResponse(
2231
+ response,
2232
+ this.bootstrap.ownerNode.endpoint,
2233
+ path,
2234
+ this.ownerNodeAddresses,
2235
+ "owner-node"
2236
+ );
2237
+ }
2238
+ assertPolicyEngineRecordCurrent() {
2239
+ if (this.policyEngineRecordExpiresAtMs <= this.now().getTime()) {
2240
+ throw new TranscriptRequesterError(
2241
+ "requester-engine-record-invalid",
2242
+ "verified policy engine record has expired",
2243
+ "bootstrap-invalid"
2244
+ );
2245
+ }
2246
+ }
2247
+ assertPolicyEngineResponse(response, path) {
2248
+ this.assertEndpointResponse(
2249
+ response,
2250
+ this.bootstrap.policyEngine.endpoint,
2251
+ path,
2252
+ this.policyEngineAddresses,
2253
+ "policy-engine"
2254
+ );
2255
+ }
2256
+ assertEndpointResponse(response, endpoint, path, allowedAddresses, kind) {
2257
+ const expected = `${trimTrailingSlash(endpoint)}${path}`;
2258
+ const error = (message) => endpointError(kind, message);
2259
+ if (response.finalUrl !== expected || response.resolvedAddress === void 0) {
2260
+ throw error(
2261
+ `${kind} transport metadata is missing or indicates a redirect`
2262
+ );
2263
+ }
2264
+ if (!allowedAddresses.has(normalizeIp(response.resolvedAddress))) {
2265
+ throw error(`${kind} address changed after endpoint validation`);
2266
+ }
2267
+ }
2268
+ assertContainedByDelegation(delegation, requested) {
2269
+ if (this.requiresRenewal(delegation)) {
2270
+ throw new TranscriptRequesterError(
2271
+ "requester-renewal-required",
2272
+ "delegation requires renewal before access",
2273
+ "renewal-required"
2274
+ );
2275
+ }
2276
+ if (!delegation.capabilities.some(
2277
+ (granted) => policyCapabilityContains(granted, requested)
2278
+ )) {
2279
+ throw new TranscriptRequesterError(
2280
+ "requester-access-not-contained",
2281
+ "requested access is outside the imported delegation capabilities",
2282
+ "not-contained"
2283
+ );
2284
+ }
2285
+ }
2286
+ sqlAccessCapabilityForDelegation(delegation, statement) {
2287
+ const grantedSql = delegation.capabilities.find(
2288
+ (capability) => capability.service === "tinycloud.sql" && capability.actions.includes("tinycloud.sql/read")
2289
+ );
2290
+ if (grantedSql === void 0) {
2291
+ throw new TranscriptRequesterError(
2292
+ "requester-access-not-contained",
2293
+ "delegation has no SQL read grant",
2294
+ "not-contained"
2295
+ );
2296
+ }
2297
+ if (grantedSql.caveats !== void 0) {
2298
+ const statements = grantedSql.caveats.statements;
2299
+ const matchesCatalogSelection = statements?.some(
2300
+ (candidate) => candidate.name === statement.name && candidate.sql === statement.sql && Array.isArray(candidate.fixedParams) && jcsCanonicalize(candidate.fixedParams) === jcsCanonicalize(statement.fixedParams)
2301
+ );
2302
+ if (!matchesCatalogSelection) {
2303
+ throw new TranscriptRequesterError(
2304
+ "requester-access-not-contained",
2305
+ "SQL statement is outside the delegated catalog-derived statement caveat",
2306
+ "not-contained"
2307
+ );
2308
+ }
2309
+ return grantedSql;
2310
+ }
2311
+ return parsePolicyCapability(
2312
+ {
2313
+ service: "tinycloud.sql",
2314
+ space: grantedSql.space,
2315
+ path: grantedSql.path,
2316
+ actions: ["tinycloud.sql/read"]
2317
+ },
2318
+ "$.sqlRead"
2319
+ );
2320
+ }
2321
+ requiresRenewal(delegation) {
2322
+ const expiresAt = parseStrictRfc33392(delegation.expiresAt);
2323
+ return expiresAt - this.now().getTime() <= REQUESTER_NEAR_EXPIRY_SECONDS * 1e3;
2324
+ }
2325
+ async challengeRequestWithRetry(request) {
2326
+ let lastError;
2327
+ const attempts = Math.max(1, this.engineRetryAttempts);
2328
+ for (let attempt = 0; attempt < attempts; attempt++) {
2329
+ try {
2330
+ const response = await this.transport.request(request);
2331
+ this.assertPolicyEngineResponse(response, "/policy/v0/challenge");
2332
+ if (response.status >= 500) {
2333
+ lastError = new Error(`engine returned ${response.status}`);
2334
+ } else if (response.status >= 400) {
2335
+ const denial = parseDenialBody(response.body);
2336
+ if (denial !== void 0) {
2337
+ throw errorForDenial(denial, response.status);
2338
+ }
2339
+ throw new TranscriptRequesterError(
2340
+ "requester-engine-response-invalid",
2341
+ "policy engine returned an invalid denial body"
2342
+ );
2343
+ } else {
2344
+ return response;
2345
+ }
2346
+ } catch (error) {
2347
+ if (error instanceof TranscriptRequesterError && error.state !== "unreachable") {
2348
+ throw error;
2349
+ }
2350
+ lastError = error;
2351
+ }
2352
+ if (attempt + 1 < attempts) {
2353
+ await this.sleep(retryDelay(attempt, this.random()));
2354
+ }
2355
+ }
2356
+ throw new TranscriptRequesterError(
2357
+ "requester-engine-unreachable",
2358
+ lastError instanceof Error ? lastError.message : "policy engine unreachable",
2359
+ "unreachable"
2360
+ );
2361
+ }
2362
+ async resolveRequestOnce(request) {
2363
+ try {
2364
+ const response = await this.transport.request(request);
2365
+ this.assertPolicyEngineResponse(response, "/policy/v0/resolve");
2366
+ if (response.status >= 500) {
2367
+ throw new TranscriptRequesterError(
2368
+ "requester-engine-unreachable",
2369
+ `engine returned ${response.status}`,
2370
+ "unreachable"
2371
+ );
2372
+ }
2373
+ if (response.status >= 400) {
2374
+ const denial = parseDenialBody(response.body);
2375
+ if (denial !== void 0) {
2376
+ throw errorForDenial(denial, response.status);
2377
+ }
2378
+ throw new TranscriptRequesterError(
2379
+ "requester-engine-response-invalid",
2380
+ "policy engine returned an invalid denial body"
2381
+ );
2382
+ }
2383
+ return response;
2384
+ } catch (error) {
2385
+ if (error instanceof TranscriptRequesterError) {
2386
+ throw error;
2387
+ }
2388
+ throw new TranscriptRequesterError(
2389
+ "requester-engine-unreachable",
2390
+ error instanceof Error ? error.message : "policy engine unreachable",
2391
+ "unreachable"
2392
+ );
2393
+ }
2394
+ }
2395
+ };
2396
+ async function createTranscriptRequester(options) {
2397
+ return TranscriptRequester.create(options);
2398
+ }
2399
+ function parseBootstrap(input) {
2400
+ const normalized = normalizeExternal(input, "requester-bootstrap-malformed");
2401
+ const parsed = BootstrapSchema.safeParse(normalized);
2402
+ if (!parsed.success) {
2403
+ throw new TranscriptRequesterError(
2404
+ parsed.error.issues.some((issue) => issue.code === "unrecognized_keys") ? "requester-bootstrap-unknown-key" : "requester-bootstrap-malformed",
2405
+ parsed.error.message,
2406
+ "bootstrap-invalid"
2407
+ );
2408
+ }
2409
+ return parsed.data;
2410
+ }
2411
+ async function verifyRecordBeforeEgress(bootstrap, options) {
2412
+ try {
2413
+ return await verifyPolicyEngineRecordForRequester({
2414
+ signedRecord: bootstrap.policyEngine.signedRecord,
2415
+ ownerDid: options.ownerDid,
2416
+ audience: options.audience,
2417
+ grantIssuerDid: options.grantIssuerDid,
2418
+ now: (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString().replace(".000Z", "Z"),
2419
+ requiredPolicyVersion: POLICY_VERSION_V0,
2420
+ requiredEvidenceVerifier: W3C_VC_CREDENTIAL_VERIFIER
2421
+ });
2422
+ } catch (error) {
2423
+ const code = errorCodeForRecordFailure(error);
2424
+ throw new TranscriptRequesterError(
2425
+ code,
2426
+ error instanceof Error ? error.message : String(error),
2427
+ "bootstrap-invalid"
2428
+ );
2429
+ }
2430
+ }
2431
+ function errorCodeForRecordFailure(error) {
2432
+ const code = typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
2433
+ if (code === "policy-engine-record-signature-invalid") {
2434
+ return "requester-engine-record-signature-invalid";
2435
+ }
2436
+ if (code === "policy-engine-record-owner-mismatch") {
2437
+ return "requester-engine-record-owner-mismatch";
2438
+ }
2439
+ if (code === "policy-engine-record-audience-mismatch") {
2440
+ return "requester-engine-record-audience-mismatch";
2441
+ }
2442
+ return "requester-engine-record-invalid";
2443
+ }
2444
+ function parsePolicyCapability(input, path) {
2445
+ try {
2446
+ return normalizePolicyCapabilityForRequester(
2447
+ input
2448
+ );
2449
+ } catch (error) {
2450
+ throw new TranscriptRequesterError(
2451
+ "requester-delegation-invalid",
2452
+ `${path}: ${error instanceof Error ? error.message : String(error)}`
2453
+ );
2454
+ }
2455
+ }
2456
+ function listenSqlStatementFromCatalog(statementName) {
2457
+ if (typeof statementName !== "string") {
2458
+ throw new TranscriptRequesterError(
2459
+ "requester-access-not-contained",
2460
+ "SQL statement name must be a string from the canonical Listen statement catalog",
2461
+ "not-contained"
2462
+ );
2463
+ }
2464
+ const statement = LISTEN_SQL_STATEMENT_BY_NAME.get(statementName);
2465
+ if (statement === void 0) {
2466
+ throw new TranscriptRequesterError(
2467
+ "requester-access-not-contained",
2468
+ "SQL statement is not in the canonical Listen statement catalog",
2469
+ "not-contained"
2470
+ );
2471
+ }
2472
+ return statement;
2473
+ }
2474
+ function normalizePolicyCapabilityForRequester(input) {
2475
+ const normalized = normalizeExternal(input, "requester-delegation-invalid");
2476
+ return normalizePolicyCapability(normalized);
2477
+ }
2478
+ function requestedCapabilitiesHash(capabilities) {
2479
+ const canonical = [...capabilities].sort(
2480
+ (left, right) => `${left.service}\0${left.space}\0${left.path}`.localeCompare(
2481
+ `${right.service}\0${right.space}\0${right.path}`
2482
+ )
2483
+ );
2484
+ const encoder = new TextEncoder();
2485
+ const domain = encoder.encode(
2486
+ "xyz.tinycloud.policy/RequestedCapabilities/v0\0"
2487
+ );
2488
+ const body = encoder.encode(jcsCanonicalize(canonical));
2489
+ const bytes = new Uint8Array(domain.length + body.length);
2490
+ bytes.set(domain, 0);
2491
+ bytes.set(body, domain.length);
2492
+ return (0, import_viem4.bytesToHex)((0, import_viem4.sha256)(bytes, "bytes")).slice(2);
2493
+ }
2494
+ function normalizeWireDelegation(delegation) {
2495
+ const signed = delegationAuthorityFromCompactJws(delegation.encoded);
2496
+ const invalid = (field) => {
2497
+ throw new TranscriptRequesterError(
2498
+ "requester-delegation-invalid",
2499
+ `portable delegation ${field} does not match its signed compact-JWS authority`
2500
+ );
2501
+ };
2502
+ if (delegation.delegationId !== deriveDelegationCid(delegation.encoded))
2503
+ invalid("delegationId");
2504
+ if (delegation.issuerDid !== signed.issuerDid) invalid("issuerDid");
2505
+ if (delegation.holderDid !== signed.holderDid) invalid("holderDid");
2506
+ if (delegation.policyId !== signed.policyId) invalid("policyId");
2507
+ if (Date.parse(delegation.issuedAt) !== signed.issuedAtMs)
2508
+ invalid("issuedAt");
2509
+ if (Date.parse(delegation.expiresAt) !== signed.expiresAtMs)
2510
+ invalid("expiresAt");
2511
+ if (delegation.terminal !== signed.terminal) invalid("terminal");
2512
+ if (delegation.issuanceId !== signed.issuanceId) invalid("issuanceId");
2513
+ if (delegation.capabilityHashHex !== signed.capabilityHashHex)
2514
+ invalid("capabilityHashHex");
2515
+ if (delegation.revocationMode !== signed.revocationMode)
2516
+ invalid("revocationMode");
2517
+ return {
2518
+ delegationId: deriveDelegationCid(delegation.encoded),
2519
+ issuerDid: signed.issuerDid,
2520
+ holderDid: signed.holderDid,
2521
+ policyId: signed.policyId,
2522
+ issuedAt: new Date(signed.issuedAtMs).toISOString().replace(".000Z", "Z"),
2523
+ expiresAt: new Date(signed.expiresAtMs).toISOString().replace(".000Z", "Z"),
2524
+ terminal: signed.terminal,
2525
+ maxTtlSeconds: Math.ceil((signed.expiresAtMs - signed.issuedAtMs) / 1e3),
2526
+ capabilities: signed.capabilities,
2527
+ encoded: delegation.encoded
2528
+ };
2529
+ }
2530
+ function parseNodeNativePortableDelegation(input) {
2531
+ return normalizeWireDelegation(
2532
+ parseEngineSuccess(input, WireDelegationSchema, "portable delegation")
2533
+ );
2534
+ }
2535
+ function delegationAuthorityFromCompactJws(encoded) {
2536
+ try {
2537
+ const parts = encoded.split(".");
2538
+ if (parts.length !== 3) throw new Error("not compact JWS");
2539
+ const payload = normalizeJson(
2540
+ JSON.parse(new TextDecoder().decode(base64UrlBytes(parts[1])))
2541
+ );
2542
+ const issuer = requiredPayloadString(payload, "iss");
2543
+ const holderDid = requiredPayloadString(payload, "aud");
2544
+ const issuedAt = requiredPayloadEpoch(payload, "nbf");
2545
+ const expiresAt = requiredPayloadEpoch(payload, "exp");
2546
+ const facts = payload.fct;
2547
+ if (!Array.isArray(facts)) throw new Error("UCAN fct is absent");
2548
+ const authorityFacts = facts.filter(
2549
+ (fact2) => fact2 !== null && typeof fact2 === "object" && !Array.isArray(fact2) && "xyz.tinycloud.policy/policyId" in fact2
2550
+ );
2551
+ if (authorityFacts.length !== 1)
2552
+ throw new Error("UCAN policy authority fact is not unique");
2553
+ const fact = authorityFacts[0];
2554
+ const policyId = requiredPayloadString(
2555
+ fact,
2556
+ "xyz.tinycloud.policy/policyId"
2557
+ );
2558
+ const issuanceId = requiredPayloadString(
2559
+ fact,
2560
+ "xyz.tinycloud.policy/issuanceId"
2561
+ );
2562
+ const capabilityHashHex = requiredPayloadString(
2563
+ fact,
2564
+ "xyz.tinycloud.policy/capabilityHashHex"
2565
+ );
2566
+ if (!/^[0-9a-f]{64}$/.test(capabilityHashHex)) {
2567
+ throw new Error(
2568
+ "UCAN capability hash is not 64 lowercase hex characters"
2569
+ );
2570
+ }
2571
+ const revocationMode = requiredPayloadString(
2572
+ fact,
2573
+ "xyz.tinycloud.policy/revocationMode"
2574
+ );
2575
+ const delegationMode = requiredPayloadString(
2576
+ fact,
2577
+ "xyz.tinycloud.policy/delegationMode"
2578
+ );
2579
+ if (revocationMode !== "refresh_only")
2580
+ throw new Error("unsupported UCAN revocation mode");
2581
+ if (delegationMode !== "terminal")
2582
+ throw new Error("UCAN delegation is not terminal");
2583
+ const att = payload.att;
2584
+ if (att === null || typeof att !== "object" || Array.isArray(att)) {
2585
+ throw new Error("UCAN att is absent");
2586
+ }
2587
+ const capabilities = [];
2588
+ for (const [resource, rawAbilities] of Object.entries(att)) {
2589
+ if (rawAbilities === null || typeof rawAbilities !== "object" || Array.isArray(rawAbilities)) {
2590
+ throw new Error("UCAN att abilities are invalid");
2591
+ }
2592
+ const abilities = rawAbilities;
2593
+ const marker = resource.indexOf("/sql/");
2594
+ if (!resource.startsWith("tinycloud:") || marker < 0)
2595
+ throw new Error("unsupported UCAN resource");
2596
+ const space = resource.slice(0, marker);
2597
+ const path = resource.slice(marker + "/sql/".length);
2598
+ for (const [action, rawCaveats] of Object.entries(abilities)) {
2599
+ const service = action.startsWith("tinycloud.sql/") ? "tinycloud.sql" : action.startsWith("tinycloud.kv/") ? "tinycloud.kv" : void 0;
2600
+ if (!Array.isArray(rawCaveats) || rawCaveats.length !== 1) {
2601
+ throw new Error("UCAN ability must have exactly one caveat branch");
2602
+ }
2603
+ const caveats = rawCaveats;
2604
+ if (service === void 0) throw new Error("unsupported UCAN ability");
2605
+ const first = caveats[0];
2606
+ capabilities.push(
2607
+ normalizePolicyCapability({
2608
+ service,
2609
+ space,
2610
+ path,
2611
+ actions: [action],
2612
+ ...first !== null && typeof first === "object" && !Array.isArray(first) && Object.keys(first).length > 0 ? { caveats: first } : {}
2613
+ })
2614
+ );
2615
+ }
2616
+ }
2617
+ if (capabilities.length === 0) throw new Error("UCAN att is empty");
2618
+ const fragment = issuer.indexOf("#");
2619
+ const issuerDid = fragment < 0 ? issuer : issuer.slice(0, fragment);
2620
+ if (!issuerDid.startsWith("did:") || !holderDid.startsWith("did:")) {
2621
+ throw new Error("UCAN issuer or audience is not a DID");
2622
+ }
2623
+ return {
2624
+ issuerDid,
2625
+ holderDid,
2626
+ policyId,
2627
+ issuanceId,
2628
+ capabilityHashHex,
2629
+ revocationMode,
2630
+ issuedAtMs: issuedAt * 1e3,
2631
+ expiresAtMs: expiresAt * 1e3,
2632
+ terminal: true,
2633
+ capabilities
2634
+ };
2635
+ } catch (error) {
2636
+ throw new TranscriptRequesterError(
2637
+ "requester-delegation-invalid",
2638
+ `node-native compact-JWS capabilities are invalid: ${error instanceof Error ? error.message : String(error)}`
2639
+ );
2640
+ }
2641
+ }
2642
+ function requiredPayloadString(payload, key) {
2643
+ const value = payload[key];
2644
+ if (typeof value !== "string" || value.length === 0) {
2645
+ throw new Error(`UCAN ${key} is not a non-empty string`);
2646
+ }
2647
+ return value;
2648
+ }
2649
+ function requiredPayloadEpoch(payload, key) {
2650
+ const value = payload[key];
2651
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
2652
+ throw new Error(`UCAN ${key} is not a non-negative integer epoch`);
2653
+ }
2654
+ return value;
2655
+ }
2656
+ function base64UrlBytes(value) {
2657
+ const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
2658
+ const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
2659
+ const binary = atob(padded);
2660
+ return Uint8Array.from(binary, (character) => character.charCodeAt(0));
2661
+ }
2662
+ function parseEngineSuccess(input, schema, label) {
2663
+ const normalized = normalizeExternal(
2664
+ input,
2665
+ "requester-engine-response-invalid"
2666
+ );
2667
+ const denial = parseDenialBody(normalized);
2668
+ if (denial !== void 0) {
2669
+ throw errorForDenial(denial);
2670
+ }
2671
+ const parsed = schema.safeParse(normalized);
2672
+ if (!parsed.success) {
2673
+ throw new TranscriptRequesterError(
2674
+ "requester-engine-response-invalid",
2675
+ `${label} failed validation: ${parsed.error.message}`
2676
+ );
2677
+ }
2678
+ return parsed.data;
2679
+ }
2680
+ function parseDelegateReceipt(response) {
2681
+ if (response.status !== 200) {
2682
+ throw new TranscriptRequesterError(
2683
+ "requester-delegation-import-failed",
2684
+ `owner node delegation import returned ${response.status}`,
2685
+ response.status >= 500 ? "invalid" : "denied",
2686
+ void 0,
2687
+ response.status
2688
+ );
2689
+ }
2690
+ const normalized = normalizeExternal(
2691
+ response.body,
2692
+ "requester-delegation-import-failed"
2693
+ );
2694
+ const parsed = DelegateReceiptSchema.safeParse(normalized);
2695
+ if (!parsed.success) {
2696
+ throw new TranscriptRequesterError(
2697
+ "requester-delegation-import-failed",
2698
+ `owner node delegation receipt failed validation: ${parsed.error.message}`
2699
+ );
2700
+ }
2701
+ return parsed.data;
2702
+ }
2703
+ function parseNodeDataResponse(response, schema, label) {
2704
+ if (response.status >= 500) {
2705
+ throw new TranscriptRequesterError(
2706
+ "requester-node-unreachable",
2707
+ `${label} node returned ${response.status}: ${nodeErrorMessage(response.body)}`,
2708
+ "unreachable",
2709
+ void 0,
2710
+ response.status
2711
+ );
2712
+ }
2713
+ if (response.status >= 400) {
2714
+ throw new TranscriptRequesterError(
2715
+ "requester-node-denied",
2716
+ `${label} node denied: ${nodeErrorMessage(response.body)}`,
2717
+ "denied",
2718
+ void 0,
2719
+ response.status
2720
+ );
2721
+ }
2722
+ const normalized = normalizeExternal(
2723
+ response.body,
2724
+ "requester-node-response-invalid"
2725
+ );
2726
+ const parsed = schema.safeParse(normalized);
2727
+ if (!parsed.success) {
2728
+ throw new TranscriptRequesterError(
2729
+ "requester-node-response-invalid",
2730
+ `${label} node response failed validation: ${parsed.error.message}`
2731
+ );
2732
+ }
2733
+ return parsed.data;
2734
+ }
2735
+ function nodeErrorMessage(body) {
2736
+ if (typeof body === "string") return body;
2737
+ if (body !== null && typeof body === "object") {
2738
+ const record = body;
2739
+ const code = typeof record.code === "string" ? record.code : void 0;
2740
+ const message = typeof record.message === "string" ? record.message : void 0;
2741
+ if (code !== void 0)
2742
+ return message === void 0 ? code : `${code}: ${message}`;
2743
+ }
2744
+ return "native node refusal";
2745
+ }
2746
+ function deriveDelegationCid(encoded) {
2747
+ const digest = (0, import_digest.create)(30, (0, import_blake3.blake3)(new TextEncoder().encode(encoded)));
2748
+ return import_cid.CID.createV1(85, digest).toString();
2749
+ }
2750
+ function headersRecord(headers) {
2751
+ return Array.isArray(headers) ? Object.fromEntries(headers) : headers;
2752
+ }
2753
+ async function validateAndResolveOwnerNode(endpoint, transport) {
2754
+ return validateAndResolvePublicEndpoint(endpoint, transport, "owner-node");
2755
+ }
2756
+ async function validateAndResolvePublicEndpoint(endpoint, transport, kind) {
2757
+ let url;
2758
+ try {
2759
+ url = new URL(endpoint);
2760
+ } catch {
2761
+ throw endpointError(kind, `${kind} endpoint is not a URL`);
2762
+ }
2763
+ if (url.protocol !== "https:" || url.username !== "" || url.password !== "" || url.hash !== "" || url.search !== "") {
2764
+ throw endpointError(
2765
+ kind,
2766
+ `${kind} endpoint must use HTTPS without credentials, query, or fragment`
2767
+ );
2768
+ }
2769
+ if (transport.resolveEndpoint === void 0) {
2770
+ throw endpointError(
2771
+ kind,
2772
+ `${kind} endpoint resolution metadata is required`
2773
+ );
2774
+ }
2775
+ let resolution;
2776
+ try {
2777
+ resolution = await transport.resolveEndpoint(url.origin);
2778
+ } catch (error) {
2779
+ throw endpointError(
2780
+ kind,
2781
+ `${kind} endpoint resolution failed: ${error instanceof Error ? error.message : String(error)}`
2782
+ );
2783
+ }
2784
+ if (resolution.addresses.length === 0) {
2785
+ throw endpointError(kind, `${kind} endpoint resolved to no addresses`);
2786
+ }
2787
+ const addresses = /* @__PURE__ */ new Set();
2788
+ for (const address of resolution.addresses) {
2789
+ const normalized = normalizeIp(address);
2790
+ if (!isPublicIp(normalized)) {
2791
+ throw endpointError(
2792
+ kind,
2793
+ `${kind} endpoint resolved to a non-public address: ${address}`
2794
+ );
2795
+ }
2796
+ addresses.add(normalized);
2797
+ }
2798
+ return addresses;
2799
+ }
2800
+ function endpointError(kind, message) {
2801
+ return new TranscriptRequesterError(
2802
+ kind === "owner-node" ? "requester-owner-node-endpoint-invalid" : "requester-policy-engine-endpoint-invalid",
2803
+ message,
2804
+ "bootstrap-invalid"
2805
+ );
2806
+ }
2807
+ function normalizeIp(value) {
2808
+ return value.toLowerCase().replace(/^\[|\]$/g, "");
2809
+ }
2810
+ function isPublicIp(value) {
2811
+ const ip = normalizeIp(value);
2812
+ const parts = ip.split(".");
2813
+ if (parts.length === 4 && parts.every((part) => /^\d+$/.test(part) && Number(part) <= 255)) {
2814
+ const [a, b] = parts.map(Number);
2815
+ return !(a === 0 || a === 10 || a === 127 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 0 || a === 192 && b === 168 || a === 198 && (b === 18 || b === 19) || a === 198 && b === 51 && parts[2] === "100" || a === 203 && b === 0 && parts[2] === "113" || a >= 224);
2816
+ }
2817
+ if (!ip.includes(":")) return false;
2818
+ if (ip === "::" || ip === "::1" || ip.startsWith("2001:db8:") || ip.startsWith("fe8") || ip.startsWith("fe9") || ip.startsWith("fea") || ip.startsWith("feb") || ip.startsWith("fc") || ip.startsWith("fd") || ip.startsWith("ff"))
2819
+ return false;
2820
+ const mapped = ip.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
2821
+ if (mapped !== null) return isPublicIp(mapped[1]);
2822
+ const mappedHex = ip.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
2823
+ if (mappedHex !== null) {
2824
+ const high = Number.parseInt(mappedHex[1], 16);
2825
+ const low = Number.parseInt(mappedHex[2], 16);
2826
+ return isPublicIp(`${high >> 8}.${high & 255}.${low >> 8}.${low & 255}`);
2827
+ }
2828
+ return true;
2829
+ }
2830
+ function parseDenialBody(input) {
2831
+ const normalized = normalizeExternal(
2832
+ input,
2833
+ "requester-engine-response-invalid"
2834
+ );
2835
+ const direct = DenialSchema.safeParse(normalized);
2836
+ if (direct.success) {
2837
+ return { code: direct.data.code, message: direct.data.message };
2838
+ }
2839
+ const envelope = ErrorEnvelopeDenialSchema.safeParse(normalized);
2840
+ if (envelope.success) {
2841
+ return {
2842
+ code: envelope.data.error.code,
2843
+ message: envelope.data.error.message
2844
+ };
2845
+ }
2846
+ return void 0;
2847
+ }
2848
+ function errorForDenial(denial, status) {
2849
+ const accessEnded = denial.code === "policy-inactive" || denial.code === "policy-revoked" || denial.code === "policy-expired";
2850
+ return new TranscriptRequesterError(
2851
+ `policy-engine-denied-${denial.code}`,
2852
+ denial.message ?? `policy engine denied ${denial.code}`,
2853
+ accessEnded ? "access-ended" : "denied",
2854
+ denial.code,
2855
+ status
2856
+ );
2857
+ }
2858
+ function retryDelay(attempt, random) {
2859
+ const base = Math.min(REQUESTER_ENGINE_RETRY_MAX_DELAY_MS, 50 * 2 ** attempt);
2860
+ return Math.min(
2861
+ REQUESTER_ENGINE_RETRY_MAX_DELAY_MS,
2862
+ Math.floor(base * (0.5 + random))
2863
+ );
2864
+ }
2865
+ function trimTrailingSlash(value) {
2866
+ return value.endsWith("/") ? value.slice(0, -1) : value;
2867
+ }
2868
+ function normalizeExternal(input, code) {
2869
+ try {
2870
+ return normalizeJson(input);
2871
+ } catch (error) {
2872
+ throw new TranscriptRequesterError(
2873
+ code,
2874
+ error instanceof Error ? error.message : String(error)
2875
+ );
2876
+ }
2877
+ }
2878
+ function parseStrictRfc33392(value) {
2879
+ if (!/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d+)?(Z|[+-]\d{2}:\d{2})$/.test(
2880
+ value
2881
+ )) {
2882
+ return void 0;
2883
+ }
2884
+ const parsed = Date.parse(value);
2885
+ if (Number.isNaN(parsed)) {
2886
+ return void 0;
2887
+ }
2888
+ const canonical = new Date(parsed).toISOString().replace(".000Z", "Z");
2889
+ const reparsed = Date.parse(value);
2890
+ return Date.parse(canonical) === reparsed ? parsed : void 0;
2891
+ }
2892
+ // Annotate the CommonJS export names for ESM import in node:
2893
+ 0 && (module.exports = {
2894
+ HOLDER_KEY_BINDING_PRESENTATION_SCHEMA,
2895
+ LISTEN_SQL_STATEMENT_CATALOG,
2896
+ POLICY_ENGINE_CHALLENGE_REQUEST_SCHEMA,
2897
+ POLICY_ENGINE_CHALLENGE_RESPONSE_SCHEMA,
2898
+ POLICY_ENGINE_DENIAL_SCHEMA,
2899
+ POLICY_ENGINE_GRANT_PRESENTATION_DENIAL_CODES,
2900
+ POLICY_ENGINE_RESOLVE_REQUEST_SCHEMA,
2901
+ PORTABLE_DELEGATION_SCHEMA,
2902
+ REQUESTER_ENGINE_RETRY_ATTEMPTS,
2903
+ REQUESTER_ENGINE_RETRY_MAX_DELAY_MS,
2904
+ REQUESTER_NEAR_EXPIRY_SECONDS,
2905
+ TranscriptRequester,
2906
+ TranscriptRequesterError,
2907
+ createTranscriptRequester,
2908
+ deriveDelegationCid,
2909
+ listenTranscriptScopedStatementName,
2910
+ parseNodeNativePortableDelegation
2911
+ });
2912
+ //# sourceMappingURL=index.cjs.map