@nomosprotocol/sdk 2.6.0 → 2.7.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
@@ -107,7 +107,7 @@ function isRetryable(status) {
107
107
  }
108
108
 
109
109
  // src/http-client.ts
110
- var SDK_VERSION = "2.6.0";
110
+ var SDK_VERSION = "2.7.0";
111
111
  var HttpClient = class {
112
112
  constructor(opts) {
113
113
  this.opts = opts;
@@ -5174,7 +5174,7 @@ function verifyUnsafe(a, o) {
5174
5174
  const now = (o.now ?? /* @__PURE__ */ new Date()).getTime();
5175
5175
  const nowDate = new Date(now);
5176
5176
  const revokedKids = o.revokedKids ?? /* @__PURE__ */ new Set(), revokedCerts = o.revokedCerts ?? /* @__PURE__ */ new Set();
5177
- const localFacts = o.localFacts ?? {}, inForce = o.inForce ?? {};
5177
+ const localFacts = o.localFacts ?? {}, inForce2 = o.inForce ?? {};
5178
5178
  const need = ["act_version", "act_id", "nonce", "issued_at", "expires_at", "relying_party", "action", "authority"];
5179
5179
  if (!isObject(a) || need.some((k) => !(k in a)) || a.act_version !== "1" || !isObject(a.authority))
5180
5180
  return { decision: "MALFORMED", reason_code: "bad_envelope" };
@@ -5218,9 +5218,9 @@ function verifyUnsafe(a, o) {
5218
5218
  if (o.revokedArtifacts?.has(seal2.hash))
5219
5219
  return { decision: "ARTIFACT_REVOKED", artifact_id: artId };
5220
5220
  let inForceSource;
5221
- if (typeof artId === "string" && Object.prototype.hasOwnProperty.call(inForce, artId)) {
5222
- if (inForce[artId] !== seal2.hash)
5223
- return { decision: "SUPERSEDED", artifact_id: artId, in_force_seal_hash: inForce[artId] };
5221
+ if (typeof artId === "string" && Object.prototype.hasOwnProperty.call(inForce2, artId)) {
5222
+ if (inForce2[artId] !== seal2.hash)
5223
+ return { decision: "SUPERSEDED", artifact_id: artId, in_force_seal_hash: inForce2[artId] };
5224
5224
  inForceSource = "record";
5225
5225
  } else if (o.currencyPolicy === "record") {
5226
5226
  return { decision: "VERSION_UNVERIFIED", artifact_id: artId, reason_code: "not_in_record" };
@@ -5437,6 +5437,288 @@ function verifyRevocationList(list, o) {
5437
5437
  return out;
5438
5438
  }
5439
5439
 
5440
+ // node_modules/@nomosprotocol/verify/dist/resolution.js
5441
+ var DEFAULT_CLOSURE_MAX_AGE_DAYS = 90;
5442
+ function evaluateScope(condition, context) {
5443
+ if (condition === "always" || condition === "all")
5444
+ return { value: "TRUE", openFacts: [] };
5445
+ let out;
5446
+ try {
5447
+ out = evaluate({
5448
+ logic: {
5449
+ resolution: { conflict_policy: "highest_priority", tie_breaker: "deny_wins", default_outcome: "escalate" },
5450
+ decisions: [
5451
+ { id: "scope-when-true", when: condition, priority: 1, then: [{ type: "allow" }] },
5452
+ { id: "scope", when: condition, priority: 2, then: [{ type: "block" }] }
5453
+ ]
5454
+ }
5455
+ }, context);
5456
+ } catch {
5457
+ return { value: "UNDECIDED", openFacts: ["__scope_unparseable__"] };
5458
+ }
5459
+ if (out.outcome === "block")
5460
+ return { value: "TRUE", openFacts: [] };
5461
+ if (out.outcome === "undecided")
5462
+ return { value: "UNDECIDED", openFacts: [...out.open_facts] };
5463
+ return { value: "FALSE", openFacts: [] };
5464
+ }
5465
+ var inForce = (c, at) => (!c.from || c.from <= at) && (!c.until || c.until > at);
5466
+ var closureFresh = (c, at) => {
5467
+ const ageMs = Date.parse(at) - Date.parse(c.at);
5468
+ if (!Number.isFinite(ageMs) || ageMs < 0)
5469
+ return false;
5470
+ return ageMs <= (c.max_age_days ?? DEFAULT_CLOSURE_MAX_AGE_DAYS) * 864e5;
5471
+ };
5472
+ var closureReplaced = (c, all, at) => {
5473
+ const t = Date.parse(c.at), now = Date.parse(at);
5474
+ return all.some((d) => d !== c && Date.parse(d.at) > t && Date.parse(d.at) <= now && (d.scope === c.scope || !!c.label && d.label === c.label));
5475
+ };
5476
+ function resolveApplicableSet(world, query, opts = {}) {
5477
+ const at = query.at;
5478
+ const ctx = query.context ?? {};
5479
+ const proof = { at, pins: [...world.pins].sort(), heads: {}, closures: [], conflicts: [], dropped: [] };
5480
+ if (opts.ablate === "C5") {
5481
+ const applicable2 = (opts.presented ?? []).map((a) => ({ authority: a, version: null, registrar: "carrier", scope: "always" })).sort((x, y) => x.authority < y.authority ? -1 : 1);
5482
+ return { status: "RESOLVED", applicable: applicable2, proof };
5483
+ }
5484
+ const openFacts = /* @__PURE__ */ new Set();
5485
+ const unreachable = [];
5486
+ const staleClosures = [];
5487
+ const disagreements = [];
5488
+ const staleHeads = [];
5489
+ const memberAt = opts.ablate === "C7" && opts.snapshot ? opts.snapshot : at;
5490
+ const candidates2 = [];
5491
+ const visited = /* @__PURE__ */ new Set();
5492
+ const queue = [...world.pins].sort();
5493
+ while (queue.length > 0) {
5494
+ const id = queue.shift();
5495
+ if (visited.has(id))
5496
+ continue;
5497
+ visited.add(id);
5498
+ const reg = world.registrars[id];
5499
+ if (!reg) {
5500
+ if (opts.ablate !== "C8")
5501
+ unreachable.push(id);
5502
+ continue;
5503
+ }
5504
+ proof.heads[id] = reg.head ?? null;
5505
+ if (opts.ablate !== "C8" && typeof world.max_head_age_seconds === "number") {
5506
+ const age = reg.head_at ? Date.parse(at) - Date.parse(reg.head_at) : NaN;
5507
+ if (!Number.isFinite(age) || age > world.max_head_age_seconds * 1e3)
5508
+ staleHeads.push(id);
5509
+ }
5510
+ const cert = evaluateScope(reg.certified_scope, ctx);
5511
+ for (const f of cert.openFacts)
5512
+ openFacts.add(f);
5513
+ for (const e of [...reg.recognitions ?? []].sort((a, b) => a.to < b.to ? -1 : 1)) {
5514
+ const s = evaluateScope(e.scope, ctx);
5515
+ if (s.value === "TRUE")
5516
+ queue.push(e.to);
5517
+ for (const f of s.openFacts)
5518
+ openFacts.add(f);
5519
+ }
5520
+ const closedLists = [];
5521
+ if (opts.ablate !== "C8") {
5522
+ let covered = false;
5523
+ for (const c of reg.closures) {
5524
+ if (closureReplaced(c, reg.closures, at))
5525
+ continue;
5526
+ const s = evaluateScope(c.scope, ctx);
5527
+ for (const f of s.openFacts)
5528
+ openFacts.add(f);
5529
+ if (s.value !== "TRUE")
5530
+ continue;
5531
+ const fresh = closureFresh(c, at);
5532
+ proof.closures.push({ registrar: id, scope: c.scope, at: c.at, fresh });
5533
+ if (fresh) {
5534
+ covered = true;
5535
+ closedLists.push(c.authorities);
5536
+ }
5537
+ }
5538
+ if (!covered)
5539
+ staleClosures.push(id);
5540
+ }
5541
+ const supersededNow = new Set((reg.supersessions ?? []).filter((s) => s.from <= memberAt).map((s) => s.superseded));
5542
+ const revoked = new Set(reg.revoked ?? []);
5543
+ const liveAuthorities = /* @__PURE__ */ new Set();
5544
+ const trueAuthorities = /* @__PURE__ */ new Set();
5545
+ for (const claim of reg.claims) {
5546
+ if (opts.ablate !== "C7" || opts.snapshot) {
5547
+ if (!inForce(claim, memberAt)) {
5548
+ proof.dropped.push({ authority: claim.authority, registrar: id, reason: "not_in_force" });
5549
+ continue;
5550
+ }
5551
+ if (supersededNow.has(claim.authority)) {
5552
+ proof.dropped.push({ authority: claim.authority, registrar: id, reason: "superseded" });
5553
+ continue;
5554
+ }
5555
+ if (revoked.has(claim.authority)) {
5556
+ proof.dropped.push({ authority: claim.authority, registrar: id, reason: "revoked" });
5557
+ continue;
5558
+ }
5559
+ }
5560
+ liveAuthorities.add(claim.authority);
5561
+ const s = evaluateScope(claim.scope, ctx);
5562
+ for (const f of s.openFacts)
5563
+ openFacts.add(f);
5564
+ if (s.value === "TRUE")
5565
+ trueAuthorities.add(claim.authority);
5566
+ if (s.value === "FALSE")
5567
+ continue;
5568
+ const within = opts.ablate === "C6" ? { value: "TRUE", openFacts: [] } : cert;
5569
+ if (s.value === "UNDECIDED" || within.value === "UNDECIDED")
5570
+ continue;
5571
+ if (within.value === "FALSE")
5572
+ continue;
5573
+ candidates2.push({ authority: claim.authority, version: claim.version ?? null, registrar: id, scope: claim.scope, exclusive: claim.exclusive === true });
5574
+ }
5575
+ if (opts.ablate !== "C8" && closedLists.length > 0) {
5576
+ for (const a of new Set(closedLists.flat())) {
5577
+ if (!liveAuthorities.has(a))
5578
+ disagreements.push({ registrar: id, authority: a, reason: "closed_but_not_claimed" });
5579
+ }
5580
+ for (const a of trueAuthorities) {
5581
+ if (!closedLists.some((l) => l.includes(a)))
5582
+ disagreements.push({ registrar: id, authority: a, reason: "claimed_but_not_closed" });
5583
+ }
5584
+ }
5585
+ }
5586
+ proof.dropped.sort((a, b) => a.authority + a.registrar < b.authority + b.registrar ? -1 : 1);
5587
+ proof.closures.sort((a, b) => a.registrar + a.scope + a.at < b.registrar + b.scope + b.at ? -1 : 1);
5588
+ if (openFacts.size > 0 || unreachable.length > 0 || staleClosures.length > 0 || disagreements.length > 0 || staleHeads.length > 0) {
5589
+ return {
5590
+ status: "INCOMPLETE",
5591
+ missing: {
5592
+ context_facts: [...openFacts].sort(),
5593
+ unreachable_registrars: unreachable.sort(),
5594
+ stale_closures: staleClosures.sort(),
5595
+ stale_heads: staleHeads.sort(),
5596
+ closure_disagreements: disagreements.sort((a, b) => a.registrar + "\0" + a.authority < b.registrar + "\0" + b.authority ? -1 : 1)
5597
+ },
5598
+ proof
5599
+ };
5600
+ }
5601
+ const exclusives = candidates2.filter((c) => c.exclusive);
5602
+ const conflictPairs = exclusives.filter((c) => exclusives.some((o) => o.registrar !== c.registrar));
5603
+ if (conflictPairs.length > 0) {
5604
+ proof.conflicts.push({
5605
+ context_covered_by: conflictPairs.map((c) => ({ registrar: c.registrar, authority: c.authority, scope: c.scope })).sort((a, b) => a.registrar + a.authority < b.registrar + b.authority ? -1 : 1)
5606
+ });
5607
+ return { status: "CONFLICT", conflicts: proof.conflicts, proof };
5608
+ }
5609
+ const seen = /* @__PURE__ */ new Set();
5610
+ const applicable = [];
5611
+ for (const c of [...candidates2].sort((a, b) => a.authority + "\0" + a.registrar < b.authority + "\0" + b.registrar ? -1 : 1)) {
5612
+ const key = c.authority + "\0" + c.registrar;
5613
+ if (seen.has(key))
5614
+ continue;
5615
+ seen.add(key);
5616
+ applicable.push({ authority: c.authority, version: c.version, registrar: c.registrar, scope: c.scope });
5617
+ }
5618
+ return { status: "RESOLVED", applicable, proof };
5619
+ }
5620
+
5621
+ // node_modules/@nomosprotocol/verify/dist/registrar.js
5622
+ var LOG_GENESIS = "0".repeat(64);
5623
+ var registrarHeadPayload = (h, log = "nomos-exchange-log") => jcs({ log, size: h.size, hash: h.hash, at: h.at });
5624
+ function verifyLogChain(entries) {
5625
+ let prev = LOG_GENESIS;
5626
+ const hashAt = /* @__PURE__ */ new Map([[0, LOG_GENESIS]]);
5627
+ for (const [i, e] of entries.entries()) {
5628
+ if (e.seq !== i + 1)
5629
+ return { ok: false, detail: `entry ${i + 1}: its seq is ${e.seq}` };
5630
+ if (e.prev_hash !== prev)
5631
+ return { ok: false, detail: `entry ${e.seq}: prev_hash does not match entry ${e.seq - 1}` };
5632
+ const { hash, ...body } = e;
5633
+ if (sha256Hex(jcs(body)) !== hash)
5634
+ return { ok: false, detail: `entry ${e.seq}: its hash does not match its contents` };
5635
+ prev = hash;
5636
+ hashAt.set(e.seq, hash);
5637
+ }
5638
+ return { ok: true, hashAt };
5639
+ }
5640
+ function verifyRegistrarLog(o) {
5641
+ const fail2 = (reason, detail) => ({ ok: false, reason, detail });
5642
+ const chain = verifyLogChain(o.entries);
5643
+ if (!chain.ok)
5644
+ return fail2("chain_broken", chain.detail);
5645
+ const h = o.head;
5646
+ if (!h || typeof h.size !== "number" || h.size > o.entries.length || chain.hashAt.get(h.size) !== h.hash) {
5647
+ return fail2("head_mismatch", `the head (size ${h?.size}) does not name an entry of this chain`);
5648
+ }
5649
+ if (!h.signature || !h.kid)
5650
+ return fail2("head_unsigned", "the head is not signed");
5651
+ const pinnedKid = kidOf(o.publicKeyPem);
5652
+ if (!pinnedKid || h.kid !== pinnedKid)
5653
+ return fail2("wrong_key", `the head is signed by ${h.kid}, not the pinned key ${pinnedKid ?? "(unreadable)"}`);
5654
+ if (!verifySuite("Ed25519", o.publicKeyPem, registrarHeadPayload(h, o.log), { signature: h.signature }).ok)
5655
+ return fail2("bad_signature", "the head signature does not verify under the pinned key");
5656
+ const age = o.now.getTime() - Date.parse(h.at);
5657
+ if (!Number.isFinite(age))
5658
+ return fail2("head_stale", "the head carries no time");
5659
+ if (age < -6e4)
5660
+ return fail2("head_in_future", `the head is dated ${h.at}, after now`);
5661
+ if (age > o.maxHeadAgeSeconds * 1e3)
5662
+ return fail2("head_stale", `the head is ${Math.round(age / 1e3)} s old; the bound is ${o.maxHeadAgeSeconds} s`);
5663
+ if (o.previous && chain.hashAt.get(o.previous.size) !== o.previous.hash) {
5664
+ return fail2("history_rewritten", `the head kept earlier (${o.previous.size}:${o.previous.hash.slice(0, 12)}\u2026) is not part of this log`);
5665
+ }
5666
+ return { ok: true, size: o.entries.length, head: { size: h.size, hash: h.hash, at: h.at, kid: h.kid } };
5667
+ }
5668
+ function foldRegistrarLog(entries, o) {
5669
+ const open = /* @__PURE__ */ new Map();
5670
+ const closedClaims = [];
5671
+ const closures = [];
5672
+ const recognitions = [];
5673
+ const supersessions = [];
5674
+ const revoked = /* @__PURE__ */ new Set();
5675
+ let head = null;
5676
+ for (const e of entries) {
5677
+ head = e.hash;
5678
+ if (e.kind === "applies") {
5679
+ const prior = open.get(e.name);
5680
+ if (prior)
5681
+ closedClaims.push({ ...prior, until: e.at });
5682
+ open.set(e.name, {
5683
+ authority: e.name,
5684
+ version: e.version ?? null,
5685
+ scope: typeof e.entry?.scope === "string" ? e.entry.scope : "always",
5686
+ exclusive: e.entry?.exclusive === true,
5687
+ from: e.at,
5688
+ until: typeof e.entry?.until === "string" ? e.entry.until : null,
5689
+ ...typeof e.entry?.basis === "string" ? { basis: e.entry.basis } : {}
5690
+ });
5691
+ } else if (e.kind === "withdrawn") {
5692
+ const prior = open.get(e.name);
5693
+ if (prior) {
5694
+ closedClaims.push({ ...prior, until: e.at });
5695
+ open.delete(e.name);
5696
+ }
5697
+ } else if (e.kind === "revoked") {
5698
+ revoked.add(e.name);
5699
+ } else if (e.kind === "closed") {
5700
+ const scope = typeof e.entry?.scope === "string" ? e.entry.scope : "always";
5701
+ const authorities = Array.isArray(e.entry?.authorities) ? e.entry.authorities.filter((a) => typeof a === "string") : [];
5702
+ closures.push({ scope, authorities, at: e.at, label: e.name, ...typeof e.entry?.max_age_days === "number" ? { max_age_days: e.entry.max_age_days } : {} });
5703
+ } else if (e.kind === "recognizes") {
5704
+ recognitions.push({ to: e.name, scope: typeof e.entry?.scope === "string" ? e.entry.scope : "always" });
5705
+ } else if (e.kind === "superseded") {
5706
+ if (typeof e.entry?.by === "string")
5707
+ supersessions.push({ superseded: e.name, by: e.entry.by, from: e.at });
5708
+ }
5709
+ }
5710
+ return {
5711
+ id: o.id,
5712
+ certified_scope: o.certifiedScope ?? "always",
5713
+ claims: [...closedClaims, ...open.values()],
5714
+ closures,
5715
+ recognitions,
5716
+ supersessions,
5717
+ revoked: [...revoked].sort(),
5718
+ head
5719
+ };
5720
+ }
5721
+
5440
5722
  // src/verify-chain.ts
5441
5723
  var CHAIN_PROTOCOL_VERSION2 = "1";
5442
5724
  var DEFAULT_BASE = "https://www.nomosprotocol.com";
@@ -5613,6 +5895,68 @@ ${tool}`);
5613
5895
  return lines.join("\n");
5614
5896
  }
5615
5897
 
5898
+ // src/act/registrars.ts
5899
+ var RegistrarUnavailable = class extends Error {
5900
+ constructor(message, retryAfterSeconds, registrar) {
5901
+ super(message);
5902
+ this.retryAfterSeconds = retryAfterSeconds;
5903
+ this.registrar = registrar;
5904
+ this.name = "RegistrarUnavailable";
5905
+ }
5906
+ };
5907
+ var PAGE = 1e3;
5908
+ function registrarSource(o) {
5909
+ const f = o.fetch ?? fetch, now = o.now ?? (() => /* @__PURE__ */ new Date());
5910
+ const base = o.url.replace(/\/$/, "");
5911
+ const maxHeadAge = o.maxHeadAgeSeconds ?? 600, refresh = (o.refreshSeconds ?? 60) * 1e3;
5912
+ let view = null, kept = o.initialHead ?? null;
5913
+ let fetchedAt = 0, inFlight = null, lastError = "";
5914
+ async function getJson2(path) {
5915
+ const res = await f(`${base}${path}`, { headers: { accept: "application/json" } });
5916
+ if (!res.ok) throw new Error(`${base}${path} answered ${res.status}`);
5917
+ return res.json();
5918
+ }
5919
+ async function load() {
5920
+ fetchedAt = now().getTime();
5921
+ try {
5922
+ const head = await getJson2("/.well-known/nomos-exchange-log/head");
5923
+ const entries = [];
5924
+ for (let after = 0; ; ) {
5925
+ const page2 = await getJson2(`/.well-known/nomos-exchange-log?after=${after}&limit=${PAGE}`);
5926
+ const got = Array.isArray(page2?.entries) ? page2.entries : [];
5927
+ entries.push(...got);
5928
+ if (got.length < PAGE || !page2.next) break;
5929
+ after = got[got.length - 1].seq;
5930
+ }
5931
+ const vouched = entries.slice(0, Math.min(entries.length, Number(head?.size) || 0));
5932
+ const check = verifyRegistrarLog({ entries: vouched, head, publicKeyPem: o.publicKeyPem, now: now(), maxHeadAgeSeconds: maxHeadAge, previous: kept });
5933
+ if (!check.ok) {
5934
+ lastError = `${check.reason}: ${check.detail}`;
5935
+ if (check.reason === "history_rewritten" || check.reason === "wrong_key" || check.reason === "bad_signature" || check.reason === "chain_broken") view = null;
5936
+ return;
5937
+ }
5938
+ view = { record: foldRegistrarLog(vouched, { id: o.id, certifiedScope: o.certifiedScope }), head: check.head };
5939
+ kept = { size: check.head.size, hash: check.head.hash };
5940
+ lastError = "";
5941
+ } catch (e) {
5942
+ lastError = String(e?.message ?? e);
5943
+ }
5944
+ }
5945
+ const source = (async () => {
5946
+ const t = now().getTime();
5947
+ if (!inFlight && t - fetchedAt >= refresh) inFlight = load().finally(() => {
5948
+ inFlight = null;
5949
+ });
5950
+ if (inFlight) await inFlight;
5951
+ if (!view || t - Date.parse(view.head.at) > maxHeadAge * 1e3) {
5952
+ throw new RegistrarUnavailable(`Registrar ${o.id} (${base}) has no current, verified log${lastError ? `: ${lastError}` : ""}. Not committing until the law that applies can be read.`, Math.max(1, Math.ceil(refresh / 1e3)), o.id);
5953
+ }
5954
+ return view;
5955
+ });
5956
+ source.id = o.id;
5957
+ return source;
5958
+ }
5959
+
5616
5960
  // src/act/bytes.ts
5617
5961
  var utf82 = (s) => new TextEncoder().encode(s);
5618
5962
  var fromUtf82 = (b) => new TextDecoder().decode(b);
@@ -5893,7 +6237,7 @@ var STATUS = { AUTHORIZED: 201, ESCALATED: 202, INCOMPLETE: 422, DENIED: 403, RE
5893
6237
  var statusFor = (decision) => STATUS[decision] ?? 400;
5894
6238
  var ACT_MEDIA_TYPE = "application/nomos-act+json";
5895
6239
  var HINTS = {
5896
- INCOMPLETE: (v) => `Missing facts that could change the outcome: ${(v.open_facts ?? []).join(", ")}. Bring them as testimony from a witness certified for each claim, or as the channel the authority declares.`,
6240
+ INCOMPLETE: (v) => v.reason_code === "laws_not_presented" ? `Other law also governs this act here: ${(v.laws ?? []).join(", ")}. This system commits only when every governing law is carried; this Act carries one.` : v.reason_code === "resolution_incomplete" ? `This system cannot yet establish which law governs this act${v.missing?.context_facts?.length ? `: its own knowledge of the act lacks ${v.missing.context_facts.join(", ")}` : v.missing?.stale_closures?.length ? ": a registrar it honours does not cover this case" : ""}. Nothing the carrier sends can supply this; nothing is committed while the governing law is unknown.` : v.reason_code === "resolution_conflict" ? "Two registrars each claim exclusive precedence over this act. A person must settle which law governs before it can be committed." : `Missing facts that could change the outcome: ${(v.open_facts ?? []).join(", ")}. Bring them as testimony from a witness certified for each claim, or as the channel the authority declares.`,
5897
6241
  ESCALATED: (v) => `Needs ${v.consents_required} approval(s) from role ${v.role}. Request consent over act_digest ${v.act_digest} and resubmit this same Act before ${v.retry_until}.`,
5898
6242
  DENIED: (v) => `Rule ${v.rule ?? "(default)"} refuses this action. No approval can change a denial.`,
5899
6243
  WRONG_RELYING_PARTY: () => "The Act is addressed to another system. Build it with this relying_party_id.",
@@ -6006,6 +6350,24 @@ function accept(o) {
6006
6350
  maxWindowSeconds
6007
6351
  });
6008
6352
  }
6353
+ async function resolveFor(act2, authority, facts, at, reuse) {
6354
+ if (!o.resolution) return null;
6355
+ const views = reuse ?? await Promise.all(o.resolution.registrars.map(async (src) => [src.id, await src()]));
6356
+ const context = { ...await o.resolution.context(act2, facts) };
6357
+ const r = resolveApplicableSet(
6358
+ { pins: views.map(([id]) => id), registrars: Object.fromEntries(views.map(([id, v]) => [id, v.record])) },
6359
+ { context, at: at.toISOString() }
6360
+ );
6361
+ const heads = Object.fromEntries(views.map(([id, v]) => [id, v.head.hash]));
6362
+ const applicable = r.status === "RESOLVED" ? [...new Set(r.applicable.map((m) => m.authority))].sort() : [];
6363
+ const info = { status: r.status, context, applicable, heads };
6364
+ if (r.status === "INCOMPLETE") return { ok: false, info, views, verdict: { decision: "INCOMPLETE", reason_code: "resolution_incomplete", missing: r.missing } };
6365
+ if (r.status === "CONFLICT") return { ok: false, info, views, verdict: { decision: "INCOMPLETE", reason_code: "resolution_conflict", conflicts: r.conflicts } };
6366
+ const carried = new Set([authority.registrarName, authority.artifactId].filter((x) => !!x));
6367
+ const notCarried = applicable.filter((a) => !carried.has(a));
6368
+ if (notCarried.length) return { ok: false, info: { ...info, laws_not_presented: notCarried }, views, verdict: { decision: "INCOMPLETE", reason_code: "laws_not_presented", laws: notCarried } };
6369
+ return { ok: true, info, views };
6370
+ }
6009
6371
  async function handle(req) {
6010
6372
  const ctype = String(req.headers["content-type"] ?? "").split(";")[0].trim().toLowerCase();
6011
6373
  if ((req.method ?? "POST").toUpperCase() !== "POST") return { status: 405, headers: { allow: "POST", "content-type": "application/json" }, body: { error: "POST an Act here." } };
@@ -6052,8 +6414,31 @@ function accept(o) {
6052
6414
  if (verdict.decision === "REPLAYED") {
6053
6415
  return answer(verdict, { act_id: act2.act_id, receipt: await store.receiptFor(act2.nonce) });
6054
6416
  }
6417
+ let resolved = null;
6418
+ if (verdict.decision === "AUTHORIZED" && o.resolution) {
6419
+ try {
6420
+ resolved = await resolveFor(act2, authority, facts, verifiedAt);
6421
+ } catch (e) {
6422
+ if (!(e instanceof RegistrarUnavailable)) throw e;
6423
+ return {
6424
+ status: 503,
6425
+ headers: { "content-type": "application/json", "retry-after": String(e.retryAfterSeconds) },
6426
+ body: { error: "The law that governs this act cannot be read right now, so nothing is committed. Resubmit this same Act later.", detail: e.message, registrar: e.registrar, retry_after: e.retryAfterSeconds }
6427
+ };
6428
+ }
6429
+ if (resolved && !resolved.ok) {
6430
+ const held = { ...resolved.verdict, act_id: act2.act_id, act_digest: verdict.act_digest, resolution: resolved.info };
6431
+ await o.onVerdict?.(held, { ...receiptOf(held, facts), resolution: resolved.info });
6432
+ if (o.mode === "shadow") {
6433
+ const a = answer(held);
6434
+ return { ...a, status: 200, body: { ...a.body, shadow: true } };
6435
+ }
6436
+ return answer(held);
6437
+ }
6438
+ }
6439
+ const withResolution = (r) => resolved?.ok ? { ...r, resolution: resolved.info } : r;
6055
6440
  if (verdict.decision !== "AUTHORIZED" || o.mode === "shadow") {
6056
- await o.onVerdict?.(verdict, receiptOf(verdict, facts));
6441
+ await o.onVerdict?.(verdict, withResolution(receiptOf(verdict, facts)));
6057
6442
  if (verdict.decision === "ESCALATED") {
6058
6443
  const esc3 = (act2.authority.artifact.governance?.escalations ?? []).find((e) => e?.id === verdict.escalation_id);
6059
6444
  return answer(verdict, { role: esc3?.role_required ?? null, retry_until: act2.expires_at });
@@ -6071,17 +6456,23 @@ function accept(o) {
6071
6456
  finalFacts = fresh;
6072
6457
  final = verifyWith(act2, authority, fresh, verifiedAt, false, rev);
6073
6458
  if (final.decision !== "AUTHORIZED") throw new Rollback(answer(final));
6459
+ if (o.resolution) {
6460
+ const again = await resolveFor(act2, authority, fresh, verifiedAt, resolved?.views);
6461
+ if (again && !again.ok) throw new Rollback(answer({ ...again.verdict, act_id: act2.act_id, resolution: again.info }));
6462
+ resolved = again;
6463
+ }
6074
6464
  }
6075
6465
  }
6076
6466
  const action = structuredClone({ type: act2.action.type, ...act2.action.subject !== void 0 ? { subject: act2.action.subject } : {}, params: act2.action.params ?? {} });
6077
6467
  const result = await o.commit(action, { tx: t.tx, actId: act2.act_id });
6078
- const receipt = receiptOf(final, finalFacts, now());
6468
+ const receipt = withResolution(receiptOf(final, finalFacts, now()));
6079
6469
  await t.putReceipt(act2.nonce, receipt, act2);
6080
6470
  await o.onVerdict?.(final, receipt);
6081
6471
  return answer(final, { receipt, ...result !== void 0 ? { result } : {} });
6082
6472
  });
6083
6473
  } catch (e) {
6084
6474
  if (e instanceof Rollback) return e.answer;
6475
+ if (e instanceof RegistrarUnavailable) return { status: 503, headers: { "content-type": "application/json", "retry-after": String(e.retryAfterSeconds) }, body: { error: "The law that governs this act cannot be read right now, so nothing is committed. Resubmit this same Act later.", detail: e.message, registrar: e.registrar, retry_after: e.retryAfterSeconds } };
6085
6476
  if (e instanceof LostRace) return answer({ decision: "REPLAYED" }, { act_id: act2.act_id, receipt: await store.receiptFor(act2.nonce) });
6086
6477
  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) } };
6087
6478
  }
@@ -6846,6 +7237,7 @@ var scriptHash = (js) => `'sha256-${btoa(String.fromCharCode(...sha256(utf82(js)
6846
7237
  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'`;
6847
7238
 
6848
7239
  // src/act/carrier.ts
7240
+ var RESOLUTION_HOLDS = /* @__PURE__ */ new Set(["laws_not_presented", "resolution_incomplete", "resolution_conflict"]);
6849
7241
  var STATUS_OF = { AUTHORIZED: "committed", ESCALATED: "escalated", INCOMPLETE: "incomplete", DENIED: "denied", REPLAYED: "replayed" };
6850
7242
  var NomosActError = class extends Error {
6851
7243
  constructor(message) {
@@ -6865,9 +7257,9 @@ async function act(o) {
6865
7257
  const entry = issuer2.authorities.find((a) => a.artifact_id === accepted.artifact_id);
6866
7258
  if (!entry) throw new NomosActError(`The issuer at ${accepted.issuer} does not publish ${accepted.artifact_id}.`);
6867
7259
  const artifact = await getJson(f, entry.artifact_url, "the authority");
6868
- let inForce;
7260
+ let inForce2;
6869
7261
  if (accepted.currency_policy === "record_or_statement" && !accepted.in_force_seal_hash && entry.in_force_url) {
6870
- inForce = await getJson(f, entry.in_force_url, "the in-force statement").catch(() => void 0);
7262
+ inForce2 = await getJson(f, entry.in_force_url, "the in-force statement").catch(() => void 0);
6871
7263
  }
6872
7264
  const inputs = artifact?.data_contract?.inputs ?? {};
6873
7265
  const witnessed = Object.entries(inputs).filter(([, i]) => i?.source === "testimony").map(([n]) => n);
@@ -6907,7 +7299,7 @@ async function act(o) {
6907
7299
  testimony.push(t);
6908
7300
  if (Array.isArray(body2.key_certs)) available.push(...body2.key_certs);
6909
7301
  }
6910
- const theAct = buildAct({ relyingParty: rp.relying_party_id, action: o.action, artifact, keyCerts: [], testimony, presenterFacts: o.presenterFacts, inForce, windowSeconds });
7302
+ const theAct = buildAct({ relyingParty: rp.relying_party_id, action: o.action, artifact, keyCerts: [], testimony, presenterFacts: o.presenterFacts, inForce: inForce2, windowSeconds });
6911
7303
  present(theAct, available);
6912
7304
  let { status, body } = await submit(f, rp.act_endpoint, theAct);
6913
7305
  let approvalUrl;
@@ -6935,6 +7327,10 @@ async function act(o) {
6935
7327
  }
6936
7328
  }
6937
7329
  const kind = STATUS_OF[body.decision] ?? "refused";
7330
+ if (kind === "incomplete" && RESOLUTION_HOLDS.has(body.reason_code)) {
7331
+ const laws = Array.isArray(body.laws) ? body.laws : [];
7332
+ return done("incomplete", body, theAct, { missing: [], summary: body.reason_code === "laws_not_presented" ? `Not committed: other law also governs this act at ${rp.relying_party_id} (${laws.join(", ")}), and this Act carries only ${theAct.authority?.artifact?.meta?.artifact_id ?? "one authority"}.` : `Not committed: ${body.hint ?? `${rp.relying_party_id} cannot yet settle which law governs this act (${body.reason_code}).`}` });
7333
+ }
6938
7334
  if (kind === "incomplete") {
6939
7335
  const missing = (body.open_facts ?? []).map((fact) => {
6940
7336
  const why = unavailable.find((u) => u.fact === fact || fact.startsWith(u.fact + "."));
@@ -6966,6 +7362,7 @@ async function getJson(f, url, what) {
6966
7362
  async function submit(f, endpoint, theAct) {
6967
7363
  const res = await f(endpoint, { method: "POST", headers: { "content-type": ACT_MEDIA_TYPE }, body: JSON.stringify(theAct) });
6968
7364
  const body = await res.json().catch(() => ({ decision: "MALFORMED", reason_code: "unreadable_response" }));
7365
+ if (res.status === 503) throw new NomosActError(`The relying party is not committing right now (503): ${body?.error ?? ""}${body?.retry_after ? ` Resubmit this same Act after ${body.retry_after} s.` : ""}`);
6969
7366
  if (res.status >= 500) throw new NomosActError(`The relying party failed to commit (${res.status}): ${body?.error ?? ""}`);
6970
7367
  return { status: res.status, body };
6971
7368
  }
@@ -7168,6 +7565,7 @@ export {
7168
7565
  NomosRateLimitError,
7169
7566
  NomosValidationError,
7170
7567
  NomosWebhookSignatureError,
7568
+ RegistrarUnavailable,
7171
7569
  RegistryError,
7172
7570
  RevocationUnavailable,
7173
7571
  WELL_KNOWN,
@@ -7198,6 +7596,7 @@ export {
7198
7596
  postgresStore,
7199
7597
  presentedCertificates,
7200
7598
  readAttestation,
7599
+ registrarSource,
7201
7600
  reliedKids,
7202
7601
  revocationListCertificates,
7203
7602
  revocationSource,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nomosprotocol/sdk",
3
- "version": "2.6.0",
3
+ "version": "2.7.0",
4
4
  "description": "NOMOS for TypeScript: accept() for the system that makes a change, act() for the agent that asks, the issuer, witness and approver roles, and the Exchange client. Verification is local (@nomosprotocol/verify).",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -83,6 +83,6 @@
83
83
  "dependencies": {
84
84
  "@noble/curves": "^2.4.0",
85
85
  "@noble/hashes": "^2.4.0",
86
- "@nomosprotocol/verify": "^1.3.0"
86
+ "@nomosprotocol/verify": "^1.4.0"
87
87
  }
88
88
  }