@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/README.md +35 -2
- package/dist/index.cjs +412 -11
- package/dist/index.d.cts +87 -2
- package/dist/index.d.ts +87 -2
- package/dist/index.js +410 -11
- package/package.json +2 -2
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.
|
|
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 ?? {},
|
|
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(
|
|
5222
|
-
if (
|
|
5223
|
-
return { decision: "SUPERSEDED", artifact_id: artId, in_force_seal_hash:
|
|
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
|
|
7260
|
+
let inForce2;
|
|
6869
7261
|
if (accepted.currency_policy === "record_or_statement" && !accepted.in_force_seal_hash && entry.in_force_url) {
|
|
6870
|
-
|
|
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.
|
|
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.
|
|
86
|
+
"@nomosprotocol/verify": "^1.4.0"
|
|
87
87
|
}
|
|
88
88
|
}
|