@nomosprotocol/sdk 2.0.0 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod2) => __copyProps(__defProp({}, "__esModule", { value: tr
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  ACT_MEDIA_TYPE: () => ACT_MEDIA_TYPE,
24
+ MAX_PRESENTED: () => MAX_PRESENTED,
24
25
  Nomos: () => Nomos,
25
26
  NomosAPIError: () => NomosAPIError,
26
27
  NomosActError: () => NomosActError,
@@ -35,31 +36,53 @@ __export(index_exports, {
35
36
  NomosRateLimitError: () => NomosRateLimitError,
36
37
  NomosValidationError: () => NomosValidationError,
37
38
  NomosWebhookSignatureError: () => NomosWebhookSignatureError,
39
+ RegistryError: () => RegistryError,
40
+ RevocationUnavailable: () => RevocationUnavailable,
38
41
  WELL_KNOWN: () => WELL_KNOWN,
39
42
  accept: () => accept,
40
43
  act: () => act,
44
+ applyToolBindings: () => applyToolBindings,
41
45
  approver: () => approver,
42
46
  bindingDigest: () => bindingDigest,
43
47
  buildAct: () => buildAct,
44
48
  can: () => can,
49
+ certificateRegistry: () => certificateRegistry,
45
50
  certify: () => certify,
51
+ describeSuggestion: () => describeSuggestion,
46
52
  fetchDocument: () => fetchDocument,
47
53
  generateSigner: () => generateSigner,
48
54
  issuer: () => issuer,
49
55
  kidOf: () => kidOf2,
50
56
  localSigner: () => localSigner,
57
+ memoryPasskeyStore: () => memoryPasskeyStore,
58
+ memoryRegistryStore: () => memoryRegistryStore,
51
59
  memoryStore: () => memoryStore,
52
60
  nomos: () => nomos,
53
61
  originOf: () => originOf,
62
+ parseToolBindings: () => parseToolBindings,
63
+ passkeyApprover: () => passkeyApprover,
64
+ postgresPasskeyStore: () => postgresPasskeyStore,
65
+ postgresRegistryStore: () => postgresRegistryStore,
66
+ postgresStore: () => postgresStore,
67
+ presentedCertificates: () => presentedCertificates,
68
+ readAttestation: () => readAttestation,
69
+ reliedKids: () => reliedKids,
70
+ revocationListCertificates: () => revocationListCertificates,
71
+ revocationSource: () => revocationSource,
54
72
  seal: () => seal,
55
73
  seededSigner: () => seededSigner,
56
74
  signConsent: () => signConsent,
75
+ signEnrolmentToken: () => signEnrolmentToken,
57
76
  signInForce: () => signInForce,
77
+ signRevocation: () => signRevocation,
78
+ signRevocationList: () => signRevocationList,
58
79
  signTestimony: () => signTestimony,
59
80
  statusFor: () => statusFor,
81
+ suggestToolBindings: () => suggestToolBindings,
60
82
  verifyAct: () => verifyAct,
61
83
  verifyArtifact: () => verifyArtifact,
62
84
  verifyChain: () => verifyChain,
85
+ verifyEnrolmentToken: () => verifyEnrolmentToken,
63
86
  verifySeal: () => verifySeal,
64
87
  witness: () => witness
65
88
  });
@@ -168,7 +191,7 @@ function isRetryable(status) {
168
191
  }
169
192
 
170
193
  // src/http-client.ts
171
- var SDK_VERSION = "1.1.0";
194
+ var SDK_VERSION = "2.3.1";
172
195
  var HttpClient = class {
173
196
  constructor(opts) {
174
197
  this.opts = opts;
@@ -833,7 +856,7 @@ async function canAuthenticated(http, params, opts) {
833
856
  var nomos = { can };
834
857
 
835
858
  // src/nomos.ts
836
- var DEFAULT_BASE_URL = "https://nomos.ai";
859
+ var DEFAULT_BASE_URL = "https://www.nomosprotocol.com";
837
860
  var DEFAULT_MAX_RETRIES = 3;
838
861
  var DEFAULT_TIMEOUT = 3e4;
839
862
  var Nomos = class {
@@ -843,7 +866,7 @@ var Nomos = class {
843
866
  constructor(apiKey, config = {}) {
844
867
  if (!apiKey || typeof apiKey !== "string") {
845
868
  throw new NomosAuthenticationError(
846
- 'A valid API key is required. Pass it as the first argument: new Nomos("nms_live_...")'
869
+ 'A valid API key is required. Pass it as the first argument: new Nomos("nmk_...")'
847
870
  );
848
871
  }
849
872
  this.mode = apiKey.startsWith("nms_test_") ? "test" : "live";
@@ -865,7 +888,7 @@ var Nomos = class {
865
888
  * return shape either way, so calling code never needs to know which rail answered.
866
889
  *
867
890
  * @example
868
- * const client = new Nomos('nms_live_...');
891
+ * const client = new Nomos('nmk_...');
869
892
  * const verdict = await client.can({ artifact_id: 'loan_approval_v1', credit_score: 720 });
870
893
  */
871
894
  async can(params, opts) {
@@ -873,7 +896,7 @@ var Nomos = class {
873
896
  }
874
897
  };
875
898
 
876
- // ../../AI_Navigator-repro/node_modules/@noble/hashes/_u64.js
899
+ // node_modules/@noble/hashes/_u64.js
877
900
  var U32_MASK64 = /* @__PURE__ */ (() => BigInt(2 ** 32 - 1))();
878
901
  var _32n = /* @__PURE__ */ BigInt(32);
879
902
  function fromBig(n, le = false) {
@@ -916,7 +939,7 @@ var add4H = (low, Ah, Bh, Ch, Dh) => Ah + Bh + Ch + Dh + (low / 2 ** 32 | 0) | 0
916
939
  var add5L = (Al, Bl, Cl, Dl, El) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0) + (El >>> 0);
917
940
  var add5H = (low, Ah, Bh, Ch, Dh, Eh) => Ah + Bh + Ch + Dh + Eh + (low / 2 ** 32 | 0) | 0;
918
941
 
919
- // ../../AI_Navigator-repro/node_modules/@noble/hashes/utils.js
942
+ // node_modules/@noble/hashes/utils.js
920
943
  function isBytes(a) {
921
944
  return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array" && "BYTES_PER_ELEMENT" in a && a.BYTES_PER_ELEMENT === 1;
922
945
  }
@@ -1081,7 +1104,7 @@ var oidNist = (suffix) => ({
1081
1104
  oid: Uint8Array.from([6, 9, 96, 134, 72, 1, 101, 3, 4, 2, suffix])
1082
1105
  });
1083
1106
 
1084
- // ../../AI_Navigator-repro/node_modules/@noble/hashes/_md.js
1107
+ // node_modules/@noble/hashes/_md.js
1085
1108
  function Chi(a, b, c) {
1086
1109
  return a & b ^ ~a & c;
1087
1110
  }
@@ -1212,7 +1235,7 @@ var SHA512_IV = /* @__PURE__ */ Uint32Array.from([
1212
1235
  327033209
1213
1236
  ]);
1214
1237
 
1215
- // ../../AI_Navigator-repro/node_modules/@noble/hashes/sha2.js
1238
+ // node_modules/@noble/hashes/sha2.js
1216
1239
  var SHA256_K = /* @__PURE__ */ Uint32Array.from([
1217
1240
  1116352408,
1218
1241
  1899447441,
@@ -1609,7 +1632,7 @@ var sha512 = /* @__PURE__ */ createHasher(
1609
1632
  /* @__PURE__ */ oidNist(3)
1610
1633
  );
1611
1634
 
1612
- // ../packages/verify/dist/bytes.js
1635
+ // node_modules/@nomosprotocol/verify/dist/bytes.js
1613
1636
  var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1614
1637
  var REV = new Map([...B64].map((c, i) => [c, i]));
1615
1638
  var utf8 = (s) => new TextEncoder().encode(s);
@@ -1671,7 +1694,7 @@ function fromBase64url(s) {
1671
1694
  return raw && toBase64url(raw) === s ? raw : null;
1672
1695
  }
1673
1696
 
1674
- // ../packages/verify/dist/jcs.js
1697
+ // node_modules/@nomosprotocol/verify/dist/jcs.js
1675
1698
  function value(v) {
1676
1699
  if (v === null)
1677
1700
  return "null";
@@ -1705,7 +1728,7 @@ function codePointCompare(a, b) {
1705
1728
  return A.length - B.length;
1706
1729
  }
1707
1730
 
1708
- // ../../AI_Navigator-repro/node_modules/@noble/curves/utils.js
1731
+ // node_modules/@noble/curves/utils.js
1709
1732
  function aarray(item, title, inner = () => {
1710
1733
  }) {
1711
1734
  if (!Array.isArray(item))
@@ -1887,7 +1910,7 @@ function validateObject(object, fields = {}, optFields = {}, title = "object") {
1887
1910
  iter(optFields, true);
1888
1911
  }
1889
1912
 
1890
- // ../../AI_Navigator-repro/node_modules/@noble/curves/abstract/modular.js
1913
+ // node_modules/@noble/curves/abstract/modular.js
1891
1914
  var _0n2 = /* @__PURE__ */ BigInt(0);
1892
1915
  var _1n2 = /* @__PURE__ */ BigInt(1);
1893
1916
  var _2n = /* @__PURE__ */ BigInt(2);
@@ -2350,7 +2373,7 @@ function mapHashToField(key, fieldOrder, isLE = false) {
2350
2373
  return isLE ? numberToBytesLE(reduced, fieldLen) : numberToBytesBE(reduced, fieldLen);
2351
2374
  }
2352
2375
 
2353
- // ../../AI_Navigator-repro/node_modules/@noble/curves/abstract/curve.js
2376
+ // node_modules/@noble/curves/abstract/curve.js
2354
2377
  var _0n3 = /* @__PURE__ */ BigInt(0);
2355
2378
  var _1n3 = /* @__PURE__ */ BigInt(1);
2356
2379
  var _4n2 = /* @__PURE__ */ BigInt(4);
@@ -2737,7 +2760,7 @@ function createKeygen(randomSecretKey, getPublicKey) {
2737
2760
  };
2738
2761
  }
2739
2762
 
2740
- // ../../AI_Navigator-repro/node_modules/@noble/curves/abstract/edwards.js
2763
+ // node_modules/@noble/curves/abstract/edwards.js
2741
2764
  var _0n4 = /* @__PURE__ */ BigInt(0);
2742
2765
  var _1n4 = /* @__PURE__ */ BigInt(1);
2743
2766
  var _2n2 = /* @__PURE__ */ BigInt(2);
@@ -3188,7 +3211,7 @@ function eddsa(Point, cHash, eddsaOpts = {}) {
3188
3211
  });
3189
3212
  }
3190
3213
 
3191
- // ../../AI_Navigator-repro/node_modules/@noble/curves/ed25519.js
3214
+ // node_modules/@noble/curves/ed25519.js
3192
3215
  var _1n5 = /* @__PURE__ */ BigInt(1);
3193
3216
  var _2n3 = /* @__PURE__ */ BigInt(2);
3194
3217
  var _5n2 = /* @__PURE__ */ BigInt(5);
@@ -3263,7 +3286,7 @@ function ed(opts) {
3263
3286
  }
3264
3287
  var ed25519 = /* @__PURE__ */ ed({});
3265
3288
 
3266
- // ../../AI_Navigator-repro/node_modules/@noble/hashes/hmac.js
3289
+ // node_modules/@noble/hashes/hmac.js
3267
3290
  var _HMAC = class {
3268
3291
  oHash;
3269
3292
  iHash;
@@ -3340,7 +3363,7 @@ var hmac = /* @__PURE__ */ (() => {
3340
3363
  return hmac_;
3341
3364
  })();
3342
3365
 
3343
- // ../../AI_Navigator-repro/node_modules/@noble/curves/abstract/der.js
3366
+ // node_modules/@noble/curves/abstract/der.js
3344
3367
  var _0n5 = /* @__PURE__ */ BigInt(0);
3345
3368
  var DERErr = class extends Error {
3346
3369
  constructor(m = "") {
@@ -3434,18 +3457,18 @@ var _DER = {
3434
3457
  }
3435
3458
  },
3436
3459
  toSig(bytes, maxScalarBytes) {
3437
- const { Err: E, _int: int, _tlv: tlv } = _DER;
3460
+ const { Err: E, _int: int, _tlv: tlv2 } = _DER;
3438
3461
  if (maxScalarBytes !== void 0) {
3439
3462
  asafenumber(maxScalarBytes, "maxScalarBytes");
3440
3463
  if (maxScalarBytes < 1)
3441
3464
  throw new E("invalid signature: maxScalarBytes must be positive");
3442
3465
  }
3443
3466
  const data = abytes2(bytes, void 0, "signature");
3444
- const { v: seqBytes, l: seqLeftBytes } = tlv.decode(48, data);
3467
+ const { v: seqBytes, l: seqLeftBytes } = tlv2.decode(48, data);
3445
3468
  if (seqLeftBytes.length)
3446
3469
  throw new E("invalid signature: left bytes after parsing");
3447
- const { v: rBytes, l: rLeftBytes } = tlv.decode(2, seqBytes);
3448
- const { v: sBytes, l: sLeftBytes } = tlv.decode(2, rLeftBytes);
3470
+ const { v: rBytes, l: rLeftBytes } = tlv2.decode(2, seqBytes);
3471
+ const { v: sBytes, l: sLeftBytes } = tlv2.decode(2, rLeftBytes);
3449
3472
  if (sLeftBytes.length)
3450
3473
  throw new E("invalid signature: left bytes after parsing");
3451
3474
  if (maxScalarBytes !== void 0 && (rBytes.length > maxScalarBytes || sBytes.length > maxScalarBytes))
@@ -3453,12 +3476,12 @@ var _DER = {
3453
3476
  return { r: int.decode(rBytes), s: int.decode(sBytes) };
3454
3477
  },
3455
3478
  hexFromSig(sig) {
3456
- const { _tlv: tlv, _int: int } = _DER;
3479
+ const { _tlv: tlv2, _int: int } = _DER;
3457
3480
  validateObject(sig, { r: "bigint", s: "bigint" }, {}, "sig");
3458
- const rs = tlv.encode(2, int.encode(sig.r));
3459
- const ss = tlv.encode(2, int.encode(sig.s));
3481
+ const rs = tlv2.encode(2, int.encode(sig.r));
3482
+ const ss = tlv2.encode(2, int.encode(sig.s));
3460
3483
  const seq = rs + ss;
3461
- return tlv.encode(48, seq);
3484
+ return tlv2.encode(48, seq);
3462
3485
  }
3463
3486
  };
3464
3487
  var DER = /* @__PURE__ */ (() => {
@@ -3467,7 +3490,7 @@ var DER = /* @__PURE__ */ (() => {
3467
3490
  return Object.freeze(_DER);
3468
3491
  })();
3469
3492
 
3470
- // ../../AI_Navigator-repro/node_modules/@noble/curves/abstract/weierstrass.js
3493
+ // node_modules/@noble/curves/abstract/weierstrass.js
3471
3494
  var divNearest = (num, den) => (num + (num >= 0 ? den : -den) / _2n4) / den;
3472
3495
  function _splitEndoScalar(k, basis, n) {
3473
3496
  aInRange("scalar", k, _0n6, n);
@@ -4299,7 +4322,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
4299
4322
  });
4300
4323
  }
4301
4324
 
4302
- // ../../AI_Navigator-repro/node_modules/@noble/curves/nist.js
4325
+ // node_modules/@noble/curves/nist.js
4303
4326
  var p256_CURVE = /* @__PURE__ */ (() => ({
4304
4327
  p: BigInt("0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff"),
4305
4328
  n: BigInt("0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551"),
@@ -4312,21 +4335,21 @@ var p256_CURVE = /* @__PURE__ */ (() => ({
4312
4335
  var p256_Point = /* @__PURE__ */ weierstrass(p256_CURVE);
4313
4336
  var p256 = /* @__PURE__ */ ecdsa(p256_Point, sha256);
4314
4337
 
4315
- // ../packages/verify/dist/suites.js
4338
+ // node_modules/@nomosprotocol/verify/dist/suites.js
4316
4339
  var SUITES = ["Ed25519", "ES256", "WebAuthn-ES256"];
4317
4340
  var N = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551n;
4318
4341
  var ED25519_PREFIX = Uint8Array.from([48, 42, 48, 5, 6, 3, 43, 101, 112, 3, 33, 0]);
4319
4342
  var P256_PREFIX = Uint8Array.from([48, 89, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134, 72, 206, 61, 3, 1, 7, 3, 66, 0, 4]);
4320
4343
  var SUITES_FOR = { "Ed25519": ["Ed25519"], "P-256": ["ES256", "WebAuthn-ES256"] };
4321
4344
  var isSuite = (s) => typeof s === "string" && SUITES.includes(s);
4322
- function pemDer(pem) {
4323
- if (typeof pem !== "string")
4345
+ function pemDer(pem2) {
4346
+ if (typeof pem2 !== "string")
4324
4347
  return null;
4325
- const m = pem.match(/^\s*-----BEGIN PUBLIC KEY-----\s*([A-Za-z0-9+/=\s]+?)\s*-----END PUBLIC KEY-----\s*$/);
4348
+ const m = pem2.match(/^\s*-----BEGIN PUBLIC KEY-----\s*([A-Za-z0-9+/=\s]+?)\s*-----END PUBLIC KEY-----\s*$/);
4326
4349
  return m ? fromBase64(m[1].replace(/\s+/g, "")) : null;
4327
4350
  }
4328
- function loadKey(pem) {
4329
- const der = pemDer(pem);
4351
+ function loadKey(pem2) {
4352
+ const der = pemDer(pem2);
4330
4353
  if (!der)
4331
4354
  return { reason: "invalid_key" };
4332
4355
  if (der.length === 44 && equal(der.subarray(0, 12), ED25519_PREFIX)) {
@@ -4350,16 +4373,16 @@ function loadKey(pem) {
4350
4373
  }
4351
4374
  return { reason: "invalid_key" };
4352
4375
  }
4353
- function kidOf(pem) {
4354
- const k = loadKey(pem);
4376
+ function kidOf(pem2) {
4377
+ const k = loadKey(pem2);
4355
4378
  return "reason" in k ? null : toBase64url(sha256(k.der)).slice(0, 16);
4356
4379
  }
4357
4380
  var toBigInt = (b) => b.reduce((n, x) => n << 8n | BigInt(x), 0n);
4358
- function verifySuite(suite, pem, payload, proof) {
4381
+ function verifySuite(suite, pem2, payload, proof) {
4359
4382
  const fail2 = (reason) => ({ ok: false, reason });
4360
4383
  if (!isSuite(suite))
4361
4384
  return fail2("unknown_suite");
4362
- const k = loadKey(pem);
4385
+ const k = loadKey(pem2);
4363
4386
  if ("reason" in k)
4364
4387
  return fail2("invalid_key");
4365
4388
  if (!SUITES_FOR[k.type].includes(suite))
@@ -4421,7 +4444,7 @@ function verifySuite(suite, pem, payload, proof) {
4421
4444
  return ok ? { ok: true } : fail2("bad_signature");
4422
4445
  }
4423
4446
 
4424
- // ../packages/verify/dist/scope.js
4447
+ // node_modules/@nomosprotocol/verify/dist/scope.js
4425
4448
  var ARTIFACT_DIMENSIONS = ["artifact", "industry", "jurisdiction"];
4426
4449
  var SET_DIMENSIONS = ["claim", "consent", "suite"];
4427
4450
  var UNRESTRICTED = { terms: {} };
@@ -4540,7 +4563,7 @@ function formatScope(scope) {
4540
4563
  return Object.entries(scope.terms).map(([d, v]) => `${d}:${v}`).join(" ");
4541
4564
  }
4542
4565
 
4543
- // ../packages/verify/dist/chain.js
4566
+ // node_modules/@nomosprotocol/verify/dist/chain.js
4544
4567
  var MAX_CHAIN = 20;
4545
4568
  var CERT_SUITES = ["Ed25519", "ES256"];
4546
4569
  var SEAL_SUITES = ["Ed25519", "ES256"];
@@ -4632,7 +4655,7 @@ var Memo = class {
4632
4655
  }
4633
4656
  };
4634
4657
  function validate(path, rootPem, now, purpose, revokedKids, revokedCerts, memo) {
4635
- let pem = rootPem, scope = UNRESTRICTED;
4658
+ let pem2 = rootPem, scope = UNRESTRICTED;
4636
4659
  const hops = [];
4637
4660
  for (const cert of path) {
4638
4661
  const f = memo.of(cert);
@@ -4642,7 +4665,7 @@ function validate(path, rootPem, now, purpose, revokedKids, revokedCerts, memo)
4642
4665
  return fail("ISSUER_NOT_RECOGNIZED", { reason_code: "kid_mismatch" });
4643
4666
  if (!suitePermitted(scope, cert.algorithm))
4644
4667
  return fail("OUT_OF_SCOPE", { dimension: "suite", reason_code: "suite_not_permitted" });
4645
- const sig = memo.sig(cert, pem);
4668
+ const sig = memo.sig(cert, pem2);
4646
4669
  if (!sig.ok)
4647
4670
  return fail("ISSUER_NOT_RECOGNIZED", { reason_code: sig.reason });
4648
4671
  const issued = parseTime(cert.issued_at), expires = parseTime(cert.expires_at);
@@ -4662,14 +4685,14 @@ function validate(path, rootPem, now, purpose, revokedKids, revokedCerts, memo)
4662
4685
  const narrowing = isNarrowerOrEqual(f.scope.scope, scope);
4663
4686
  if (!narrowing.ok)
4664
4687
  return fail("OUT_OF_SCOPE", { dimension: narrowing.dimension, reason_code: "widening" });
4665
- hops.push({ cert, parentPem: pem, parentScope: scope });
4666
- pem = cert.child_public_key_pem;
4688
+ hops.push({ cert, parentPem: pem2, parentScope: scope });
4689
+ pem2 = cert.child_public_key_pem;
4667
4690
  scope = mergeScopes(scope, f.scope.scope);
4668
4691
  }
4669
4692
  const admitted = purpose(scope);
4670
4693
  if (!admitted.ok)
4671
4694
  return fail("OUT_OF_SCOPE", { dimension: admitted.dimension });
4672
- return { decision: "RESOLVED", pem, path: [], effective: scope, hops };
4695
+ return { decision: "RESOLVED", pem: pem2, path: [], effective: scope, hops };
4673
4696
  }
4674
4697
  function resolveKey(o) {
4675
4698
  const revokedKids = o.revokedKids ?? /* @__PURE__ */ new Set(), revokedCerts = o.revokedCerts ?? /* @__PURE__ */ new Set();
@@ -4755,7 +4778,7 @@ function verifyArtifact(o) {
4755
4778
  }
4756
4779
  }
4757
4780
 
4758
- // ../packages/verify/dist/evaluate.js
4781
+ // node_modules/@nomosprotocol/verify/dist/evaluate.js
4759
4782
  var jcsString = (v) => canonicalize(v);
4760
4783
  var FIELD = /* @__PURE__ */ Symbol("field");
4761
4784
  var ABSENT = /* @__PURE__ */ Symbol("absent");
@@ -5084,7 +5107,7 @@ function evaluate(artifact, facts) {
5084
5107
  return ev;
5085
5108
  }
5086
5109
 
5087
- // ../packages/verify/dist/act.js
5110
+ // node_modules/@nomosprotocol/verify/dist/act.js
5088
5111
  var MAX_WINDOW_SECONDS = 300;
5089
5112
  var MAX_IN_FORCE_SECONDS = 86400;
5090
5113
  var TESTIMONY_SUITES = ["Ed25519", "ES256"];
@@ -5122,7 +5145,7 @@ function hasAmbiguousEscalation(artifact) {
5122
5145
  return [...named.values()].some((ids) => ids.size > 1);
5123
5146
  }
5124
5147
  var proofOf = (st) => st.algorithm === "WebAuthn-ES256" ? pick(st, WEBAUTHN_FIELDS) : { signature: st.signature };
5125
- var statementSig = (pem, st, payload) => verifySuite(st.algorithm, pem, jcs(payload), proofOf(st));
5148
+ var statementSig = (pem2, st, payload) => verifySuite(st.algorithm, pem2, jcs(payload), proofOf(st));
5126
5149
  var time = (s) => typeof s === "string" ? Date.parse(s) : NaN;
5127
5150
  var isObject = (x) => x !== null && typeof x === "object" && !Array.isArray(x);
5128
5151
  function statementFailure(r, invalid, outOfScope, who) {
@@ -5287,11 +5310,11 @@ function verifyUnsafe(a, o) {
5287
5310
  return { decision: "AUTHORIZED", commit: true, ...base };
5288
5311
  if (ev.outcome === "block")
5289
5312
  return { decision: "DENIED", commit: false, ...base };
5290
- const esc2 = (art.governance?.escalations ?? []).find((e) => e?.id === ev.escalation_id);
5291
- if (!esc2)
5313
+ const esc3 = (art.governance?.escalations ?? []).find((e) => e?.id === ev.escalation_id);
5314
+ if (!esc3)
5292
5315
  return { decision: "ESCALATED", commit: false, escalation_id: ev.escalation_id, consents_valid: 0, consents_required: null, ...base };
5293
- const role = esc2.role_required;
5294
- const needN = Math.trunc(Number(esc2.required_consents ?? 1));
5316
+ const role = esc3.role_required;
5317
+ const needN = Math.trunc(Number(esc3.required_consents ?? 1));
5295
5318
  const approvers = /* @__PURE__ */ new Set();
5296
5319
  for (const c of Array.isArray(act2.consents) ? act2.consents : []) {
5297
5320
  if (!isObject(c) || !["binding_digest", "role", "decision", "signed_at", "kid", "signature"].every((k2) => typeof c[k2] === "string") || !CONSENT_SUITES.includes(c.algorithm) || c.algorithm === "WebAuthn-ES256" && !WEBAUTHN_FIELDS.every((k2) => typeof c[k2] === "string")) {
@@ -5317,7 +5340,7 @@ function verifyUnsafe(a, o) {
5317
5340
  return { decision: "ESCALATED", commit: false, ...counted };
5318
5341
  }
5319
5342
 
5320
- // ../packages/verify/dist/seal.js
5343
+ // node_modules/@nomosprotocol/verify/dist/seal.js
5321
5344
  function verifySeal(artifact, publicKeyPem) {
5322
5345
  const bad = checkSealShape(artifact);
5323
5346
  if (bad)
@@ -5331,6 +5354,83 @@ function verifySeal(artifact, publicKeyPem) {
5331
5354
  return sealFailure(artifact, publicKeyPem) ?? { decision: "VALID", kid };
5332
5355
  }
5333
5356
 
5357
+ // node_modules/@nomosprotocol/verify/dist/revocation.js
5358
+ var MAX_LIST_CERTS = 1e3;
5359
+ var REVOCATION_SUITES = ["Ed25519", "ES256"];
5360
+ var CERT_FIELDS2 = ["parent_kid", "child_kid", "child_public_key_pem", "issued_at", "expires_at", "algorithm", "signature"];
5361
+ var FP = /^[0-9a-f]{64}$/;
5362
+ var revocationPayload = (st) => "revoked_fingerprint" in st ? { revoked_fingerprint: st.revoked_fingerprint, reason: st.reason, issued_at: st.issued_at } : { revoked_kid: st.revoked_kid, reason: st.reason, issued_at: st.issued_at };
5363
+ var revocationListPayload = (l) => ({ generated_at: l.generated_at, statements: l.statements });
5364
+ var forSigning = (suite) => (scope) => suitePermitted(scope, suite) ? { ok: true } : { ok: false, dimension: "suite" };
5365
+ var str = (v) => typeof v === "string" && v !== "";
5366
+ var time2 = (s) => typeof s === "string" ? Date.parse(s) : NaN;
5367
+ function signerPem(kid, pool, root, at, suite, revokedKids, revokedCerts) {
5368
+ const r = resolveKey({ targetKid: kid, pool, rootPublicKeyPem: root, purpose: forSigning(suite), now: new Date(at), revokedKids, revokedCerts });
5369
+ return r.decision === "RESOLVED" ? r.pem : null;
5370
+ }
5371
+ function evaluateStatement(st, pool, rootPublicKeyPem, now) {
5372
+ if (st === null || typeof st !== "object" || Array.isArray(st))
5373
+ return "bad_statement";
5374
+ const hasKid = "revoked_kid" in st, hasFp = "revoked_fingerprint" in st;
5375
+ if (hasKid === hasFp)
5376
+ return "bad_statement";
5377
+ const target = hasKid ? st.revoked_kid : st.revoked_fingerprint;
5378
+ if (!str(target) || hasFp && !FP.test(target))
5379
+ return "bad_statement";
5380
+ if (typeof st.reason !== "string" || !["issued_at", "kid", "algorithm", "signature"].every((f) => str(st[f])))
5381
+ return "bad_statement";
5382
+ if (!REVOCATION_SUITES.includes(st.algorithm))
5383
+ return "bad_statement";
5384
+ const issued = time2(st.issued_at);
5385
+ if (Number.isNaN(issued) || issued > now.getTime())
5386
+ return "bad_statement";
5387
+ const certs = pool.filter((c) => c.parent_kid === st.kid && (hasKid ? c.child_kid === target : fingerprint(c) === target));
5388
+ if (certs.length === 0)
5389
+ return "no_certificate";
5390
+ const parent = signerPem(st.kid, pool, rootPublicKeyPem, issued, st.algorithm, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Set());
5391
+ if (!parent)
5392
+ return "signer_not_recognized";
5393
+ if (!certs.some((c) => verifySuite(c.algorithm, parent, jcs(certPayload(c)), { signature: c.signature }).ok))
5394
+ return "bad_certificate_signature";
5395
+ return verifySuite(st.algorithm, parent, jcs(revocationPayload(st)), { signature: st.signature }).ok ? null : "bad_signature";
5396
+ }
5397
+ function verifyRevocationList(list, o) {
5398
+ const L = list;
5399
+ if (L === null || typeof L !== "object" || !["generated_at", "kid", "algorithm", "signature"].every((f) => typeof L[f] === "string") || !Array.isArray(L.statements)) {
5400
+ return { decision: "MALFORMED", reason_code: "bad_list" };
5401
+ }
5402
+ const extra = L.key_certs ?? [];
5403
+ if (!Array.isArray(extra) || extra.length > MAX_LIST_CERTS || !extra.every((c) => c && typeof c === "object" && CERT_FIELDS2.every((f) => str(c[f])) && ["Ed25519", "ES256"].includes(c.algorithm) && (c.scope === void 0 || c.scope === null || typeof c.scope === "string"))) {
5404
+ return { decision: "MALFORMED", reason_code: "bad_list_certs" };
5405
+ }
5406
+ const seen = /* @__PURE__ */ new Set(), pool = [];
5407
+ for (const c of [...extra, ...o.ownCerts ?? []]) {
5408
+ const fp = fingerprint(c);
5409
+ if (!seen.has(fp)) {
5410
+ seen.add(fp);
5411
+ pool.push(c);
5412
+ }
5413
+ }
5414
+ const kids = /* @__PURE__ */ new Set(), certs = /* @__PURE__ */ new Set(), ignored = [];
5415
+ L.statements.forEach((st, index) => {
5416
+ const why = evaluateStatement(st, pool, o.rootPublicKeyPem, o.now);
5417
+ if (why)
5418
+ ignored.push({ index, reason: why });
5419
+ else if ("revoked_kid" in st)
5420
+ kids.add(st.revoked_kid);
5421
+ else
5422
+ certs.add(st.revoked_fingerprint);
5423
+ });
5424
+ const out = { decision: "OK", revoked_kids: [...kids].sort(), revoked_certs: [...certs].sort(), ignored };
5425
+ const generated = time2(L.generated_at);
5426
+ if (Number.isNaN(generated) || generated > o.now.getTime() || !REVOCATION_SUITES.includes(L.algorithm))
5427
+ return out;
5428
+ const signer = signerPem(L.kid, pool, o.rootPublicKeyPem, generated, L.algorithm, kids, certs);
5429
+ if (signer && verifySuite(L.algorithm, signer, jcs(revocationListPayload(L)), { signature: L.signature }).ok)
5430
+ out.as_of = L.generated_at;
5431
+ return out;
5432
+ }
5433
+
5334
5434
  // src/verify-chain.ts
5335
5435
  var CHAIN_PROTOCOL_VERSION2 = "1";
5336
5436
  var DEFAULT_BASE = "https://www.nomosprotocol.com";
@@ -5377,43 +5477,371 @@ async function verifyChainRemote(params, opts) {
5377
5477
  throw new NomosAPIError(message, res.status, requestId);
5378
5478
  }
5379
5479
 
5480
+ // src/bindings.ts
5481
+ var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
5482
+ var hasOwn = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
5483
+ var isScalar = (v) => typeof v === "string" || typeof v === "boolean" || typeof v === "number" && Number.isFinite(v);
5484
+ function parseToolBindings(raw) {
5485
+ const no = (why) => {
5486
+ throw new Error(why);
5487
+ };
5488
+ const obj = raw;
5489
+ if (!obj || typeof obj !== "object" || Array.isArray(obj)) no("it is not a JSON object");
5490
+ if (obj.version !== 1) no('"version" must be 1');
5491
+ if (typeof obj.artifact !== "string" || !obj.artifact) no('"artifact" must name the sealed policy');
5492
+ if (typeof obj.reviewed !== "boolean") no('"reviewed" must be true or false');
5493
+ if (!obj.tools || typeof obj.tools !== "object" || Array.isArray(obj.tools)) no('"tools" must be an object');
5494
+ for (const k of Object.keys(obj)) if (!["version", "artifact", "reviewed", "tools", "_review"].includes(k)) no(`unknown setting "${k}"`);
5495
+ const tools = {};
5496
+ for (const tool of Object.keys(obj.tools)) {
5497
+ if (FORBIDDEN_KEYS.has(tool)) no(`"${tool}" isn't a usable tool name`);
5498
+ const entry = obj.tools[tool];
5499
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) no(`tool "${tool}" must be an object with "facts"`);
5500
+ for (const k of Object.keys(entry)) if (!["facts", "_why"].includes(k)) no(`tool "${tool}": unknown setting "${k}"`);
5501
+ const facts = entry.facts;
5502
+ if (!facts || typeof facts !== "object" || Array.isArray(facts)) no(`tool "${tool}" needs "facts"`);
5503
+ tools[tool] = { facts: {} };
5504
+ for (const fact of Object.keys(facts)) {
5505
+ const where = `${tool}.${fact}`;
5506
+ if (fact === "tool") no(`${where}: "tool" is set by the guard to the tool's name`);
5507
+ if (FORBIDDEN_KEYS.has(fact)) no(`${where} isn't a usable fact name`);
5508
+ const b = facts[fact];
5509
+ if (!b || typeof b !== "object" || Array.isArray(b)) no(`${where} must be an object with "arg"`);
5510
+ for (const k of Object.keys(b)) if (!["arg", "scale", "map", "_why"].includes(k)) no(`${where}: "${k}" isn't allowed. A fact can only come from an argument of the call`);
5511
+ if (typeof b.arg !== "string" || !b.arg) no(`${where} needs "arg", the argument it comes from`);
5512
+ if (b.arg.split(".").some((s) => !s || FORBIDDEN_KEYS.has(s))) no(`${where}: "${b.arg}" isn't a usable argument path`);
5513
+ if (b.scale !== void 0 && b.map !== void 0) no(`${where}: use "scale" or "map", not both`);
5514
+ if (b.scale !== void 0 && (typeof b.scale !== "number" || !Number.isFinite(b.scale) || b.scale === 0)) no(`${where}: "scale" must be a non-zero number`);
5515
+ if (b.map !== void 0) {
5516
+ if (!b.map || typeof b.map !== "object" || Array.isArray(b.map)) no(`${where}: "map" must be an object`);
5517
+ for (const k of Object.keys(b.map)) if (FORBIDDEN_KEYS.has(k) || !isScalar(b.map[k])) no(`${where}: "map" can only hold plain values`);
5518
+ }
5519
+ tools[tool].facts[fact] = { arg: b.arg, ...b.scale !== void 0 ? { scale: b.scale } : {}, ...b.map !== void 0 ? { map: b.map } : {} };
5520
+ }
5521
+ }
5522
+ return { version: 1, artifact: obj.artifact, reviewed: obj.reviewed, tools };
5523
+ }
5524
+ function applyToolBindings(bindings, tool, args) {
5525
+ const inputs = { tool };
5526
+ const unresolved = [];
5527
+ const facts = hasOwn(bindings.tools, tool) ? bindings.tools[tool].facts : {};
5528
+ for (const [fact, b] of Object.entries(facts)) {
5529
+ let v = args;
5530
+ for (const seg of b.arg.split(".")) v = v && typeof v === "object" && !Array.isArray(v) && hasOwn(v, seg) ? v[seg] : void 0;
5531
+ if (v === void 0 || v === null) {
5532
+ unresolved.push({ fact, arg: b.arg, why: "missing" });
5533
+ continue;
5534
+ }
5535
+ if (b.scale !== void 0) {
5536
+ if (typeof v !== "number" || !Number.isFinite(v)) {
5537
+ unresolved.push({ fact, arg: b.arg, why: "not_a_number" });
5538
+ continue;
5539
+ }
5540
+ inputs[fact] = Number((v * b.scale).toFixed(9));
5541
+ continue;
5542
+ }
5543
+ if (!isScalar(v)) {
5544
+ unresolved.push({ fact, arg: b.arg, why: "not_a_value" });
5545
+ continue;
5546
+ }
5547
+ if (b.map) {
5548
+ if (!hasOwn(b.map, String(v))) {
5549
+ unresolved.push({ fact, arg: b.arg, why: "not_in_map" });
5550
+ continue;
5551
+ }
5552
+ inputs[fact] = b.map[String(v)];
5553
+ continue;
5554
+ }
5555
+ inputs[fact] = v;
5556
+ }
5557
+ return { inputs, unresolved };
5558
+ }
5559
+ var WEAK = /* @__PURE__ */ new Set(["is", "has", "the", "a", "an", "of", "to", "in", "id", "name", "type", "value", "status", "code", "data", "info", "flag", "number", "num", "total", "ai", "system"]);
5560
+ var UNITS = { cents: 0.01, pence: 0.01, fils: 0.01, usd: null, dollars: null, aed: null, eur: null };
5561
+ var words = (name) => name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).map((w) => w.toLowerCase()).filter(Boolean);
5562
+ var flatProperties = (props, prefix = "") => Object.entries(props ?? {}).flatMap(([k, s]) => FORBIDDEN_KEYS.has(k) ? [] : s?.type === "object" && s.properties ? flatProperties(s.properties, `${prefix}${k}.`) : [{ path: `${prefix}${k}`, schema: s }]);
5563
+ function suggestToolBindings(artifact, tools, fields) {
5564
+ const bindings = { version: 1, artifact, reviewed: false, tools: {} };
5565
+ const proposals = [];
5566
+ const unbound = [];
5567
+ for (const tool of tools) {
5568
+ bindings.tools[tool.name] = { facts: {} };
5569
+ const args = flatProperties(tool.inputSchema?.properties);
5570
+ for (const field of fields) {
5571
+ if (field.name === "tool") continue;
5572
+ const want = words(field.name).filter((w) => !WEAK.has(w) && !(w in UNITS));
5573
+ const scored = args.map((a) => {
5574
+ const raw = words(a.path);
5575
+ const have = raw.filter((w) => !WEAK.has(w) && !(w in UNITS));
5576
+ const shared = want.filter((w) => have.includes(w));
5577
+ const unit = raw.find((w) => w in UNITS);
5578
+ const same = words(field.name).join("_") === raw.join("_");
5579
+ const sameCore = want.length > 0 && want.length === have.length && shared.length === want.length;
5580
+ return { a, shared, unit, same, sameCore, score: same ? 3 : sameCore ? 2 : shared.length ? shared.length / Math.max(want.length, have.length) : 0 };
5581
+ }).filter((s) => s.score > 0).sort((x, y) => y.score - x.score);
5582
+ if (!scored.length) {
5583
+ unbound.push({ tool: tool.name, fact: field.name, candidates: [] });
5584
+ continue;
5585
+ }
5586
+ const best = scored[0];
5587
+ if (scored[1] && scored[1].score === best.score) {
5588
+ unbound.push({ tool: tool.name, fact: field.name, candidates: scored.filter((s) => s.score === best.score).map((s) => s.a.path) });
5589
+ continue;
5590
+ }
5591
+ const scale = best.unit ? UNITS[best.unit] ?? void 0 : void 0;
5592
+ const why = best.same ? "the same name" : best.sameCore ? "the same words" : `shares "${best.shared.join(", ")}"`;
5593
+ proposals.push({ tool: tool.name, fact: field.name, arg: best.a.path, confidence: best.same || best.sameCore ? "exact" : "likely", why: scale ? `${why}; ${best.unit} scaled by ${scale}` : why, ...scale ? { scale } : {} });
5594
+ bindings.tools[tool.name].facts[field.name] = { arg: best.a.path, ...scale ? { scale } : {} };
5595
+ }
5596
+ }
5597
+ return { bindings, proposals, unbound };
5598
+ }
5599
+ function describeSuggestion(s) {
5600
+ const lines = [];
5601
+ for (const tool of Object.keys(s.bindings.tools)) {
5602
+ lines.push(`
5603
+ ${tool}`);
5604
+ for (const p of s.proposals.filter((x) => x.tool === tool)) lines.push(` ${p.confidence === "exact" ? "matched " : "CHECK "} ${p.fact} <- ${p.arg}${p.scale ? ` x ${p.scale}` : ""} (${p.why})`);
5605
+ for (const u of s.unbound.filter((x) => x.tool === tool)) lines.push(` NO MATCH ${u.fact}${u.candidates.length ? ` (could be: ${u.candidates.join(", ")})` : ""}`);
5606
+ }
5607
+ return lines.join("\n");
5608
+ }
5609
+
5610
+ // src/act/bytes.ts
5611
+ var utf82 = (s) => new TextEncoder().encode(s);
5612
+ var fromUtf82 = (b) => new TextDecoder().decode(b);
5613
+ function concat2(...parts) {
5614
+ const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
5615
+ let o = 0;
5616
+ for (const p of parts) {
5617
+ out.set(p, o);
5618
+ o += p.length;
5619
+ }
5620
+ return out;
5621
+ }
5622
+ function equal2(a, b) {
5623
+ if (a.length !== b.length) return false;
5624
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
5625
+ return true;
5626
+ }
5627
+ function fromHex(h) {
5628
+ const out = new Uint8Array(h.length / 2);
5629
+ for (let i = 0; i < out.length; i++) out[i] = parseInt(h.slice(i * 2, i * 2 + 2), 16);
5630
+ return out;
5631
+ }
5632
+ function toBase642(b) {
5633
+ let s = "";
5634
+ for (let i = 0; i < b.length; i += 32768) s += String.fromCharCode(...b.subarray(i, i + 32768));
5635
+ return btoa(s);
5636
+ }
5637
+ function fromBase642(s) {
5638
+ const bin = atob(s);
5639
+ const out = new Uint8Array(bin.length);
5640
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
5641
+ return out;
5642
+ }
5643
+ var toBase64url2 = (b) => toBase642(b).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
5644
+ var fromBase64url2 = (s) => {
5645
+ const t = s.replace(/-/g, "+").replace(/_/g, "/");
5646
+ return fromBase642(t + "=".repeat((4 - t.length % 4) % 4));
5647
+ };
5648
+ function randomBytes3(n) {
5649
+ return globalThis.crypto.getRandomValues(new Uint8Array(n));
5650
+ }
5651
+ var randomUUID = () => globalThis.crypto.randomUUID();
5652
+
5380
5653
  // src/act/signer.ts
5381
- var import_node_crypto = require("crypto");
5382
- var P256_N = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551n;
5383
5654
  function kidOf2(publicKeyPem) {
5384
- const der = (0, import_node_crypto.createPublicKey)(publicKeyPem).export({ type: "spki", format: "der" });
5385
- return (0, import_node_crypto.createHash)("sha256").update(der).digest("base64url").slice(0, 16);
5655
+ const kid = kidOf(publicKeyPem);
5656
+ if (!kid) throw new Error("kidOf: not a canonical Ed25519 or P-256 public key (NOMOS-SPEC-010 \xA73.1)");
5657
+ return kid;
5658
+ }
5659
+ var ED25519_SPKI = fromHex("302a300506032b6570032100");
5660
+ var P256_SPKI = fromHex("3059301306072a8648ce3d020106082a8648ce3d030107034200");
5661
+ var ED25519_PKCS8 = fromHex("302e020100300506032b657004220420");
5662
+ var P256_PKCS8_HEAD = fromHex("308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b0201010420");
5663
+ var P256_PKCS8_PUB = fromHex("a144034200");
5664
+ var OID_ED25519 = fromHex("2b6570");
5665
+ var OID_EC = fromHex("2a8648ce3d0201");
5666
+ var OID_P256 = fromHex("2a8648ce3d030107");
5667
+ function pem(label, der) {
5668
+ const b64 = toBase642(der);
5669
+ return `-----BEGIN ${label}-----
5670
+ ${b64.match(/.{1,64}/g).join("\n")}
5671
+ -----END ${label}-----
5672
+ `;
5673
+ }
5674
+ function pemBody(text) {
5675
+ const m = text.trim().match(/^-----BEGIN ([A-Z0-9 ]+)-----([\s\S]*?)-----END \1-----$/);
5676
+ if (!m) throw new Error("Signer: expected a PEM private key");
5677
+ return { label: m[1], der: fromBase642(m[2].replace(/\s+/g, "")) };
5678
+ }
5679
+ function tlv(b, at) {
5680
+ const tag = b[at];
5681
+ let len = b[at + 1], start = at + 2;
5682
+ if (len & 128) {
5683
+ const n = len & 127;
5684
+ if (n < 1 || n > 2) throw new Error("Signer: unsupported DER length");
5685
+ len = 0;
5686
+ for (let i = 0; i < n; i++) len = len << 8 | b[start + i];
5687
+ start += n;
5688
+ }
5689
+ if (start + len > b.length) throw new Error("Signer: truncated DER");
5690
+ return { tag, start, end: start + len };
5691
+ }
5692
+ function children(b, outer) {
5693
+ const out = [];
5694
+ for (let at = outer.start; at < outer.end; ) {
5695
+ const t = tlv(b, at);
5696
+ out.push(t);
5697
+ at = t.end;
5698
+ }
5699
+ return out;
5700
+ }
5701
+ function sec1Scalar(b, seq) {
5702
+ const [version, d, ...rest] = children(b, seq);
5703
+ if (version.tag !== 2 || d.tag !== 4 || d.end - d.start !== 32) throw new Error("Signer: malformed EC private key");
5704
+ for (const r of rest) {
5705
+ if (r.tag === 160) {
5706
+ const oid = tlv(b, r.start);
5707
+ if (!equal2(b.subarray(oid.start, oid.end), OID_P256)) throw new Error("Signer: only P-256 EC keys are a NOMOS suite (NOMOS-SPEC-010)");
5708
+ }
5709
+ }
5710
+ return b.slice(d.start, d.end);
5386
5711
  }
5387
- function fromKeyObject(privateKey) {
5388
- const publicKeyPem = (0, import_node_crypto.createPublicKey)(privateKey).export({ type: "spki", format: "pem" }).toString();
5389
- const isEd = privateKey.asymmetricKeyType === "ed25519";
5390
- if (!isEd && !(privateKey.asymmetricKeyType === "ec" && privateKey.asymmetricKeyDetails?.namedCurve === "prime256v1")) {
5391
- throw new Error("Signer: only Ed25519 and P-256 keys are NOMOS signature suites (NOMOS-SPEC-010)");
5712
+ function readPrivateKey(text) {
5713
+ const { label, der } = pemBody(text);
5714
+ const top = tlv(der, 0);
5715
+ if (label === "EC PRIVATE KEY") return { type: "ES256", key: sec1Scalar(der, top) };
5716
+ if (label !== "PRIVATE KEY") throw new Error(`Signer: unsupported key type "${label}" (PKCS#8 "PRIVATE KEY" or SEC1 "EC PRIVATE KEY")`);
5717
+ const [, alg, key] = children(der, top);
5718
+ const [oid, params] = children(der, alg);
5719
+ const algOid = der.subarray(oid.start, oid.end);
5720
+ if (equal2(algOid, OID_ED25519)) {
5721
+ const inner = tlv(der, key.start);
5722
+ if (inner.tag !== 4 || inner.end - inner.start !== 32) throw new Error("Signer: malformed Ed25519 private key");
5723
+ return { type: "Ed25519", key: der.slice(inner.start, inner.end) };
5392
5724
  }
5725
+ if (equal2(algOid, OID_EC)) {
5726
+ if (!params || !equal2(der.subarray(params.start, params.end), OID_P256)) throw new Error("Signer: only P-256 EC keys are a NOMOS suite (NOMOS-SPEC-010)");
5727
+ return { type: "ES256", key: sec1Scalar(der, tlv(der, key.start)) };
5728
+ }
5729
+ throw new Error("Signer: only Ed25519 and P-256 keys are NOMOS signature suites (NOMOS-SPEC-010)");
5730
+ }
5731
+ function publicPem(s) {
5732
+ return s.type === "Ed25519" ? pem("PUBLIC KEY", concat2(ED25519_SPKI, ed25519.getPublicKey(s.key))) : pem("PUBLIC KEY", concat2(P256_SPKI, p256.getPublicKey(s.key, false)));
5733
+ }
5734
+ function privatePem(s) {
5735
+ return s.type === "Ed25519" ? pem("PRIVATE KEY", concat2(ED25519_PKCS8, s.key)) : pem("PRIVATE KEY", concat2(P256_PKCS8_HEAD, s.key, P256_PKCS8_PUB, p256.getPublicKey(s.key, false)));
5736
+ }
5737
+ function fromSecret(s) {
5738
+ const publicKeyPem = publicPem(s);
5393
5739
  return {
5394
5740
  kid: kidOf2(publicKeyPem),
5395
5741
  publicKeyPem,
5396
- algorithm: isEd ? "Ed25519" : "ES256",
5742
+ algorithm: s.type,
5397
5743
  async sign(payload) {
5398
- if (isEd) return (0, import_node_crypto.sign)(null, Buffer.from(payload), privateKey).toString("base64");
5399
- const raw = (0, import_node_crypto.sign)("sha256", Buffer.from(payload), { key: privateKey, dsaEncoding: "ieee-p1363" });
5400
- let s = BigInt("0x" + raw.subarray(32).toString("hex"));
5401
- if (s > P256_N / 2n) s = P256_N - s;
5402
- return Buffer.concat([raw.subarray(0, 32), Buffer.from(s.toString(16).padStart(64, "0"), "hex")]).toString("base64");
5744
+ return toBase642(s.type === "Ed25519" ? ed25519.sign(payload, s.key) : p256.sign(payload, s.key, { prehash: true, lowS: true, format: "compact" }));
5403
5745
  }
5404
5746
  };
5405
5747
  }
5406
5748
  function localSigner(privateKeyPem) {
5407
- return fromKeyObject((0, import_node_crypto.createPrivateKey)(privateKeyPem));
5749
+ return fromSecret(readPrivateKey(privateKeyPem));
5408
5750
  }
5409
5751
  function generateSigner(algorithm = "Ed25519") {
5410
- const { privateKey } = algorithm === "Ed25519" ? (0, import_node_crypto.generateKeyPairSync)("ed25519") : (0, import_node_crypto.generateKeyPairSync)("ec", { namedCurve: "prime256v1" });
5411
- return { ...fromKeyObject(privateKey), privateKeyPem: privateKey.export({ type: "pkcs8", format: "pem" }).toString() };
5752
+ const s = algorithm === "Ed25519" ? { type: "Ed25519", key: ed25519.utils.randomSecretKey() } : { type: "ES256", key: p256.utils.randomSecretKey() };
5753
+ return { ...fromSecret(s), privateKeyPem: privatePem(s) };
5412
5754
  }
5413
5755
  function seededSigner(seed) {
5414
- const raw = (0, import_node_crypto.createHash)("sha256").update(seed).digest();
5415
- const der = Buffer.concat([Buffer.from("302e020100300506032b657004220420", "hex"), raw]);
5416
- return fromKeyObject((0, import_node_crypto.createPrivateKey)({ key: der, format: "der", type: "pkcs8" }));
5756
+ return fromSecret({ type: "Ed25519", key: sha2562(seed) });
5757
+ }
5758
+ var sha2562 = (s) => sha256(new TextEncoder().encode(s));
5759
+
5760
+ // src/act/certificates.ts
5761
+ var MAX_PRESENTED = 20;
5762
+ function reliedKids(act2) {
5763
+ const kids = [act2?.authority?.artifact?.seal?.kid, ...(act2?.testimony ?? []).map((t) => t?.kid), ...(act2?.consents ?? []).map((c) => c?.kid)];
5764
+ return [...new Set(kids.filter((k) => typeof k === "string" && k !== ""))];
5765
+ }
5766
+ function presentedCertificates(available, act2) {
5767
+ const unique = /* @__PURE__ */ new Map();
5768
+ for (const c of available) unique.set(fingerprint(c), c);
5769
+ const kids = new Set(reliedKids(act2)), chosen = /* @__PURE__ */ new Map();
5770
+ for (let grew = true; grew; ) {
5771
+ grew = false;
5772
+ for (const [fp, c] of unique) {
5773
+ if (!chosen.has(fp) && kids.has(c.child_kid)) {
5774
+ chosen.set(fp, c);
5775
+ kids.add(c.parent_kid);
5776
+ grew = true;
5777
+ }
5778
+ }
5779
+ }
5780
+ return [...chosen.values()];
5781
+ }
5782
+ async function signRevocation(parent, target, reason, issuedAt = /* @__PURE__ */ new Date()) {
5783
+ const st = "kid" in target ? { revoked_kid: target.kid, reason, issued_at: issuedAt.toISOString() } : { revoked_fingerprint: fingerprint(target.certificate), reason, issued_at: issuedAt.toISOString() };
5784
+ if ("certificate" in target && target.certificate.parent_kid !== parent.kid) throw new Error("signRevocation: only the parent that issued a certificate can revoke it");
5785
+ return { ...st, kid: parent.kid, algorithm: parent.algorithm, signature: await parent.sign(jcs(revocationPayload(st))) };
5786
+ }
5787
+ async function signRevocationList(signer, statements, keyCerts = [], generatedAt = /* @__PURE__ */ new Date()) {
5788
+ const L = { generated_at: generatedAt.toISOString(), kid: signer.kid, algorithm: signer.algorithm, statements, ...keyCerts.length ? { key_certs: keyCerts } : {} };
5789
+ L.signature = await signer.sign(jcs(revocationListPayload(L)));
5790
+ return L;
5791
+ }
5792
+ function revocationListCertificates(available, statements, listSignerKid) {
5793
+ const byFp = new Map(available.map((c) => [fingerprint(c), c]));
5794
+ const revokedCerts = statements.map((s) => s.revoked_fingerprint && byFp.get(s.revoked_fingerprint)).filter((c) => !!c);
5795
+ const kids = [listSignerKid, ...statements.flatMap((s) => [s.kid, s.revoked_kid].filter((k) => !!k)), ...revokedCerts.map((c) => c.parent_kid)];
5796
+ const closure = presentedCertificates(available, { authority: { artifact: { seal: {} } }, testimony: kids.map((kid) => ({ kid })) });
5797
+ const out = new Map(closure.map((c) => [fingerprint(c), c]));
5798
+ for (const c of revokedCerts) out.set(fingerprint(c), c);
5799
+ return [...out.values()];
5800
+ }
5801
+ var RevocationUnavailable = class extends Error {
5802
+ constructor(message, retryAfterSeconds) {
5803
+ super(message);
5804
+ this.retryAfterSeconds = retryAfterSeconds;
5805
+ this.name = "RevocationUnavailable";
5806
+ }
5807
+ };
5808
+ function revocationSource(o) {
5809
+ const f = o.fetch ?? fetch, now = o.now ?? (() => /* @__PURE__ */ new Date());
5810
+ const maxAge = (o.maxAgeSeconds ?? 3600) * 1e3, refresh = (o.refreshSeconds ?? 60) * 1e3;
5811
+ const kids = /* @__PURE__ */ new Set(), certs = /* @__PURE__ */ new Set();
5812
+ let asOf = 0, fetchedAt = 0, inFlight = null, lastError = "";
5813
+ async function load() {
5814
+ fetchedAt = now().getTime();
5815
+ try {
5816
+ const res = await f(o.url, { headers: { accept: "application/json" } });
5817
+ if (!res.ok) {
5818
+ lastError = `${o.url} answered ${res.status}`;
5819
+ return;
5820
+ }
5821
+ const r = verifyRevocationList(await res.json(), { rootPublicKeyPem: o.root, now: now(), ownCerts: o.ownCerts });
5822
+ if (r.decision !== "OK") {
5823
+ lastError = `${o.url} served a malformed list (${r.reason_code})`;
5824
+ return;
5825
+ }
5826
+ for (const k of r.revoked_kids) kids.add(k);
5827
+ for (const c of r.revoked_certs) certs.add(c);
5828
+ if (r.as_of) asOf = Math.max(asOf, Date.parse(r.as_of));
5829
+ else lastError = `${o.url}'s list is not signed by a key your root reaches, so it does not show it is current`;
5830
+ } catch (e) {
5831
+ lastError = `${o.url}: ${e?.message ?? e}`;
5832
+ }
5833
+ }
5834
+ return async () => {
5835
+ const t = now().getTime();
5836
+ if (!inFlight && t - fetchedAt >= refresh) inFlight = load().finally(() => {
5837
+ inFlight = null;
5838
+ });
5839
+ if (inFlight) await inFlight;
5840
+ if (!asOf || t - asOf > maxAge) {
5841
+ throw new RevocationUnavailable(`No current revocation list${asOf ? `: the newest is from ${new Date(asOf).toISOString()}` : ""}${lastError ? ` (${lastError})` : ""}. Not committing until revocations can be checked.`, Math.ceil(refresh / 1e3));
5842
+ }
5843
+ return { kids: new Set(kids), certs: new Set(certs) };
5844
+ };
5417
5845
  }
5418
5846
 
5419
5847
  // src/act/documents.ts
@@ -5518,6 +5946,11 @@ var Rollback = class extends Error {
5518
5946
  this.answer = answer2;
5519
5947
  }
5520
5948
  };
5949
+ var LostRace = class extends Error {
5950
+ constructor() {
5951
+ super("replayed");
5952
+ }
5953
+ };
5521
5954
  function accept(o) {
5522
5955
  const kid = kidOf2(o.root);
5523
5956
  for (const a of o.authorities) {
@@ -5525,7 +5958,7 @@ function accept(o) {
5525
5958
  throw new Error(`accept(): authority ${a.artifactId} uses currencyPolicy "record" but has no inForceSealHash`);
5526
5959
  }
5527
5960
  }
5528
- if (!o.store) console.warn("[nomos] accept(): using the in-memory store. Nonces and receipts are lost on restart. Development only.");
5961
+ if (!o.store) console.warn("[nomos] accept(): using the in-memory store. Nonces and receipts are lost on restart. Development only; use postgresStore() in production.");
5529
5962
  const store = o.store ?? memoryStore();
5530
5963
  const maxWindowSeconds = o.maxWindowSeconds ?? 900;
5531
5964
  const maxActBytes = o.maxActBytes ?? 1024 * 1024;
@@ -5551,8 +5984,7 @@ function accept(o) {
5551
5984
  valid_until: new Date(now().getTime() + (o.documentTtlSeconds ?? 86400) * 1e3).toISOString()
5552
5985
  };
5553
5986
  }
5554
- async function verifyWith(act2, authority, facts, at, seen) {
5555
- const rev = await o.revocations?.() ?? {};
5987
+ function verifyWith(act2, authority, facts, at, seen, rev) {
5556
5988
  return verifyAct(act2, {
5557
5989
  relyingPartyId: o.relyingPartyId,
5558
5990
  rootPublicKeyPem: o.root,
@@ -5584,9 +6016,20 @@ function accept(o) {
5584
6016
  const authority = o.authorities.find((a) => a.artifactId === artifactId);
5585
6017
  if (!authority) return answer({ decision: "NOT_ACCEPTED", reason_code: "authority_not_accepted", artifact_id: artifactId ?? null });
5586
6018
  if (!authority.actions.includes(act2?.action?.type)) return answer({ decision: "NOT_ACCEPTED", reason_code: "action_not_accepted", action_type: act2?.action?.type ?? null });
6019
+ let rev;
6020
+ try {
6021
+ rev = await o.revocations?.() ?? {};
6022
+ } catch (e) {
6023
+ const retry = e instanceof RevocationUnavailable ? e.retryAfterSeconds : 60;
6024
+ return {
6025
+ status: 503,
6026
+ headers: { "content-type": "application/json", "retry-after": String(retry) },
6027
+ body: { error: "Revocation status is not current, so nothing is committed. Resubmit this same Act later.", detail: String(e?.message ?? e), retry_after: retry }
6028
+ };
6029
+ }
5587
6030
  const verifiedAt = now();
5588
6031
  const facts = await o.localFacts?.(act2) ?? {};
5589
- const verdict = await verifyWith(act2, authority, facts, verifiedAt, await store.hasNonce(act2.nonce));
6032
+ const verdict = verifyWith(act2, authority, facts, verifiedAt, await store.hasNonce(act2.nonce), rev);
5590
6033
  const receiptOf = (v, f, committedAt) => ({
5591
6034
  act_id: act2.act_id,
5592
6035
  act_digest: String(v.act_digest ?? ""),
@@ -5606,35 +6049,34 @@ function accept(o) {
5606
6049
  if (verdict.decision !== "AUTHORIZED" || o.mode === "shadow") {
5607
6050
  await o.onVerdict?.(verdict, receiptOf(verdict, facts));
5608
6051
  if (verdict.decision === "ESCALATED") {
5609
- const esc2 = (act2.authority.artifact.governance?.escalations ?? []).find((e) => e?.id === verdict.escalation_id);
5610
- return answer(verdict, { role: esc2?.role_required ?? null, retry_until: act2.expires_at });
6052
+ const esc3 = (act2.authority.artifact.governance?.escalations ?? []).find((e) => e?.id === verdict.escalation_id);
6053
+ return answer(verdict, { role: esc3?.role_required ?? null, retry_until: act2.expires_at });
5611
6054
  }
5612
6055
  if (o.mode === "shadow") return { ...answer(verdict), status: 200, body: { ...answer(verdict).body, shadow: true } };
5613
6056
  return answer(verdict);
5614
6057
  }
5615
6058
  try {
5616
6059
  return await store.transaction(async (t) => {
5617
- if (!await t.claimNonce(act2.nonce)) {
5618
- throw new Rollback(answer({ decision: "REPLAYED" }, { act_id: act2.act_id, receipt: await store.receiptFor(act2.nonce) }));
5619
- }
6060
+ if (!await t.claimNonce(act2.nonce)) throw new LostRace();
5620
6061
  let final = verdict, finalFacts = facts;
5621
6062
  if (o.localFacts) {
5622
6063
  const fresh = await o.localFacts(act2, t.tx);
5623
6064
  if (!sameFacts(fresh, facts)) {
5624
6065
  finalFacts = fresh;
5625
- final = await verifyWith(act2, authority, fresh, verifiedAt, false);
6066
+ final = verifyWith(act2, authority, fresh, verifiedAt, false, rev);
5626
6067
  if (final.decision !== "AUTHORIZED") throw new Rollback(answer(final));
5627
6068
  }
5628
6069
  }
5629
6070
  const action = structuredClone({ type: act2.action.type, ...act2.action.subject !== void 0 ? { subject: act2.action.subject } : {}, params: act2.action.params ?? {} });
5630
6071
  const result = await o.commit(action, { tx: t.tx, actId: act2.act_id });
5631
6072
  const receipt = receiptOf(final, finalFacts, now());
5632
- await t.putReceipt(act2.nonce, receipt);
6073
+ await t.putReceipt(act2.nonce, receipt, act2);
5633
6074
  await o.onVerdict?.(final, receipt);
5634
6075
  return answer(final, { receipt, ...result !== void 0 ? { result } : {} });
5635
6076
  });
5636
6077
  } catch (e) {
5637
6078
  if (e instanceof Rollback) return e.answer;
6079
+ if (e instanceof LostRace) return answer({ decision: "REPLAYED" }, { act_id: act2.act_id, receipt: await store.receiptFor(act2.nonce) });
5638
6080
  return { status: 500, headers: { "content-type": "application/json" }, body: { error: "The commit failed and was rolled back; nothing was applied.", detail: String(e?.message ?? e) } };
5639
6081
  }
5640
6082
  }
@@ -5673,7 +6115,7 @@ function accept(o) {
5673
6115
  if (size > maxActBytes) break;
5674
6116
  chunks.push(c);
5675
6117
  }
5676
- body = size > maxActBytes ? new Uint8Array(maxActBytes + 1) : Buffer.concat(chunks);
6118
+ body = size > maxActBytes ? new Uint8Array(maxActBytes + 1) : concat2(...chunks);
5677
6119
  }
5678
6120
  const a = await handle({ method: req.method, headers: req.headers, body });
5679
6121
  send(a.status, a.headers, JSON.stringify(a.body));
@@ -5684,7 +6126,7 @@ function accept(o) {
5684
6126
  function consentOrigins(act2) {
5685
6127
  const origins = (Array.isArray(act2?.consents) ? act2.consents : []).filter((c) => c?.algorithm === "WebAuthn-ES256" && typeof c.client_data_json === "string").map((c) => {
5686
6128
  try {
5687
- return JSON.parse(Buffer.from(c.client_data_json, "base64url").toString("utf8")).origin;
6129
+ return JSON.parse(fromUtf82(fromBase64url2(c.client_data_json))).origin;
5688
6130
  } catch {
5689
6131
  return null;
5690
6132
  }
@@ -5692,8 +6134,74 @@ function consentOrigins(act2) {
5692
6134
  return origins.length ? { consent_origins: [...new Set(origins)] } : {};
5693
6135
  }
5694
6136
 
6137
+ // src/act/postgres.ts
6138
+ var IDENT = /^[a-z_][a-z0-9_]{0,62}(\.[a-z_][a-z0-9_]{0,62})?$/;
6139
+ function postgresStore(pool, opts = {}) {
6140
+ const table = opts.table ?? "nomos_acts";
6141
+ if (!IDENT.test(table)) throw new Error(`postgresStore: "${table}" is not a plain lower-case table name`);
6142
+ const schema = `CREATE TABLE IF NOT EXISTS ${table} (
6143
+ nonce text PRIMARY KEY, -- SPEC-008 \xA75.2: the nonce ledger
6144
+ act_id text NOT NULL,
6145
+ receipt json NOT NULL, -- SPEC-008 \xA79; json, not jsonb: kept byte for byte
6146
+ act json NOT NULL, -- the exact Act verified, for re-verification
6147
+ committed_at timestamptz NOT NULL DEFAULT now()
6148
+ );`;
6149
+ return {
6150
+ schema,
6151
+ async migrate() {
6152
+ await pool.query(schema);
6153
+ },
6154
+ async hasNonce(nonce) {
6155
+ const r = await pool.query(`SELECT 1 FROM ${table} WHERE nonce = $1`, [nonce]);
6156
+ return (r.rowCount ?? r.rows.length) > 0;
6157
+ },
6158
+ async receiptFor(nonce) {
6159
+ const r = await pool.query(`SELECT receipt FROM ${table} WHERE nonce = $1`, [nonce]);
6160
+ return r.rows[0]?.receipt ?? null;
6161
+ },
6162
+ async transaction(fn) {
6163
+ const client = await pool.connect();
6164
+ let claimed = null, completed = false;
6165
+ try {
6166
+ await client.query("BEGIN");
6167
+ const out = await fn({
6168
+ tx: client,
6169
+ async claimNonce(nonce) {
6170
+ const r = await client.query(
6171
+ `INSERT INTO ${table} (nonce, act_id, receipt, act) VALUES ($1, '', '{}'::json, '{}'::json)
6172
+ ON CONFLICT (nonce) DO NOTHING RETURNING nonce`,
6173
+ [nonce]
6174
+ );
6175
+ const won = (r.rowCount ?? r.rows.length) === 1;
6176
+ if (won) claimed = nonce;
6177
+ return won;
6178
+ },
6179
+ async putReceipt(nonce, receipt, act2) {
6180
+ if (nonce !== claimed) throw new Error("postgresStore: putReceipt for a nonce this transaction did not claim");
6181
+ await client.query(
6182
+ `UPDATE ${table} SET act_id = $2, receipt = $3::json, act = $4::json WHERE nonce = $1`,
6183
+ [nonce, receipt.act_id, JSON.stringify(receipt), JSON.stringify(act2 ?? {})]
6184
+ );
6185
+ completed = true;
6186
+ }
6187
+ });
6188
+ if (claimed && !completed) throw new Error("postgresStore: the transaction claimed a nonce but stored no receipt");
6189
+ await client.query("COMMIT");
6190
+ client.release();
6191
+ return out;
6192
+ } catch (e) {
6193
+ let broken;
6194
+ await client.query("ROLLBACK").catch((err) => {
6195
+ broken = err;
6196
+ });
6197
+ client.release(broken);
6198
+ throw e;
6199
+ }
6200
+ }
6201
+ };
6202
+ }
6203
+
5695
6204
  // src/act/statements.ts
5696
- var import_node_crypto2 = require("crypto");
5697
6205
  async function certify(parent, child, opts) {
5698
6206
  const unsigned = {
5699
6207
  parent_kid: parent.kid,
@@ -5747,8 +6255,8 @@ function buildAct(o) {
5747
6255
  const window = o.windowSeconds ?? 300;
5748
6256
  const act2 = {
5749
6257
  act_version: "1",
5750
- act_id: `act-${(0, import_node_crypto2.randomUUID)()}`,
5751
- nonce: (0, import_node_crypto2.randomBytes)(16).toString("base64"),
6258
+ act_id: `act-${randomUUID()}`,
6259
+ nonce: toBase642(randomBytes3(16)),
5752
6260
  // Issued a second in the past so a relying party's clock a moment behind ours still accepts it.
5753
6261
  issued_at: new Date(now.getTime() - 1e3).toISOString(),
5754
6262
  expires_at: new Date(now.getTime() - 1e3 + window * 1e3).toISOString(),
@@ -5762,6 +6270,575 @@ function buildAct(o) {
5762
6270
  return act2;
5763
6271
  }
5764
6272
 
6273
+ // src/act/registry.ts
6274
+ function memoryRegistryStore() {
6275
+ const certs = [], sts = [];
6276
+ return {
6277
+ async certificates() {
6278
+ return [...certs];
6279
+ },
6280
+ async addCertificate(c) {
6281
+ if (!certs.some((x) => fingerprint(x) === fingerprint(c))) certs.push(c);
6282
+ },
6283
+ async statements() {
6284
+ return [...sts];
6285
+ },
6286
+ async addStatement(s) {
6287
+ sts.push(s);
6288
+ }
6289
+ };
6290
+ }
6291
+ var RegistryError = class extends Error {
6292
+ constructor(message, code) {
6293
+ super(message);
6294
+ this.code = code;
6295
+ this.name = "RegistryError";
6296
+ }
6297
+ };
6298
+ var any = () => ({ ok: true });
6299
+ function certificateRegistry(o) {
6300
+ const rootKid = kidOf(o.root);
6301
+ if (!rootKid) throw new RegistryError("certificateRegistry: the root is not a canonical Ed25519 or P-256 public key", "bad_root");
6302
+ const now = o.now ?? (() => /* @__PURE__ */ new Date());
6303
+ async function revokedSets() {
6304
+ const kids = /* @__PURE__ */ new Set(), certs = /* @__PURE__ */ new Set();
6305
+ for (const s of await o.store.statements()) s.revoked_kid ? kids.add(s.revoked_kid) : s.revoked_fingerprint && certs.add(s.revoked_fingerprint);
6306
+ return { kids, certs };
6307
+ }
6308
+ async function scopesOf(kid) {
6309
+ if (kid === rootKid) return { ok: true, scopes: [{ terms: {} }] };
6310
+ const { kids, certs } = await revokedSets();
6311
+ const r = resolveKey({ targetKid: kid, pool: await o.store.certificates(), rootPublicKeyPem: o.root, purpose: any, now: now(), revokedKids: kids, revokedCerts: certs, allPaths: true });
6312
+ if (r.decision !== "RESOLVED") return { ok: false, reason: `${r.decision}${r.reason_code ? ` (${r.reason_code})` : ""}` };
6313
+ return { ok: true, scopes: r.admitted ?? [r.effective] };
6314
+ }
6315
+ async function pool(filter) {
6316
+ const { kids, certs } = await revokedSets(), t = now().getTime();
6317
+ return (await o.store.certificates()).filter((c) => !kids.has(c.child_kid) && !certs.has(fingerprint(c)) && Date.parse(c.expires_at) > t && (!filter || filter(c)));
6318
+ }
6319
+ return {
6320
+ rootKid,
6321
+ pool,
6322
+ /** NOMOS-SPEC-007 §3: certify `child` under `parent`. Refuses anything a verifier would refuse. */
6323
+ async issue(io) {
6324
+ if (!kidOf(io.child.publicKeyPem)) throw new RegistryError("The child key is not a canonical Ed25519 or P-256 key (NOMOS-SPEC-010 \xA73.1)", "invalid_key");
6325
+ const scope = parseScope(io.scope ?? null);
6326
+ if (!scope.ok) throw new RegistryError(`Malformed scope: ${scope.detail}`, "malformed_scope");
6327
+ const parentScopes = await scopesOf(io.parent.kid);
6328
+ if (!parentScopes.ok) throw new RegistryError(`The parent ${io.parent.kid} is not reached from the root: ${parentScopes.reason}`, "parent_not_recognized");
6329
+ const fits = parentScopes.scopes.filter((p) => suitePermitted(p, io.parent.algorithm) && isNarrowerOrEqual(scope.scope, p).ok);
6330
+ if (!fits.length) {
6331
+ const widened = parentScopes.scopes.map((p) => isNarrowerOrEqual(scope.scope, p)).find((x) => !x.ok);
6332
+ throw new RegistryError(widened ? `The scope widens the parent's: ${widened.detail}` : `The parent may not sign under ${io.parent.algorithm}`, widened ? "widening" : "suite_not_permitted");
6333
+ }
6334
+ const expiresAt = io.expiresAt ?? (io.days ? new Date(now().getTime() + io.days * 864e5) : void 0);
6335
+ if (!expiresAt) throw new RegistryError("Give expiresAt or days: a certificate always expires", "no_expiry");
6336
+ const c = await certify(io.parent, io.child, { scope: io.scope, issuedAt: io.issuedAt ?? now(), expiresAt });
6337
+ await o.store.addCertificate(c);
6338
+ return c;
6339
+ },
6340
+ /** rev. 1.10 §5.1: `parent` revokes a key it certified. The key is dead on every path. */
6341
+ async revokeKey(io) {
6342
+ const certs = (await o.store.certificates()).filter((c) => c.child_kid === io.kid && c.parent_kid === io.parent.kid);
6343
+ if (!certs.length) throw new RegistryError(`${io.parent.kid} never certified ${io.kid}: only a key's parent can revoke it`, "not_the_parent");
6344
+ const st = await signRevocation(io.parent, { kid: io.kid }, io.reason, now());
6345
+ await o.store.addStatement(st);
6346
+ return st;
6347
+ },
6348
+ /** rev. 1.10 §5.1: withdraw one certificate; the key stays valid under any other. */
6349
+ async revokeCertificate(io) {
6350
+ const c = (await o.store.certificates()).find((x) => fingerprint(x) === io.fingerprint);
6351
+ if (!c) throw new RegistryError(`No certificate ${io.fingerprint} in this registry`, "unknown_certificate");
6352
+ const st = await signRevocation(io.parent, { certificate: c }, io.reason, now());
6353
+ await o.store.addStatement(st);
6354
+ return st;
6355
+ },
6356
+ /** The certificates that connect `kid` to the root: what a witness or approver returns with
6357
+ * what it signs (SPEC-009 0.2.0 §4, §5). */
6358
+ async chainFor(kid) {
6359
+ return presentedCertificates(await pool(), { authority: { artifact: { seal: { kid } } } });
6360
+ },
6361
+ /** How `kid` is reached from the root, and with which effective scopes, or why it is not. */
6362
+ async check(kid) {
6363
+ return scopesOf(kid);
6364
+ },
6365
+ certificates: () => o.store.certificates(),
6366
+ statements: () => o.store.statements(),
6367
+ /** rev. 1.10 §5.3: the signed list, carrying the certificates that make it checkable. The
6368
+ * signer should be reached from the root, or the list establishes no date (§5.3). */
6369
+ async revocationList(signer) {
6370
+ const statements = await o.store.statements();
6371
+ return signRevocationList(signer, statements, revocationListCertificates(await o.store.certificates(), statements, signer.kid), now());
6372
+ }
6373
+ };
6374
+ }
6375
+ function postgresRegistryStore(pool, opts = {}) {
6376
+ const p = opts.prefix ?? "nomos_registry";
6377
+ if (!/^[a-z_][a-z0-9_]{0,40}$/.test(p)) throw new RegistryError(`"${p}" is not a plain lower-case prefix`, "bad_prefix");
6378
+ const schema = `CREATE TABLE IF NOT EXISTS ${p}_certificates (
6379
+ fingerprint text PRIMARY KEY, child_kid text NOT NULL, parent_kid text NOT NULL, certificate json NOT NULL, added_at timestamptz NOT NULL DEFAULT now()
6380
+ );
6381
+ CREATE TABLE IF NOT EXISTS ${p}_statements (
6382
+ id bigserial PRIMARY KEY, target text NOT NULL, kid text NOT NULL, statement json NOT NULL, added_at timestamptz NOT NULL DEFAULT now()
6383
+ );`;
6384
+ return {
6385
+ schema,
6386
+ async migrate() {
6387
+ await pool.query(schema);
6388
+ },
6389
+ async certificates() {
6390
+ return (await pool.query(`SELECT certificate FROM ${p}_certificates ORDER BY added_at, fingerprint`)).rows.map((r) => r.certificate);
6391
+ },
6392
+ async addCertificate(c) {
6393
+ await pool.query(
6394
+ `INSERT INTO ${p}_certificates (fingerprint, child_kid, parent_kid, certificate) VALUES ($1, $2, $3, $4::json) ON CONFLICT (fingerprint) DO NOTHING`,
6395
+ [fingerprint(c), c.child_kid, c.parent_kid, JSON.stringify(c)]
6396
+ );
6397
+ },
6398
+ async statements() {
6399
+ return (await pool.query(`SELECT statement FROM ${p}_statements ORDER BY id`)).rows.map((r) => r.statement);
6400
+ },
6401
+ async addStatement(s) {
6402
+ await pool.query(`INSERT INTO ${p}_statements (target, kid, statement) VALUES ($1, $2, $3::json)`, [s.revoked_kid ?? s.revoked_fingerprint, s.kid, JSON.stringify(s)]);
6403
+ }
6404
+ };
6405
+ }
6406
+
6407
+ // src/act/passkeys.ts
6408
+ function cbor(b) {
6409
+ let i = 0;
6410
+ const len = (info) => {
6411
+ if (info < 24) return info;
6412
+ const n = info === 24 ? 1 : info === 25 ? 2 : info === 26 ? 4 : -1;
6413
+ if (n < 0) throw new Error("unsupported CBOR length");
6414
+ let v2 = 0;
6415
+ for (let k = 0; k < n; k++) v2 = v2 * 256 + b[i++];
6416
+ return v2;
6417
+ };
6418
+ const item = (depth) => {
6419
+ if (depth > 16 || i >= b.length) throw new Error("malformed CBOR");
6420
+ const head = b[i++], major = head >> 5, info = head & 31;
6421
+ switch (major) {
6422
+ case 0:
6423
+ return len(info);
6424
+ case 1:
6425
+ return -1 - len(info);
6426
+ case 2: {
6427
+ const n = len(info);
6428
+ const v2 = b.slice(i, i + n);
6429
+ i += n;
6430
+ if (v2.length !== n) throw new Error("truncated CBOR");
6431
+ return v2;
6432
+ }
6433
+ case 3: {
6434
+ const n = len(info);
6435
+ const v2 = fromUtf82(b.slice(i, i + n));
6436
+ i += n;
6437
+ return v2;
6438
+ }
6439
+ case 4: {
6440
+ const n = len(info), a = [];
6441
+ for (let k = 0; k < n; k++) a.push(item(depth + 1));
6442
+ return a;
6443
+ }
6444
+ case 5: {
6445
+ const n = len(info), m = /* @__PURE__ */ new Map();
6446
+ for (let k = 0; k < n; k++) m.set(item(depth + 1), item(depth + 1));
6447
+ return m;
6448
+ }
6449
+ case 7:
6450
+ if (info === 20) return false;
6451
+ if (info === 21) return true;
6452
+ if (info === 22) return null;
6453
+ throw new Error("unsupported CBOR simple value");
6454
+ default:
6455
+ throw new Error("unsupported CBOR type");
6456
+ }
6457
+ };
6458
+ const v = item(0);
6459
+ return { value: v, end: i };
6460
+ }
6461
+ function readAttestation(attestationObject) {
6462
+ const att = cbor(attestationObject).value;
6463
+ if (!(att instanceof Map) || !(att.get("authData") instanceof Uint8Array)) throw new Error("not an attestation object");
6464
+ const a = att.get("authData");
6465
+ if (a.length < 55 || !(a[32] & 64)) throw new Error("no attested credential data");
6466
+ const idLen = a[53] << 8 | a[54];
6467
+ const credentialId = a.slice(55, 55 + idLen);
6468
+ const cose = cbor(a.slice(55 + idLen)).value;
6469
+ if (!(cose instanceof Map) || cose.get(1) !== 2 || cose.get(3) !== -7 || cose.get(-1) !== 1) throw new Error("the passkey is not an ES256 P-256 key (COSE kty 2, alg -7, crv 1)");
6470
+ const x = cose.get(-2), y = cose.get(-3);
6471
+ if (!(x instanceof Uint8Array) || !(y instanceof Uint8Array) || x.length !== 32 || y.length !== 32) throw new Error("malformed COSE key");
6472
+ const spki = concat2(Uint8Array.from([48, 89, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134, 72, 206, 61, 3, 1, 7, 3, 66, 0, 4]), x, y);
6473
+ const publicKeyPem = pem("PUBLIC KEY", spki);
6474
+ if (!kidOf(publicKeyPem)) throw new Error("the passkey's key is not a valid P-256 point");
6475
+ return { authData: a, flags: a[32], credentialId, publicKeyPem };
6476
+ }
6477
+ async function signEnrolmentToken(authority, c) {
6478
+ if (!/^[^,\s]+$/.test(c.approver)) throw new Error("An approver id has no commas or spaces (NOMOS-SPEC-007 rev. 1.9 \xA73.7)");
6479
+ const claims = { approver: c.approver, roles: [...c.roles], origin: c.origin.replace(/\/$/, ""), expires_at: c.expiresAt.toISOString(), nonce: toBase64url2(randomBytes3(16)) };
6480
+ const token = { ...claims, kid: authority.kid, algorithm: authority.algorithm, signature: await authority.sign(jcs(claims)) };
6481
+ return toBase64url2(utf82(JSON.stringify(token)));
6482
+ }
6483
+ function verifyEnrolmentToken(token, authorityPem, origin, now) {
6484
+ let t;
6485
+ try {
6486
+ t = JSON.parse(fromUtf82(fromBase64url2(token)));
6487
+ } catch {
6488
+ return { error: "not an enrolment link" };
6489
+ }
6490
+ const claims = { approver: t?.approver, roles: t?.roles, origin: t?.origin, expires_at: t?.expires_at, nonce: t?.nonce };
6491
+ if (typeof claims.approver !== "string" || !Array.isArray(claims.roles) || !claims.roles.every((r) => typeof r === "string") || typeof claims.nonce !== "string") return { error: "malformed enrolment link" };
6492
+ if (t.kid !== kidOf(authorityPem) || !verifySuite(t.algorithm, authorityPem, jcs(claims), { signature: t.signature }).ok) return { error: "this enrolment link was not signed by the enrolment authority" };
6493
+ if (claims.origin !== origin) return { error: "this enrolment link is for another service" };
6494
+ if (!(Date.parse(claims.expires_at) > now.getTime())) return { error: "this enrolment link has expired" };
6495
+ return claims;
6496
+ }
6497
+ function memoryPasskeyStore() {
6498
+ const used = /* @__PURE__ */ new Set(), challenges = /* @__PURE__ */ new Map(), creds = [], pend = /* @__PURE__ */ new Map();
6499
+ return {
6500
+ async claimToken(n) {
6501
+ if (used.has(n)) return false;
6502
+ used.add(n);
6503
+ return true;
6504
+ },
6505
+ async tokenUsed(n) {
6506
+ return used.has(n);
6507
+ },
6508
+ async saveEnrolmentChallenge(n, c) {
6509
+ if (!used.has(n)) challenges.set(n, c);
6510
+ },
6511
+ async enrolmentChallenge(n) {
6512
+ return used.has(n) ? null : challenges.get(n) ?? null;
6513
+ },
6514
+ async addCredential(c) {
6515
+ creds.push(structuredClone(c));
6516
+ },
6517
+ async credentials() {
6518
+ return structuredClone(creds);
6519
+ },
6520
+ async createPending(p) {
6521
+ pend.set(p.id, structuredClone(p));
6522
+ },
6523
+ async pending(id) {
6524
+ const p = pend.get(id);
6525
+ return p ? structuredClone(p) : null;
6526
+ },
6527
+ async addAttempt(id, c, a) {
6528
+ const p = pend.get(id);
6529
+ if (!p || p.status !== "pending") return false;
6530
+ p.attempts[c] = { ...a };
6531
+ return true;
6532
+ },
6533
+ async resolvePending(id, out) {
6534
+ const p = pend.get(id);
6535
+ if (!p || p.status !== "pending") return false;
6536
+ Object.assign(p, structuredClone(out));
6537
+ return true;
6538
+ }
6539
+ };
6540
+ }
6541
+ function postgresPasskeyStore(pool, opts = {}) {
6542
+ const p = opts.prefix ?? "nomos_passkeys";
6543
+ if (!/^[a-z_][a-z0-9_]{0,40}$/.test(p)) throw new Error(`"${p}" is not a plain lower-case prefix`);
6544
+ const schema = `CREATE TABLE IF NOT EXISTS ${p}_tokens (nonce text PRIMARY KEY, challenge text, used boolean NOT NULL DEFAULT false);
6545
+ CREATE TABLE IF NOT EXISTS ${p}_credentials (credential_id text PRIMARY KEY, credential json NOT NULL, added_at timestamptz NOT NULL DEFAULT now());
6546
+ CREATE TABLE IF NOT EXISTS ${p}_pending (id text PRIMARY KEY, approval jsonb NOT NULL, updated_at timestamptz NOT NULL DEFAULT now());`;
6547
+ return {
6548
+ schema,
6549
+ async migrate() {
6550
+ await pool.query(schema);
6551
+ },
6552
+ async claimToken(n) {
6553
+ const r = await pool.query(`INSERT INTO ${p}_tokens (nonce, used) VALUES ($1, true) ON CONFLICT (nonce) DO UPDATE SET used = true WHERE ${p}_tokens.used = false RETURNING nonce`, [n]);
6554
+ return (r.rowCount ?? r.rows.length) === 1;
6555
+ },
6556
+ async tokenUsed(n) {
6557
+ return (await pool.query(`SELECT 1 FROM ${p}_tokens WHERE nonce = $1 AND used = true`, [n])).rows.length > 0;
6558
+ },
6559
+ async saveEnrolmentChallenge(n, c) {
6560
+ await pool.query(`INSERT INTO ${p}_tokens (nonce, challenge) VALUES ($1, $2) ON CONFLICT (nonce) DO UPDATE SET challenge = $2 WHERE ${p}_tokens.used = false`, [n, c]);
6561
+ },
6562
+ async enrolmentChallenge(n) {
6563
+ return (await pool.query(`SELECT challenge FROM ${p}_tokens WHERE nonce = $1 AND used = false`, [n])).rows[0]?.challenge ?? null;
6564
+ },
6565
+ async addCredential(c) {
6566
+ await pool.query(`INSERT INTO ${p}_credentials (credential_id, credential) VALUES ($1, $2::json)`, [c.credential_id, JSON.stringify(c)]);
6567
+ },
6568
+ async credentials() {
6569
+ return (await pool.query(`SELECT credential FROM ${p}_credentials ORDER BY added_at`)).rows.map((r) => r.credential);
6570
+ },
6571
+ async createPending(a) {
6572
+ await pool.query(`INSERT INTO ${p}_pending (id, approval) VALUES ($1, $2::jsonb)`, [a.id, JSON.stringify(a)]);
6573
+ },
6574
+ async pending(id) {
6575
+ return (await pool.query(`SELECT approval FROM ${p}_pending WHERE id = $1`, [id])).rows[0]?.approval ?? null;
6576
+ },
6577
+ // Each change is one conditional statement: Postgres serializes them on the row, and none
6578
+ // rewrites the whole record, so a stale reader cannot write an old status back.
6579
+ async addAttempt(id, c, a) {
6580
+ const r = await pool.query(`UPDATE ${p}_pending SET approval = jsonb_set(approval, ARRAY['attempts', $2::text], $3::jsonb), updated_at = now() WHERE id = $1 AND approval->>'status' = 'pending' RETURNING id`, [id, c, JSON.stringify(a)]);
6581
+ return (r.rowCount ?? r.rows.length) === 1;
6582
+ },
6583
+ async resolvePending(id, out) {
6584
+ const r = await pool.query(`UPDATE ${p}_pending SET approval = approval || $2::jsonb, updated_at = now() WHERE id = $1 AND approval->>'status' = 'pending' RETURNING id`, [id, JSON.stringify(out)]);
6585
+ return (r.rowCount ?? r.rows.length) === 1;
6586
+ }
6587
+ };
6588
+ }
6589
+ var MAX_ATTEMPTS = 50;
6590
+ var esc = (s) => String(s).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
6591
+ function passkeyApprover(o) {
6592
+ const base = o.origin.replace(/\/$/, "");
6593
+ const rpId = o.rpId ?? new URL(base).hostname;
6594
+ const store = o.store ?? memoryPasskeyStore();
6595
+ if (!o.store) console.warn("[nomos] passkeyApprover(): using the in-memory store. Enrolled passkeys are lost on restart. Development only; use postgresPasskeyStore() in production.");
6596
+ const now = o.now ?? (() => /* @__PURE__ */ new Date());
6597
+ const rpIdHash = sha256(utf82(rpId));
6598
+ const expired = (p) => p.status === "pending" && Date.parse(p.act.expires_at) <= now().getTime();
6599
+ const statusOf = (p) => expired(p) ? "expired" : p.status;
6600
+ function clientData(b64, type, challenge) {
6601
+ let c;
6602
+ try {
6603
+ c = JSON.parse(fromUtf82(fromBase64url2(b64)));
6604
+ } catch {
6605
+ return "unreadable client data";
6606
+ }
6607
+ if (c?.type !== type) return `expected a ${type} ceremony`;
6608
+ if (c?.challenge !== challenge) return "the challenge does not match";
6609
+ if (c?.origin !== base) return `the ceremony ran on ${c?.origin}, not ${base}`;
6610
+ return null;
6611
+ }
6612
+ async function route(method, path, json) {
6613
+ if (method === "POST" && path === "/nomos/approvals") {
6614
+ const { role, act: act2 } = await json();
6615
+ if (!o.roles.includes(role)) return { status: 404, body: { error: `This service does not approve as ${role}.` } };
6616
+ if (typeof act2?.expires_at !== "string" || !act2?.action) return { status: 400, body: { error: "The request needs the Act." } };
6617
+ const id = randomUUID();
6618
+ await store.createPending({ id, role, act: act2, status: "pending", created_at: now().toISOString(), attempts: {} });
6619
+ await o.onPending?.({ id, role, act: act2, approvalUrl: `${base}/approve/${id}` });
6620
+ return { status: 202, body: { approval_id: id, status: "pending", poll_url: `${base}/nomos/approvals/${id}`, approval_url: `${base}/approve/${id}` } };
6621
+ }
6622
+ let m = path.match(/^\/nomos\/approvals\/([0-9a-f-]{36})$/);
6623
+ if (method === "GET" && m) {
6624
+ const p = await store.pending(m[1]);
6625
+ if (!p) return null;
6626
+ const s = statusOf(p);
6627
+ return { status: 200, body: { status: s, ...s === "approved" ? { consent: p.consent, ...p.key_certs?.length ? { key_certs: p.key_certs } : {} } : {} } };
6628
+ }
6629
+ m = path.match(/^\/approve\/([0-9a-f-]{36})(?:\/(devices|options|assertion|refuse))?$/);
6630
+ if (m) {
6631
+ const p = await store.pending(m[1]);
6632
+ if (!p) return null;
6633
+ if (method === "GET" && !m[2]) return { status: 200, type: "text/html", body: approvalPage(p, statusOf(p), o.listApprovers === true ? (await store.credentials()).filter((c) => c.roles.includes(p.role)) : null) };
6634
+ if (method !== "POST" || !m[2]) return null;
6635
+ if (statusOf(p) !== "pending") return { status: 409, body: { error: `This request is ${statusOf(p)}.` } };
6636
+ if (m[2] === "refuse") return await store.resolvePending(p.id, { status: "refused" }) ? { status: 200, body: { status: "refused" } } : { status: 409, body: { error: "This request is no longer pending." } };
6637
+ const req = await json();
6638
+ if (m[2] === "devices") {
6639
+ const mine = (await store.credentials()).filter((c) => c.approver === String(req?.approver ?? "") && c.roles.includes(p.role));
6640
+ return { status: 200, body: { devices: mine.map((c) => ({ credential_id: c.credential_id, label: c.label })) } };
6641
+ }
6642
+ const cred = (await store.credentials()).find((c) => c.credential_id === req?.credential_id);
6643
+ if (!cred || !cred.roles.includes(p.role)) return { status: 403, body: { error: `That passkey does not approve as ${p.role}.` } };
6644
+ if (m[2] === "options") {
6645
+ const signed_at = now().toISOString();
6646
+ const payload2 = consentPayload({ binding_digest: bindingDigest(p.act), role: p.role, decision: "approve", signed_at, kid: cred.kid });
6647
+ const challenge = toBase64url2(sha256(jcs(payload2)));
6648
+ if (Object.keys(p.attempts).length >= MAX_ATTEMPTS) return { status: 429, body: { error: "Too many approval ceremonies for this request." } };
6649
+ const expires_at = new Date(Math.min(Date.parse(p.act.expires_at), now().getTime() + 5 * 6e4)).toISOString();
6650
+ if (!await store.addAttempt(p.id, challenge, { credential_id: cred.credential_id, signed_at, expires_at })) return { status: 409, body: { error: "This request is no longer pending." } };
6651
+ return { status: 200, body: { challenge, rpId, allowCredentials: [{ type: "public-key", id: cred.credential_id }], userVerification: "required", timeout: 12e4 } };
6652
+ }
6653
+ let offered;
6654
+ try {
6655
+ offered = JSON.parse(fromUtf82(fromBase64url2(String(req.client_data_json ?? "")))).challenge;
6656
+ } catch {
6657
+ return { status: 400, body: { error: "unreadable client data" } };
6658
+ }
6659
+ const attempt = p.attempts[offered];
6660
+ if (!attempt || attempt.credential_id !== cred.credential_id) return { status: 400, body: { error: "This ceremony was not offered for this passkey." } };
6661
+ if (Date.parse(attempt.expires_at) <= now().getTime()) return { status: 400, body: { error: "This ceremony has expired; start again." } };
6662
+ const consent = {
6663
+ binding_digest: bindingDigest(p.act),
6664
+ role: p.role,
6665
+ decision: "approve",
6666
+ signed_at: attempt.signed_at,
6667
+ kid: cred.kid,
6668
+ algorithm: "WebAuthn-ES256",
6669
+ authenticator_data: String(req.authenticator_data ?? ""),
6670
+ client_data_json: String(req.client_data_json ?? ""),
6671
+ signature: String(req.signature ?? "")
6672
+ };
6673
+ const payload = jcs(consentPayload(consent));
6674
+ const cd = clientData(consent.client_data_json, "webauthn.get", toBase64url2(sha256(payload)));
6675
+ if (cd) return { status: 400, body: { error: cd } };
6676
+ const ad = fromBase64url2(consent.authenticator_data);
6677
+ if (ad.length < 37 || !equal2(ad.slice(0, 32), rpIdHash)) return { status: 400, body: { error: "The assertion is for another relying-party id." } };
6678
+ const v = verifySuite("WebAuthn-ES256", cred.public_key_pem, payload, consent);
6679
+ if (!v.ok) return { status: 400, body: { error: `The passkey's signature does not verify (${v.reason}).` } };
6680
+ const key_certs = await o.registry.chainFor(cred.kid);
6681
+ if (!key_certs.length) return { status: 409, body: { error: "This passkey is no longer certified (revoked or expired)." } };
6682
+ if (!await store.resolvePending(p.id, { status: "approved", consent, key_certs })) return { status: 409, body: { error: "This request is no longer pending." } };
6683
+ return { status: 200, body: { status: "approved" } };
6684
+ }
6685
+ if (method === "GET" && path === "/enroll") return { status: 200, type: "text/html", body: enrolPage(o.rpName ?? rpId) };
6686
+ if (method === "POST" && (path === "/nomos/enrol/begin" || path === "/nomos/enrol/finish")) {
6687
+ const req = await json();
6688
+ const claims = verifyEnrolmentToken(String(req?.token ?? ""), o.enrolmentAuthority, base, now());
6689
+ if ("error" in claims) return { status: 403, body: { error: claims.error } };
6690
+ if (!claims.roles.every((r) => o.roles.includes(r))) return { status: 403, body: { error: `This service does not enrol for ${claims.roles.join(", ")}.` } };
6691
+ if (path === "/nomos/enrol/begin") {
6692
+ if (await store.tokenUsed(claims.nonce)) return { status: 403, body: { error: "This enrolment link has been used." } };
6693
+ const challenge2 = toBase64url2(randomBytes3(32));
6694
+ await store.saveEnrolmentChallenge(claims.nonce, challenge2);
6695
+ const existing = (await store.credentials()).filter((c) => c.approver === claims.approver);
6696
+ return { status: 200, body: {
6697
+ challenge: challenge2,
6698
+ rp: { id: rpId, name: o.rpName ?? rpId },
6699
+ user: { id: toBase64url2(utf82(claims.approver)), name: claims.approver, displayName: claims.approver },
6700
+ pubKeyCredParams: [{ type: "public-key", alg: -7 }],
6701
+ authenticatorSelection: { userVerification: "required", residentKey: "preferred" },
6702
+ attestation: "none",
6703
+ timeout: 12e4,
6704
+ excludeCredentials: existing.map((c) => ({ type: "public-key", id: c.credential_id })),
6705
+ approver: claims.approver,
6706
+ roles: claims.roles
6707
+ } };
6708
+ }
6709
+ const challenge = await store.enrolmentChallenge(claims.nonce);
6710
+ if (!challenge) return { status: 403, body: { error: "Start enrolment again: this link has been used or has no ceremony in progress." } };
6711
+ const cd = clientData(String(req.client_data_json ?? ""), "webauthn.create", challenge);
6712
+ if (cd) return { status: 400, body: { error: cd } };
6713
+ let att;
6714
+ try {
6715
+ att = readAttestation(fromBase64url2(String(req.attestation_object ?? "")));
6716
+ } catch (e) {
6717
+ return { status: 400, body: { error: String(e?.message ?? e) } };
6718
+ }
6719
+ if (!equal2(att.authData.slice(0, 32), rpIdHash)) return { status: 400, body: { error: "The passkey was created for another relying-party id." } };
6720
+ if (!(att.flags & 1) || !(att.flags & 4)) return { status: 400, body: { error: "The device did not verify the person (Face ID, Touch ID or PIN is required)." } };
6721
+ if (toBase64url2(att.credentialId) !== req.credential_id) return { status: 400, body: { error: "The credential id does not match the attestation." } };
6722
+ if (!await store.claimToken(claims.nonce)) return { status: 403, body: { error: "This enrolment link has been used." } };
6723
+ const kid = kidOf(att.publicKeyPem);
6724
+ const certificate = await o.registry.issue({
6725
+ parent: o.enroller,
6726
+ child: { kid, publicKeyPem: att.publicKeyPem },
6727
+ scope: `consent:${claims.roles.join(",")} approver:${claims.approver} suite:WebAuthn-ES256`,
6728
+ days: o.certificateDays ?? 365
6729
+ });
6730
+ const label = String(req.label ?? "").slice(0, 60) || "Passkey";
6731
+ await store.addCredential({ credential_id: req.credential_id, approver: claims.approver, roles: claims.roles, kid, public_key_pem: att.publicKeyPem, label, created_at: now().toISOString() });
6732
+ return { status: 200, body: { enrolled: true, approver: claims.approver, roles: claims.roles, kid, certificate } };
6733
+ }
6734
+ return null;
6735
+ }
6736
+ async function fetchHandler(request) {
6737
+ const out = await route(request.method, new URL(request.url).pathname, () => request.json()).catch((e) => ({ status: 400, body: { error: String(e?.message ?? e) } }));
6738
+ if (!out) return new Response(JSON.stringify({ error: "Not found" }), { status: 404, headers: { "content-type": "application/json" } });
6739
+ const html = out.type === "text/html";
6740
+ return new Response(html ? String(out.body) : JSON.stringify(out.body), { status: out.status, headers: { "content-type": html ? "text/html; charset=utf-8" : "application/json", ...html ? { "content-security-policy": CSP() } : {} } });
6741
+ }
6742
+ function node() {
6743
+ return async (req, res, next) => {
6744
+ const path = String(req.originalUrl ?? req.url ?? "").split("?")[0];
6745
+ const json = async () => {
6746
+ if (req.body && typeof req.body === "object" && !(req.body instanceof Uint8Array)) return req.body;
6747
+ const chunks = [];
6748
+ for await (const c of req) chunks.push(c);
6749
+ return JSON.parse(fromUtf82(concat2(...chunks)) || "{}");
6750
+ };
6751
+ let out;
6752
+ try {
6753
+ out = await route(req.method, path, json);
6754
+ } catch (e) {
6755
+ out = { status: 400, body: { error: String(e?.message ?? e) } };
6756
+ }
6757
+ if (!out) {
6758
+ if (next) return next();
6759
+ res.statusCode = 404;
6760
+ return res.end(JSON.stringify({ error: "Not found" }));
6761
+ }
6762
+ res.statusCode = out.status;
6763
+ const html = out.type === "text/html";
6764
+ res.setHeader("content-type", html ? "text/html; charset=utf-8" : "application/json");
6765
+ if (html) res.setHeader("content-security-policy", CSP());
6766
+ res.end(html ? String(out.body) : JSON.stringify(out.body));
6767
+ };
6768
+ }
6769
+ return { store, fetch: fetchHandler, express: node, node };
6770
+ }
6771
+ var STYLE = `body{font:15px/1.55 system-ui,-apple-system,sans-serif;max-width:560px;margin:40px auto;padding:0 16px;color:#111;background:#fff}
6772
+ @media (prefers-color-scheme:dark){body{color:#eee;background:#111}table td{border-color:#333}}
6773
+ h2{margin:0 0 12px}table{border-collapse:collapse;margin:12px 0}td{padding:6px 10px;border-bottom:1px solid #ddd}
6774
+ button{font:inherit;padding:10px 18px;border-radius:8px;border:1px solid #888;background:transparent;color:inherit;cursor:pointer;margin:4px 8px 4px 0}
6775
+ button.primary{background:#2563eb;border-color:#2563eb;color:#fff}.muted{color:#777;font-size:13px}#msg{margin-top:16px;font-weight:600}`;
6776
+ var B642 = `const b64u={enc:b=>btoa(String.fromCharCode(...new Uint8Array(b))).replace(/\\+/g,'-').replace(/\\//g,'_').replace(/=+$/,''),dec:s=>Uint8Array.from(atob(s.replace(/-/g,'+').replace(/_/g,'/')+'='.repeat((4-s.length%4)%4)),c=>c.charCodeAt(0))};
6777
+ const post=(u,b)=>fetch(u,{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify(b)}).then(async r=>({ok:r.ok,body:await r.json().catch(()=>({}))}));
6778
+ const say=t=>{document.getElementById('msg').textContent=t};`;
6779
+ function approvalPage(p, status, devices) {
6780
+ const a = p.act.action ?? {};
6781
+ const params = Object.entries(a.params ?? {}).map(([k, v]) => `<tr><td>${esc(k)}</td><td><b>${esc(JSON.stringify(v))}</b></td></tr>`).join("");
6782
+ const device = (d) => `<button class="primary" data-cred="${esc(d.credential_id)}">Approve${d.approver ? ` as ${esc(d.approver)}` : ""} \xB7 ${esc(d.label)}</button>`;
6783
+ const choose = devices === null ? `<p><label>Your approver id <input id="who" autocomplete="username"></label> <button id="find">Continue</button></p><div id="devices"></div>` : devices.length ? `<div id="devices">${devices.map(device).join("")}</div>` : `<p class="muted">No passkey is enrolled for ${esc(p.role)} yet.</p>`;
6784
+ const body = status === "pending" ? `${choose}<button id="refuse">Refuse</button>` : `<p><b>${esc(status)}</b></p>`;
6785
+ return `<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Approve: ${esc(a.type)}</title><style>${STYLE}</style>
6786
+ <h2>Approve as ${esc(p.role)}?</h2>
6787
+ <p><b>${esc(a.type)}</b>${a.subject ? ` for <b>${esc(a.subject)}</b>` : ""}, at <b>${esc(p.act.relying_party)}</b>, under ${esc(p.act.authority?.artifact?.meta?.artifact_id)} v${esc(p.act.authority?.artifact?.meta?.version)}.</p>
6788
+ <table>${params}</table>
6789
+ <p class="muted">Your passkey signs this exact change and nothing else. It expires at ${esc(p.act.expires_at)}.</p>
6790
+ ${body}<div id="msg" data-id="${esc(p.id)}"></div>
6791
+ <script>${APPROVE_JS}</script>`;
6792
+ }
6793
+ var APPROVE_JS = `${B642}
6794
+ const id=document.getElementById('msg').dataset.id;
6795
+ async function approve(credential_id){
6796
+ try{
6797
+ const o=await post('/approve/'+id+'/options',{credential_id});
6798
+ if(!o.ok) return say(o.body.error||'Not allowed.');
6799
+ const a=await navigator.credentials.get({publicKey:{challenge:b64u.dec(o.body.challenge),rpId:o.body.rpId,allowCredentials:o.body.allowCredentials.map(c=>({type:c.type,id:b64u.dec(c.id)})),userVerification:'required',timeout:o.body.timeout}});
6800
+ const r=await post('/approve/'+id+'/assertion',{credential_id,authenticator_data:b64u.enc(a.response.authenticatorData),client_data_json:b64u.enc(a.response.clientDataJSON),signature:b64u.enc(a.response.signature)});
6801
+ say(r.ok?'Approved. You can close this page.':(r.body.error||'Not approved.'));
6802
+ if(r.ok) document.querySelectorAll('button').forEach(b=>b.disabled=true);
6803
+ }catch(e){say(e.name==='NotAllowedError'?'Cancelled.':String(e.message||e))}
6804
+ }
6805
+ async function refuse(){const r=await post('/approve/'+id+'/refuse',{});say(r.ok?'Refused.':(r.body.error||''));if(r.ok)document.querySelectorAll('button').forEach(b=>b.disabled=true)}
6806
+ function wire(root){root.querySelectorAll('[data-cred]').forEach(b=>b.addEventListener('click',()=>approve(b.dataset.cred)))}
6807
+ wire(document);
6808
+ document.getElementById('refuse')?.addEventListener('click',refuse);
6809
+ document.getElementById('find')?.addEventListener('click',async()=>{
6810
+ const r=await post('/approve/'+id+'/devices',{approver:document.getElementById('who').value});
6811
+ const box=document.getElementById('devices');box.textContent='';
6812
+ if(!r.body.devices?.length) return say('No passkey of yours approves this.');
6813
+ for(const d of r.body.devices){const b=document.createElement('button');b.className='primary';b.dataset.cred=d.credential_id;b.textContent='Approve \xB7 '+d.label;box.appendChild(b)}
6814
+ wire(box);
6815
+ });`;
6816
+ function enrolPage(rpName) {
6817
+ return `<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Enrol a passkey</title><style>${STYLE}</style>
6818
+ <h2>Enrol a passkey for ${esc(rpName)}</h2>
6819
+ <p id="who" class="muted">Checking your enrolment link\u2026</p>
6820
+ <p><label>Name this device <input id="label" value="" placeholder="e.g. iPhone"></label></p>
6821
+ <button class="primary" id="go" disabled>Create passkey</button>
6822
+ <div id="msg"></div>
6823
+ <p class="muted">The passkey stays on your device. It can approve requests in your role and nothing else, and this link works once.</p>
6824
+ <script>${ENROL_JS}</script>`;
6825
+ }
6826
+ var ENROL_JS = `${B642}
6827
+ const token=location.hash.slice(1);let opts=null;
6828
+ (async()=>{const r=await post('/nomos/enrol/begin',{token});if(!r.ok){document.getElementById('who').textContent=r.body.error||'This link is not valid.';return}
6829
+ opts=r.body;document.getElementById('who').textContent='Enrolling '+opts.approver+' to approve as '+opts.roles.join(', ')+'.';document.getElementById('go').disabled=false})();
6830
+ async function enrol(){
6831
+ try{
6832
+ const c=await navigator.credentials.create({publicKey:{challenge:b64u.dec(opts.challenge),rp:opts.rp,user:{id:b64u.dec(opts.user.id),name:opts.user.name,displayName:opts.user.displayName},pubKeyCredParams:opts.pubKeyCredParams,authenticatorSelection:opts.authenticatorSelection,attestation:opts.attestation,timeout:opts.timeout,excludeCredentials:opts.excludeCredentials.map(x=>({type:x.type,id:b64u.dec(x.id)}))}});
6833
+ const r=await post('/nomos/enrol/finish',{token,label:document.getElementById('label').value,credential_id:b64u.enc(c.rawId),client_data_json:b64u.enc(c.response.clientDataJSON),attestation_object:b64u.enc(c.response.attestationObject)});
6834
+ say(r.ok?'Enrolled. This device can now approve as '+r.body.roles.join(', ')+'.':(r.body.error||'Not enrolled.'));
6835
+ if(r.ok) document.getElementById('go').disabled=true;
6836
+ }catch(e){say(e.name==='NotAllowedError'?'Cancelled.':String(e.message||e))}
6837
+ }
6838
+ document.getElementById('go').addEventListener('click',enrol);`;
6839
+ var scriptHash = (js) => `'sha256-${btoa(String.fromCharCode(...sha256(utf82(js))))}'`;
6840
+ var CSP = () => `default-src 'none'; script-src ${scriptHash(APPROVE_JS)} ${scriptHash(ENROL_JS)}; style-src 'unsafe-inline'; connect-src 'self'; form-action 'none'; frame-ancestors 'none'; base-uri 'none'`;
6841
+
5765
6842
  // src/act/carrier.ts
5766
6843
  var STATUS_OF = { AUTHORIZED: "committed", ESCALATED: "escalated", INCOMPLETE: "incomplete", DENIED: "denied", REPLAYED: "replayed" };
5767
6844
  var NomosActError = class extends Error {
@@ -5798,6 +6875,7 @@ async function act(o) {
5798
6875
  }
5799
6876
  }
5800
6877
  const testimony = [];
6878
+ const available = [...issuer2.key_certs ?? []];
5801
6879
  const unavailable = [];
5802
6880
  for (const claim of witnessed) {
5803
6881
  const w = witnesses.find((x) => x.claims.includes(claim));
@@ -5821,8 +6899,10 @@ async function act(o) {
5821
6899
  continue;
5822
6900
  }
5823
6901
  testimony.push(t);
6902
+ if (Array.isArray(body2.key_certs)) available.push(...body2.key_certs);
5824
6903
  }
5825
- const theAct = buildAct({ relyingParty: rp.relying_party_id, action: o.action, artifact, keyCerts: issuer2.key_certs, testimony, presenterFacts: o.presenterFacts, inForce, windowSeconds });
6904
+ const theAct = buildAct({ relyingParty: rp.relying_party_id, action: o.action, artifact, keyCerts: [], testimony, presenterFacts: o.presenterFacts, inForce, windowSeconds });
6905
+ present(theAct, available);
5826
6906
  let { status, body } = await submit(f, rp.act_endpoint, theAct);
5827
6907
  let approvalUrl;
5828
6908
  while (status === 202 && body.decision === "ESCALATED") {
@@ -5830,16 +6910,18 @@ async function act(o) {
5830
6910
  const endpoint = (issuer2.approvals ?? []).find((a) => a.role === role)?.endpoint;
5831
6911
  if (!endpoint) return done("escalated", body, theAct, { summary: `Needs approval from ${role}, and ${issuer2.issuer_id} publishes no approval endpoint for that role.` });
5832
6912
  const deadline = Math.min(Date.parse(theAct.expires_at), Date.now() + (o.approvalTimeoutMs ?? Infinity));
5833
- const consent = await requestConsent(f, endpoint, role, theAct, deadline, o.pollMs ?? 1e3, (url) => {
6913
+ const answer2 = await requestConsent(f, endpoint, role, theAct, deadline, o.pollMs ?? 1e3, (url) => {
5834
6914
  approvalUrl = url;
5835
6915
  o.onApproval?.({ role, url, actDigest: body.act_digest });
5836
6916
  });
5837
- if (consent === "refused") return done("denied", body, theAct, { summary: `The ${role} refused this action.` });
5838
- if (!consent) {
6917
+ if (answer2 === "refused") return done("denied", body, theAct, { summary: `The ${role} refused this action.` });
6918
+ if (!answer2) {
5839
6919
  const expired = Date.now() >= Date.parse(theAct.expires_at);
5840
6920
  return done("escalated", body, theAct, { approvalUrl, summary: expired ? `Not approved by ${role} before the Act expired at ${theAct.expires_at}; build a new Act to ask again.` : `Waiting for approval from ${role}${approvalUrl ? ` at ${approvalUrl}` : ""}. Not approved yet; this Act stays valid until ${theAct.expires_at}.` });
5841
6921
  }
5842
- theAct.consents.push(consent);
6922
+ theAct.consents.push(answer2.consent);
6923
+ if (Array.isArray(answer2.keyCerts)) available.push(...answer2.keyCerts);
6924
+ present(theAct, available);
5843
6925
  const before = Number(body.consents_valid ?? 0);
5844
6926
  ({ status, body } = await submit(f, rp.act_endpoint, theAct));
5845
6927
  if (status === 202 && Number(body.consents_valid ?? 0) <= before) {
@@ -5860,6 +6942,13 @@ async function act(o) {
5860
6942
  summary: kind === "committed" ? `Committed by ${rp.relying_party_id}${body.by_consent ? ", with approval" : ""}.` : kind === "denied" ? `Denied by rule ${body.rule ?? "(default)"}.` : kind === "replayed" ? "Already committed; this is the original receipt." : `Refused: ${body.decision}${body.reason_code ? ` (${body.reason_code})` : ""}.${body.hint ? " " + body.hint : ""}`
5861
6943
  });
5862
6944
  }
6945
+ function present(theAct, available) {
6946
+ const set = presentedCertificates(available, theAct);
6947
+ if (set.length > MAX_PRESENTED) {
6948
+ throw new NomosActError(`This Act relies on ${set.length} certificates; at most ${MAX_PRESENTED} can be presented (NOMOS-SPEC-008 \xA76 step 1). The issuer's hierarchy is too deep for one Act.`);
6949
+ }
6950
+ theAct.authority.key_certs = set;
6951
+ }
5863
6952
  function done(status, verdict, theAct, extra) {
5864
6953
  return { status, verdict, act: theAct, ...extra };
5865
6954
  }
@@ -5877,13 +6966,13 @@ async function submit(f, endpoint, theAct) {
5877
6966
  async function requestConsent(f, endpoint, role, theAct, deadline, pollMs, onPending) {
5878
6967
  const res = await f(endpoint, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ role, act: { ...theAct, consents: [] } }) });
5879
6968
  const body = await res.json().catch(() => ({}));
5880
- if (res.status === 200 && body.status === "approved") return body.consent;
6969
+ if (res.status === 200 && body.status === "approved") return { consent: body.consent, keyCerts: body.key_certs };
5881
6970
  if (res.status !== 202 || !body.poll_url) return null;
5882
6971
  onPending(body.approval_url);
5883
6972
  while (Date.now() < deadline) {
5884
6973
  await new Promise((r) => setTimeout(r, pollMs));
5885
6974
  const p = await (await f(body.poll_url)).json().catch(() => ({}));
5886
- if (p.status === "approved") return p.consent;
6975
+ if (p.status === "approved") return { consent: p.consent, keyCerts: p.key_certs };
5887
6976
  if (p.status === "refused") return "refused";
5888
6977
  if (p.status === "expired") return null;
5889
6978
  }
@@ -5891,7 +6980,6 @@ async function requestConsent(f, endpoint, role, theAct, deadline, pollMs, onPen
5891
6980
  }
5892
6981
 
5893
6982
  // src/act/roles.ts
5894
- var import_node_crypto3 = require("crypto");
5895
6983
  function serve(route) {
5896
6984
  async function fetchHandler(request) {
5897
6985
  const out = await route({ method: request.method, path: new URL(request.url).pathname, json: () => request.json() });
@@ -5906,7 +6994,7 @@ function serve(route) {
5906
6994
  if (req.body && typeof req.body === "object" && !(req.body instanceof Uint8Array)) return req.body;
5907
6995
  const chunks = [];
5908
6996
  for await (const c of req) chunks.push(c);
5909
- return JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
6997
+ return JSON.parse(fromUtf82(concat2(...chunks)) || "{}");
5910
6998
  };
5911
6999
  let out;
5912
7000
  try {
@@ -5930,7 +7018,9 @@ var day = 864e5;
5930
7018
  function issuer(o) {
5931
7019
  const byId = new Map(o.authorities.map((a) => [a.meta.artifact_id, a]));
5932
7020
  const base = o.origin.replace(/\/$/, "");
5933
- const document = () => ({
7021
+ const pool = async () => typeof o.keyCerts === "function" ? await o.keyCerts() : o.keyCerts;
7022
+ const revocationsUrl = o.revocations ? `${base}/nomos/revocations` : o.revocationsUrl;
7023
+ const build = (keyCerts) => ({
5934
7024
  nomos_document: "issuer",
5935
7025
  version: "1",
5936
7026
  issuer_id: o.issuerId,
@@ -5941,17 +7031,24 @@ function issuer(o) {
5941
7031
  artifact_url: `${base}/nomos/authorities/${encodeURIComponent(a.meta.artifact_id)}.nomos`,
5942
7032
  in_force_url: `${base}/nomos/authorities/${encodeURIComponent(a.meta.artifact_id)}/in-force`
5943
7033
  })),
5944
- key_certs: o.keyCerts,
7034
+ key_certs: keyCerts,
5945
7035
  ...o.witnesses ? { witnesses: o.witnesses } : {},
5946
7036
  ...o.approvals ? { approvals: o.approvals } : {},
5947
- ...o.revocationsUrl ? { revocations_url: o.revocationsUrl } : {},
7037
+ ...revocationsUrl ? { revocations_url: revocationsUrl } : {},
5948
7038
  valid_until: new Date(Date.now() + day).toISOString()
5949
7039
  });
7040
+ const document = () => {
7041
+ if (typeof o.keyCerts === "function") throw new Error("issuer(): keyCerts is a function, so the document must be read with `await resolveDocument()`");
7042
+ return build(o.keyCerts);
7043
+ };
7044
+ const resolveDocument = async () => build(await pool());
5950
7045
  return {
5951
7046
  document,
7047
+ resolveDocument,
5952
7048
  ...serve(async ({ method, path }) => {
5953
7049
  if (method !== "GET") return null;
5954
- if (path === WELL_KNOWN.issuer) return { status: 200, body: document() };
7050
+ if (path === WELL_KNOWN.issuer) return { status: 200, body: await resolveDocument() };
7051
+ if (path === "/nomos/revocations" && o.revocations) return { status: 200, body: await o.revocations() };
5955
7052
  const m = path.match(/^\/nomos\/authorities\/([^/]+?)(\.nomos|\/in-force)$/);
5956
7053
  const art = m && byId.get(decodeURIComponent(m[1]));
5957
7054
  if (!art) return null;
@@ -5986,13 +7083,14 @@ function witness(o) {
5986
7083
  if (value2 === void 0) return { status: 404, body: { error: `${o.witnessId} knows nothing about ${about}.` } };
5987
7084
  const max = o.maxValiditySeconds ?? 900;
5988
7085
  const seconds = Math.max(1, Math.min(max, Number(req?.valid_for_seconds) || max));
5989
- return { status: 200, body: { testimony: await signTestimony(o.signer, { claim, value: value2, about, validUntil: new Date(Date.now() + seconds * 1e3) }) } };
7086
+ return { status: 200, body: { testimony: await signTestimony(o.signer, { claim, value: value2, about, validUntil: new Date(Date.now() + seconds * 1e3) }), ...o.keyCerts?.length ? { key_certs: o.keyCerts } : {} } };
5990
7087
  })
5991
7088
  };
5992
7089
  }
5993
7090
  function approver(o) {
5994
7091
  const base = o.origin.replace(/\/$/, "");
5995
7092
  const pending = /* @__PURE__ */ new Map();
7093
+ const chain = o.keyCerts?.length ? { key_certs: o.keyCerts } : {};
5996
7094
  const statusOf = (p) => p.status === "pending" && Date.parse(p.act.expires_at) < Date.now() ? "expired" : p.status;
5997
7095
  return {
5998
7096
  pending,
@@ -6002,9 +7100,9 @@ function approver(o) {
6002
7100
  if (!o.roles.includes(role)) return { status: 404, body: { error: `This approver does not consent as ${role}.` } };
6003
7101
  if (o.decide === "auto" || typeof o.decide === "function") {
6004
7102
  const ok = o.decide === "auto" ? true : await o.decide({ role, act: act2 });
6005
- return ok ? { status: 200, body: { status: "approved", consent: await signConsent(o.signer, act2, role) } } : { status: 200, body: { status: "refused" } };
7103
+ return ok ? { status: 200, body: { status: "approved", consent: await signConsent(o.signer, act2, role), ...chain } } : { status: 200, body: { status: "refused" } };
6006
7104
  }
6007
- const id = (0, import_node_crypto3.randomUUID)();
7105
+ const id = randomUUID();
6008
7106
  pending.set(id, { role, act: act2, status: "pending" });
6009
7107
  return { status: 202, body: { approval_id: id, status: "pending", poll_url: `${base}/nomos/approvals/${id}`, approval_url: `${base}/approve/${id}` } };
6010
7108
  }
@@ -6013,7 +7111,7 @@ function approver(o) {
6013
7111
  const p = pending.get(m[1]);
6014
7112
  if (!p) return null;
6015
7113
  const s = statusOf(p);
6016
- return { status: 200, body: { status: s, ...s === "approved" ? { consent: p.consent } : {} } };
7114
+ return { status: 200, body: { status: s, ...s === "approved" ? { consent: p.consent, ...chain } : {} } };
6017
7115
  }
6018
7116
  m = path.match(/^\/approve\/([0-9a-f-]+)(\/(approve|refuse))?$/);
6019
7117
  if (m) {
@@ -6033,23 +7131,24 @@ function approver(o) {
6033
7131
  })
6034
7132
  };
6035
7133
  }
6036
- var esc = (s) => String(s).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
7134
+ var esc2 = (s) => String(s).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
6037
7135
  function page(p, status, id) {
6038
7136
  const a = p.act.action ?? {};
6039
- const params = Object.entries(a.params ?? {}).map(([k, v]) => `<tr><td>${esc(k)}</td><td><b>${esc(JSON.stringify(v))}</b></td></tr>`).join("");
7137
+ const params = Object.entries(a.params ?? {}).map(([k, v]) => `<tr><td>${esc2(k)}</td><td><b>${esc2(JSON.stringify(v))}</b></td></tr>`).join("");
6040
7138
  const buttons = status === "pending" ? `<form method="post" action="/approve/${id}/approve" style="display:inline"><button style="padding:10px 18px">Approve</button></form>
6041
- <form method="post" action="/approve/${id}/refuse" style="display:inline"><button style="padding:10px 18px">Refuse</button></form>` : `<p><b>${esc(status)}</b></p>`;
6042
- return `<!doctype html><meta charset="utf-8"><title>Approve: ${esc(a.type)}</title>
7139
+ <form method="post" action="/approve/${id}/refuse" style="display:inline"><button style="padding:10px 18px">Refuse</button></form>` : `<p><b>${esc2(status)}</b></p>`;
7140
+ return `<!doctype html><meta charset="utf-8"><title>Approve: ${esc2(a.type)}</title>
6043
7141
  <body style="font:15px system-ui;max-width:560px;margin:40px auto;padding:0 16px">
6044
- <h2>Approve as ${esc(p.role)}?</h2>
6045
- <p><b>${esc(a.type)}</b>${a.subject ? ` for <b>${esc(a.subject)}</b>` : ""}, at <b>${esc(p.act.relying_party)}</b>, under ${esc(p.act.authority?.artifact?.meta?.artifact_id)} v${esc(p.act.authority?.artifact?.meta?.version)}.</p>
7142
+ <h2>Approve as ${esc2(p.role)}?</h2>
7143
+ <p><b>${esc2(a.type)}</b>${a.subject ? ` for <b>${esc2(a.subject)}</b>` : ""}, at <b>${esc2(p.act.relying_party)}</b>, under ${esc2(p.act.authority?.artifact?.meta?.artifact_id)} v${esc2(p.act.authority?.artifact?.meta?.version)}.</p>
6046
7144
  <table style="border-collapse:collapse" cellpadding="4">${params}</table>
6047
- <p style="color:#666">Your approval signs this exact change (digest ${esc(bindingDigest(p.act).slice(0, 16))}\u2026) and nothing else. It expires at ${esc(p.act.expires_at)}.</p>
7145
+ <p style="color:#666">Your approval signs this exact change (digest ${esc2(bindingDigest(p.act).slice(0, 16))}\u2026) and nothing else. It expires at ${esc2(p.act.expires_at)}.</p>
6048
7146
  ${buttons}</body>`;
6049
7147
  }
6050
7148
  // Annotate the CommonJS export names for ESM import in node:
6051
7149
  0 && (module.exports = {
6052
7150
  ACT_MEDIA_TYPE,
7151
+ MAX_PRESENTED,
6053
7152
  Nomos,
6054
7153
  NomosAPIError,
6055
7154
  NomosActError,
@@ -6064,31 +7163,53 @@ ${buttons}</body>`;
6064
7163
  NomosRateLimitError,
6065
7164
  NomosValidationError,
6066
7165
  NomosWebhookSignatureError,
7166
+ RegistryError,
7167
+ RevocationUnavailable,
6067
7168
  WELL_KNOWN,
6068
7169
  accept,
6069
7170
  act,
7171
+ applyToolBindings,
6070
7172
  approver,
6071
7173
  bindingDigest,
6072
7174
  buildAct,
6073
7175
  can,
7176
+ certificateRegistry,
6074
7177
  certify,
7178
+ describeSuggestion,
6075
7179
  fetchDocument,
6076
7180
  generateSigner,
6077
7181
  issuer,
6078
7182
  kidOf,
6079
7183
  localSigner,
7184
+ memoryPasskeyStore,
7185
+ memoryRegistryStore,
6080
7186
  memoryStore,
6081
7187
  nomos,
6082
7188
  originOf,
7189
+ parseToolBindings,
7190
+ passkeyApprover,
7191
+ postgresPasskeyStore,
7192
+ postgresRegistryStore,
7193
+ postgresStore,
7194
+ presentedCertificates,
7195
+ readAttestation,
7196
+ reliedKids,
7197
+ revocationListCertificates,
7198
+ revocationSource,
6083
7199
  seal,
6084
7200
  seededSigner,
6085
7201
  signConsent,
7202
+ signEnrolmentToken,
6086
7203
  signInForce,
7204
+ signRevocation,
7205
+ signRevocationList,
6087
7206
  signTestimony,
6088
7207
  statusFor,
7208
+ suggestToolBindings,
6089
7209
  verifyAct,
6090
7210
  verifyArtifact,
6091
7211
  verifyChain,
7212
+ verifyEnrolmentToken,
6092
7213
  verifySeal,
6093
7214
  witness
6094
7215
  });