@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.js CHANGED
@@ -107,7 +107,7 @@ function isRetryable(status) {
107
107
  }
108
108
 
109
109
  // src/http-client.ts
110
- var SDK_VERSION = "1.1.0";
110
+ var SDK_VERSION = "2.3.1";
111
111
  var HttpClient = class {
112
112
  constructor(opts) {
113
113
  this.opts = opts;
@@ -772,7 +772,7 @@ async function canAuthenticated(http, params, opts) {
772
772
  var nomos = { can };
773
773
 
774
774
  // src/nomos.ts
775
- var DEFAULT_BASE_URL = "https://nomos.ai";
775
+ var DEFAULT_BASE_URL = "https://www.nomosprotocol.com";
776
776
  var DEFAULT_MAX_RETRIES = 3;
777
777
  var DEFAULT_TIMEOUT = 3e4;
778
778
  var Nomos = class {
@@ -782,7 +782,7 @@ var Nomos = class {
782
782
  constructor(apiKey, config = {}) {
783
783
  if (!apiKey || typeof apiKey !== "string") {
784
784
  throw new NomosAuthenticationError(
785
- 'A valid API key is required. Pass it as the first argument: new Nomos("nms_live_...")'
785
+ 'A valid API key is required. Pass it as the first argument: new Nomos("nmk_...")'
786
786
  );
787
787
  }
788
788
  this.mode = apiKey.startsWith("nms_test_") ? "test" : "live";
@@ -804,7 +804,7 @@ var Nomos = class {
804
804
  * return shape either way, so calling code never needs to know which rail answered.
805
805
  *
806
806
  * @example
807
- * const client = new Nomos('nms_live_...');
807
+ * const client = new Nomos('nmk_...');
808
808
  * const verdict = await client.can({ artifact_id: 'loan_approval_v1', credit_score: 720 });
809
809
  */
810
810
  async can(params, opts) {
@@ -812,7 +812,7 @@ var Nomos = class {
812
812
  }
813
813
  };
814
814
 
815
- // ../../AI_Navigator-repro/node_modules/@noble/hashes/_u64.js
815
+ // node_modules/@noble/hashes/_u64.js
816
816
  var U32_MASK64 = /* @__PURE__ */ (() => BigInt(2 ** 32 - 1))();
817
817
  var _32n = /* @__PURE__ */ BigInt(32);
818
818
  function fromBig(n, le = false) {
@@ -855,7 +855,7 @@ var add4H = (low, Ah, Bh, Ch, Dh) => Ah + Bh + Ch + Dh + (low / 2 ** 32 | 0) | 0
855
855
  var add5L = (Al, Bl, Cl, Dl, El) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0) + (El >>> 0);
856
856
  var add5H = (low, Ah, Bh, Ch, Dh, Eh) => Ah + Bh + Ch + Dh + Eh + (low / 2 ** 32 | 0) | 0;
857
857
 
858
- // ../../AI_Navigator-repro/node_modules/@noble/hashes/utils.js
858
+ // node_modules/@noble/hashes/utils.js
859
859
  function isBytes(a) {
860
860
  return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array" && "BYTES_PER_ELEMENT" in a && a.BYTES_PER_ELEMENT === 1;
861
861
  }
@@ -1020,7 +1020,7 @@ var oidNist = (suffix) => ({
1020
1020
  oid: Uint8Array.from([6, 9, 96, 134, 72, 1, 101, 3, 4, 2, suffix])
1021
1021
  });
1022
1022
 
1023
- // ../../AI_Navigator-repro/node_modules/@noble/hashes/_md.js
1023
+ // node_modules/@noble/hashes/_md.js
1024
1024
  function Chi(a, b, c) {
1025
1025
  return a & b ^ ~a & c;
1026
1026
  }
@@ -1151,7 +1151,7 @@ var SHA512_IV = /* @__PURE__ */ Uint32Array.from([
1151
1151
  327033209
1152
1152
  ]);
1153
1153
 
1154
- // ../../AI_Navigator-repro/node_modules/@noble/hashes/sha2.js
1154
+ // node_modules/@noble/hashes/sha2.js
1155
1155
  var SHA256_K = /* @__PURE__ */ Uint32Array.from([
1156
1156
  1116352408,
1157
1157
  1899447441,
@@ -1548,7 +1548,7 @@ var sha512 = /* @__PURE__ */ createHasher(
1548
1548
  /* @__PURE__ */ oidNist(3)
1549
1549
  );
1550
1550
 
1551
- // ../packages/verify/dist/bytes.js
1551
+ // node_modules/@nomosprotocol/verify/dist/bytes.js
1552
1552
  var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1553
1553
  var REV = new Map([...B64].map((c, i) => [c, i]));
1554
1554
  var utf8 = (s) => new TextEncoder().encode(s);
@@ -1610,7 +1610,7 @@ function fromBase64url(s) {
1610
1610
  return raw && toBase64url(raw) === s ? raw : null;
1611
1611
  }
1612
1612
 
1613
- // ../packages/verify/dist/jcs.js
1613
+ // node_modules/@nomosprotocol/verify/dist/jcs.js
1614
1614
  function value(v) {
1615
1615
  if (v === null)
1616
1616
  return "null";
@@ -1644,7 +1644,7 @@ function codePointCompare(a, b) {
1644
1644
  return A.length - B.length;
1645
1645
  }
1646
1646
 
1647
- // ../../AI_Navigator-repro/node_modules/@noble/curves/utils.js
1647
+ // node_modules/@noble/curves/utils.js
1648
1648
  function aarray(item, title, inner = () => {
1649
1649
  }) {
1650
1650
  if (!Array.isArray(item))
@@ -1826,7 +1826,7 @@ function validateObject(object, fields = {}, optFields = {}, title = "object") {
1826
1826
  iter(optFields, true);
1827
1827
  }
1828
1828
 
1829
- // ../../AI_Navigator-repro/node_modules/@noble/curves/abstract/modular.js
1829
+ // node_modules/@noble/curves/abstract/modular.js
1830
1830
  var _0n2 = /* @__PURE__ */ BigInt(0);
1831
1831
  var _1n2 = /* @__PURE__ */ BigInt(1);
1832
1832
  var _2n = /* @__PURE__ */ BigInt(2);
@@ -2289,7 +2289,7 @@ function mapHashToField(key, fieldOrder, isLE = false) {
2289
2289
  return isLE ? numberToBytesLE(reduced, fieldLen) : numberToBytesBE(reduced, fieldLen);
2290
2290
  }
2291
2291
 
2292
- // ../../AI_Navigator-repro/node_modules/@noble/curves/abstract/curve.js
2292
+ // node_modules/@noble/curves/abstract/curve.js
2293
2293
  var _0n3 = /* @__PURE__ */ BigInt(0);
2294
2294
  var _1n3 = /* @__PURE__ */ BigInt(1);
2295
2295
  var _4n2 = /* @__PURE__ */ BigInt(4);
@@ -2676,7 +2676,7 @@ function createKeygen(randomSecretKey, getPublicKey) {
2676
2676
  };
2677
2677
  }
2678
2678
 
2679
- // ../../AI_Navigator-repro/node_modules/@noble/curves/abstract/edwards.js
2679
+ // node_modules/@noble/curves/abstract/edwards.js
2680
2680
  var _0n4 = /* @__PURE__ */ BigInt(0);
2681
2681
  var _1n4 = /* @__PURE__ */ BigInt(1);
2682
2682
  var _2n2 = /* @__PURE__ */ BigInt(2);
@@ -3127,7 +3127,7 @@ function eddsa(Point, cHash, eddsaOpts = {}) {
3127
3127
  });
3128
3128
  }
3129
3129
 
3130
- // ../../AI_Navigator-repro/node_modules/@noble/curves/ed25519.js
3130
+ // node_modules/@noble/curves/ed25519.js
3131
3131
  var _1n5 = /* @__PURE__ */ BigInt(1);
3132
3132
  var _2n3 = /* @__PURE__ */ BigInt(2);
3133
3133
  var _5n2 = /* @__PURE__ */ BigInt(5);
@@ -3202,7 +3202,7 @@ function ed(opts) {
3202
3202
  }
3203
3203
  var ed25519 = /* @__PURE__ */ ed({});
3204
3204
 
3205
- // ../../AI_Navigator-repro/node_modules/@noble/hashes/hmac.js
3205
+ // node_modules/@noble/hashes/hmac.js
3206
3206
  var _HMAC = class {
3207
3207
  oHash;
3208
3208
  iHash;
@@ -3279,7 +3279,7 @@ var hmac = /* @__PURE__ */ (() => {
3279
3279
  return hmac_;
3280
3280
  })();
3281
3281
 
3282
- // ../../AI_Navigator-repro/node_modules/@noble/curves/abstract/der.js
3282
+ // node_modules/@noble/curves/abstract/der.js
3283
3283
  var _0n5 = /* @__PURE__ */ BigInt(0);
3284
3284
  var DERErr = class extends Error {
3285
3285
  constructor(m = "") {
@@ -3373,18 +3373,18 @@ var _DER = {
3373
3373
  }
3374
3374
  },
3375
3375
  toSig(bytes, maxScalarBytes) {
3376
- const { Err: E, _int: int, _tlv: tlv } = _DER;
3376
+ const { Err: E, _int: int, _tlv: tlv2 } = _DER;
3377
3377
  if (maxScalarBytes !== void 0) {
3378
3378
  asafenumber(maxScalarBytes, "maxScalarBytes");
3379
3379
  if (maxScalarBytes < 1)
3380
3380
  throw new E("invalid signature: maxScalarBytes must be positive");
3381
3381
  }
3382
3382
  const data = abytes2(bytes, void 0, "signature");
3383
- const { v: seqBytes, l: seqLeftBytes } = tlv.decode(48, data);
3383
+ const { v: seqBytes, l: seqLeftBytes } = tlv2.decode(48, data);
3384
3384
  if (seqLeftBytes.length)
3385
3385
  throw new E("invalid signature: left bytes after parsing");
3386
- const { v: rBytes, l: rLeftBytes } = tlv.decode(2, seqBytes);
3387
- const { v: sBytes, l: sLeftBytes } = tlv.decode(2, rLeftBytes);
3386
+ const { v: rBytes, l: rLeftBytes } = tlv2.decode(2, seqBytes);
3387
+ const { v: sBytes, l: sLeftBytes } = tlv2.decode(2, rLeftBytes);
3388
3388
  if (sLeftBytes.length)
3389
3389
  throw new E("invalid signature: left bytes after parsing");
3390
3390
  if (maxScalarBytes !== void 0 && (rBytes.length > maxScalarBytes || sBytes.length > maxScalarBytes))
@@ -3392,12 +3392,12 @@ var _DER = {
3392
3392
  return { r: int.decode(rBytes), s: int.decode(sBytes) };
3393
3393
  },
3394
3394
  hexFromSig(sig) {
3395
- const { _tlv: tlv, _int: int } = _DER;
3395
+ const { _tlv: tlv2, _int: int } = _DER;
3396
3396
  validateObject(sig, { r: "bigint", s: "bigint" }, {}, "sig");
3397
- const rs = tlv.encode(2, int.encode(sig.r));
3398
- const ss = tlv.encode(2, int.encode(sig.s));
3397
+ const rs = tlv2.encode(2, int.encode(sig.r));
3398
+ const ss = tlv2.encode(2, int.encode(sig.s));
3399
3399
  const seq = rs + ss;
3400
- return tlv.encode(48, seq);
3400
+ return tlv2.encode(48, seq);
3401
3401
  }
3402
3402
  };
3403
3403
  var DER = /* @__PURE__ */ (() => {
@@ -3406,7 +3406,7 @@ var DER = /* @__PURE__ */ (() => {
3406
3406
  return Object.freeze(_DER);
3407
3407
  })();
3408
3408
 
3409
- // ../../AI_Navigator-repro/node_modules/@noble/curves/abstract/weierstrass.js
3409
+ // node_modules/@noble/curves/abstract/weierstrass.js
3410
3410
  var divNearest = (num, den) => (num + (num >= 0 ? den : -den) / _2n4) / den;
3411
3411
  function _splitEndoScalar(k, basis, n) {
3412
3412
  aInRange("scalar", k, _0n6, n);
@@ -4238,7 +4238,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
4238
4238
  });
4239
4239
  }
4240
4240
 
4241
- // ../../AI_Navigator-repro/node_modules/@noble/curves/nist.js
4241
+ // node_modules/@noble/curves/nist.js
4242
4242
  var p256_CURVE = /* @__PURE__ */ (() => ({
4243
4243
  p: BigInt("0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff"),
4244
4244
  n: BigInt("0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551"),
@@ -4251,21 +4251,21 @@ var p256_CURVE = /* @__PURE__ */ (() => ({
4251
4251
  var p256_Point = /* @__PURE__ */ weierstrass(p256_CURVE);
4252
4252
  var p256 = /* @__PURE__ */ ecdsa(p256_Point, sha256);
4253
4253
 
4254
- // ../packages/verify/dist/suites.js
4254
+ // node_modules/@nomosprotocol/verify/dist/suites.js
4255
4255
  var SUITES = ["Ed25519", "ES256", "WebAuthn-ES256"];
4256
4256
  var N = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551n;
4257
4257
  var ED25519_PREFIX = Uint8Array.from([48, 42, 48, 5, 6, 3, 43, 101, 112, 3, 33, 0]);
4258
4258
  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]);
4259
4259
  var SUITES_FOR = { "Ed25519": ["Ed25519"], "P-256": ["ES256", "WebAuthn-ES256"] };
4260
4260
  var isSuite = (s) => typeof s === "string" && SUITES.includes(s);
4261
- function pemDer(pem) {
4262
- if (typeof pem !== "string")
4261
+ function pemDer(pem2) {
4262
+ if (typeof pem2 !== "string")
4263
4263
  return null;
4264
- const m = pem.match(/^\s*-----BEGIN PUBLIC KEY-----\s*([A-Za-z0-9+/=\s]+?)\s*-----END PUBLIC KEY-----\s*$/);
4264
+ const m = pem2.match(/^\s*-----BEGIN PUBLIC KEY-----\s*([A-Za-z0-9+/=\s]+?)\s*-----END PUBLIC KEY-----\s*$/);
4265
4265
  return m ? fromBase64(m[1].replace(/\s+/g, "")) : null;
4266
4266
  }
4267
- function loadKey(pem) {
4268
- const der = pemDer(pem);
4267
+ function loadKey(pem2) {
4268
+ const der = pemDer(pem2);
4269
4269
  if (!der)
4270
4270
  return { reason: "invalid_key" };
4271
4271
  if (der.length === 44 && equal(der.subarray(0, 12), ED25519_PREFIX)) {
@@ -4289,16 +4289,16 @@ function loadKey(pem) {
4289
4289
  }
4290
4290
  return { reason: "invalid_key" };
4291
4291
  }
4292
- function kidOf(pem) {
4293
- const k = loadKey(pem);
4292
+ function kidOf(pem2) {
4293
+ const k = loadKey(pem2);
4294
4294
  return "reason" in k ? null : toBase64url(sha256(k.der)).slice(0, 16);
4295
4295
  }
4296
4296
  var toBigInt = (b) => b.reduce((n, x) => n << 8n | BigInt(x), 0n);
4297
- function verifySuite(suite, pem, payload, proof) {
4297
+ function verifySuite(suite, pem2, payload, proof) {
4298
4298
  const fail2 = (reason) => ({ ok: false, reason });
4299
4299
  if (!isSuite(suite))
4300
4300
  return fail2("unknown_suite");
4301
- const k = loadKey(pem);
4301
+ const k = loadKey(pem2);
4302
4302
  if ("reason" in k)
4303
4303
  return fail2("invalid_key");
4304
4304
  if (!SUITES_FOR[k.type].includes(suite))
@@ -4360,7 +4360,7 @@ function verifySuite(suite, pem, payload, proof) {
4360
4360
  return ok ? { ok: true } : fail2("bad_signature");
4361
4361
  }
4362
4362
 
4363
- // ../packages/verify/dist/scope.js
4363
+ // node_modules/@nomosprotocol/verify/dist/scope.js
4364
4364
  var ARTIFACT_DIMENSIONS = ["artifact", "industry", "jurisdiction"];
4365
4365
  var SET_DIMENSIONS = ["claim", "consent", "suite"];
4366
4366
  var UNRESTRICTED = { terms: {} };
@@ -4479,7 +4479,7 @@ function formatScope(scope) {
4479
4479
  return Object.entries(scope.terms).map(([d, v]) => `${d}:${v}`).join(" ");
4480
4480
  }
4481
4481
 
4482
- // ../packages/verify/dist/chain.js
4482
+ // node_modules/@nomosprotocol/verify/dist/chain.js
4483
4483
  var MAX_CHAIN = 20;
4484
4484
  var CERT_SUITES = ["Ed25519", "ES256"];
4485
4485
  var SEAL_SUITES = ["Ed25519", "ES256"];
@@ -4571,7 +4571,7 @@ var Memo = class {
4571
4571
  }
4572
4572
  };
4573
4573
  function validate(path, rootPem, now, purpose, revokedKids, revokedCerts, memo) {
4574
- let pem = rootPem, scope = UNRESTRICTED;
4574
+ let pem2 = rootPem, scope = UNRESTRICTED;
4575
4575
  const hops = [];
4576
4576
  for (const cert of path) {
4577
4577
  const f = memo.of(cert);
@@ -4581,7 +4581,7 @@ function validate(path, rootPem, now, purpose, revokedKids, revokedCerts, memo)
4581
4581
  return fail("ISSUER_NOT_RECOGNIZED", { reason_code: "kid_mismatch" });
4582
4582
  if (!suitePermitted(scope, cert.algorithm))
4583
4583
  return fail("OUT_OF_SCOPE", { dimension: "suite", reason_code: "suite_not_permitted" });
4584
- const sig = memo.sig(cert, pem);
4584
+ const sig = memo.sig(cert, pem2);
4585
4585
  if (!sig.ok)
4586
4586
  return fail("ISSUER_NOT_RECOGNIZED", { reason_code: sig.reason });
4587
4587
  const issued = parseTime(cert.issued_at), expires = parseTime(cert.expires_at);
@@ -4601,14 +4601,14 @@ function validate(path, rootPem, now, purpose, revokedKids, revokedCerts, memo)
4601
4601
  const narrowing = isNarrowerOrEqual(f.scope.scope, scope);
4602
4602
  if (!narrowing.ok)
4603
4603
  return fail("OUT_OF_SCOPE", { dimension: narrowing.dimension, reason_code: "widening" });
4604
- hops.push({ cert, parentPem: pem, parentScope: scope });
4605
- pem = cert.child_public_key_pem;
4604
+ hops.push({ cert, parentPem: pem2, parentScope: scope });
4605
+ pem2 = cert.child_public_key_pem;
4606
4606
  scope = mergeScopes(scope, f.scope.scope);
4607
4607
  }
4608
4608
  const admitted = purpose(scope);
4609
4609
  if (!admitted.ok)
4610
4610
  return fail("OUT_OF_SCOPE", { dimension: admitted.dimension });
4611
- return { decision: "RESOLVED", pem, path: [], effective: scope, hops };
4611
+ return { decision: "RESOLVED", pem: pem2, path: [], effective: scope, hops };
4612
4612
  }
4613
4613
  function resolveKey(o) {
4614
4614
  const revokedKids = o.revokedKids ?? /* @__PURE__ */ new Set(), revokedCerts = o.revokedCerts ?? /* @__PURE__ */ new Set();
@@ -4694,7 +4694,7 @@ function verifyArtifact(o) {
4694
4694
  }
4695
4695
  }
4696
4696
 
4697
- // ../packages/verify/dist/evaluate.js
4697
+ // node_modules/@nomosprotocol/verify/dist/evaluate.js
4698
4698
  var jcsString = (v) => canonicalize(v);
4699
4699
  var FIELD = /* @__PURE__ */ Symbol("field");
4700
4700
  var ABSENT = /* @__PURE__ */ Symbol("absent");
@@ -5023,7 +5023,7 @@ function evaluate(artifact, facts) {
5023
5023
  return ev;
5024
5024
  }
5025
5025
 
5026
- // ../packages/verify/dist/act.js
5026
+ // node_modules/@nomosprotocol/verify/dist/act.js
5027
5027
  var MAX_WINDOW_SECONDS = 300;
5028
5028
  var MAX_IN_FORCE_SECONDS = 86400;
5029
5029
  var TESTIMONY_SUITES = ["Ed25519", "ES256"];
@@ -5061,7 +5061,7 @@ function hasAmbiguousEscalation(artifact) {
5061
5061
  return [...named.values()].some((ids) => ids.size > 1);
5062
5062
  }
5063
5063
  var proofOf = (st) => st.algorithm === "WebAuthn-ES256" ? pick(st, WEBAUTHN_FIELDS) : { signature: st.signature };
5064
- var statementSig = (pem, st, payload) => verifySuite(st.algorithm, pem, jcs(payload), proofOf(st));
5064
+ var statementSig = (pem2, st, payload) => verifySuite(st.algorithm, pem2, jcs(payload), proofOf(st));
5065
5065
  var time = (s) => typeof s === "string" ? Date.parse(s) : NaN;
5066
5066
  var isObject = (x) => x !== null && typeof x === "object" && !Array.isArray(x);
5067
5067
  function statementFailure(r, invalid, outOfScope, who) {
@@ -5226,11 +5226,11 @@ function verifyUnsafe(a, o) {
5226
5226
  return { decision: "AUTHORIZED", commit: true, ...base };
5227
5227
  if (ev.outcome === "block")
5228
5228
  return { decision: "DENIED", commit: false, ...base };
5229
- const esc2 = (art.governance?.escalations ?? []).find((e) => e?.id === ev.escalation_id);
5230
- if (!esc2)
5229
+ const esc3 = (art.governance?.escalations ?? []).find((e) => e?.id === ev.escalation_id);
5230
+ if (!esc3)
5231
5231
  return { decision: "ESCALATED", commit: false, escalation_id: ev.escalation_id, consents_valid: 0, consents_required: null, ...base };
5232
- const role = esc2.role_required;
5233
- const needN = Math.trunc(Number(esc2.required_consents ?? 1));
5232
+ const role = esc3.role_required;
5233
+ const needN = Math.trunc(Number(esc3.required_consents ?? 1));
5234
5234
  const approvers = /* @__PURE__ */ new Set();
5235
5235
  for (const c of Array.isArray(act2.consents) ? act2.consents : []) {
5236
5236
  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")) {
@@ -5256,7 +5256,7 @@ function verifyUnsafe(a, o) {
5256
5256
  return { decision: "ESCALATED", commit: false, ...counted };
5257
5257
  }
5258
5258
 
5259
- // ../packages/verify/dist/seal.js
5259
+ // node_modules/@nomosprotocol/verify/dist/seal.js
5260
5260
  function verifySeal(artifact, publicKeyPem) {
5261
5261
  const bad = checkSealShape(artifact);
5262
5262
  if (bad)
@@ -5270,6 +5270,83 @@ function verifySeal(artifact, publicKeyPem) {
5270
5270
  return sealFailure(artifact, publicKeyPem) ?? { decision: "VALID", kid };
5271
5271
  }
5272
5272
 
5273
+ // node_modules/@nomosprotocol/verify/dist/revocation.js
5274
+ var MAX_LIST_CERTS = 1e3;
5275
+ var REVOCATION_SUITES = ["Ed25519", "ES256"];
5276
+ var CERT_FIELDS2 = ["parent_kid", "child_kid", "child_public_key_pem", "issued_at", "expires_at", "algorithm", "signature"];
5277
+ var FP = /^[0-9a-f]{64}$/;
5278
+ 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 };
5279
+ var revocationListPayload = (l) => ({ generated_at: l.generated_at, statements: l.statements });
5280
+ var forSigning = (suite) => (scope) => suitePermitted(scope, suite) ? { ok: true } : { ok: false, dimension: "suite" };
5281
+ var str = (v) => typeof v === "string" && v !== "";
5282
+ var time2 = (s) => typeof s === "string" ? Date.parse(s) : NaN;
5283
+ function signerPem(kid, pool, root, at, suite, revokedKids, revokedCerts) {
5284
+ const r = resolveKey({ targetKid: kid, pool, rootPublicKeyPem: root, purpose: forSigning(suite), now: new Date(at), revokedKids, revokedCerts });
5285
+ return r.decision === "RESOLVED" ? r.pem : null;
5286
+ }
5287
+ function evaluateStatement(st, pool, rootPublicKeyPem, now) {
5288
+ if (st === null || typeof st !== "object" || Array.isArray(st))
5289
+ return "bad_statement";
5290
+ const hasKid = "revoked_kid" in st, hasFp = "revoked_fingerprint" in st;
5291
+ if (hasKid === hasFp)
5292
+ return "bad_statement";
5293
+ const target = hasKid ? st.revoked_kid : st.revoked_fingerprint;
5294
+ if (!str(target) || hasFp && !FP.test(target))
5295
+ return "bad_statement";
5296
+ if (typeof st.reason !== "string" || !["issued_at", "kid", "algorithm", "signature"].every((f) => str(st[f])))
5297
+ return "bad_statement";
5298
+ if (!REVOCATION_SUITES.includes(st.algorithm))
5299
+ return "bad_statement";
5300
+ const issued = time2(st.issued_at);
5301
+ if (Number.isNaN(issued) || issued > now.getTime())
5302
+ return "bad_statement";
5303
+ const certs = pool.filter((c) => c.parent_kid === st.kid && (hasKid ? c.child_kid === target : fingerprint(c) === target));
5304
+ if (certs.length === 0)
5305
+ return "no_certificate";
5306
+ const parent = signerPem(st.kid, pool, rootPublicKeyPem, issued, st.algorithm, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Set());
5307
+ if (!parent)
5308
+ return "signer_not_recognized";
5309
+ if (!certs.some((c) => verifySuite(c.algorithm, parent, jcs(certPayload(c)), { signature: c.signature }).ok))
5310
+ return "bad_certificate_signature";
5311
+ return verifySuite(st.algorithm, parent, jcs(revocationPayload(st)), { signature: st.signature }).ok ? null : "bad_signature";
5312
+ }
5313
+ function verifyRevocationList(list, o) {
5314
+ const L = list;
5315
+ if (L === null || typeof L !== "object" || !["generated_at", "kid", "algorithm", "signature"].every((f) => typeof L[f] === "string") || !Array.isArray(L.statements)) {
5316
+ return { decision: "MALFORMED", reason_code: "bad_list" };
5317
+ }
5318
+ const extra = L.key_certs ?? [];
5319
+ 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"))) {
5320
+ return { decision: "MALFORMED", reason_code: "bad_list_certs" };
5321
+ }
5322
+ const seen = /* @__PURE__ */ new Set(), pool = [];
5323
+ for (const c of [...extra, ...o.ownCerts ?? []]) {
5324
+ const fp = fingerprint(c);
5325
+ if (!seen.has(fp)) {
5326
+ seen.add(fp);
5327
+ pool.push(c);
5328
+ }
5329
+ }
5330
+ const kids = /* @__PURE__ */ new Set(), certs = /* @__PURE__ */ new Set(), ignored = [];
5331
+ L.statements.forEach((st, index) => {
5332
+ const why = evaluateStatement(st, pool, o.rootPublicKeyPem, o.now);
5333
+ if (why)
5334
+ ignored.push({ index, reason: why });
5335
+ else if ("revoked_kid" in st)
5336
+ kids.add(st.revoked_kid);
5337
+ else
5338
+ certs.add(st.revoked_fingerprint);
5339
+ });
5340
+ const out = { decision: "OK", revoked_kids: [...kids].sort(), revoked_certs: [...certs].sort(), ignored };
5341
+ const generated = time2(L.generated_at);
5342
+ if (Number.isNaN(generated) || generated > o.now.getTime() || !REVOCATION_SUITES.includes(L.algorithm))
5343
+ return out;
5344
+ const signer = signerPem(L.kid, pool, o.rootPublicKeyPem, generated, L.algorithm, kids, certs);
5345
+ if (signer && verifySuite(L.algorithm, signer, jcs(revocationListPayload(L)), { signature: L.signature }).ok)
5346
+ out.as_of = L.generated_at;
5347
+ return out;
5348
+ }
5349
+
5273
5350
  // src/verify-chain.ts
5274
5351
  var CHAIN_PROTOCOL_VERSION2 = "1";
5275
5352
  var DEFAULT_BASE = "https://www.nomosprotocol.com";
@@ -5316,43 +5393,371 @@ async function verifyChainRemote(params, opts) {
5316
5393
  throw new NomosAPIError(message, res.status, requestId);
5317
5394
  }
5318
5395
 
5396
+ // src/bindings.ts
5397
+ var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
5398
+ var hasOwn = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
5399
+ var isScalar = (v) => typeof v === "string" || typeof v === "boolean" || typeof v === "number" && Number.isFinite(v);
5400
+ function parseToolBindings(raw) {
5401
+ const no = (why) => {
5402
+ throw new Error(why);
5403
+ };
5404
+ const obj = raw;
5405
+ if (!obj || typeof obj !== "object" || Array.isArray(obj)) no("it is not a JSON object");
5406
+ if (obj.version !== 1) no('"version" must be 1');
5407
+ if (typeof obj.artifact !== "string" || !obj.artifact) no('"artifact" must name the sealed policy');
5408
+ if (typeof obj.reviewed !== "boolean") no('"reviewed" must be true or false');
5409
+ if (!obj.tools || typeof obj.tools !== "object" || Array.isArray(obj.tools)) no('"tools" must be an object');
5410
+ for (const k of Object.keys(obj)) if (!["version", "artifact", "reviewed", "tools", "_review"].includes(k)) no(`unknown setting "${k}"`);
5411
+ const tools = {};
5412
+ for (const tool of Object.keys(obj.tools)) {
5413
+ if (FORBIDDEN_KEYS.has(tool)) no(`"${tool}" isn't a usable tool name`);
5414
+ const entry = obj.tools[tool];
5415
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) no(`tool "${tool}" must be an object with "facts"`);
5416
+ for (const k of Object.keys(entry)) if (!["facts", "_why"].includes(k)) no(`tool "${tool}": unknown setting "${k}"`);
5417
+ const facts = entry.facts;
5418
+ if (!facts || typeof facts !== "object" || Array.isArray(facts)) no(`tool "${tool}" needs "facts"`);
5419
+ tools[tool] = { facts: {} };
5420
+ for (const fact of Object.keys(facts)) {
5421
+ const where = `${tool}.${fact}`;
5422
+ if (fact === "tool") no(`${where}: "tool" is set by the guard to the tool's name`);
5423
+ if (FORBIDDEN_KEYS.has(fact)) no(`${where} isn't a usable fact name`);
5424
+ const b = facts[fact];
5425
+ if (!b || typeof b !== "object" || Array.isArray(b)) no(`${where} must be an object with "arg"`);
5426
+ 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`);
5427
+ if (typeof b.arg !== "string" || !b.arg) no(`${where} needs "arg", the argument it comes from`);
5428
+ if (b.arg.split(".").some((s) => !s || FORBIDDEN_KEYS.has(s))) no(`${where}: "${b.arg}" isn't a usable argument path`);
5429
+ if (b.scale !== void 0 && b.map !== void 0) no(`${where}: use "scale" or "map", not both`);
5430
+ 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`);
5431
+ if (b.map !== void 0) {
5432
+ if (!b.map || typeof b.map !== "object" || Array.isArray(b.map)) no(`${where}: "map" must be an object`);
5433
+ 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`);
5434
+ }
5435
+ tools[tool].facts[fact] = { arg: b.arg, ...b.scale !== void 0 ? { scale: b.scale } : {}, ...b.map !== void 0 ? { map: b.map } : {} };
5436
+ }
5437
+ }
5438
+ return { version: 1, artifact: obj.artifact, reviewed: obj.reviewed, tools };
5439
+ }
5440
+ function applyToolBindings(bindings, tool, args) {
5441
+ const inputs = { tool };
5442
+ const unresolved = [];
5443
+ const facts = hasOwn(bindings.tools, tool) ? bindings.tools[tool].facts : {};
5444
+ for (const [fact, b] of Object.entries(facts)) {
5445
+ let v = args;
5446
+ for (const seg of b.arg.split(".")) v = v && typeof v === "object" && !Array.isArray(v) && hasOwn(v, seg) ? v[seg] : void 0;
5447
+ if (v === void 0 || v === null) {
5448
+ unresolved.push({ fact, arg: b.arg, why: "missing" });
5449
+ continue;
5450
+ }
5451
+ if (b.scale !== void 0) {
5452
+ if (typeof v !== "number" || !Number.isFinite(v)) {
5453
+ unresolved.push({ fact, arg: b.arg, why: "not_a_number" });
5454
+ continue;
5455
+ }
5456
+ inputs[fact] = Number((v * b.scale).toFixed(9));
5457
+ continue;
5458
+ }
5459
+ if (!isScalar(v)) {
5460
+ unresolved.push({ fact, arg: b.arg, why: "not_a_value" });
5461
+ continue;
5462
+ }
5463
+ if (b.map) {
5464
+ if (!hasOwn(b.map, String(v))) {
5465
+ unresolved.push({ fact, arg: b.arg, why: "not_in_map" });
5466
+ continue;
5467
+ }
5468
+ inputs[fact] = b.map[String(v)];
5469
+ continue;
5470
+ }
5471
+ inputs[fact] = v;
5472
+ }
5473
+ return { inputs, unresolved };
5474
+ }
5475
+ 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"]);
5476
+ var UNITS = { cents: 0.01, pence: 0.01, fils: 0.01, usd: null, dollars: null, aed: null, eur: null };
5477
+ var words = (name) => name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).map((w) => w.toLowerCase()).filter(Boolean);
5478
+ 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 }]);
5479
+ function suggestToolBindings(artifact, tools, fields) {
5480
+ const bindings = { version: 1, artifact, reviewed: false, tools: {} };
5481
+ const proposals = [];
5482
+ const unbound = [];
5483
+ for (const tool of tools) {
5484
+ bindings.tools[tool.name] = { facts: {} };
5485
+ const args = flatProperties(tool.inputSchema?.properties);
5486
+ for (const field of fields) {
5487
+ if (field.name === "tool") continue;
5488
+ const want = words(field.name).filter((w) => !WEAK.has(w) && !(w in UNITS));
5489
+ const scored = args.map((a) => {
5490
+ const raw = words(a.path);
5491
+ const have = raw.filter((w) => !WEAK.has(w) && !(w in UNITS));
5492
+ const shared = want.filter((w) => have.includes(w));
5493
+ const unit = raw.find((w) => w in UNITS);
5494
+ const same = words(field.name).join("_") === raw.join("_");
5495
+ const sameCore = want.length > 0 && want.length === have.length && shared.length === want.length;
5496
+ return { a, shared, unit, same, sameCore, score: same ? 3 : sameCore ? 2 : shared.length ? shared.length / Math.max(want.length, have.length) : 0 };
5497
+ }).filter((s) => s.score > 0).sort((x, y) => y.score - x.score);
5498
+ if (!scored.length) {
5499
+ unbound.push({ tool: tool.name, fact: field.name, candidates: [] });
5500
+ continue;
5501
+ }
5502
+ const best = scored[0];
5503
+ if (scored[1] && scored[1].score === best.score) {
5504
+ unbound.push({ tool: tool.name, fact: field.name, candidates: scored.filter((s) => s.score === best.score).map((s) => s.a.path) });
5505
+ continue;
5506
+ }
5507
+ const scale = best.unit ? UNITS[best.unit] ?? void 0 : void 0;
5508
+ const why = best.same ? "the same name" : best.sameCore ? "the same words" : `shares "${best.shared.join(", ")}"`;
5509
+ 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 } : {} });
5510
+ bindings.tools[tool.name].facts[field.name] = { arg: best.a.path, ...scale ? { scale } : {} };
5511
+ }
5512
+ }
5513
+ return { bindings, proposals, unbound };
5514
+ }
5515
+ function describeSuggestion(s) {
5516
+ const lines = [];
5517
+ for (const tool of Object.keys(s.bindings.tools)) {
5518
+ lines.push(`
5519
+ ${tool}`);
5520
+ 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})`);
5521
+ 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(", ")})` : ""}`);
5522
+ }
5523
+ return lines.join("\n");
5524
+ }
5525
+
5526
+ // src/act/bytes.ts
5527
+ var utf82 = (s) => new TextEncoder().encode(s);
5528
+ var fromUtf82 = (b) => new TextDecoder().decode(b);
5529
+ function concat2(...parts) {
5530
+ const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
5531
+ let o = 0;
5532
+ for (const p of parts) {
5533
+ out.set(p, o);
5534
+ o += p.length;
5535
+ }
5536
+ return out;
5537
+ }
5538
+ function equal2(a, b) {
5539
+ if (a.length !== b.length) return false;
5540
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
5541
+ return true;
5542
+ }
5543
+ function fromHex(h) {
5544
+ const out = new Uint8Array(h.length / 2);
5545
+ for (let i = 0; i < out.length; i++) out[i] = parseInt(h.slice(i * 2, i * 2 + 2), 16);
5546
+ return out;
5547
+ }
5548
+ function toBase642(b) {
5549
+ let s = "";
5550
+ for (let i = 0; i < b.length; i += 32768) s += String.fromCharCode(...b.subarray(i, i + 32768));
5551
+ return btoa(s);
5552
+ }
5553
+ function fromBase642(s) {
5554
+ const bin = atob(s);
5555
+ const out = new Uint8Array(bin.length);
5556
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
5557
+ return out;
5558
+ }
5559
+ var toBase64url2 = (b) => toBase642(b).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
5560
+ var fromBase64url2 = (s) => {
5561
+ const t = s.replace(/-/g, "+").replace(/_/g, "/");
5562
+ return fromBase642(t + "=".repeat((4 - t.length % 4) % 4));
5563
+ };
5564
+ function randomBytes3(n) {
5565
+ return globalThis.crypto.getRandomValues(new Uint8Array(n));
5566
+ }
5567
+ var randomUUID = () => globalThis.crypto.randomUUID();
5568
+
5319
5569
  // src/act/signer.ts
5320
- import { createHash, createPrivateKey, createPublicKey, generateKeyPairSync, sign as nodeSign } from "crypto";
5321
- var P256_N = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551n;
5322
5570
  function kidOf2(publicKeyPem) {
5323
- const der = createPublicKey(publicKeyPem).export({ type: "spki", format: "der" });
5324
- return createHash("sha256").update(der).digest("base64url").slice(0, 16);
5571
+ const kid = kidOf(publicKeyPem);
5572
+ if (!kid) throw new Error("kidOf: not a canonical Ed25519 or P-256 public key (NOMOS-SPEC-010 \xA73.1)");
5573
+ return kid;
5574
+ }
5575
+ var ED25519_SPKI = fromHex("302a300506032b6570032100");
5576
+ var P256_SPKI = fromHex("3059301306072a8648ce3d020106082a8648ce3d030107034200");
5577
+ var ED25519_PKCS8 = fromHex("302e020100300506032b657004220420");
5578
+ var P256_PKCS8_HEAD = fromHex("308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b0201010420");
5579
+ var P256_PKCS8_PUB = fromHex("a144034200");
5580
+ var OID_ED25519 = fromHex("2b6570");
5581
+ var OID_EC = fromHex("2a8648ce3d0201");
5582
+ var OID_P256 = fromHex("2a8648ce3d030107");
5583
+ function pem(label, der) {
5584
+ const b64 = toBase642(der);
5585
+ return `-----BEGIN ${label}-----
5586
+ ${b64.match(/.{1,64}/g).join("\n")}
5587
+ -----END ${label}-----
5588
+ `;
5589
+ }
5590
+ function pemBody(text) {
5591
+ const m = text.trim().match(/^-----BEGIN ([A-Z0-9 ]+)-----([\s\S]*?)-----END \1-----$/);
5592
+ if (!m) throw new Error("Signer: expected a PEM private key");
5593
+ return { label: m[1], der: fromBase642(m[2].replace(/\s+/g, "")) };
5594
+ }
5595
+ function tlv(b, at) {
5596
+ const tag = b[at];
5597
+ let len = b[at + 1], start = at + 2;
5598
+ if (len & 128) {
5599
+ const n = len & 127;
5600
+ if (n < 1 || n > 2) throw new Error("Signer: unsupported DER length");
5601
+ len = 0;
5602
+ for (let i = 0; i < n; i++) len = len << 8 | b[start + i];
5603
+ start += n;
5604
+ }
5605
+ if (start + len > b.length) throw new Error("Signer: truncated DER");
5606
+ return { tag, start, end: start + len };
5607
+ }
5608
+ function children(b, outer) {
5609
+ const out = [];
5610
+ for (let at = outer.start; at < outer.end; ) {
5611
+ const t = tlv(b, at);
5612
+ out.push(t);
5613
+ at = t.end;
5614
+ }
5615
+ return out;
5616
+ }
5617
+ function sec1Scalar(b, seq) {
5618
+ const [version, d, ...rest] = children(b, seq);
5619
+ if (version.tag !== 2 || d.tag !== 4 || d.end - d.start !== 32) throw new Error("Signer: malformed EC private key");
5620
+ for (const r of rest) {
5621
+ if (r.tag === 160) {
5622
+ const oid = tlv(b, r.start);
5623
+ 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)");
5624
+ }
5625
+ }
5626
+ return b.slice(d.start, d.end);
5325
5627
  }
5326
- function fromKeyObject(privateKey) {
5327
- const publicKeyPem = createPublicKey(privateKey).export({ type: "spki", format: "pem" }).toString();
5328
- const isEd = privateKey.asymmetricKeyType === "ed25519";
5329
- if (!isEd && !(privateKey.asymmetricKeyType === "ec" && privateKey.asymmetricKeyDetails?.namedCurve === "prime256v1")) {
5330
- throw new Error("Signer: only Ed25519 and P-256 keys are NOMOS signature suites (NOMOS-SPEC-010)");
5628
+ function readPrivateKey(text) {
5629
+ const { label, der } = pemBody(text);
5630
+ const top = tlv(der, 0);
5631
+ if (label === "EC PRIVATE KEY") return { type: "ES256", key: sec1Scalar(der, top) };
5632
+ if (label !== "PRIVATE KEY") throw new Error(`Signer: unsupported key type "${label}" (PKCS#8 "PRIVATE KEY" or SEC1 "EC PRIVATE KEY")`);
5633
+ const [, alg, key] = children(der, top);
5634
+ const [oid, params] = children(der, alg);
5635
+ const algOid = der.subarray(oid.start, oid.end);
5636
+ if (equal2(algOid, OID_ED25519)) {
5637
+ const inner = tlv(der, key.start);
5638
+ if (inner.tag !== 4 || inner.end - inner.start !== 32) throw new Error("Signer: malformed Ed25519 private key");
5639
+ return { type: "Ed25519", key: der.slice(inner.start, inner.end) };
5331
5640
  }
5641
+ if (equal2(algOid, OID_EC)) {
5642
+ 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)");
5643
+ return { type: "ES256", key: sec1Scalar(der, tlv(der, key.start)) };
5644
+ }
5645
+ throw new Error("Signer: only Ed25519 and P-256 keys are NOMOS signature suites (NOMOS-SPEC-010)");
5646
+ }
5647
+ function publicPem(s) {
5648
+ 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)));
5649
+ }
5650
+ function privatePem(s) {
5651
+ 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)));
5652
+ }
5653
+ function fromSecret(s) {
5654
+ const publicKeyPem = publicPem(s);
5332
5655
  return {
5333
5656
  kid: kidOf2(publicKeyPem),
5334
5657
  publicKeyPem,
5335
- algorithm: isEd ? "Ed25519" : "ES256",
5658
+ algorithm: s.type,
5336
5659
  async sign(payload) {
5337
- if (isEd) return nodeSign(null, Buffer.from(payload), privateKey).toString("base64");
5338
- const raw = nodeSign("sha256", Buffer.from(payload), { key: privateKey, dsaEncoding: "ieee-p1363" });
5339
- let s = BigInt("0x" + raw.subarray(32).toString("hex"));
5340
- if (s > P256_N / 2n) s = P256_N - s;
5341
- return Buffer.concat([raw.subarray(0, 32), Buffer.from(s.toString(16).padStart(64, "0"), "hex")]).toString("base64");
5660
+ return toBase642(s.type === "Ed25519" ? ed25519.sign(payload, s.key) : p256.sign(payload, s.key, { prehash: true, lowS: true, format: "compact" }));
5342
5661
  }
5343
5662
  };
5344
5663
  }
5345
5664
  function localSigner(privateKeyPem) {
5346
- return fromKeyObject(createPrivateKey(privateKeyPem));
5665
+ return fromSecret(readPrivateKey(privateKeyPem));
5347
5666
  }
5348
5667
  function generateSigner(algorithm = "Ed25519") {
5349
- const { privateKey } = algorithm === "Ed25519" ? generateKeyPairSync("ed25519") : generateKeyPairSync("ec", { namedCurve: "prime256v1" });
5350
- return { ...fromKeyObject(privateKey), privateKeyPem: privateKey.export({ type: "pkcs8", format: "pem" }).toString() };
5668
+ const s = algorithm === "Ed25519" ? { type: "Ed25519", key: ed25519.utils.randomSecretKey() } : { type: "ES256", key: p256.utils.randomSecretKey() };
5669
+ return { ...fromSecret(s), privateKeyPem: privatePem(s) };
5351
5670
  }
5352
5671
  function seededSigner(seed) {
5353
- const raw = createHash("sha256").update(seed).digest();
5354
- const der = Buffer.concat([Buffer.from("302e020100300506032b657004220420", "hex"), raw]);
5355
- return fromKeyObject(createPrivateKey({ key: der, format: "der", type: "pkcs8" }));
5672
+ return fromSecret({ type: "Ed25519", key: sha2562(seed) });
5673
+ }
5674
+ var sha2562 = (s) => sha256(new TextEncoder().encode(s));
5675
+
5676
+ // src/act/certificates.ts
5677
+ var MAX_PRESENTED = 20;
5678
+ function reliedKids(act2) {
5679
+ const kids = [act2?.authority?.artifact?.seal?.kid, ...(act2?.testimony ?? []).map((t) => t?.kid), ...(act2?.consents ?? []).map((c) => c?.kid)];
5680
+ return [...new Set(kids.filter((k) => typeof k === "string" && k !== ""))];
5681
+ }
5682
+ function presentedCertificates(available, act2) {
5683
+ const unique = /* @__PURE__ */ new Map();
5684
+ for (const c of available) unique.set(fingerprint(c), c);
5685
+ const kids = new Set(reliedKids(act2)), chosen = /* @__PURE__ */ new Map();
5686
+ for (let grew = true; grew; ) {
5687
+ grew = false;
5688
+ for (const [fp, c] of unique) {
5689
+ if (!chosen.has(fp) && kids.has(c.child_kid)) {
5690
+ chosen.set(fp, c);
5691
+ kids.add(c.parent_kid);
5692
+ grew = true;
5693
+ }
5694
+ }
5695
+ }
5696
+ return [...chosen.values()];
5697
+ }
5698
+ async function signRevocation(parent, target, reason, issuedAt = /* @__PURE__ */ new Date()) {
5699
+ const st = "kid" in target ? { revoked_kid: target.kid, reason, issued_at: issuedAt.toISOString() } : { revoked_fingerprint: fingerprint(target.certificate), reason, issued_at: issuedAt.toISOString() };
5700
+ if ("certificate" in target && target.certificate.parent_kid !== parent.kid) throw new Error("signRevocation: only the parent that issued a certificate can revoke it");
5701
+ return { ...st, kid: parent.kid, algorithm: parent.algorithm, signature: await parent.sign(jcs(revocationPayload(st))) };
5702
+ }
5703
+ async function signRevocationList(signer, statements, keyCerts = [], generatedAt = /* @__PURE__ */ new Date()) {
5704
+ const L = { generated_at: generatedAt.toISOString(), kid: signer.kid, algorithm: signer.algorithm, statements, ...keyCerts.length ? { key_certs: keyCerts } : {} };
5705
+ L.signature = await signer.sign(jcs(revocationListPayload(L)));
5706
+ return L;
5707
+ }
5708
+ function revocationListCertificates(available, statements, listSignerKid) {
5709
+ const byFp = new Map(available.map((c) => [fingerprint(c), c]));
5710
+ const revokedCerts = statements.map((s) => s.revoked_fingerprint && byFp.get(s.revoked_fingerprint)).filter((c) => !!c);
5711
+ const kids = [listSignerKid, ...statements.flatMap((s) => [s.kid, s.revoked_kid].filter((k) => !!k)), ...revokedCerts.map((c) => c.parent_kid)];
5712
+ const closure = presentedCertificates(available, { authority: { artifact: { seal: {} } }, testimony: kids.map((kid) => ({ kid })) });
5713
+ const out = new Map(closure.map((c) => [fingerprint(c), c]));
5714
+ for (const c of revokedCerts) out.set(fingerprint(c), c);
5715
+ return [...out.values()];
5716
+ }
5717
+ var RevocationUnavailable = class extends Error {
5718
+ constructor(message, retryAfterSeconds) {
5719
+ super(message);
5720
+ this.retryAfterSeconds = retryAfterSeconds;
5721
+ this.name = "RevocationUnavailable";
5722
+ }
5723
+ };
5724
+ function revocationSource(o) {
5725
+ const f = o.fetch ?? fetch, now = o.now ?? (() => /* @__PURE__ */ new Date());
5726
+ const maxAge = (o.maxAgeSeconds ?? 3600) * 1e3, refresh = (o.refreshSeconds ?? 60) * 1e3;
5727
+ const kids = /* @__PURE__ */ new Set(), certs = /* @__PURE__ */ new Set();
5728
+ let asOf = 0, fetchedAt = 0, inFlight = null, lastError = "";
5729
+ async function load() {
5730
+ fetchedAt = now().getTime();
5731
+ try {
5732
+ const res = await f(o.url, { headers: { accept: "application/json" } });
5733
+ if (!res.ok) {
5734
+ lastError = `${o.url} answered ${res.status}`;
5735
+ return;
5736
+ }
5737
+ const r = verifyRevocationList(await res.json(), { rootPublicKeyPem: o.root, now: now(), ownCerts: o.ownCerts });
5738
+ if (r.decision !== "OK") {
5739
+ lastError = `${o.url} served a malformed list (${r.reason_code})`;
5740
+ return;
5741
+ }
5742
+ for (const k of r.revoked_kids) kids.add(k);
5743
+ for (const c of r.revoked_certs) certs.add(c);
5744
+ if (r.as_of) asOf = Math.max(asOf, Date.parse(r.as_of));
5745
+ else lastError = `${o.url}'s list is not signed by a key your root reaches, so it does not show it is current`;
5746
+ } catch (e) {
5747
+ lastError = `${o.url}: ${e?.message ?? e}`;
5748
+ }
5749
+ }
5750
+ return async () => {
5751
+ const t = now().getTime();
5752
+ if (!inFlight && t - fetchedAt >= refresh) inFlight = load().finally(() => {
5753
+ inFlight = null;
5754
+ });
5755
+ if (inFlight) await inFlight;
5756
+ if (!asOf || t - asOf > maxAge) {
5757
+ 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));
5758
+ }
5759
+ return { kids: new Set(kids), certs: new Set(certs) };
5760
+ };
5356
5761
  }
5357
5762
 
5358
5763
  // src/act/documents.ts
@@ -5457,6 +5862,11 @@ var Rollback = class extends Error {
5457
5862
  this.answer = answer2;
5458
5863
  }
5459
5864
  };
5865
+ var LostRace = class extends Error {
5866
+ constructor() {
5867
+ super("replayed");
5868
+ }
5869
+ };
5460
5870
  function accept(o) {
5461
5871
  const kid = kidOf2(o.root);
5462
5872
  for (const a of o.authorities) {
@@ -5464,7 +5874,7 @@ function accept(o) {
5464
5874
  throw new Error(`accept(): authority ${a.artifactId} uses currencyPolicy "record" but has no inForceSealHash`);
5465
5875
  }
5466
5876
  }
5467
- if (!o.store) console.warn("[nomos] accept(): using the in-memory store. Nonces and receipts are lost on restart. Development only.");
5877
+ 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.");
5468
5878
  const store = o.store ?? memoryStore();
5469
5879
  const maxWindowSeconds = o.maxWindowSeconds ?? 900;
5470
5880
  const maxActBytes = o.maxActBytes ?? 1024 * 1024;
@@ -5490,8 +5900,7 @@ function accept(o) {
5490
5900
  valid_until: new Date(now().getTime() + (o.documentTtlSeconds ?? 86400) * 1e3).toISOString()
5491
5901
  };
5492
5902
  }
5493
- async function verifyWith(act2, authority, facts, at, seen) {
5494
- const rev = await o.revocations?.() ?? {};
5903
+ function verifyWith(act2, authority, facts, at, seen, rev) {
5495
5904
  return verifyAct(act2, {
5496
5905
  relyingPartyId: o.relyingPartyId,
5497
5906
  rootPublicKeyPem: o.root,
@@ -5523,9 +5932,20 @@ function accept(o) {
5523
5932
  const authority = o.authorities.find((a) => a.artifactId === artifactId);
5524
5933
  if (!authority) return answer({ decision: "NOT_ACCEPTED", reason_code: "authority_not_accepted", artifact_id: artifactId ?? null });
5525
5934
  if (!authority.actions.includes(act2?.action?.type)) return answer({ decision: "NOT_ACCEPTED", reason_code: "action_not_accepted", action_type: act2?.action?.type ?? null });
5935
+ let rev;
5936
+ try {
5937
+ rev = await o.revocations?.() ?? {};
5938
+ } catch (e) {
5939
+ const retry = e instanceof RevocationUnavailable ? e.retryAfterSeconds : 60;
5940
+ return {
5941
+ status: 503,
5942
+ headers: { "content-type": "application/json", "retry-after": String(retry) },
5943
+ body: { error: "Revocation status is not current, so nothing is committed. Resubmit this same Act later.", detail: String(e?.message ?? e), retry_after: retry }
5944
+ };
5945
+ }
5526
5946
  const verifiedAt = now();
5527
5947
  const facts = await o.localFacts?.(act2) ?? {};
5528
- const verdict = await verifyWith(act2, authority, facts, verifiedAt, await store.hasNonce(act2.nonce));
5948
+ const verdict = verifyWith(act2, authority, facts, verifiedAt, await store.hasNonce(act2.nonce), rev);
5529
5949
  const receiptOf = (v, f, committedAt) => ({
5530
5950
  act_id: act2.act_id,
5531
5951
  act_digest: String(v.act_digest ?? ""),
@@ -5545,35 +5965,34 @@ function accept(o) {
5545
5965
  if (verdict.decision !== "AUTHORIZED" || o.mode === "shadow") {
5546
5966
  await o.onVerdict?.(verdict, receiptOf(verdict, facts));
5547
5967
  if (verdict.decision === "ESCALATED") {
5548
- const esc2 = (act2.authority.artifact.governance?.escalations ?? []).find((e) => e?.id === verdict.escalation_id);
5549
- return answer(verdict, { role: esc2?.role_required ?? null, retry_until: act2.expires_at });
5968
+ const esc3 = (act2.authority.artifact.governance?.escalations ?? []).find((e) => e?.id === verdict.escalation_id);
5969
+ return answer(verdict, { role: esc3?.role_required ?? null, retry_until: act2.expires_at });
5550
5970
  }
5551
5971
  if (o.mode === "shadow") return { ...answer(verdict), status: 200, body: { ...answer(verdict).body, shadow: true } };
5552
5972
  return answer(verdict);
5553
5973
  }
5554
5974
  try {
5555
5975
  return await store.transaction(async (t) => {
5556
- if (!await t.claimNonce(act2.nonce)) {
5557
- throw new Rollback(answer({ decision: "REPLAYED" }, { act_id: act2.act_id, receipt: await store.receiptFor(act2.nonce) }));
5558
- }
5976
+ if (!await t.claimNonce(act2.nonce)) throw new LostRace();
5559
5977
  let final = verdict, finalFacts = facts;
5560
5978
  if (o.localFacts) {
5561
5979
  const fresh = await o.localFacts(act2, t.tx);
5562
5980
  if (!sameFacts(fresh, facts)) {
5563
5981
  finalFacts = fresh;
5564
- final = await verifyWith(act2, authority, fresh, verifiedAt, false);
5982
+ final = verifyWith(act2, authority, fresh, verifiedAt, false, rev);
5565
5983
  if (final.decision !== "AUTHORIZED") throw new Rollback(answer(final));
5566
5984
  }
5567
5985
  }
5568
5986
  const action = structuredClone({ type: act2.action.type, ...act2.action.subject !== void 0 ? { subject: act2.action.subject } : {}, params: act2.action.params ?? {} });
5569
5987
  const result = await o.commit(action, { tx: t.tx, actId: act2.act_id });
5570
5988
  const receipt = receiptOf(final, finalFacts, now());
5571
- await t.putReceipt(act2.nonce, receipt);
5989
+ await t.putReceipt(act2.nonce, receipt, act2);
5572
5990
  await o.onVerdict?.(final, receipt);
5573
5991
  return answer(final, { receipt, ...result !== void 0 ? { result } : {} });
5574
5992
  });
5575
5993
  } catch (e) {
5576
5994
  if (e instanceof Rollback) return e.answer;
5995
+ if (e instanceof LostRace) return answer({ decision: "REPLAYED" }, { act_id: act2.act_id, receipt: await store.receiptFor(act2.nonce) });
5577
5996
  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) } };
5578
5997
  }
5579
5998
  }
@@ -5612,7 +6031,7 @@ function accept(o) {
5612
6031
  if (size > maxActBytes) break;
5613
6032
  chunks.push(c);
5614
6033
  }
5615
- body = size > maxActBytes ? new Uint8Array(maxActBytes + 1) : Buffer.concat(chunks);
6034
+ body = size > maxActBytes ? new Uint8Array(maxActBytes + 1) : concat2(...chunks);
5616
6035
  }
5617
6036
  const a = await handle({ method: req.method, headers: req.headers, body });
5618
6037
  send(a.status, a.headers, JSON.stringify(a.body));
@@ -5623,7 +6042,7 @@ function accept(o) {
5623
6042
  function consentOrigins(act2) {
5624
6043
  const origins = (Array.isArray(act2?.consents) ? act2.consents : []).filter((c) => c?.algorithm === "WebAuthn-ES256" && typeof c.client_data_json === "string").map((c) => {
5625
6044
  try {
5626
- return JSON.parse(Buffer.from(c.client_data_json, "base64url").toString("utf8")).origin;
6045
+ return JSON.parse(fromUtf82(fromBase64url2(c.client_data_json))).origin;
5627
6046
  } catch {
5628
6047
  return null;
5629
6048
  }
@@ -5631,8 +6050,74 @@ function consentOrigins(act2) {
5631
6050
  return origins.length ? { consent_origins: [...new Set(origins)] } : {};
5632
6051
  }
5633
6052
 
6053
+ // src/act/postgres.ts
6054
+ var IDENT = /^[a-z_][a-z0-9_]{0,62}(\.[a-z_][a-z0-9_]{0,62})?$/;
6055
+ function postgresStore(pool, opts = {}) {
6056
+ const table = opts.table ?? "nomos_acts";
6057
+ if (!IDENT.test(table)) throw new Error(`postgresStore: "${table}" is not a plain lower-case table name`);
6058
+ const schema = `CREATE TABLE IF NOT EXISTS ${table} (
6059
+ nonce text PRIMARY KEY, -- SPEC-008 \xA75.2: the nonce ledger
6060
+ act_id text NOT NULL,
6061
+ receipt json NOT NULL, -- SPEC-008 \xA79; json, not jsonb: kept byte for byte
6062
+ act json NOT NULL, -- the exact Act verified, for re-verification
6063
+ committed_at timestamptz NOT NULL DEFAULT now()
6064
+ );`;
6065
+ return {
6066
+ schema,
6067
+ async migrate() {
6068
+ await pool.query(schema);
6069
+ },
6070
+ async hasNonce(nonce) {
6071
+ const r = await pool.query(`SELECT 1 FROM ${table} WHERE nonce = $1`, [nonce]);
6072
+ return (r.rowCount ?? r.rows.length) > 0;
6073
+ },
6074
+ async receiptFor(nonce) {
6075
+ const r = await pool.query(`SELECT receipt FROM ${table} WHERE nonce = $1`, [nonce]);
6076
+ return r.rows[0]?.receipt ?? null;
6077
+ },
6078
+ async transaction(fn) {
6079
+ const client = await pool.connect();
6080
+ let claimed = null, completed = false;
6081
+ try {
6082
+ await client.query("BEGIN");
6083
+ const out = await fn({
6084
+ tx: client,
6085
+ async claimNonce(nonce) {
6086
+ const r = await client.query(
6087
+ `INSERT INTO ${table} (nonce, act_id, receipt, act) VALUES ($1, '', '{}'::json, '{}'::json)
6088
+ ON CONFLICT (nonce) DO NOTHING RETURNING nonce`,
6089
+ [nonce]
6090
+ );
6091
+ const won = (r.rowCount ?? r.rows.length) === 1;
6092
+ if (won) claimed = nonce;
6093
+ return won;
6094
+ },
6095
+ async putReceipt(nonce, receipt, act2) {
6096
+ if (nonce !== claimed) throw new Error("postgresStore: putReceipt for a nonce this transaction did not claim");
6097
+ await client.query(
6098
+ `UPDATE ${table} SET act_id = $2, receipt = $3::json, act = $4::json WHERE nonce = $1`,
6099
+ [nonce, receipt.act_id, JSON.stringify(receipt), JSON.stringify(act2 ?? {})]
6100
+ );
6101
+ completed = true;
6102
+ }
6103
+ });
6104
+ if (claimed && !completed) throw new Error("postgresStore: the transaction claimed a nonce but stored no receipt");
6105
+ await client.query("COMMIT");
6106
+ client.release();
6107
+ return out;
6108
+ } catch (e) {
6109
+ let broken;
6110
+ await client.query("ROLLBACK").catch((err) => {
6111
+ broken = err;
6112
+ });
6113
+ client.release(broken);
6114
+ throw e;
6115
+ }
6116
+ }
6117
+ };
6118
+ }
6119
+
5634
6120
  // src/act/statements.ts
5635
- import { randomBytes as randomBytes3, randomUUID } from "crypto";
5636
6121
  async function certify(parent, child, opts) {
5637
6122
  const unsigned = {
5638
6123
  parent_kid: parent.kid,
@@ -5687,7 +6172,7 @@ function buildAct(o) {
5687
6172
  const act2 = {
5688
6173
  act_version: "1",
5689
6174
  act_id: `act-${randomUUID()}`,
5690
- nonce: randomBytes3(16).toString("base64"),
6175
+ nonce: toBase642(randomBytes3(16)),
5691
6176
  // Issued a second in the past so a relying party's clock a moment behind ours still accepts it.
5692
6177
  issued_at: new Date(now.getTime() - 1e3).toISOString(),
5693
6178
  expires_at: new Date(now.getTime() - 1e3 + window * 1e3).toISOString(),
@@ -5701,6 +6186,575 @@ function buildAct(o) {
5701
6186
  return act2;
5702
6187
  }
5703
6188
 
6189
+ // src/act/registry.ts
6190
+ function memoryRegistryStore() {
6191
+ const certs = [], sts = [];
6192
+ return {
6193
+ async certificates() {
6194
+ return [...certs];
6195
+ },
6196
+ async addCertificate(c) {
6197
+ if (!certs.some((x) => fingerprint(x) === fingerprint(c))) certs.push(c);
6198
+ },
6199
+ async statements() {
6200
+ return [...sts];
6201
+ },
6202
+ async addStatement(s) {
6203
+ sts.push(s);
6204
+ }
6205
+ };
6206
+ }
6207
+ var RegistryError = class extends Error {
6208
+ constructor(message, code) {
6209
+ super(message);
6210
+ this.code = code;
6211
+ this.name = "RegistryError";
6212
+ }
6213
+ };
6214
+ var any = () => ({ ok: true });
6215
+ function certificateRegistry(o) {
6216
+ const rootKid = kidOf(o.root);
6217
+ if (!rootKid) throw new RegistryError("certificateRegistry: the root is not a canonical Ed25519 or P-256 public key", "bad_root");
6218
+ const now = o.now ?? (() => /* @__PURE__ */ new Date());
6219
+ async function revokedSets() {
6220
+ const kids = /* @__PURE__ */ new Set(), certs = /* @__PURE__ */ new Set();
6221
+ for (const s of await o.store.statements()) s.revoked_kid ? kids.add(s.revoked_kid) : s.revoked_fingerprint && certs.add(s.revoked_fingerprint);
6222
+ return { kids, certs };
6223
+ }
6224
+ async function scopesOf(kid) {
6225
+ if (kid === rootKid) return { ok: true, scopes: [{ terms: {} }] };
6226
+ const { kids, certs } = await revokedSets();
6227
+ const r = resolveKey({ targetKid: kid, pool: await o.store.certificates(), rootPublicKeyPem: o.root, purpose: any, now: now(), revokedKids: kids, revokedCerts: certs, allPaths: true });
6228
+ if (r.decision !== "RESOLVED") return { ok: false, reason: `${r.decision}${r.reason_code ? ` (${r.reason_code})` : ""}` };
6229
+ return { ok: true, scopes: r.admitted ?? [r.effective] };
6230
+ }
6231
+ async function pool(filter) {
6232
+ const { kids, certs } = await revokedSets(), t = now().getTime();
6233
+ 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)));
6234
+ }
6235
+ return {
6236
+ rootKid,
6237
+ pool,
6238
+ /** NOMOS-SPEC-007 §3: certify `child` under `parent`. Refuses anything a verifier would refuse. */
6239
+ async issue(io) {
6240
+ 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");
6241
+ const scope = parseScope(io.scope ?? null);
6242
+ if (!scope.ok) throw new RegistryError(`Malformed scope: ${scope.detail}`, "malformed_scope");
6243
+ const parentScopes = await scopesOf(io.parent.kid);
6244
+ if (!parentScopes.ok) throw new RegistryError(`The parent ${io.parent.kid} is not reached from the root: ${parentScopes.reason}`, "parent_not_recognized");
6245
+ const fits = parentScopes.scopes.filter((p) => suitePermitted(p, io.parent.algorithm) && isNarrowerOrEqual(scope.scope, p).ok);
6246
+ if (!fits.length) {
6247
+ const widened = parentScopes.scopes.map((p) => isNarrowerOrEqual(scope.scope, p)).find((x) => !x.ok);
6248
+ 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");
6249
+ }
6250
+ const expiresAt = io.expiresAt ?? (io.days ? new Date(now().getTime() + io.days * 864e5) : void 0);
6251
+ if (!expiresAt) throw new RegistryError("Give expiresAt or days: a certificate always expires", "no_expiry");
6252
+ const c = await certify(io.parent, io.child, { scope: io.scope, issuedAt: io.issuedAt ?? now(), expiresAt });
6253
+ await o.store.addCertificate(c);
6254
+ return c;
6255
+ },
6256
+ /** rev. 1.10 §5.1: `parent` revokes a key it certified. The key is dead on every path. */
6257
+ async revokeKey(io) {
6258
+ const certs = (await o.store.certificates()).filter((c) => c.child_kid === io.kid && c.parent_kid === io.parent.kid);
6259
+ if (!certs.length) throw new RegistryError(`${io.parent.kid} never certified ${io.kid}: only a key's parent can revoke it`, "not_the_parent");
6260
+ const st = await signRevocation(io.parent, { kid: io.kid }, io.reason, now());
6261
+ await o.store.addStatement(st);
6262
+ return st;
6263
+ },
6264
+ /** rev. 1.10 §5.1: withdraw one certificate; the key stays valid under any other. */
6265
+ async revokeCertificate(io) {
6266
+ const c = (await o.store.certificates()).find((x) => fingerprint(x) === io.fingerprint);
6267
+ if (!c) throw new RegistryError(`No certificate ${io.fingerprint} in this registry`, "unknown_certificate");
6268
+ const st = await signRevocation(io.parent, { certificate: c }, io.reason, now());
6269
+ await o.store.addStatement(st);
6270
+ return st;
6271
+ },
6272
+ /** The certificates that connect `kid` to the root: what a witness or approver returns with
6273
+ * what it signs (SPEC-009 0.2.0 §4, §5). */
6274
+ async chainFor(kid) {
6275
+ return presentedCertificates(await pool(), { authority: { artifact: { seal: { kid } } } });
6276
+ },
6277
+ /** How `kid` is reached from the root, and with which effective scopes, or why it is not. */
6278
+ async check(kid) {
6279
+ return scopesOf(kid);
6280
+ },
6281
+ certificates: () => o.store.certificates(),
6282
+ statements: () => o.store.statements(),
6283
+ /** rev. 1.10 §5.3: the signed list, carrying the certificates that make it checkable. The
6284
+ * signer should be reached from the root, or the list establishes no date (§5.3). */
6285
+ async revocationList(signer) {
6286
+ const statements = await o.store.statements();
6287
+ return signRevocationList(signer, statements, revocationListCertificates(await o.store.certificates(), statements, signer.kid), now());
6288
+ }
6289
+ };
6290
+ }
6291
+ function postgresRegistryStore(pool, opts = {}) {
6292
+ const p = opts.prefix ?? "nomos_registry";
6293
+ if (!/^[a-z_][a-z0-9_]{0,40}$/.test(p)) throw new RegistryError(`"${p}" is not a plain lower-case prefix`, "bad_prefix");
6294
+ const schema = `CREATE TABLE IF NOT EXISTS ${p}_certificates (
6295
+ 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()
6296
+ );
6297
+ CREATE TABLE IF NOT EXISTS ${p}_statements (
6298
+ id bigserial PRIMARY KEY, target text NOT NULL, kid text NOT NULL, statement json NOT NULL, added_at timestamptz NOT NULL DEFAULT now()
6299
+ );`;
6300
+ return {
6301
+ schema,
6302
+ async migrate() {
6303
+ await pool.query(schema);
6304
+ },
6305
+ async certificates() {
6306
+ return (await pool.query(`SELECT certificate FROM ${p}_certificates ORDER BY added_at, fingerprint`)).rows.map((r) => r.certificate);
6307
+ },
6308
+ async addCertificate(c) {
6309
+ await pool.query(
6310
+ `INSERT INTO ${p}_certificates (fingerprint, child_kid, parent_kid, certificate) VALUES ($1, $2, $3, $4::json) ON CONFLICT (fingerprint) DO NOTHING`,
6311
+ [fingerprint(c), c.child_kid, c.parent_kid, JSON.stringify(c)]
6312
+ );
6313
+ },
6314
+ async statements() {
6315
+ return (await pool.query(`SELECT statement FROM ${p}_statements ORDER BY id`)).rows.map((r) => r.statement);
6316
+ },
6317
+ async addStatement(s) {
6318
+ 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)]);
6319
+ }
6320
+ };
6321
+ }
6322
+
6323
+ // src/act/passkeys.ts
6324
+ function cbor(b) {
6325
+ let i = 0;
6326
+ const len = (info) => {
6327
+ if (info < 24) return info;
6328
+ const n = info === 24 ? 1 : info === 25 ? 2 : info === 26 ? 4 : -1;
6329
+ if (n < 0) throw new Error("unsupported CBOR length");
6330
+ let v2 = 0;
6331
+ for (let k = 0; k < n; k++) v2 = v2 * 256 + b[i++];
6332
+ return v2;
6333
+ };
6334
+ const item = (depth) => {
6335
+ if (depth > 16 || i >= b.length) throw new Error("malformed CBOR");
6336
+ const head = b[i++], major = head >> 5, info = head & 31;
6337
+ switch (major) {
6338
+ case 0:
6339
+ return len(info);
6340
+ case 1:
6341
+ return -1 - len(info);
6342
+ case 2: {
6343
+ const n = len(info);
6344
+ const v2 = b.slice(i, i + n);
6345
+ i += n;
6346
+ if (v2.length !== n) throw new Error("truncated CBOR");
6347
+ return v2;
6348
+ }
6349
+ case 3: {
6350
+ const n = len(info);
6351
+ const v2 = fromUtf82(b.slice(i, i + n));
6352
+ i += n;
6353
+ return v2;
6354
+ }
6355
+ case 4: {
6356
+ const n = len(info), a = [];
6357
+ for (let k = 0; k < n; k++) a.push(item(depth + 1));
6358
+ return a;
6359
+ }
6360
+ case 5: {
6361
+ const n = len(info), m = /* @__PURE__ */ new Map();
6362
+ for (let k = 0; k < n; k++) m.set(item(depth + 1), item(depth + 1));
6363
+ return m;
6364
+ }
6365
+ case 7:
6366
+ if (info === 20) return false;
6367
+ if (info === 21) return true;
6368
+ if (info === 22) return null;
6369
+ throw new Error("unsupported CBOR simple value");
6370
+ default:
6371
+ throw new Error("unsupported CBOR type");
6372
+ }
6373
+ };
6374
+ const v = item(0);
6375
+ return { value: v, end: i };
6376
+ }
6377
+ function readAttestation(attestationObject) {
6378
+ const att = cbor(attestationObject).value;
6379
+ if (!(att instanceof Map) || !(att.get("authData") instanceof Uint8Array)) throw new Error("not an attestation object");
6380
+ const a = att.get("authData");
6381
+ if (a.length < 55 || !(a[32] & 64)) throw new Error("no attested credential data");
6382
+ const idLen = a[53] << 8 | a[54];
6383
+ const credentialId = a.slice(55, 55 + idLen);
6384
+ const cose = cbor(a.slice(55 + idLen)).value;
6385
+ 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)");
6386
+ const x = cose.get(-2), y = cose.get(-3);
6387
+ if (!(x instanceof Uint8Array) || !(y instanceof Uint8Array) || x.length !== 32 || y.length !== 32) throw new Error("malformed COSE key");
6388
+ 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);
6389
+ const publicKeyPem = pem("PUBLIC KEY", spki);
6390
+ if (!kidOf(publicKeyPem)) throw new Error("the passkey's key is not a valid P-256 point");
6391
+ return { authData: a, flags: a[32], credentialId, publicKeyPem };
6392
+ }
6393
+ async function signEnrolmentToken(authority, c) {
6394
+ if (!/^[^,\s]+$/.test(c.approver)) throw new Error("An approver id has no commas or spaces (NOMOS-SPEC-007 rev. 1.9 \xA73.7)");
6395
+ const claims = { approver: c.approver, roles: [...c.roles], origin: c.origin.replace(/\/$/, ""), expires_at: c.expiresAt.toISOString(), nonce: toBase64url2(randomBytes3(16)) };
6396
+ const token = { ...claims, kid: authority.kid, algorithm: authority.algorithm, signature: await authority.sign(jcs(claims)) };
6397
+ return toBase64url2(utf82(JSON.stringify(token)));
6398
+ }
6399
+ function verifyEnrolmentToken(token, authorityPem, origin, now) {
6400
+ let t;
6401
+ try {
6402
+ t = JSON.parse(fromUtf82(fromBase64url2(token)));
6403
+ } catch {
6404
+ return { error: "not an enrolment link" };
6405
+ }
6406
+ const claims = { approver: t?.approver, roles: t?.roles, origin: t?.origin, expires_at: t?.expires_at, nonce: t?.nonce };
6407
+ 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" };
6408
+ 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" };
6409
+ if (claims.origin !== origin) return { error: "this enrolment link is for another service" };
6410
+ if (!(Date.parse(claims.expires_at) > now.getTime())) return { error: "this enrolment link has expired" };
6411
+ return claims;
6412
+ }
6413
+ function memoryPasskeyStore() {
6414
+ const used = /* @__PURE__ */ new Set(), challenges = /* @__PURE__ */ new Map(), creds = [], pend = /* @__PURE__ */ new Map();
6415
+ return {
6416
+ async claimToken(n) {
6417
+ if (used.has(n)) return false;
6418
+ used.add(n);
6419
+ return true;
6420
+ },
6421
+ async tokenUsed(n) {
6422
+ return used.has(n);
6423
+ },
6424
+ async saveEnrolmentChallenge(n, c) {
6425
+ if (!used.has(n)) challenges.set(n, c);
6426
+ },
6427
+ async enrolmentChallenge(n) {
6428
+ return used.has(n) ? null : challenges.get(n) ?? null;
6429
+ },
6430
+ async addCredential(c) {
6431
+ creds.push(structuredClone(c));
6432
+ },
6433
+ async credentials() {
6434
+ return structuredClone(creds);
6435
+ },
6436
+ async createPending(p) {
6437
+ pend.set(p.id, structuredClone(p));
6438
+ },
6439
+ async pending(id) {
6440
+ const p = pend.get(id);
6441
+ return p ? structuredClone(p) : null;
6442
+ },
6443
+ async addAttempt(id, c, a) {
6444
+ const p = pend.get(id);
6445
+ if (!p || p.status !== "pending") return false;
6446
+ p.attempts[c] = { ...a };
6447
+ return true;
6448
+ },
6449
+ async resolvePending(id, out) {
6450
+ const p = pend.get(id);
6451
+ if (!p || p.status !== "pending") return false;
6452
+ Object.assign(p, structuredClone(out));
6453
+ return true;
6454
+ }
6455
+ };
6456
+ }
6457
+ function postgresPasskeyStore(pool, opts = {}) {
6458
+ const p = opts.prefix ?? "nomos_passkeys";
6459
+ if (!/^[a-z_][a-z0-9_]{0,40}$/.test(p)) throw new Error(`"${p}" is not a plain lower-case prefix`);
6460
+ const schema = `CREATE TABLE IF NOT EXISTS ${p}_tokens (nonce text PRIMARY KEY, challenge text, used boolean NOT NULL DEFAULT false);
6461
+ CREATE TABLE IF NOT EXISTS ${p}_credentials (credential_id text PRIMARY KEY, credential json NOT NULL, added_at timestamptz NOT NULL DEFAULT now());
6462
+ CREATE TABLE IF NOT EXISTS ${p}_pending (id text PRIMARY KEY, approval jsonb NOT NULL, updated_at timestamptz NOT NULL DEFAULT now());`;
6463
+ return {
6464
+ schema,
6465
+ async migrate() {
6466
+ await pool.query(schema);
6467
+ },
6468
+ async claimToken(n) {
6469
+ 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]);
6470
+ return (r.rowCount ?? r.rows.length) === 1;
6471
+ },
6472
+ async tokenUsed(n) {
6473
+ return (await pool.query(`SELECT 1 FROM ${p}_tokens WHERE nonce = $1 AND used = true`, [n])).rows.length > 0;
6474
+ },
6475
+ async saveEnrolmentChallenge(n, c) {
6476
+ 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]);
6477
+ },
6478
+ async enrolmentChallenge(n) {
6479
+ return (await pool.query(`SELECT challenge FROM ${p}_tokens WHERE nonce = $1 AND used = false`, [n])).rows[0]?.challenge ?? null;
6480
+ },
6481
+ async addCredential(c) {
6482
+ await pool.query(`INSERT INTO ${p}_credentials (credential_id, credential) VALUES ($1, $2::json)`, [c.credential_id, JSON.stringify(c)]);
6483
+ },
6484
+ async credentials() {
6485
+ return (await pool.query(`SELECT credential FROM ${p}_credentials ORDER BY added_at`)).rows.map((r) => r.credential);
6486
+ },
6487
+ async createPending(a) {
6488
+ await pool.query(`INSERT INTO ${p}_pending (id, approval) VALUES ($1, $2::jsonb)`, [a.id, JSON.stringify(a)]);
6489
+ },
6490
+ async pending(id) {
6491
+ return (await pool.query(`SELECT approval FROM ${p}_pending WHERE id = $1`, [id])).rows[0]?.approval ?? null;
6492
+ },
6493
+ // Each change is one conditional statement: Postgres serializes them on the row, and none
6494
+ // rewrites the whole record, so a stale reader cannot write an old status back.
6495
+ async addAttempt(id, c, a) {
6496
+ 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)]);
6497
+ return (r.rowCount ?? r.rows.length) === 1;
6498
+ },
6499
+ async resolvePending(id, out) {
6500
+ 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)]);
6501
+ return (r.rowCount ?? r.rows.length) === 1;
6502
+ }
6503
+ };
6504
+ }
6505
+ var MAX_ATTEMPTS = 50;
6506
+ var esc = (s) => String(s).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
6507
+ function passkeyApprover(o) {
6508
+ const base = o.origin.replace(/\/$/, "");
6509
+ const rpId = o.rpId ?? new URL(base).hostname;
6510
+ const store = o.store ?? memoryPasskeyStore();
6511
+ if (!o.store) console.warn("[nomos] passkeyApprover(): using the in-memory store. Enrolled passkeys are lost on restart. Development only; use postgresPasskeyStore() in production.");
6512
+ const now = o.now ?? (() => /* @__PURE__ */ new Date());
6513
+ const rpIdHash = sha256(utf82(rpId));
6514
+ const expired = (p) => p.status === "pending" && Date.parse(p.act.expires_at) <= now().getTime();
6515
+ const statusOf = (p) => expired(p) ? "expired" : p.status;
6516
+ function clientData(b64, type, challenge) {
6517
+ let c;
6518
+ try {
6519
+ c = JSON.parse(fromUtf82(fromBase64url2(b64)));
6520
+ } catch {
6521
+ return "unreadable client data";
6522
+ }
6523
+ if (c?.type !== type) return `expected a ${type} ceremony`;
6524
+ if (c?.challenge !== challenge) return "the challenge does not match";
6525
+ if (c?.origin !== base) return `the ceremony ran on ${c?.origin}, not ${base}`;
6526
+ return null;
6527
+ }
6528
+ async function route(method, path, json) {
6529
+ if (method === "POST" && path === "/nomos/approvals") {
6530
+ const { role, act: act2 } = await json();
6531
+ if (!o.roles.includes(role)) return { status: 404, body: { error: `This service does not approve as ${role}.` } };
6532
+ if (typeof act2?.expires_at !== "string" || !act2?.action) return { status: 400, body: { error: "The request needs the Act." } };
6533
+ const id = randomUUID();
6534
+ await store.createPending({ id, role, act: act2, status: "pending", created_at: now().toISOString(), attempts: {} });
6535
+ await o.onPending?.({ id, role, act: act2, approvalUrl: `${base}/approve/${id}` });
6536
+ return { status: 202, body: { approval_id: id, status: "pending", poll_url: `${base}/nomos/approvals/${id}`, approval_url: `${base}/approve/${id}` } };
6537
+ }
6538
+ let m = path.match(/^\/nomos\/approvals\/([0-9a-f-]{36})$/);
6539
+ if (method === "GET" && m) {
6540
+ const p = await store.pending(m[1]);
6541
+ if (!p) return null;
6542
+ const s = statusOf(p);
6543
+ return { status: 200, body: { status: s, ...s === "approved" ? { consent: p.consent, ...p.key_certs?.length ? { key_certs: p.key_certs } : {} } : {} } };
6544
+ }
6545
+ m = path.match(/^\/approve\/([0-9a-f-]{36})(?:\/(devices|options|assertion|refuse))?$/);
6546
+ if (m) {
6547
+ const p = await store.pending(m[1]);
6548
+ if (!p) return null;
6549
+ 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) };
6550
+ if (method !== "POST" || !m[2]) return null;
6551
+ if (statusOf(p) !== "pending") return { status: 409, body: { error: `This request is ${statusOf(p)}.` } };
6552
+ 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." } };
6553
+ const req = await json();
6554
+ if (m[2] === "devices") {
6555
+ const mine = (await store.credentials()).filter((c) => c.approver === String(req?.approver ?? "") && c.roles.includes(p.role));
6556
+ return { status: 200, body: { devices: mine.map((c) => ({ credential_id: c.credential_id, label: c.label })) } };
6557
+ }
6558
+ const cred = (await store.credentials()).find((c) => c.credential_id === req?.credential_id);
6559
+ if (!cred || !cred.roles.includes(p.role)) return { status: 403, body: { error: `That passkey does not approve as ${p.role}.` } };
6560
+ if (m[2] === "options") {
6561
+ const signed_at = now().toISOString();
6562
+ const payload2 = consentPayload({ binding_digest: bindingDigest(p.act), role: p.role, decision: "approve", signed_at, kid: cred.kid });
6563
+ const challenge = toBase64url2(sha256(jcs(payload2)));
6564
+ if (Object.keys(p.attempts).length >= MAX_ATTEMPTS) return { status: 429, body: { error: "Too many approval ceremonies for this request." } };
6565
+ const expires_at = new Date(Math.min(Date.parse(p.act.expires_at), now().getTime() + 5 * 6e4)).toISOString();
6566
+ 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." } };
6567
+ return { status: 200, body: { challenge, rpId, allowCredentials: [{ type: "public-key", id: cred.credential_id }], userVerification: "required", timeout: 12e4 } };
6568
+ }
6569
+ let offered;
6570
+ try {
6571
+ offered = JSON.parse(fromUtf82(fromBase64url2(String(req.client_data_json ?? "")))).challenge;
6572
+ } catch {
6573
+ return { status: 400, body: { error: "unreadable client data" } };
6574
+ }
6575
+ const attempt = p.attempts[offered];
6576
+ if (!attempt || attempt.credential_id !== cred.credential_id) return { status: 400, body: { error: "This ceremony was not offered for this passkey." } };
6577
+ if (Date.parse(attempt.expires_at) <= now().getTime()) return { status: 400, body: { error: "This ceremony has expired; start again." } };
6578
+ const consent = {
6579
+ binding_digest: bindingDigest(p.act),
6580
+ role: p.role,
6581
+ decision: "approve",
6582
+ signed_at: attempt.signed_at,
6583
+ kid: cred.kid,
6584
+ algorithm: "WebAuthn-ES256",
6585
+ authenticator_data: String(req.authenticator_data ?? ""),
6586
+ client_data_json: String(req.client_data_json ?? ""),
6587
+ signature: String(req.signature ?? "")
6588
+ };
6589
+ const payload = jcs(consentPayload(consent));
6590
+ const cd = clientData(consent.client_data_json, "webauthn.get", toBase64url2(sha256(payload)));
6591
+ if (cd) return { status: 400, body: { error: cd } };
6592
+ const ad = fromBase64url2(consent.authenticator_data);
6593
+ if (ad.length < 37 || !equal2(ad.slice(0, 32), rpIdHash)) return { status: 400, body: { error: "The assertion is for another relying-party id." } };
6594
+ const v = verifySuite("WebAuthn-ES256", cred.public_key_pem, payload, consent);
6595
+ if (!v.ok) return { status: 400, body: { error: `The passkey's signature does not verify (${v.reason}).` } };
6596
+ const key_certs = await o.registry.chainFor(cred.kid);
6597
+ if (!key_certs.length) return { status: 409, body: { error: "This passkey is no longer certified (revoked or expired)." } };
6598
+ if (!await store.resolvePending(p.id, { status: "approved", consent, key_certs })) return { status: 409, body: { error: "This request is no longer pending." } };
6599
+ return { status: 200, body: { status: "approved" } };
6600
+ }
6601
+ if (method === "GET" && path === "/enroll") return { status: 200, type: "text/html", body: enrolPage(o.rpName ?? rpId) };
6602
+ if (method === "POST" && (path === "/nomos/enrol/begin" || path === "/nomos/enrol/finish")) {
6603
+ const req = await json();
6604
+ const claims = verifyEnrolmentToken(String(req?.token ?? ""), o.enrolmentAuthority, base, now());
6605
+ if ("error" in claims) return { status: 403, body: { error: claims.error } };
6606
+ if (!claims.roles.every((r) => o.roles.includes(r))) return { status: 403, body: { error: `This service does not enrol for ${claims.roles.join(", ")}.` } };
6607
+ if (path === "/nomos/enrol/begin") {
6608
+ if (await store.tokenUsed(claims.nonce)) return { status: 403, body: { error: "This enrolment link has been used." } };
6609
+ const challenge2 = toBase64url2(randomBytes3(32));
6610
+ await store.saveEnrolmentChallenge(claims.nonce, challenge2);
6611
+ const existing = (await store.credentials()).filter((c) => c.approver === claims.approver);
6612
+ return { status: 200, body: {
6613
+ challenge: challenge2,
6614
+ rp: { id: rpId, name: o.rpName ?? rpId },
6615
+ user: { id: toBase64url2(utf82(claims.approver)), name: claims.approver, displayName: claims.approver },
6616
+ pubKeyCredParams: [{ type: "public-key", alg: -7 }],
6617
+ authenticatorSelection: { userVerification: "required", residentKey: "preferred" },
6618
+ attestation: "none",
6619
+ timeout: 12e4,
6620
+ excludeCredentials: existing.map((c) => ({ type: "public-key", id: c.credential_id })),
6621
+ approver: claims.approver,
6622
+ roles: claims.roles
6623
+ } };
6624
+ }
6625
+ const challenge = await store.enrolmentChallenge(claims.nonce);
6626
+ if (!challenge) return { status: 403, body: { error: "Start enrolment again: this link has been used or has no ceremony in progress." } };
6627
+ const cd = clientData(String(req.client_data_json ?? ""), "webauthn.create", challenge);
6628
+ if (cd) return { status: 400, body: { error: cd } };
6629
+ let att;
6630
+ try {
6631
+ att = readAttestation(fromBase64url2(String(req.attestation_object ?? "")));
6632
+ } catch (e) {
6633
+ return { status: 400, body: { error: String(e?.message ?? e) } };
6634
+ }
6635
+ if (!equal2(att.authData.slice(0, 32), rpIdHash)) return { status: 400, body: { error: "The passkey was created for another relying-party id." } };
6636
+ 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)." } };
6637
+ if (toBase64url2(att.credentialId) !== req.credential_id) return { status: 400, body: { error: "The credential id does not match the attestation." } };
6638
+ if (!await store.claimToken(claims.nonce)) return { status: 403, body: { error: "This enrolment link has been used." } };
6639
+ const kid = kidOf(att.publicKeyPem);
6640
+ const certificate = await o.registry.issue({
6641
+ parent: o.enroller,
6642
+ child: { kid, publicKeyPem: att.publicKeyPem },
6643
+ scope: `consent:${claims.roles.join(",")} approver:${claims.approver} suite:WebAuthn-ES256`,
6644
+ days: o.certificateDays ?? 365
6645
+ });
6646
+ const label = String(req.label ?? "").slice(0, 60) || "Passkey";
6647
+ 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() });
6648
+ return { status: 200, body: { enrolled: true, approver: claims.approver, roles: claims.roles, kid, certificate } };
6649
+ }
6650
+ return null;
6651
+ }
6652
+ async function fetchHandler(request) {
6653
+ const out = await route(request.method, new URL(request.url).pathname, () => request.json()).catch((e) => ({ status: 400, body: { error: String(e?.message ?? e) } }));
6654
+ if (!out) return new Response(JSON.stringify({ error: "Not found" }), { status: 404, headers: { "content-type": "application/json" } });
6655
+ const html = out.type === "text/html";
6656
+ 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() } : {} } });
6657
+ }
6658
+ function node() {
6659
+ return async (req, res, next) => {
6660
+ const path = String(req.originalUrl ?? req.url ?? "").split("?")[0];
6661
+ const json = async () => {
6662
+ if (req.body && typeof req.body === "object" && !(req.body instanceof Uint8Array)) return req.body;
6663
+ const chunks = [];
6664
+ for await (const c of req) chunks.push(c);
6665
+ return JSON.parse(fromUtf82(concat2(...chunks)) || "{}");
6666
+ };
6667
+ let out;
6668
+ try {
6669
+ out = await route(req.method, path, json);
6670
+ } catch (e) {
6671
+ out = { status: 400, body: { error: String(e?.message ?? e) } };
6672
+ }
6673
+ if (!out) {
6674
+ if (next) return next();
6675
+ res.statusCode = 404;
6676
+ return res.end(JSON.stringify({ error: "Not found" }));
6677
+ }
6678
+ res.statusCode = out.status;
6679
+ const html = out.type === "text/html";
6680
+ res.setHeader("content-type", html ? "text/html; charset=utf-8" : "application/json");
6681
+ if (html) res.setHeader("content-security-policy", CSP());
6682
+ res.end(html ? String(out.body) : JSON.stringify(out.body));
6683
+ };
6684
+ }
6685
+ return { store, fetch: fetchHandler, express: node, node };
6686
+ }
6687
+ 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}
6688
+ @media (prefers-color-scheme:dark){body{color:#eee;background:#111}table td{border-color:#333}}
6689
+ h2{margin:0 0 12px}table{border-collapse:collapse;margin:12px 0}td{padding:6px 10px;border-bottom:1px solid #ddd}
6690
+ button{font:inherit;padding:10px 18px;border-radius:8px;border:1px solid #888;background:transparent;color:inherit;cursor:pointer;margin:4px 8px 4px 0}
6691
+ button.primary{background:#2563eb;border-color:#2563eb;color:#fff}.muted{color:#777;font-size:13px}#msg{margin-top:16px;font-weight:600}`;
6692
+ 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))};
6693
+ 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(()=>({}))}));
6694
+ const say=t=>{document.getElementById('msg').textContent=t};`;
6695
+ function approvalPage(p, status, devices) {
6696
+ const a = p.act.action ?? {};
6697
+ const params = Object.entries(a.params ?? {}).map(([k, v]) => `<tr><td>${esc(k)}</td><td><b>${esc(JSON.stringify(v))}</b></td></tr>`).join("");
6698
+ const device = (d) => `<button class="primary" data-cred="${esc(d.credential_id)}">Approve${d.approver ? ` as ${esc(d.approver)}` : ""} \xB7 ${esc(d.label)}</button>`;
6699
+ 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>`;
6700
+ const body = status === "pending" ? `${choose}<button id="refuse">Refuse</button>` : `<p><b>${esc(status)}</b></p>`;
6701
+ 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>
6702
+ <h2>Approve as ${esc(p.role)}?</h2>
6703
+ <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>
6704
+ <table>${params}</table>
6705
+ <p class="muted">Your passkey signs this exact change and nothing else. It expires at ${esc(p.act.expires_at)}.</p>
6706
+ ${body}<div id="msg" data-id="${esc(p.id)}"></div>
6707
+ <script>${APPROVE_JS}</script>`;
6708
+ }
6709
+ var APPROVE_JS = `${B642}
6710
+ const id=document.getElementById('msg').dataset.id;
6711
+ async function approve(credential_id){
6712
+ try{
6713
+ const o=await post('/approve/'+id+'/options',{credential_id});
6714
+ if(!o.ok) return say(o.body.error||'Not allowed.');
6715
+ 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}});
6716
+ 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)});
6717
+ say(r.ok?'Approved. You can close this page.':(r.body.error||'Not approved.'));
6718
+ if(r.ok) document.querySelectorAll('button').forEach(b=>b.disabled=true);
6719
+ }catch(e){say(e.name==='NotAllowedError'?'Cancelled.':String(e.message||e))}
6720
+ }
6721
+ 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)}
6722
+ function wire(root){root.querySelectorAll('[data-cred]').forEach(b=>b.addEventListener('click',()=>approve(b.dataset.cred)))}
6723
+ wire(document);
6724
+ document.getElementById('refuse')?.addEventListener('click',refuse);
6725
+ document.getElementById('find')?.addEventListener('click',async()=>{
6726
+ const r=await post('/approve/'+id+'/devices',{approver:document.getElementById('who').value});
6727
+ const box=document.getElementById('devices');box.textContent='';
6728
+ if(!r.body.devices?.length) return say('No passkey of yours approves this.');
6729
+ 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)}
6730
+ wire(box);
6731
+ });`;
6732
+ function enrolPage(rpName) {
6733
+ 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>
6734
+ <h2>Enrol a passkey for ${esc(rpName)}</h2>
6735
+ <p id="who" class="muted">Checking your enrolment link\u2026</p>
6736
+ <p><label>Name this device <input id="label" value="" placeholder="e.g. iPhone"></label></p>
6737
+ <button class="primary" id="go" disabled>Create passkey</button>
6738
+ <div id="msg"></div>
6739
+ <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>
6740
+ <script>${ENROL_JS}</script>`;
6741
+ }
6742
+ var ENROL_JS = `${B642}
6743
+ const token=location.hash.slice(1);let opts=null;
6744
+ (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}
6745
+ opts=r.body;document.getElementById('who').textContent='Enrolling '+opts.approver+' to approve as '+opts.roles.join(', ')+'.';document.getElementById('go').disabled=false})();
6746
+ async function enrol(){
6747
+ try{
6748
+ 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)}))}});
6749
+ 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)});
6750
+ say(r.ok?'Enrolled. This device can now approve as '+r.body.roles.join(', ')+'.':(r.body.error||'Not enrolled.'));
6751
+ if(r.ok) document.getElementById('go').disabled=true;
6752
+ }catch(e){say(e.name==='NotAllowedError'?'Cancelled.':String(e.message||e))}
6753
+ }
6754
+ document.getElementById('go').addEventListener('click',enrol);`;
6755
+ var scriptHash = (js) => `'sha256-${btoa(String.fromCharCode(...sha256(utf82(js))))}'`;
6756
+ 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'`;
6757
+
5704
6758
  // src/act/carrier.ts
5705
6759
  var STATUS_OF = { AUTHORIZED: "committed", ESCALATED: "escalated", INCOMPLETE: "incomplete", DENIED: "denied", REPLAYED: "replayed" };
5706
6760
  var NomosActError = class extends Error {
@@ -5737,6 +6791,7 @@ async function act(o) {
5737
6791
  }
5738
6792
  }
5739
6793
  const testimony = [];
6794
+ const available = [...issuer2.key_certs ?? []];
5740
6795
  const unavailable = [];
5741
6796
  for (const claim of witnessed) {
5742
6797
  const w = witnesses.find((x) => x.claims.includes(claim));
@@ -5760,8 +6815,10 @@ async function act(o) {
5760
6815
  continue;
5761
6816
  }
5762
6817
  testimony.push(t);
6818
+ if (Array.isArray(body2.key_certs)) available.push(...body2.key_certs);
5763
6819
  }
5764
- const theAct = buildAct({ relyingParty: rp.relying_party_id, action: o.action, artifact, keyCerts: issuer2.key_certs, testimony, presenterFacts: o.presenterFacts, inForce, windowSeconds });
6820
+ const theAct = buildAct({ relyingParty: rp.relying_party_id, action: o.action, artifact, keyCerts: [], testimony, presenterFacts: o.presenterFacts, inForce, windowSeconds });
6821
+ present(theAct, available);
5765
6822
  let { status, body } = await submit(f, rp.act_endpoint, theAct);
5766
6823
  let approvalUrl;
5767
6824
  while (status === 202 && body.decision === "ESCALATED") {
@@ -5769,16 +6826,18 @@ async function act(o) {
5769
6826
  const endpoint = (issuer2.approvals ?? []).find((a) => a.role === role)?.endpoint;
5770
6827
  if (!endpoint) return done("escalated", body, theAct, { summary: `Needs approval from ${role}, and ${issuer2.issuer_id} publishes no approval endpoint for that role.` });
5771
6828
  const deadline = Math.min(Date.parse(theAct.expires_at), Date.now() + (o.approvalTimeoutMs ?? Infinity));
5772
- const consent = await requestConsent(f, endpoint, role, theAct, deadline, o.pollMs ?? 1e3, (url) => {
6829
+ const answer2 = await requestConsent(f, endpoint, role, theAct, deadline, o.pollMs ?? 1e3, (url) => {
5773
6830
  approvalUrl = url;
5774
6831
  o.onApproval?.({ role, url, actDigest: body.act_digest });
5775
6832
  });
5776
- if (consent === "refused") return done("denied", body, theAct, { summary: `The ${role} refused this action.` });
5777
- if (!consent) {
6833
+ if (answer2 === "refused") return done("denied", body, theAct, { summary: `The ${role} refused this action.` });
6834
+ if (!answer2) {
5778
6835
  const expired = Date.now() >= Date.parse(theAct.expires_at);
5779
6836
  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}.` });
5780
6837
  }
5781
- theAct.consents.push(consent);
6838
+ theAct.consents.push(answer2.consent);
6839
+ if (Array.isArray(answer2.keyCerts)) available.push(...answer2.keyCerts);
6840
+ present(theAct, available);
5782
6841
  const before = Number(body.consents_valid ?? 0);
5783
6842
  ({ status, body } = await submit(f, rp.act_endpoint, theAct));
5784
6843
  if (status === 202 && Number(body.consents_valid ?? 0) <= before) {
@@ -5799,6 +6858,13 @@ async function act(o) {
5799
6858
  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 : ""}`
5800
6859
  });
5801
6860
  }
6861
+ function present(theAct, available) {
6862
+ const set = presentedCertificates(available, theAct);
6863
+ if (set.length > MAX_PRESENTED) {
6864
+ 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.`);
6865
+ }
6866
+ theAct.authority.key_certs = set;
6867
+ }
5802
6868
  function done(status, verdict, theAct, extra) {
5803
6869
  return { status, verdict, act: theAct, ...extra };
5804
6870
  }
@@ -5816,13 +6882,13 @@ async function submit(f, endpoint, theAct) {
5816
6882
  async function requestConsent(f, endpoint, role, theAct, deadline, pollMs, onPending) {
5817
6883
  const res = await f(endpoint, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ role, act: { ...theAct, consents: [] } }) });
5818
6884
  const body = await res.json().catch(() => ({}));
5819
- if (res.status === 200 && body.status === "approved") return body.consent;
6885
+ if (res.status === 200 && body.status === "approved") return { consent: body.consent, keyCerts: body.key_certs };
5820
6886
  if (res.status !== 202 || !body.poll_url) return null;
5821
6887
  onPending(body.approval_url);
5822
6888
  while (Date.now() < deadline) {
5823
6889
  await new Promise((r) => setTimeout(r, pollMs));
5824
6890
  const p = await (await f(body.poll_url)).json().catch(() => ({}));
5825
- if (p.status === "approved") return p.consent;
6891
+ if (p.status === "approved") return { consent: p.consent, keyCerts: p.key_certs };
5826
6892
  if (p.status === "refused") return "refused";
5827
6893
  if (p.status === "expired") return null;
5828
6894
  }
@@ -5830,7 +6896,6 @@ async function requestConsent(f, endpoint, role, theAct, deadline, pollMs, onPen
5830
6896
  }
5831
6897
 
5832
6898
  // src/act/roles.ts
5833
- import { randomUUID as randomUUID2 } from "crypto";
5834
6899
  function serve(route) {
5835
6900
  async function fetchHandler(request) {
5836
6901
  const out = await route({ method: request.method, path: new URL(request.url).pathname, json: () => request.json() });
@@ -5845,7 +6910,7 @@ function serve(route) {
5845
6910
  if (req.body && typeof req.body === "object" && !(req.body instanceof Uint8Array)) return req.body;
5846
6911
  const chunks = [];
5847
6912
  for await (const c of req) chunks.push(c);
5848
- return JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
6913
+ return JSON.parse(fromUtf82(concat2(...chunks)) || "{}");
5849
6914
  };
5850
6915
  let out;
5851
6916
  try {
@@ -5869,7 +6934,9 @@ var day = 864e5;
5869
6934
  function issuer(o) {
5870
6935
  const byId = new Map(o.authorities.map((a) => [a.meta.artifact_id, a]));
5871
6936
  const base = o.origin.replace(/\/$/, "");
5872
- const document = () => ({
6937
+ const pool = async () => typeof o.keyCerts === "function" ? await o.keyCerts() : o.keyCerts;
6938
+ const revocationsUrl = o.revocations ? `${base}/nomos/revocations` : o.revocationsUrl;
6939
+ const build = (keyCerts) => ({
5873
6940
  nomos_document: "issuer",
5874
6941
  version: "1",
5875
6942
  issuer_id: o.issuerId,
@@ -5880,17 +6947,24 @@ function issuer(o) {
5880
6947
  artifact_url: `${base}/nomos/authorities/${encodeURIComponent(a.meta.artifact_id)}.nomos`,
5881
6948
  in_force_url: `${base}/nomos/authorities/${encodeURIComponent(a.meta.artifact_id)}/in-force`
5882
6949
  })),
5883
- key_certs: o.keyCerts,
6950
+ key_certs: keyCerts,
5884
6951
  ...o.witnesses ? { witnesses: o.witnesses } : {},
5885
6952
  ...o.approvals ? { approvals: o.approvals } : {},
5886
- ...o.revocationsUrl ? { revocations_url: o.revocationsUrl } : {},
6953
+ ...revocationsUrl ? { revocations_url: revocationsUrl } : {},
5887
6954
  valid_until: new Date(Date.now() + day).toISOString()
5888
6955
  });
6956
+ const document = () => {
6957
+ if (typeof o.keyCerts === "function") throw new Error("issuer(): keyCerts is a function, so the document must be read with `await resolveDocument()`");
6958
+ return build(o.keyCerts);
6959
+ };
6960
+ const resolveDocument = async () => build(await pool());
5889
6961
  return {
5890
6962
  document,
6963
+ resolveDocument,
5891
6964
  ...serve(async ({ method, path }) => {
5892
6965
  if (method !== "GET") return null;
5893
- if (path === WELL_KNOWN.issuer) return { status: 200, body: document() };
6966
+ if (path === WELL_KNOWN.issuer) return { status: 200, body: await resolveDocument() };
6967
+ if (path === "/nomos/revocations" && o.revocations) return { status: 200, body: await o.revocations() };
5894
6968
  const m = path.match(/^\/nomos\/authorities\/([^/]+?)(\.nomos|\/in-force)$/);
5895
6969
  const art = m && byId.get(decodeURIComponent(m[1]));
5896
6970
  if (!art) return null;
@@ -5925,13 +6999,14 @@ function witness(o) {
5925
6999
  if (value2 === void 0) return { status: 404, body: { error: `${o.witnessId} knows nothing about ${about}.` } };
5926
7000
  const max = o.maxValiditySeconds ?? 900;
5927
7001
  const seconds = Math.max(1, Math.min(max, Number(req?.valid_for_seconds) || max));
5928
- return { status: 200, body: { testimony: await signTestimony(o.signer, { claim, value: value2, about, validUntil: new Date(Date.now() + seconds * 1e3) }) } };
7002
+ 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 } : {} } };
5929
7003
  })
5930
7004
  };
5931
7005
  }
5932
7006
  function approver(o) {
5933
7007
  const base = o.origin.replace(/\/$/, "");
5934
7008
  const pending = /* @__PURE__ */ new Map();
7009
+ const chain = o.keyCerts?.length ? { key_certs: o.keyCerts } : {};
5935
7010
  const statusOf = (p) => p.status === "pending" && Date.parse(p.act.expires_at) < Date.now() ? "expired" : p.status;
5936
7011
  return {
5937
7012
  pending,
@@ -5941,9 +7016,9 @@ function approver(o) {
5941
7016
  if (!o.roles.includes(role)) return { status: 404, body: { error: `This approver does not consent as ${role}.` } };
5942
7017
  if (o.decide === "auto" || typeof o.decide === "function") {
5943
7018
  const ok = o.decide === "auto" ? true : await o.decide({ role, act: act2 });
5944
- return ok ? { status: 200, body: { status: "approved", consent: await signConsent(o.signer, act2, role) } } : { status: 200, body: { status: "refused" } };
7019
+ return ok ? { status: 200, body: { status: "approved", consent: await signConsent(o.signer, act2, role), ...chain } } : { status: 200, body: { status: "refused" } };
5945
7020
  }
5946
- const id = randomUUID2();
7021
+ const id = randomUUID();
5947
7022
  pending.set(id, { role, act: act2, status: "pending" });
5948
7023
  return { status: 202, body: { approval_id: id, status: "pending", poll_url: `${base}/nomos/approvals/${id}`, approval_url: `${base}/approve/${id}` } };
5949
7024
  }
@@ -5952,7 +7027,7 @@ function approver(o) {
5952
7027
  const p = pending.get(m[1]);
5953
7028
  if (!p) return null;
5954
7029
  const s = statusOf(p);
5955
- return { status: 200, body: { status: s, ...s === "approved" ? { consent: p.consent } : {} } };
7030
+ return { status: 200, body: { status: s, ...s === "approved" ? { consent: p.consent, ...chain } : {} } };
5956
7031
  }
5957
7032
  m = path.match(/^\/approve\/([0-9a-f-]+)(\/(approve|refuse))?$/);
5958
7033
  if (m) {
@@ -5972,22 +7047,23 @@ function approver(o) {
5972
7047
  })
5973
7048
  };
5974
7049
  }
5975
- var esc = (s) => String(s).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
7050
+ var esc2 = (s) => String(s).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
5976
7051
  function page(p, status, id) {
5977
7052
  const a = p.act.action ?? {};
5978
- const params = Object.entries(a.params ?? {}).map(([k, v]) => `<tr><td>${esc(k)}</td><td><b>${esc(JSON.stringify(v))}</b></td></tr>`).join("");
7053
+ const params = Object.entries(a.params ?? {}).map(([k, v]) => `<tr><td>${esc2(k)}</td><td><b>${esc2(JSON.stringify(v))}</b></td></tr>`).join("");
5979
7054
  const buttons = status === "pending" ? `<form method="post" action="/approve/${id}/approve" style="display:inline"><button style="padding:10px 18px">Approve</button></form>
5980
- <form method="post" action="/approve/${id}/refuse" style="display:inline"><button style="padding:10px 18px">Refuse</button></form>` : `<p><b>${esc(status)}</b></p>`;
5981
- return `<!doctype html><meta charset="utf-8"><title>Approve: ${esc(a.type)}</title>
7055
+ <form method="post" action="/approve/${id}/refuse" style="display:inline"><button style="padding:10px 18px">Refuse</button></form>` : `<p><b>${esc2(status)}</b></p>`;
7056
+ return `<!doctype html><meta charset="utf-8"><title>Approve: ${esc2(a.type)}</title>
5982
7057
  <body style="font:15px system-ui;max-width:560px;margin:40px auto;padding:0 16px">
5983
- <h2>Approve as ${esc(p.role)}?</h2>
5984
- <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>
7058
+ <h2>Approve as ${esc2(p.role)}?</h2>
7059
+ <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>
5985
7060
  <table style="border-collapse:collapse" cellpadding="4">${params}</table>
5986
- <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>
7061
+ <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>
5987
7062
  ${buttons}</body>`;
5988
7063
  }
5989
7064
  export {
5990
7065
  ACT_MEDIA_TYPE,
7066
+ MAX_PRESENTED,
5991
7067
  Nomos,
5992
7068
  NomosAPIError,
5993
7069
  NomosActError,
@@ -6002,31 +7078,53 @@ export {
6002
7078
  NomosRateLimitError,
6003
7079
  NomosValidationError,
6004
7080
  NomosWebhookSignatureError,
7081
+ RegistryError,
7082
+ RevocationUnavailable,
6005
7083
  WELL_KNOWN,
6006
7084
  accept,
6007
7085
  act,
7086
+ applyToolBindings,
6008
7087
  approver,
6009
7088
  bindingDigest,
6010
7089
  buildAct,
6011
7090
  can,
7091
+ certificateRegistry,
6012
7092
  certify,
7093
+ describeSuggestion,
6013
7094
  fetchDocument,
6014
7095
  generateSigner,
6015
7096
  issuer,
6016
7097
  kidOf2 as kidOf,
6017
7098
  localSigner,
7099
+ memoryPasskeyStore,
7100
+ memoryRegistryStore,
6018
7101
  memoryStore,
6019
7102
  nomos,
6020
7103
  originOf,
7104
+ parseToolBindings,
7105
+ passkeyApprover,
7106
+ postgresPasskeyStore,
7107
+ postgresRegistryStore,
7108
+ postgresStore,
7109
+ presentedCertificates,
7110
+ readAttestation,
7111
+ reliedKids,
7112
+ revocationListCertificates,
7113
+ revocationSource,
6021
7114
  seal,
6022
7115
  seededSigner,
6023
7116
  signConsent,
7117
+ signEnrolmentToken,
6024
7118
  signInForce,
7119
+ signRevocation,
7120
+ signRevocationList,
6025
7121
  signTestimony,
6026
7122
  statusFor,
7123
+ suggestToolBindings,
6027
7124
  verifyAct,
6028
7125
  verifyArtifact,
6029
7126
  verifyChain,
7127
+ verifyEnrolmentToken,
6030
7128
  verifySeal,
6031
7129
  witness
6032
7130
  };