@nomosprotocol/sdk 2.0.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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,241 @@ async function verifyChainRemote(params, opts) {
5316
5393
  throw new NomosAPIError(message, res.status, requestId);
5317
5394
  }
5318
5395
 
5396
+ // src/act/bytes.ts
5397
+ var utf82 = (s) => new TextEncoder().encode(s);
5398
+ var fromUtf82 = (b) => new TextDecoder().decode(b);
5399
+ function concat2(...parts) {
5400
+ const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
5401
+ let o = 0;
5402
+ for (const p of parts) {
5403
+ out.set(p, o);
5404
+ o += p.length;
5405
+ }
5406
+ return out;
5407
+ }
5408
+ function equal2(a, b) {
5409
+ if (a.length !== b.length) return false;
5410
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
5411
+ return true;
5412
+ }
5413
+ function fromHex(h) {
5414
+ const out = new Uint8Array(h.length / 2);
5415
+ for (let i = 0; i < out.length; i++) out[i] = parseInt(h.slice(i * 2, i * 2 + 2), 16);
5416
+ return out;
5417
+ }
5418
+ function toBase642(b) {
5419
+ let s = "";
5420
+ for (let i = 0; i < b.length; i += 32768) s += String.fromCharCode(...b.subarray(i, i + 32768));
5421
+ return btoa(s);
5422
+ }
5423
+ function fromBase642(s) {
5424
+ const bin = atob(s);
5425
+ const out = new Uint8Array(bin.length);
5426
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
5427
+ return out;
5428
+ }
5429
+ var toBase64url2 = (b) => toBase642(b).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
5430
+ var fromBase64url2 = (s) => {
5431
+ const t = s.replace(/-/g, "+").replace(/_/g, "/");
5432
+ return fromBase642(t + "=".repeat((4 - t.length % 4) % 4));
5433
+ };
5434
+ function randomBytes3(n) {
5435
+ return globalThis.crypto.getRandomValues(new Uint8Array(n));
5436
+ }
5437
+ var randomUUID = () => globalThis.crypto.randomUUID();
5438
+
5319
5439
  // src/act/signer.ts
5320
- import { createHash, createPrivateKey, createPublicKey, generateKeyPairSync, sign as nodeSign } from "crypto";
5321
- var P256_N = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551n;
5322
5440
  function kidOf2(publicKeyPem) {
5323
- const der = createPublicKey(publicKeyPem).export({ type: "spki", format: "der" });
5324
- return createHash("sha256").update(der).digest("base64url").slice(0, 16);
5441
+ const kid = kidOf(publicKeyPem);
5442
+ if (!kid) throw new Error("kidOf: not a canonical Ed25519 or P-256 public key (NOMOS-SPEC-010 \xA73.1)");
5443
+ return kid;
5444
+ }
5445
+ var ED25519_SPKI = fromHex("302a300506032b6570032100");
5446
+ var P256_SPKI = fromHex("3059301306072a8648ce3d020106082a8648ce3d030107034200");
5447
+ var ED25519_PKCS8 = fromHex("302e020100300506032b657004220420");
5448
+ var P256_PKCS8_HEAD = fromHex("308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b0201010420");
5449
+ var P256_PKCS8_PUB = fromHex("a144034200");
5450
+ var OID_ED25519 = fromHex("2b6570");
5451
+ var OID_EC = fromHex("2a8648ce3d0201");
5452
+ var OID_P256 = fromHex("2a8648ce3d030107");
5453
+ function pem(label, der) {
5454
+ const b64 = toBase642(der);
5455
+ return `-----BEGIN ${label}-----
5456
+ ${b64.match(/.{1,64}/g).join("\n")}
5457
+ -----END ${label}-----
5458
+ `;
5459
+ }
5460
+ function pemBody(text) {
5461
+ const m = text.trim().match(/^-----BEGIN ([A-Z0-9 ]+)-----([\s\S]*?)-----END \1-----$/);
5462
+ if (!m) throw new Error("Signer: expected a PEM private key");
5463
+ return { label: m[1], der: fromBase642(m[2].replace(/\s+/g, "")) };
5464
+ }
5465
+ function tlv(b, at) {
5466
+ const tag = b[at];
5467
+ let len = b[at + 1], start = at + 2;
5468
+ if (len & 128) {
5469
+ const n = len & 127;
5470
+ if (n < 1 || n > 2) throw new Error("Signer: unsupported DER length");
5471
+ len = 0;
5472
+ for (let i = 0; i < n; i++) len = len << 8 | b[start + i];
5473
+ start += n;
5474
+ }
5475
+ if (start + len > b.length) throw new Error("Signer: truncated DER");
5476
+ return { tag, start, end: start + len };
5477
+ }
5478
+ function children(b, outer) {
5479
+ const out = [];
5480
+ for (let at = outer.start; at < outer.end; ) {
5481
+ const t = tlv(b, at);
5482
+ out.push(t);
5483
+ at = t.end;
5484
+ }
5485
+ return out;
5325
5486
  }
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)");
5487
+ function sec1Scalar(b, seq) {
5488
+ const [version, d, ...rest] = children(b, seq);
5489
+ if (version.tag !== 2 || d.tag !== 4 || d.end - d.start !== 32) throw new Error("Signer: malformed EC private key");
5490
+ for (const r of rest) {
5491
+ if (r.tag === 160) {
5492
+ const oid = tlv(b, r.start);
5493
+ 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)");
5494
+ }
5331
5495
  }
5496
+ return b.slice(d.start, d.end);
5497
+ }
5498
+ function readPrivateKey(text) {
5499
+ const { label, der } = pemBody(text);
5500
+ const top = tlv(der, 0);
5501
+ if (label === "EC PRIVATE KEY") return { type: "ES256", key: sec1Scalar(der, top) };
5502
+ if (label !== "PRIVATE KEY") throw new Error(`Signer: unsupported key type "${label}" (PKCS#8 "PRIVATE KEY" or SEC1 "EC PRIVATE KEY")`);
5503
+ const [, alg, key] = children(der, top);
5504
+ const [oid, params] = children(der, alg);
5505
+ const algOid = der.subarray(oid.start, oid.end);
5506
+ if (equal2(algOid, OID_ED25519)) {
5507
+ const inner = tlv(der, key.start);
5508
+ if (inner.tag !== 4 || inner.end - inner.start !== 32) throw new Error("Signer: malformed Ed25519 private key");
5509
+ return { type: "Ed25519", key: der.slice(inner.start, inner.end) };
5510
+ }
5511
+ if (equal2(algOid, OID_EC)) {
5512
+ 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)");
5513
+ return { type: "ES256", key: sec1Scalar(der, tlv(der, key.start)) };
5514
+ }
5515
+ throw new Error("Signer: only Ed25519 and P-256 keys are NOMOS signature suites (NOMOS-SPEC-010)");
5516
+ }
5517
+ function publicPem(s) {
5518
+ 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)));
5519
+ }
5520
+ function privatePem(s) {
5521
+ 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)));
5522
+ }
5523
+ function fromSecret(s) {
5524
+ const publicKeyPem = publicPem(s);
5332
5525
  return {
5333
5526
  kid: kidOf2(publicKeyPem),
5334
5527
  publicKeyPem,
5335
- algorithm: isEd ? "Ed25519" : "ES256",
5528
+ algorithm: s.type,
5336
5529
  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");
5530
+ return toBase642(s.type === "Ed25519" ? ed25519.sign(payload, s.key) : p256.sign(payload, s.key, { prehash: true, lowS: true, format: "compact" }));
5342
5531
  }
5343
5532
  };
5344
5533
  }
5345
5534
  function localSigner(privateKeyPem) {
5346
- return fromKeyObject(createPrivateKey(privateKeyPem));
5535
+ return fromSecret(readPrivateKey(privateKeyPem));
5347
5536
  }
5348
5537
  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() };
5538
+ const s = algorithm === "Ed25519" ? { type: "Ed25519", key: ed25519.utils.randomSecretKey() } : { type: "ES256", key: p256.utils.randomSecretKey() };
5539
+ return { ...fromSecret(s), privateKeyPem: privatePem(s) };
5351
5540
  }
5352
5541
  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" }));
5542
+ return fromSecret({ type: "Ed25519", key: sha2562(seed) });
5543
+ }
5544
+ var sha2562 = (s) => sha256(new TextEncoder().encode(s));
5545
+
5546
+ // src/act/certificates.ts
5547
+ var MAX_PRESENTED = 20;
5548
+ function reliedKids(act2) {
5549
+ const kids = [act2?.authority?.artifact?.seal?.kid, ...(act2?.testimony ?? []).map((t) => t?.kid), ...(act2?.consents ?? []).map((c) => c?.kid)];
5550
+ return [...new Set(kids.filter((k) => typeof k === "string" && k !== ""))];
5551
+ }
5552
+ function presentedCertificates(available, act2) {
5553
+ const unique = /* @__PURE__ */ new Map();
5554
+ for (const c of available) unique.set(fingerprint(c), c);
5555
+ const kids = new Set(reliedKids(act2)), chosen = /* @__PURE__ */ new Map();
5556
+ for (let grew = true; grew; ) {
5557
+ grew = false;
5558
+ for (const [fp, c] of unique) {
5559
+ if (!chosen.has(fp) && kids.has(c.child_kid)) {
5560
+ chosen.set(fp, c);
5561
+ kids.add(c.parent_kid);
5562
+ grew = true;
5563
+ }
5564
+ }
5565
+ }
5566
+ return [...chosen.values()];
5567
+ }
5568
+ async function signRevocation(parent, target, reason, issuedAt = /* @__PURE__ */ new Date()) {
5569
+ const st = "kid" in target ? { revoked_kid: target.kid, reason, issued_at: issuedAt.toISOString() } : { revoked_fingerprint: fingerprint(target.certificate), reason, issued_at: issuedAt.toISOString() };
5570
+ if ("certificate" in target && target.certificate.parent_kid !== parent.kid) throw new Error("signRevocation: only the parent that issued a certificate can revoke it");
5571
+ return { ...st, kid: parent.kid, algorithm: parent.algorithm, signature: await parent.sign(jcs(revocationPayload(st))) };
5572
+ }
5573
+ async function signRevocationList(signer, statements, keyCerts = [], generatedAt = /* @__PURE__ */ new Date()) {
5574
+ const L = { generated_at: generatedAt.toISOString(), kid: signer.kid, algorithm: signer.algorithm, statements, ...keyCerts.length ? { key_certs: keyCerts } : {} };
5575
+ L.signature = await signer.sign(jcs(revocationListPayload(L)));
5576
+ return L;
5577
+ }
5578
+ function revocationListCertificates(available, statements, listSignerKid) {
5579
+ const byFp = new Map(available.map((c) => [fingerprint(c), c]));
5580
+ const revokedCerts = statements.map((s) => s.revoked_fingerprint && byFp.get(s.revoked_fingerprint)).filter((c) => !!c);
5581
+ const kids = [listSignerKid, ...statements.flatMap((s) => [s.kid, s.revoked_kid].filter((k) => !!k)), ...revokedCerts.map((c) => c.parent_kid)];
5582
+ const closure = presentedCertificates(available, { authority: { artifact: { seal: {} } }, testimony: kids.map((kid) => ({ kid })) });
5583
+ const out = new Map(closure.map((c) => [fingerprint(c), c]));
5584
+ for (const c of revokedCerts) out.set(fingerprint(c), c);
5585
+ return [...out.values()];
5586
+ }
5587
+ var RevocationUnavailable = class extends Error {
5588
+ constructor(message, retryAfterSeconds) {
5589
+ super(message);
5590
+ this.retryAfterSeconds = retryAfterSeconds;
5591
+ this.name = "RevocationUnavailable";
5592
+ }
5593
+ };
5594
+ function revocationSource(o) {
5595
+ const f = o.fetch ?? fetch, now = o.now ?? (() => /* @__PURE__ */ new Date());
5596
+ const maxAge = (o.maxAgeSeconds ?? 3600) * 1e3, refresh = (o.refreshSeconds ?? 60) * 1e3;
5597
+ const kids = /* @__PURE__ */ new Set(), certs = /* @__PURE__ */ new Set();
5598
+ let asOf = 0, fetchedAt = 0, inFlight = null, lastError = "";
5599
+ async function load() {
5600
+ fetchedAt = now().getTime();
5601
+ try {
5602
+ const res = await f(o.url, { headers: { accept: "application/json" } });
5603
+ if (!res.ok) {
5604
+ lastError = `${o.url} answered ${res.status}`;
5605
+ return;
5606
+ }
5607
+ const r = verifyRevocationList(await res.json(), { rootPublicKeyPem: o.root, now: now(), ownCerts: o.ownCerts });
5608
+ if (r.decision !== "OK") {
5609
+ lastError = `${o.url} served a malformed list (${r.reason_code})`;
5610
+ return;
5611
+ }
5612
+ for (const k of r.revoked_kids) kids.add(k);
5613
+ for (const c of r.revoked_certs) certs.add(c);
5614
+ if (r.as_of) asOf = Math.max(asOf, Date.parse(r.as_of));
5615
+ else lastError = `${o.url}'s list is not signed by a key your root reaches, so it does not show it is current`;
5616
+ } catch (e) {
5617
+ lastError = `${o.url}: ${e?.message ?? e}`;
5618
+ }
5619
+ }
5620
+ return async () => {
5621
+ const t = now().getTime();
5622
+ if (!inFlight && t - fetchedAt >= refresh) inFlight = load().finally(() => {
5623
+ inFlight = null;
5624
+ });
5625
+ if (inFlight) await inFlight;
5626
+ if (!asOf || t - asOf > maxAge) {
5627
+ 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));
5628
+ }
5629
+ return { kids: new Set(kids), certs: new Set(certs) };
5630
+ };
5356
5631
  }
5357
5632
 
5358
5633
  // src/act/documents.ts
@@ -5457,6 +5732,11 @@ var Rollback = class extends Error {
5457
5732
  this.answer = answer2;
5458
5733
  }
5459
5734
  };
5735
+ var LostRace = class extends Error {
5736
+ constructor() {
5737
+ super("replayed");
5738
+ }
5739
+ };
5460
5740
  function accept(o) {
5461
5741
  const kid = kidOf2(o.root);
5462
5742
  for (const a of o.authorities) {
@@ -5464,7 +5744,7 @@ function accept(o) {
5464
5744
  throw new Error(`accept(): authority ${a.artifactId} uses currencyPolicy "record" but has no inForceSealHash`);
5465
5745
  }
5466
5746
  }
5467
- if (!o.store) console.warn("[nomos] accept(): using the in-memory store. Nonces and receipts are lost on restart. Development only.");
5747
+ 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
5748
  const store = o.store ?? memoryStore();
5469
5749
  const maxWindowSeconds = o.maxWindowSeconds ?? 900;
5470
5750
  const maxActBytes = o.maxActBytes ?? 1024 * 1024;
@@ -5490,8 +5770,7 @@ function accept(o) {
5490
5770
  valid_until: new Date(now().getTime() + (o.documentTtlSeconds ?? 86400) * 1e3).toISOString()
5491
5771
  };
5492
5772
  }
5493
- async function verifyWith(act2, authority, facts, at, seen) {
5494
- const rev = await o.revocations?.() ?? {};
5773
+ function verifyWith(act2, authority, facts, at, seen, rev) {
5495
5774
  return verifyAct(act2, {
5496
5775
  relyingPartyId: o.relyingPartyId,
5497
5776
  rootPublicKeyPem: o.root,
@@ -5523,9 +5802,20 @@ function accept(o) {
5523
5802
  const authority = o.authorities.find((a) => a.artifactId === artifactId);
5524
5803
  if (!authority) return answer({ decision: "NOT_ACCEPTED", reason_code: "authority_not_accepted", artifact_id: artifactId ?? null });
5525
5804
  if (!authority.actions.includes(act2?.action?.type)) return answer({ decision: "NOT_ACCEPTED", reason_code: "action_not_accepted", action_type: act2?.action?.type ?? null });
5805
+ let rev;
5806
+ try {
5807
+ rev = await o.revocations?.() ?? {};
5808
+ } catch (e) {
5809
+ const retry = e instanceof RevocationUnavailable ? e.retryAfterSeconds : 60;
5810
+ return {
5811
+ status: 503,
5812
+ headers: { "content-type": "application/json", "retry-after": String(retry) },
5813
+ body: { error: "Revocation status is not current, so nothing is committed. Resubmit this same Act later.", detail: String(e?.message ?? e), retry_after: retry }
5814
+ };
5815
+ }
5526
5816
  const verifiedAt = now();
5527
5817
  const facts = await o.localFacts?.(act2) ?? {};
5528
- const verdict = await verifyWith(act2, authority, facts, verifiedAt, await store.hasNonce(act2.nonce));
5818
+ const verdict = verifyWith(act2, authority, facts, verifiedAt, await store.hasNonce(act2.nonce), rev);
5529
5819
  const receiptOf = (v, f, committedAt) => ({
5530
5820
  act_id: act2.act_id,
5531
5821
  act_digest: String(v.act_digest ?? ""),
@@ -5545,35 +5835,34 @@ function accept(o) {
5545
5835
  if (verdict.decision !== "AUTHORIZED" || o.mode === "shadow") {
5546
5836
  await o.onVerdict?.(verdict, receiptOf(verdict, facts));
5547
5837
  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 });
5838
+ const esc3 = (act2.authority.artifact.governance?.escalations ?? []).find((e) => e?.id === verdict.escalation_id);
5839
+ return answer(verdict, { role: esc3?.role_required ?? null, retry_until: act2.expires_at });
5550
5840
  }
5551
5841
  if (o.mode === "shadow") return { ...answer(verdict), status: 200, body: { ...answer(verdict).body, shadow: true } };
5552
5842
  return answer(verdict);
5553
5843
  }
5554
5844
  try {
5555
5845
  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
- }
5846
+ if (!await t.claimNonce(act2.nonce)) throw new LostRace();
5559
5847
  let final = verdict, finalFacts = facts;
5560
5848
  if (o.localFacts) {
5561
5849
  const fresh = await o.localFacts(act2, t.tx);
5562
5850
  if (!sameFacts(fresh, facts)) {
5563
5851
  finalFacts = fresh;
5564
- final = await verifyWith(act2, authority, fresh, verifiedAt, false);
5852
+ final = verifyWith(act2, authority, fresh, verifiedAt, false, rev);
5565
5853
  if (final.decision !== "AUTHORIZED") throw new Rollback(answer(final));
5566
5854
  }
5567
5855
  }
5568
5856
  const action = structuredClone({ type: act2.action.type, ...act2.action.subject !== void 0 ? { subject: act2.action.subject } : {}, params: act2.action.params ?? {} });
5569
5857
  const result = await o.commit(action, { tx: t.tx, actId: act2.act_id });
5570
5858
  const receipt = receiptOf(final, finalFacts, now());
5571
- await t.putReceipt(act2.nonce, receipt);
5859
+ await t.putReceipt(act2.nonce, receipt, act2);
5572
5860
  await o.onVerdict?.(final, receipt);
5573
5861
  return answer(final, { receipt, ...result !== void 0 ? { result } : {} });
5574
5862
  });
5575
5863
  } catch (e) {
5576
5864
  if (e instanceof Rollback) return e.answer;
5865
+ if (e instanceof LostRace) return answer({ decision: "REPLAYED" }, { act_id: act2.act_id, receipt: await store.receiptFor(act2.nonce) });
5577
5866
  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
5867
  }
5579
5868
  }
@@ -5612,7 +5901,7 @@ function accept(o) {
5612
5901
  if (size > maxActBytes) break;
5613
5902
  chunks.push(c);
5614
5903
  }
5615
- body = size > maxActBytes ? new Uint8Array(maxActBytes + 1) : Buffer.concat(chunks);
5904
+ body = size > maxActBytes ? new Uint8Array(maxActBytes + 1) : concat2(...chunks);
5616
5905
  }
5617
5906
  const a = await handle({ method: req.method, headers: req.headers, body });
5618
5907
  send(a.status, a.headers, JSON.stringify(a.body));
@@ -5623,7 +5912,7 @@ function accept(o) {
5623
5912
  function consentOrigins(act2) {
5624
5913
  const origins = (Array.isArray(act2?.consents) ? act2.consents : []).filter((c) => c?.algorithm === "WebAuthn-ES256" && typeof c.client_data_json === "string").map((c) => {
5625
5914
  try {
5626
- return JSON.parse(Buffer.from(c.client_data_json, "base64url").toString("utf8")).origin;
5915
+ return JSON.parse(fromUtf82(fromBase64url2(c.client_data_json))).origin;
5627
5916
  } catch {
5628
5917
  return null;
5629
5918
  }
@@ -5631,8 +5920,74 @@ function consentOrigins(act2) {
5631
5920
  return origins.length ? { consent_origins: [...new Set(origins)] } : {};
5632
5921
  }
5633
5922
 
5923
+ // src/act/postgres.ts
5924
+ var IDENT = /^[a-z_][a-z0-9_]{0,62}(\.[a-z_][a-z0-9_]{0,62})?$/;
5925
+ function postgresStore(pool, opts = {}) {
5926
+ const table = opts.table ?? "nomos_acts";
5927
+ if (!IDENT.test(table)) throw new Error(`postgresStore: "${table}" is not a plain lower-case table name`);
5928
+ const schema = `CREATE TABLE IF NOT EXISTS ${table} (
5929
+ nonce text PRIMARY KEY, -- SPEC-008 \xA75.2: the nonce ledger
5930
+ act_id text NOT NULL,
5931
+ receipt json NOT NULL, -- SPEC-008 \xA79; json, not jsonb: kept byte for byte
5932
+ act json NOT NULL, -- the exact Act verified, for re-verification
5933
+ committed_at timestamptz NOT NULL DEFAULT now()
5934
+ );`;
5935
+ return {
5936
+ schema,
5937
+ async migrate() {
5938
+ await pool.query(schema);
5939
+ },
5940
+ async hasNonce(nonce) {
5941
+ const r = await pool.query(`SELECT 1 FROM ${table} WHERE nonce = $1`, [nonce]);
5942
+ return (r.rowCount ?? r.rows.length) > 0;
5943
+ },
5944
+ async receiptFor(nonce) {
5945
+ const r = await pool.query(`SELECT receipt FROM ${table} WHERE nonce = $1`, [nonce]);
5946
+ return r.rows[0]?.receipt ?? null;
5947
+ },
5948
+ async transaction(fn) {
5949
+ const client = await pool.connect();
5950
+ let claimed = null, completed = false;
5951
+ try {
5952
+ await client.query("BEGIN");
5953
+ const out = await fn({
5954
+ tx: client,
5955
+ async claimNonce(nonce) {
5956
+ const r = await client.query(
5957
+ `INSERT INTO ${table} (nonce, act_id, receipt, act) VALUES ($1, '', '{}'::json, '{}'::json)
5958
+ ON CONFLICT (nonce) DO NOTHING RETURNING nonce`,
5959
+ [nonce]
5960
+ );
5961
+ const won = (r.rowCount ?? r.rows.length) === 1;
5962
+ if (won) claimed = nonce;
5963
+ return won;
5964
+ },
5965
+ async putReceipt(nonce, receipt, act2) {
5966
+ if (nonce !== claimed) throw new Error("postgresStore: putReceipt for a nonce this transaction did not claim");
5967
+ await client.query(
5968
+ `UPDATE ${table} SET act_id = $2, receipt = $3::json, act = $4::json WHERE nonce = $1`,
5969
+ [nonce, receipt.act_id, JSON.stringify(receipt), JSON.stringify(act2 ?? {})]
5970
+ );
5971
+ completed = true;
5972
+ }
5973
+ });
5974
+ if (claimed && !completed) throw new Error("postgresStore: the transaction claimed a nonce but stored no receipt");
5975
+ await client.query("COMMIT");
5976
+ client.release();
5977
+ return out;
5978
+ } catch (e) {
5979
+ let broken;
5980
+ await client.query("ROLLBACK").catch((err) => {
5981
+ broken = err;
5982
+ });
5983
+ client.release(broken);
5984
+ throw e;
5985
+ }
5986
+ }
5987
+ };
5988
+ }
5989
+
5634
5990
  // src/act/statements.ts
5635
- import { randomBytes as randomBytes3, randomUUID } from "crypto";
5636
5991
  async function certify(parent, child, opts) {
5637
5992
  const unsigned = {
5638
5993
  parent_kid: parent.kid,
@@ -5687,7 +6042,7 @@ function buildAct(o) {
5687
6042
  const act2 = {
5688
6043
  act_version: "1",
5689
6044
  act_id: `act-${randomUUID()}`,
5690
- nonce: randomBytes3(16).toString("base64"),
6045
+ nonce: toBase642(randomBytes3(16)),
5691
6046
  // Issued a second in the past so a relying party's clock a moment behind ours still accepts it.
5692
6047
  issued_at: new Date(now.getTime() - 1e3).toISOString(),
5693
6048
  expires_at: new Date(now.getTime() - 1e3 + window * 1e3).toISOString(),
@@ -5701,6 +6056,575 @@ function buildAct(o) {
5701
6056
  return act2;
5702
6057
  }
5703
6058
 
6059
+ // src/act/registry.ts
6060
+ function memoryRegistryStore() {
6061
+ const certs = [], sts = [];
6062
+ return {
6063
+ async certificates() {
6064
+ return [...certs];
6065
+ },
6066
+ async addCertificate(c) {
6067
+ if (!certs.some((x) => fingerprint(x) === fingerprint(c))) certs.push(c);
6068
+ },
6069
+ async statements() {
6070
+ return [...sts];
6071
+ },
6072
+ async addStatement(s) {
6073
+ sts.push(s);
6074
+ }
6075
+ };
6076
+ }
6077
+ var RegistryError = class extends Error {
6078
+ constructor(message, code) {
6079
+ super(message);
6080
+ this.code = code;
6081
+ this.name = "RegistryError";
6082
+ }
6083
+ };
6084
+ var any = () => ({ ok: true });
6085
+ function certificateRegistry(o) {
6086
+ const rootKid = kidOf(o.root);
6087
+ if (!rootKid) throw new RegistryError("certificateRegistry: the root is not a canonical Ed25519 or P-256 public key", "bad_root");
6088
+ const now = o.now ?? (() => /* @__PURE__ */ new Date());
6089
+ async function revokedSets() {
6090
+ const kids = /* @__PURE__ */ new Set(), certs = /* @__PURE__ */ new Set();
6091
+ for (const s of await o.store.statements()) s.revoked_kid ? kids.add(s.revoked_kid) : s.revoked_fingerprint && certs.add(s.revoked_fingerprint);
6092
+ return { kids, certs };
6093
+ }
6094
+ async function scopesOf(kid) {
6095
+ if (kid === rootKid) return { ok: true, scopes: [{ terms: {} }] };
6096
+ const { kids, certs } = await revokedSets();
6097
+ const r = resolveKey({ targetKid: kid, pool: await o.store.certificates(), rootPublicKeyPem: o.root, purpose: any, now: now(), revokedKids: kids, revokedCerts: certs, allPaths: true });
6098
+ if (r.decision !== "RESOLVED") return { ok: false, reason: `${r.decision}${r.reason_code ? ` (${r.reason_code})` : ""}` };
6099
+ return { ok: true, scopes: r.admitted ?? [r.effective] };
6100
+ }
6101
+ async function pool(filter) {
6102
+ const { kids, certs } = await revokedSets(), t = now().getTime();
6103
+ 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)));
6104
+ }
6105
+ return {
6106
+ rootKid,
6107
+ pool,
6108
+ /** NOMOS-SPEC-007 §3: certify `child` under `parent`. Refuses anything a verifier would refuse. */
6109
+ async issue(io) {
6110
+ 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");
6111
+ const scope = parseScope(io.scope ?? null);
6112
+ if (!scope.ok) throw new RegistryError(`Malformed scope: ${scope.detail}`, "malformed_scope");
6113
+ const parentScopes = await scopesOf(io.parent.kid);
6114
+ if (!parentScopes.ok) throw new RegistryError(`The parent ${io.parent.kid} is not reached from the root: ${parentScopes.reason}`, "parent_not_recognized");
6115
+ const fits = parentScopes.scopes.filter((p) => suitePermitted(p, io.parent.algorithm) && isNarrowerOrEqual(scope.scope, p).ok);
6116
+ if (!fits.length) {
6117
+ const widened = parentScopes.scopes.map((p) => isNarrowerOrEqual(scope.scope, p)).find((x) => !x.ok);
6118
+ 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");
6119
+ }
6120
+ const expiresAt = io.expiresAt ?? (io.days ? new Date(now().getTime() + io.days * 864e5) : void 0);
6121
+ if (!expiresAt) throw new RegistryError("Give expiresAt or days: a certificate always expires", "no_expiry");
6122
+ const c = await certify(io.parent, io.child, { scope: io.scope, issuedAt: io.issuedAt ?? now(), expiresAt });
6123
+ await o.store.addCertificate(c);
6124
+ return c;
6125
+ },
6126
+ /** rev. 1.10 §5.1: `parent` revokes a key it certified. The key is dead on every path. */
6127
+ async revokeKey(io) {
6128
+ const certs = (await o.store.certificates()).filter((c) => c.child_kid === io.kid && c.parent_kid === io.parent.kid);
6129
+ if (!certs.length) throw new RegistryError(`${io.parent.kid} never certified ${io.kid}: only a key's parent can revoke it`, "not_the_parent");
6130
+ const st = await signRevocation(io.parent, { kid: io.kid }, io.reason, now());
6131
+ await o.store.addStatement(st);
6132
+ return st;
6133
+ },
6134
+ /** rev. 1.10 §5.1: withdraw one certificate; the key stays valid under any other. */
6135
+ async revokeCertificate(io) {
6136
+ const c = (await o.store.certificates()).find((x) => fingerprint(x) === io.fingerprint);
6137
+ if (!c) throw new RegistryError(`No certificate ${io.fingerprint} in this registry`, "unknown_certificate");
6138
+ const st = await signRevocation(io.parent, { certificate: c }, io.reason, now());
6139
+ await o.store.addStatement(st);
6140
+ return st;
6141
+ },
6142
+ /** The certificates that connect `kid` to the root: what a witness or approver returns with
6143
+ * what it signs (SPEC-009 0.2.0 §4, §5). */
6144
+ async chainFor(kid) {
6145
+ return presentedCertificates(await pool(), { authority: { artifact: { seal: { kid } } } });
6146
+ },
6147
+ /** How `kid` is reached from the root, and with which effective scopes, or why it is not. */
6148
+ async check(kid) {
6149
+ return scopesOf(kid);
6150
+ },
6151
+ certificates: () => o.store.certificates(),
6152
+ statements: () => o.store.statements(),
6153
+ /** rev. 1.10 §5.3: the signed list, carrying the certificates that make it checkable. The
6154
+ * signer should be reached from the root, or the list establishes no date (§5.3). */
6155
+ async revocationList(signer) {
6156
+ const statements = await o.store.statements();
6157
+ return signRevocationList(signer, statements, revocationListCertificates(await o.store.certificates(), statements, signer.kid), now());
6158
+ }
6159
+ };
6160
+ }
6161
+ function postgresRegistryStore(pool, opts = {}) {
6162
+ const p = opts.prefix ?? "nomos_registry";
6163
+ if (!/^[a-z_][a-z0-9_]{0,40}$/.test(p)) throw new RegistryError(`"${p}" is not a plain lower-case prefix`, "bad_prefix");
6164
+ const schema = `CREATE TABLE IF NOT EXISTS ${p}_certificates (
6165
+ 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()
6166
+ );
6167
+ CREATE TABLE IF NOT EXISTS ${p}_statements (
6168
+ id bigserial PRIMARY KEY, target text NOT NULL, kid text NOT NULL, statement json NOT NULL, added_at timestamptz NOT NULL DEFAULT now()
6169
+ );`;
6170
+ return {
6171
+ schema,
6172
+ async migrate() {
6173
+ await pool.query(schema);
6174
+ },
6175
+ async certificates() {
6176
+ return (await pool.query(`SELECT certificate FROM ${p}_certificates ORDER BY added_at, fingerprint`)).rows.map((r) => r.certificate);
6177
+ },
6178
+ async addCertificate(c) {
6179
+ await pool.query(
6180
+ `INSERT INTO ${p}_certificates (fingerprint, child_kid, parent_kid, certificate) VALUES ($1, $2, $3, $4::json) ON CONFLICT (fingerprint) DO NOTHING`,
6181
+ [fingerprint(c), c.child_kid, c.parent_kid, JSON.stringify(c)]
6182
+ );
6183
+ },
6184
+ async statements() {
6185
+ return (await pool.query(`SELECT statement FROM ${p}_statements ORDER BY id`)).rows.map((r) => r.statement);
6186
+ },
6187
+ async addStatement(s) {
6188
+ 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)]);
6189
+ }
6190
+ };
6191
+ }
6192
+
6193
+ // src/act/passkeys.ts
6194
+ function cbor(b) {
6195
+ let i = 0;
6196
+ const len = (info) => {
6197
+ if (info < 24) return info;
6198
+ const n = info === 24 ? 1 : info === 25 ? 2 : info === 26 ? 4 : -1;
6199
+ if (n < 0) throw new Error("unsupported CBOR length");
6200
+ let v2 = 0;
6201
+ for (let k = 0; k < n; k++) v2 = v2 * 256 + b[i++];
6202
+ return v2;
6203
+ };
6204
+ const item = (depth) => {
6205
+ if (depth > 16 || i >= b.length) throw new Error("malformed CBOR");
6206
+ const head = b[i++], major = head >> 5, info = head & 31;
6207
+ switch (major) {
6208
+ case 0:
6209
+ return len(info);
6210
+ case 1:
6211
+ return -1 - len(info);
6212
+ case 2: {
6213
+ const n = len(info);
6214
+ const v2 = b.slice(i, i + n);
6215
+ i += n;
6216
+ if (v2.length !== n) throw new Error("truncated CBOR");
6217
+ return v2;
6218
+ }
6219
+ case 3: {
6220
+ const n = len(info);
6221
+ const v2 = fromUtf82(b.slice(i, i + n));
6222
+ i += n;
6223
+ return v2;
6224
+ }
6225
+ case 4: {
6226
+ const n = len(info), a = [];
6227
+ for (let k = 0; k < n; k++) a.push(item(depth + 1));
6228
+ return a;
6229
+ }
6230
+ case 5: {
6231
+ const n = len(info), m = /* @__PURE__ */ new Map();
6232
+ for (let k = 0; k < n; k++) m.set(item(depth + 1), item(depth + 1));
6233
+ return m;
6234
+ }
6235
+ case 7:
6236
+ if (info === 20) return false;
6237
+ if (info === 21) return true;
6238
+ if (info === 22) return null;
6239
+ throw new Error("unsupported CBOR simple value");
6240
+ default:
6241
+ throw new Error("unsupported CBOR type");
6242
+ }
6243
+ };
6244
+ const v = item(0);
6245
+ return { value: v, end: i };
6246
+ }
6247
+ function readAttestation(attestationObject) {
6248
+ const att = cbor(attestationObject).value;
6249
+ if (!(att instanceof Map) || !(att.get("authData") instanceof Uint8Array)) throw new Error("not an attestation object");
6250
+ const a = att.get("authData");
6251
+ if (a.length < 55 || !(a[32] & 64)) throw new Error("no attested credential data");
6252
+ const idLen = a[53] << 8 | a[54];
6253
+ const credentialId = a.slice(55, 55 + idLen);
6254
+ const cose = cbor(a.slice(55 + idLen)).value;
6255
+ 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)");
6256
+ const x = cose.get(-2), y = cose.get(-3);
6257
+ if (!(x instanceof Uint8Array) || !(y instanceof Uint8Array) || x.length !== 32 || y.length !== 32) throw new Error("malformed COSE key");
6258
+ 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);
6259
+ const publicKeyPem = pem("PUBLIC KEY", spki);
6260
+ if (!kidOf(publicKeyPem)) throw new Error("the passkey's key is not a valid P-256 point");
6261
+ return { authData: a, flags: a[32], credentialId, publicKeyPem };
6262
+ }
6263
+ async function signEnrolmentToken(authority, c) {
6264
+ if (!/^[^,\s]+$/.test(c.approver)) throw new Error("An approver id has no commas or spaces (NOMOS-SPEC-007 rev. 1.9 \xA73.7)");
6265
+ const claims = { approver: c.approver, roles: [...c.roles], origin: c.origin.replace(/\/$/, ""), expires_at: c.expiresAt.toISOString(), nonce: toBase64url2(randomBytes3(16)) };
6266
+ const token = { ...claims, kid: authority.kid, algorithm: authority.algorithm, signature: await authority.sign(jcs(claims)) };
6267
+ return toBase64url2(utf82(JSON.stringify(token)));
6268
+ }
6269
+ function verifyEnrolmentToken(token, authorityPem, origin, now) {
6270
+ let t;
6271
+ try {
6272
+ t = JSON.parse(fromUtf82(fromBase64url2(token)));
6273
+ } catch {
6274
+ return { error: "not an enrolment link" };
6275
+ }
6276
+ const claims = { approver: t?.approver, roles: t?.roles, origin: t?.origin, expires_at: t?.expires_at, nonce: t?.nonce };
6277
+ 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" };
6278
+ 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" };
6279
+ if (claims.origin !== origin) return { error: "this enrolment link is for another service" };
6280
+ if (!(Date.parse(claims.expires_at) > now.getTime())) return { error: "this enrolment link has expired" };
6281
+ return claims;
6282
+ }
6283
+ function memoryPasskeyStore() {
6284
+ const used = /* @__PURE__ */ new Set(), challenges = /* @__PURE__ */ new Map(), creds = [], pend = /* @__PURE__ */ new Map();
6285
+ return {
6286
+ async claimToken(n) {
6287
+ if (used.has(n)) return false;
6288
+ used.add(n);
6289
+ return true;
6290
+ },
6291
+ async tokenUsed(n) {
6292
+ return used.has(n);
6293
+ },
6294
+ async saveEnrolmentChallenge(n, c) {
6295
+ if (!used.has(n)) challenges.set(n, c);
6296
+ },
6297
+ async enrolmentChallenge(n) {
6298
+ return used.has(n) ? null : challenges.get(n) ?? null;
6299
+ },
6300
+ async addCredential(c) {
6301
+ creds.push(structuredClone(c));
6302
+ },
6303
+ async credentials() {
6304
+ return structuredClone(creds);
6305
+ },
6306
+ async createPending(p) {
6307
+ pend.set(p.id, structuredClone(p));
6308
+ },
6309
+ async pending(id) {
6310
+ const p = pend.get(id);
6311
+ return p ? structuredClone(p) : null;
6312
+ },
6313
+ async addAttempt(id, c, a) {
6314
+ const p = pend.get(id);
6315
+ if (!p || p.status !== "pending") return false;
6316
+ p.attempts[c] = { ...a };
6317
+ return true;
6318
+ },
6319
+ async resolvePending(id, out) {
6320
+ const p = pend.get(id);
6321
+ if (!p || p.status !== "pending") return false;
6322
+ Object.assign(p, structuredClone(out));
6323
+ return true;
6324
+ }
6325
+ };
6326
+ }
6327
+ function postgresPasskeyStore(pool, opts = {}) {
6328
+ const p = opts.prefix ?? "nomos_passkeys";
6329
+ if (!/^[a-z_][a-z0-9_]{0,40}$/.test(p)) throw new Error(`"${p}" is not a plain lower-case prefix`);
6330
+ const schema = `CREATE TABLE IF NOT EXISTS ${p}_tokens (nonce text PRIMARY KEY, challenge text, used boolean NOT NULL DEFAULT false);
6331
+ CREATE TABLE IF NOT EXISTS ${p}_credentials (credential_id text PRIMARY KEY, credential json NOT NULL, added_at timestamptz NOT NULL DEFAULT now());
6332
+ CREATE TABLE IF NOT EXISTS ${p}_pending (id text PRIMARY KEY, approval jsonb NOT NULL, updated_at timestamptz NOT NULL DEFAULT now());`;
6333
+ return {
6334
+ schema,
6335
+ async migrate() {
6336
+ await pool.query(schema);
6337
+ },
6338
+ async claimToken(n) {
6339
+ 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]);
6340
+ return (r.rowCount ?? r.rows.length) === 1;
6341
+ },
6342
+ async tokenUsed(n) {
6343
+ return (await pool.query(`SELECT 1 FROM ${p}_tokens WHERE nonce = $1 AND used = true`, [n])).rows.length > 0;
6344
+ },
6345
+ async saveEnrolmentChallenge(n, c) {
6346
+ 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]);
6347
+ },
6348
+ async enrolmentChallenge(n) {
6349
+ return (await pool.query(`SELECT challenge FROM ${p}_tokens WHERE nonce = $1 AND used = false`, [n])).rows[0]?.challenge ?? null;
6350
+ },
6351
+ async addCredential(c) {
6352
+ await pool.query(`INSERT INTO ${p}_credentials (credential_id, credential) VALUES ($1, $2::json)`, [c.credential_id, JSON.stringify(c)]);
6353
+ },
6354
+ async credentials() {
6355
+ return (await pool.query(`SELECT credential FROM ${p}_credentials ORDER BY added_at`)).rows.map((r) => r.credential);
6356
+ },
6357
+ async createPending(a) {
6358
+ await pool.query(`INSERT INTO ${p}_pending (id, approval) VALUES ($1, $2::jsonb)`, [a.id, JSON.stringify(a)]);
6359
+ },
6360
+ async pending(id) {
6361
+ return (await pool.query(`SELECT approval FROM ${p}_pending WHERE id = $1`, [id])).rows[0]?.approval ?? null;
6362
+ },
6363
+ // Each change is one conditional statement: Postgres serializes them on the row, and none
6364
+ // rewrites the whole record, so a stale reader cannot write an old status back.
6365
+ async addAttempt(id, c, a) {
6366
+ 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)]);
6367
+ return (r.rowCount ?? r.rows.length) === 1;
6368
+ },
6369
+ async resolvePending(id, out) {
6370
+ 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)]);
6371
+ return (r.rowCount ?? r.rows.length) === 1;
6372
+ }
6373
+ };
6374
+ }
6375
+ var MAX_ATTEMPTS = 50;
6376
+ var esc = (s) => String(s).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
6377
+ function passkeyApprover(o) {
6378
+ const base = o.origin.replace(/\/$/, "");
6379
+ const rpId = o.rpId ?? new URL(base).hostname;
6380
+ const store = o.store ?? memoryPasskeyStore();
6381
+ if (!o.store) console.warn("[nomos] passkeyApprover(): using the in-memory store. Enrolled passkeys are lost on restart. Development only; use postgresPasskeyStore() in production.");
6382
+ const now = o.now ?? (() => /* @__PURE__ */ new Date());
6383
+ const rpIdHash = sha256(utf82(rpId));
6384
+ const expired = (p) => p.status === "pending" && Date.parse(p.act.expires_at) <= now().getTime();
6385
+ const statusOf = (p) => expired(p) ? "expired" : p.status;
6386
+ function clientData(b64, type, challenge) {
6387
+ let c;
6388
+ try {
6389
+ c = JSON.parse(fromUtf82(fromBase64url2(b64)));
6390
+ } catch {
6391
+ return "unreadable client data";
6392
+ }
6393
+ if (c?.type !== type) return `expected a ${type} ceremony`;
6394
+ if (c?.challenge !== challenge) return "the challenge does not match";
6395
+ if (c?.origin !== base) return `the ceremony ran on ${c?.origin}, not ${base}`;
6396
+ return null;
6397
+ }
6398
+ async function route(method, path, json) {
6399
+ if (method === "POST" && path === "/nomos/approvals") {
6400
+ const { role, act: act2 } = await json();
6401
+ if (!o.roles.includes(role)) return { status: 404, body: { error: `This service does not approve as ${role}.` } };
6402
+ if (typeof act2?.expires_at !== "string" || !act2?.action) return { status: 400, body: { error: "The request needs the Act." } };
6403
+ const id = randomUUID();
6404
+ await store.createPending({ id, role, act: act2, status: "pending", created_at: now().toISOString(), attempts: {} });
6405
+ await o.onPending?.({ id, role, act: act2, approvalUrl: `${base}/approve/${id}` });
6406
+ return { status: 202, body: { approval_id: id, status: "pending", poll_url: `${base}/nomos/approvals/${id}`, approval_url: `${base}/approve/${id}` } };
6407
+ }
6408
+ let m = path.match(/^\/nomos\/approvals\/([0-9a-f-]{36})$/);
6409
+ if (method === "GET" && m) {
6410
+ const p = await store.pending(m[1]);
6411
+ if (!p) return null;
6412
+ const s = statusOf(p);
6413
+ return { status: 200, body: { status: s, ...s === "approved" ? { consent: p.consent, ...p.key_certs?.length ? { key_certs: p.key_certs } : {} } : {} } };
6414
+ }
6415
+ m = path.match(/^\/approve\/([0-9a-f-]{36})(?:\/(devices|options|assertion|refuse))?$/);
6416
+ if (m) {
6417
+ const p = await store.pending(m[1]);
6418
+ if (!p) return null;
6419
+ 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) };
6420
+ if (method !== "POST" || !m[2]) return null;
6421
+ if (statusOf(p) !== "pending") return { status: 409, body: { error: `This request is ${statusOf(p)}.` } };
6422
+ 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." } };
6423
+ const req = await json();
6424
+ if (m[2] === "devices") {
6425
+ const mine = (await store.credentials()).filter((c) => c.approver === String(req?.approver ?? "") && c.roles.includes(p.role));
6426
+ return { status: 200, body: { devices: mine.map((c) => ({ credential_id: c.credential_id, label: c.label })) } };
6427
+ }
6428
+ const cred = (await store.credentials()).find((c) => c.credential_id === req?.credential_id);
6429
+ if (!cred || !cred.roles.includes(p.role)) return { status: 403, body: { error: `That passkey does not approve as ${p.role}.` } };
6430
+ if (m[2] === "options") {
6431
+ const signed_at = now().toISOString();
6432
+ const payload2 = consentPayload({ binding_digest: bindingDigest(p.act), role: p.role, decision: "approve", signed_at, kid: cred.kid });
6433
+ const challenge = toBase64url2(sha256(jcs(payload2)));
6434
+ if (Object.keys(p.attempts).length >= MAX_ATTEMPTS) return { status: 429, body: { error: "Too many approval ceremonies for this request." } };
6435
+ const expires_at = new Date(Math.min(Date.parse(p.act.expires_at), now().getTime() + 5 * 6e4)).toISOString();
6436
+ 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." } };
6437
+ return { status: 200, body: { challenge, rpId, allowCredentials: [{ type: "public-key", id: cred.credential_id }], userVerification: "required", timeout: 12e4 } };
6438
+ }
6439
+ let offered;
6440
+ try {
6441
+ offered = JSON.parse(fromUtf82(fromBase64url2(String(req.client_data_json ?? "")))).challenge;
6442
+ } catch {
6443
+ return { status: 400, body: { error: "unreadable client data" } };
6444
+ }
6445
+ const attempt = p.attempts[offered];
6446
+ if (!attempt || attempt.credential_id !== cred.credential_id) return { status: 400, body: { error: "This ceremony was not offered for this passkey." } };
6447
+ if (Date.parse(attempt.expires_at) <= now().getTime()) return { status: 400, body: { error: "This ceremony has expired; start again." } };
6448
+ const consent = {
6449
+ binding_digest: bindingDigest(p.act),
6450
+ role: p.role,
6451
+ decision: "approve",
6452
+ signed_at: attempt.signed_at,
6453
+ kid: cred.kid,
6454
+ algorithm: "WebAuthn-ES256",
6455
+ authenticator_data: String(req.authenticator_data ?? ""),
6456
+ client_data_json: String(req.client_data_json ?? ""),
6457
+ signature: String(req.signature ?? "")
6458
+ };
6459
+ const payload = jcs(consentPayload(consent));
6460
+ const cd = clientData(consent.client_data_json, "webauthn.get", toBase64url2(sha256(payload)));
6461
+ if (cd) return { status: 400, body: { error: cd } };
6462
+ const ad = fromBase64url2(consent.authenticator_data);
6463
+ if (ad.length < 37 || !equal2(ad.slice(0, 32), rpIdHash)) return { status: 400, body: { error: "The assertion is for another relying-party id." } };
6464
+ const v = verifySuite("WebAuthn-ES256", cred.public_key_pem, payload, consent);
6465
+ if (!v.ok) return { status: 400, body: { error: `The passkey's signature does not verify (${v.reason}).` } };
6466
+ const key_certs = await o.registry.chainFor(cred.kid);
6467
+ if (!key_certs.length) return { status: 409, body: { error: "This passkey is no longer certified (revoked or expired)." } };
6468
+ if (!await store.resolvePending(p.id, { status: "approved", consent, key_certs })) return { status: 409, body: { error: "This request is no longer pending." } };
6469
+ return { status: 200, body: { status: "approved" } };
6470
+ }
6471
+ if (method === "GET" && path === "/enroll") return { status: 200, type: "text/html", body: enrolPage(o.rpName ?? rpId) };
6472
+ if (method === "POST" && (path === "/nomos/enrol/begin" || path === "/nomos/enrol/finish")) {
6473
+ const req = await json();
6474
+ const claims = verifyEnrolmentToken(String(req?.token ?? ""), o.enrolmentAuthority, base, now());
6475
+ if ("error" in claims) return { status: 403, body: { error: claims.error } };
6476
+ if (!claims.roles.every((r) => o.roles.includes(r))) return { status: 403, body: { error: `This service does not enrol for ${claims.roles.join(", ")}.` } };
6477
+ if (path === "/nomos/enrol/begin") {
6478
+ if (await store.tokenUsed(claims.nonce)) return { status: 403, body: { error: "This enrolment link has been used." } };
6479
+ const challenge2 = toBase64url2(randomBytes3(32));
6480
+ await store.saveEnrolmentChallenge(claims.nonce, challenge2);
6481
+ const existing = (await store.credentials()).filter((c) => c.approver === claims.approver);
6482
+ return { status: 200, body: {
6483
+ challenge: challenge2,
6484
+ rp: { id: rpId, name: o.rpName ?? rpId },
6485
+ user: { id: toBase64url2(utf82(claims.approver)), name: claims.approver, displayName: claims.approver },
6486
+ pubKeyCredParams: [{ type: "public-key", alg: -7 }],
6487
+ authenticatorSelection: { userVerification: "required", residentKey: "preferred" },
6488
+ attestation: "none",
6489
+ timeout: 12e4,
6490
+ excludeCredentials: existing.map((c) => ({ type: "public-key", id: c.credential_id })),
6491
+ approver: claims.approver,
6492
+ roles: claims.roles
6493
+ } };
6494
+ }
6495
+ const challenge = await store.enrolmentChallenge(claims.nonce);
6496
+ if (!challenge) return { status: 403, body: { error: "Start enrolment again: this link has been used or has no ceremony in progress." } };
6497
+ const cd = clientData(String(req.client_data_json ?? ""), "webauthn.create", challenge);
6498
+ if (cd) return { status: 400, body: { error: cd } };
6499
+ let att;
6500
+ try {
6501
+ att = readAttestation(fromBase64url2(String(req.attestation_object ?? "")));
6502
+ } catch (e) {
6503
+ return { status: 400, body: { error: String(e?.message ?? e) } };
6504
+ }
6505
+ if (!equal2(att.authData.slice(0, 32), rpIdHash)) return { status: 400, body: { error: "The passkey was created for another relying-party id." } };
6506
+ 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)." } };
6507
+ if (toBase64url2(att.credentialId) !== req.credential_id) return { status: 400, body: { error: "The credential id does not match the attestation." } };
6508
+ if (!await store.claimToken(claims.nonce)) return { status: 403, body: { error: "This enrolment link has been used." } };
6509
+ const kid = kidOf(att.publicKeyPem);
6510
+ const certificate = await o.registry.issue({
6511
+ parent: o.enroller,
6512
+ child: { kid, publicKeyPem: att.publicKeyPem },
6513
+ scope: `consent:${claims.roles.join(",")} approver:${claims.approver} suite:WebAuthn-ES256`,
6514
+ days: o.certificateDays ?? 365
6515
+ });
6516
+ const label = String(req.label ?? "").slice(0, 60) || "Passkey";
6517
+ 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() });
6518
+ return { status: 200, body: { enrolled: true, approver: claims.approver, roles: claims.roles, kid, certificate } };
6519
+ }
6520
+ return null;
6521
+ }
6522
+ async function fetchHandler(request) {
6523
+ const out = await route(request.method, new URL(request.url).pathname, () => request.json()).catch((e) => ({ status: 400, body: { error: String(e?.message ?? e) } }));
6524
+ if (!out) return new Response(JSON.stringify({ error: "Not found" }), { status: 404, headers: { "content-type": "application/json" } });
6525
+ const html = out.type === "text/html";
6526
+ 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() } : {} } });
6527
+ }
6528
+ function node() {
6529
+ return async (req, res, next) => {
6530
+ const path = String(req.originalUrl ?? req.url ?? "").split("?")[0];
6531
+ const json = async () => {
6532
+ if (req.body && typeof req.body === "object" && !(req.body instanceof Uint8Array)) return req.body;
6533
+ const chunks = [];
6534
+ for await (const c of req) chunks.push(c);
6535
+ return JSON.parse(fromUtf82(concat2(...chunks)) || "{}");
6536
+ };
6537
+ let out;
6538
+ try {
6539
+ out = await route(req.method, path, json);
6540
+ } catch (e) {
6541
+ out = { status: 400, body: { error: String(e?.message ?? e) } };
6542
+ }
6543
+ if (!out) {
6544
+ if (next) return next();
6545
+ res.statusCode = 404;
6546
+ return res.end(JSON.stringify({ error: "Not found" }));
6547
+ }
6548
+ res.statusCode = out.status;
6549
+ const html = out.type === "text/html";
6550
+ res.setHeader("content-type", html ? "text/html; charset=utf-8" : "application/json");
6551
+ if (html) res.setHeader("content-security-policy", CSP());
6552
+ res.end(html ? String(out.body) : JSON.stringify(out.body));
6553
+ };
6554
+ }
6555
+ return { store, fetch: fetchHandler, express: node, node };
6556
+ }
6557
+ 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}
6558
+ @media (prefers-color-scheme:dark){body{color:#eee;background:#111}table td{border-color:#333}}
6559
+ h2{margin:0 0 12px}table{border-collapse:collapse;margin:12px 0}td{padding:6px 10px;border-bottom:1px solid #ddd}
6560
+ button{font:inherit;padding:10px 18px;border-radius:8px;border:1px solid #888;background:transparent;color:inherit;cursor:pointer;margin:4px 8px 4px 0}
6561
+ button.primary{background:#2563eb;border-color:#2563eb;color:#fff}.muted{color:#777;font-size:13px}#msg{margin-top:16px;font-weight:600}`;
6562
+ 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))};
6563
+ 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(()=>({}))}));
6564
+ const say=t=>{document.getElementById('msg').textContent=t};`;
6565
+ function approvalPage(p, status, devices) {
6566
+ const a = p.act.action ?? {};
6567
+ const params = Object.entries(a.params ?? {}).map(([k, v]) => `<tr><td>${esc(k)}</td><td><b>${esc(JSON.stringify(v))}</b></td></tr>`).join("");
6568
+ const device = (d) => `<button class="primary" data-cred="${esc(d.credential_id)}">Approve${d.approver ? ` as ${esc(d.approver)}` : ""} \xB7 ${esc(d.label)}</button>`;
6569
+ 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>`;
6570
+ const body = status === "pending" ? `${choose}<button id="refuse">Refuse</button>` : `<p><b>${esc(status)}</b></p>`;
6571
+ 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>
6572
+ <h2>Approve as ${esc(p.role)}?</h2>
6573
+ <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>
6574
+ <table>${params}</table>
6575
+ <p class="muted">Your passkey signs this exact change and nothing else. It expires at ${esc(p.act.expires_at)}.</p>
6576
+ ${body}<div id="msg" data-id="${esc(p.id)}"></div>
6577
+ <script>${APPROVE_JS}</script>`;
6578
+ }
6579
+ var APPROVE_JS = `${B642}
6580
+ const id=document.getElementById('msg').dataset.id;
6581
+ async function approve(credential_id){
6582
+ try{
6583
+ const o=await post('/approve/'+id+'/options',{credential_id});
6584
+ if(!o.ok) return say(o.body.error||'Not allowed.');
6585
+ 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}});
6586
+ 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)});
6587
+ say(r.ok?'Approved. You can close this page.':(r.body.error||'Not approved.'));
6588
+ if(r.ok) document.querySelectorAll('button').forEach(b=>b.disabled=true);
6589
+ }catch(e){say(e.name==='NotAllowedError'?'Cancelled.':String(e.message||e))}
6590
+ }
6591
+ 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)}
6592
+ function wire(root){root.querySelectorAll('[data-cred]').forEach(b=>b.addEventListener('click',()=>approve(b.dataset.cred)))}
6593
+ wire(document);
6594
+ document.getElementById('refuse')?.addEventListener('click',refuse);
6595
+ document.getElementById('find')?.addEventListener('click',async()=>{
6596
+ const r=await post('/approve/'+id+'/devices',{approver:document.getElementById('who').value});
6597
+ const box=document.getElementById('devices');box.textContent='';
6598
+ if(!r.body.devices?.length) return say('No passkey of yours approves this.');
6599
+ 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)}
6600
+ wire(box);
6601
+ });`;
6602
+ function enrolPage(rpName) {
6603
+ 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>
6604
+ <h2>Enrol a passkey for ${esc(rpName)}</h2>
6605
+ <p id="who" class="muted">Checking your enrolment link\u2026</p>
6606
+ <p><label>Name this device <input id="label" value="" placeholder="e.g. iPhone"></label></p>
6607
+ <button class="primary" id="go" disabled>Create passkey</button>
6608
+ <div id="msg"></div>
6609
+ <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>
6610
+ <script>${ENROL_JS}</script>`;
6611
+ }
6612
+ var ENROL_JS = `${B642}
6613
+ const token=location.hash.slice(1);let opts=null;
6614
+ (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}
6615
+ opts=r.body;document.getElementById('who').textContent='Enrolling '+opts.approver+' to approve as '+opts.roles.join(', ')+'.';document.getElementById('go').disabled=false})();
6616
+ async function enrol(){
6617
+ try{
6618
+ 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)}))}});
6619
+ 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)});
6620
+ say(r.ok?'Enrolled. This device can now approve as '+r.body.roles.join(', ')+'.':(r.body.error||'Not enrolled.'));
6621
+ if(r.ok) document.getElementById('go').disabled=true;
6622
+ }catch(e){say(e.name==='NotAllowedError'?'Cancelled.':String(e.message||e))}
6623
+ }
6624
+ document.getElementById('go').addEventListener('click',enrol);`;
6625
+ var scriptHash = (js) => `'sha256-${btoa(String.fromCharCode(...sha256(utf82(js))))}'`;
6626
+ 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'`;
6627
+
5704
6628
  // src/act/carrier.ts
5705
6629
  var STATUS_OF = { AUTHORIZED: "committed", ESCALATED: "escalated", INCOMPLETE: "incomplete", DENIED: "denied", REPLAYED: "replayed" };
5706
6630
  var NomosActError = class extends Error {
@@ -5737,6 +6661,7 @@ async function act(o) {
5737
6661
  }
5738
6662
  }
5739
6663
  const testimony = [];
6664
+ const available = [...issuer2.key_certs ?? []];
5740
6665
  const unavailable = [];
5741
6666
  for (const claim of witnessed) {
5742
6667
  const w = witnesses.find((x) => x.claims.includes(claim));
@@ -5760,8 +6685,10 @@ async function act(o) {
5760
6685
  continue;
5761
6686
  }
5762
6687
  testimony.push(t);
6688
+ if (Array.isArray(body2.key_certs)) available.push(...body2.key_certs);
5763
6689
  }
5764
- const theAct = buildAct({ relyingParty: rp.relying_party_id, action: o.action, artifact, keyCerts: issuer2.key_certs, testimony, presenterFacts: o.presenterFacts, inForce, windowSeconds });
6690
+ const theAct = buildAct({ relyingParty: rp.relying_party_id, action: o.action, artifact, keyCerts: [], testimony, presenterFacts: o.presenterFacts, inForce, windowSeconds });
6691
+ present(theAct, available);
5765
6692
  let { status, body } = await submit(f, rp.act_endpoint, theAct);
5766
6693
  let approvalUrl;
5767
6694
  while (status === 202 && body.decision === "ESCALATED") {
@@ -5769,16 +6696,18 @@ async function act(o) {
5769
6696
  const endpoint = (issuer2.approvals ?? []).find((a) => a.role === role)?.endpoint;
5770
6697
  if (!endpoint) return done("escalated", body, theAct, { summary: `Needs approval from ${role}, and ${issuer2.issuer_id} publishes no approval endpoint for that role.` });
5771
6698
  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) => {
6699
+ const answer2 = await requestConsent(f, endpoint, role, theAct, deadline, o.pollMs ?? 1e3, (url) => {
5773
6700
  approvalUrl = url;
5774
6701
  o.onApproval?.({ role, url, actDigest: body.act_digest });
5775
6702
  });
5776
- if (consent === "refused") return done("denied", body, theAct, { summary: `The ${role} refused this action.` });
5777
- if (!consent) {
6703
+ if (answer2 === "refused") return done("denied", body, theAct, { summary: `The ${role} refused this action.` });
6704
+ if (!answer2) {
5778
6705
  const expired = Date.now() >= Date.parse(theAct.expires_at);
5779
6706
  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
6707
  }
5781
- theAct.consents.push(consent);
6708
+ theAct.consents.push(answer2.consent);
6709
+ if (Array.isArray(answer2.keyCerts)) available.push(...answer2.keyCerts);
6710
+ present(theAct, available);
5782
6711
  const before = Number(body.consents_valid ?? 0);
5783
6712
  ({ status, body } = await submit(f, rp.act_endpoint, theAct));
5784
6713
  if (status === 202 && Number(body.consents_valid ?? 0) <= before) {
@@ -5799,6 +6728,13 @@ async function act(o) {
5799
6728
  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
6729
  });
5801
6730
  }
6731
+ function present(theAct, available) {
6732
+ const set = presentedCertificates(available, theAct);
6733
+ if (set.length > MAX_PRESENTED) {
6734
+ 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.`);
6735
+ }
6736
+ theAct.authority.key_certs = set;
6737
+ }
5802
6738
  function done(status, verdict, theAct, extra) {
5803
6739
  return { status, verdict, act: theAct, ...extra };
5804
6740
  }
@@ -5816,13 +6752,13 @@ async function submit(f, endpoint, theAct) {
5816
6752
  async function requestConsent(f, endpoint, role, theAct, deadline, pollMs, onPending) {
5817
6753
  const res = await f(endpoint, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ role, act: { ...theAct, consents: [] } }) });
5818
6754
  const body = await res.json().catch(() => ({}));
5819
- if (res.status === 200 && body.status === "approved") return body.consent;
6755
+ if (res.status === 200 && body.status === "approved") return { consent: body.consent, keyCerts: body.key_certs };
5820
6756
  if (res.status !== 202 || !body.poll_url) return null;
5821
6757
  onPending(body.approval_url);
5822
6758
  while (Date.now() < deadline) {
5823
6759
  await new Promise((r) => setTimeout(r, pollMs));
5824
6760
  const p = await (await f(body.poll_url)).json().catch(() => ({}));
5825
- if (p.status === "approved") return p.consent;
6761
+ if (p.status === "approved") return { consent: p.consent, keyCerts: p.key_certs };
5826
6762
  if (p.status === "refused") return "refused";
5827
6763
  if (p.status === "expired") return null;
5828
6764
  }
@@ -5830,7 +6766,6 @@ async function requestConsent(f, endpoint, role, theAct, deadline, pollMs, onPen
5830
6766
  }
5831
6767
 
5832
6768
  // src/act/roles.ts
5833
- import { randomUUID as randomUUID2 } from "crypto";
5834
6769
  function serve(route) {
5835
6770
  async function fetchHandler(request) {
5836
6771
  const out = await route({ method: request.method, path: new URL(request.url).pathname, json: () => request.json() });
@@ -5845,7 +6780,7 @@ function serve(route) {
5845
6780
  if (req.body && typeof req.body === "object" && !(req.body instanceof Uint8Array)) return req.body;
5846
6781
  const chunks = [];
5847
6782
  for await (const c of req) chunks.push(c);
5848
- return JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
6783
+ return JSON.parse(fromUtf82(concat2(...chunks)) || "{}");
5849
6784
  };
5850
6785
  let out;
5851
6786
  try {
@@ -5869,7 +6804,9 @@ var day = 864e5;
5869
6804
  function issuer(o) {
5870
6805
  const byId = new Map(o.authorities.map((a) => [a.meta.artifact_id, a]));
5871
6806
  const base = o.origin.replace(/\/$/, "");
5872
- const document = () => ({
6807
+ const pool = async () => typeof o.keyCerts === "function" ? await o.keyCerts() : o.keyCerts;
6808
+ const revocationsUrl = o.revocations ? `${base}/nomos/revocations` : o.revocationsUrl;
6809
+ const build = (keyCerts) => ({
5873
6810
  nomos_document: "issuer",
5874
6811
  version: "1",
5875
6812
  issuer_id: o.issuerId,
@@ -5880,17 +6817,24 @@ function issuer(o) {
5880
6817
  artifact_url: `${base}/nomos/authorities/${encodeURIComponent(a.meta.artifact_id)}.nomos`,
5881
6818
  in_force_url: `${base}/nomos/authorities/${encodeURIComponent(a.meta.artifact_id)}/in-force`
5882
6819
  })),
5883
- key_certs: o.keyCerts,
6820
+ key_certs: keyCerts,
5884
6821
  ...o.witnesses ? { witnesses: o.witnesses } : {},
5885
6822
  ...o.approvals ? { approvals: o.approvals } : {},
5886
- ...o.revocationsUrl ? { revocations_url: o.revocationsUrl } : {},
6823
+ ...revocationsUrl ? { revocations_url: revocationsUrl } : {},
5887
6824
  valid_until: new Date(Date.now() + day).toISOString()
5888
6825
  });
6826
+ const document = () => {
6827
+ if (typeof o.keyCerts === "function") throw new Error("issuer(): keyCerts is a function, so the document must be read with `await resolveDocument()`");
6828
+ return build(o.keyCerts);
6829
+ };
6830
+ const resolveDocument = async () => build(await pool());
5889
6831
  return {
5890
6832
  document,
6833
+ resolveDocument,
5891
6834
  ...serve(async ({ method, path }) => {
5892
6835
  if (method !== "GET") return null;
5893
- if (path === WELL_KNOWN.issuer) return { status: 200, body: document() };
6836
+ if (path === WELL_KNOWN.issuer) return { status: 200, body: await resolveDocument() };
6837
+ if (path === "/nomos/revocations" && o.revocations) return { status: 200, body: await o.revocations() };
5894
6838
  const m = path.match(/^\/nomos\/authorities\/([^/]+?)(\.nomos|\/in-force)$/);
5895
6839
  const art = m && byId.get(decodeURIComponent(m[1]));
5896
6840
  if (!art) return null;
@@ -5925,13 +6869,14 @@ function witness(o) {
5925
6869
  if (value2 === void 0) return { status: 404, body: { error: `${o.witnessId} knows nothing about ${about}.` } };
5926
6870
  const max = o.maxValiditySeconds ?? 900;
5927
6871
  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) }) } };
6872
+ 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
6873
  })
5930
6874
  };
5931
6875
  }
5932
6876
  function approver(o) {
5933
6877
  const base = o.origin.replace(/\/$/, "");
5934
6878
  const pending = /* @__PURE__ */ new Map();
6879
+ const chain = o.keyCerts?.length ? { key_certs: o.keyCerts } : {};
5935
6880
  const statusOf = (p) => p.status === "pending" && Date.parse(p.act.expires_at) < Date.now() ? "expired" : p.status;
5936
6881
  return {
5937
6882
  pending,
@@ -5941,9 +6886,9 @@ function approver(o) {
5941
6886
  if (!o.roles.includes(role)) return { status: 404, body: { error: `This approver does not consent as ${role}.` } };
5942
6887
  if (o.decide === "auto" || typeof o.decide === "function") {
5943
6888
  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" } };
6889
+ return ok ? { status: 200, body: { status: "approved", consent: await signConsent(o.signer, act2, role), ...chain } } : { status: 200, body: { status: "refused" } };
5945
6890
  }
5946
- const id = randomUUID2();
6891
+ const id = randomUUID();
5947
6892
  pending.set(id, { role, act: act2, status: "pending" });
5948
6893
  return { status: 202, body: { approval_id: id, status: "pending", poll_url: `${base}/nomos/approvals/${id}`, approval_url: `${base}/approve/${id}` } };
5949
6894
  }
@@ -5952,7 +6897,7 @@ function approver(o) {
5952
6897
  const p = pending.get(m[1]);
5953
6898
  if (!p) return null;
5954
6899
  const s = statusOf(p);
5955
- return { status: 200, body: { status: s, ...s === "approved" ? { consent: p.consent } : {} } };
6900
+ return { status: 200, body: { status: s, ...s === "approved" ? { consent: p.consent, ...chain } : {} } };
5956
6901
  }
5957
6902
  m = path.match(/^\/approve\/([0-9a-f-]+)(\/(approve|refuse))?$/);
5958
6903
  if (m) {
@@ -5972,22 +6917,23 @@ function approver(o) {
5972
6917
  })
5973
6918
  };
5974
6919
  }
5975
- var esc = (s) => String(s).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
6920
+ var esc2 = (s) => String(s).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
5976
6921
  function page(p, status, id) {
5977
6922
  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("");
6923
+ 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
6924
  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>
6925
+ <form method="post" action="/approve/${id}/refuse" style="display:inline"><button style="padding:10px 18px">Refuse</button></form>` : `<p><b>${esc2(status)}</b></p>`;
6926
+ return `<!doctype html><meta charset="utf-8"><title>Approve: ${esc2(a.type)}</title>
5982
6927
  <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>
6928
+ <h2>Approve as ${esc2(p.role)}?</h2>
6929
+ <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
6930
  <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>
6931
+ <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
6932
  ${buttons}</body>`;
5988
6933
  }
5989
6934
  export {
5990
6935
  ACT_MEDIA_TYPE,
6936
+ MAX_PRESENTED,
5991
6937
  Nomos,
5992
6938
  NomosAPIError,
5993
6939
  NomosActError,
@@ -6002,6 +6948,8 @@ export {
6002
6948
  NomosRateLimitError,
6003
6949
  NomosValidationError,
6004
6950
  NomosWebhookSignatureError,
6951
+ RegistryError,
6952
+ RevocationUnavailable,
6005
6953
  WELL_KNOWN,
6006
6954
  accept,
6007
6955
  act,
@@ -6009,24 +6957,40 @@ export {
6009
6957
  bindingDigest,
6010
6958
  buildAct,
6011
6959
  can,
6960
+ certificateRegistry,
6012
6961
  certify,
6013
6962
  fetchDocument,
6014
6963
  generateSigner,
6015
6964
  issuer,
6016
6965
  kidOf2 as kidOf,
6017
6966
  localSigner,
6967
+ memoryPasskeyStore,
6968
+ memoryRegistryStore,
6018
6969
  memoryStore,
6019
6970
  nomos,
6020
6971
  originOf,
6972
+ passkeyApprover,
6973
+ postgresPasskeyStore,
6974
+ postgresRegistryStore,
6975
+ postgresStore,
6976
+ presentedCertificates,
6977
+ readAttestation,
6978
+ reliedKids,
6979
+ revocationListCertificates,
6980
+ revocationSource,
6021
6981
  seal,
6022
6982
  seededSigner,
6023
6983
  signConsent,
6984
+ signEnrolmentToken,
6024
6985
  signInForce,
6986
+ signRevocation,
6987
+ signRevocationList,
6025
6988
  signTestimony,
6026
6989
  statusFor,
6027
6990
  verifyAct,
6028
6991
  verifyArtifact,
6029
6992
  verifyChain,
6993
+ verifyEnrolmentToken,
6030
6994
  verifySeal,
6031
6995
  witness
6032
6996
  };