@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/README.md
CHANGED
|
@@ -97,6 +97,39 @@ can return their own chains with what they sign (`keyCerts`). On the relying par
|
|
|
97
97
|
(NOMOS-SPEC-007 rev. 1.10). Revocations are never forgotten, and without a current list
|
|
98
98
|
`accept()` answers 503 instead of committing.
|
|
99
99
|
|
|
100
|
+
**Which laws govern the act (2.7.0).** An Act carries one authority, so on its own `accept()` checks
|
|
101
|
+
the law the carrier chose. Pin registrars and `accept()` works out every law that governs the act here,
|
|
102
|
+
now, and commits only if the Act carries every one (NOMOS-SPEC-014, Draft):
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { accept, registrarSource } from '@nomosprotocol/sdk';
|
|
106
|
+
|
|
107
|
+
const platform = registrarSource({
|
|
108
|
+
id: 'platform',
|
|
109
|
+
url: 'https://www.nomosprotocol.com',
|
|
110
|
+
publicKeyPem: PLATFORM_KEY_PEM, // pinned by you; heads signed by any other key are refused
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const rp = accept({
|
|
114
|
+
// ...relyingPartyId, root, authorities (with registrarName), commit, as above
|
|
115
|
+
resolution: {
|
|
116
|
+
registrars: [platform],
|
|
117
|
+
// From YOUR knowledge and state, never from the Act: a carrier that stated its own
|
|
118
|
+
// jurisdiction would choose the law again.
|
|
119
|
+
context: (act, localFacts) => ({ jurisdiction: 'AE', established_in: 'DIFC', subjects_in: 'AE', output_used_in: 'AE' }),
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
If another law also governs the act, the Act is held (`422 INCOMPLETE`, `reason_code:
|
|
125
|
+
'laws_not_presented'`, the laws named) and nothing is committed. A context fact you leave out holds it
|
|
126
|
+
too (`resolution_incomplete`, naming the fact). A registrar that cannot be read, a head older than
|
|
127
|
+
`maxHeadAgeSeconds`, a head signed by a key you did not pin, or a log whose history was rewritten
|
|
128
|
+
answers 503 with Retry-After. Receipts record the resolution (status, context, laws, log heads).
|
|
129
|
+
Create each `registrarSource()` once and reuse it: the check that a registrar's history was only ever
|
|
130
|
+
added to spans the source's lifetime (pass `initialHead` to carry the last head across restarts).
|
|
131
|
+
Without `resolution`, `accept()` is unchanged.
|
|
132
|
+
|
|
100
133
|
**The other roles** are one call each:
|
|
101
134
|
- `issuer()` publishes your sealed policies and certificates;
|
|
102
135
|
- `witness()` turns an existing lookup into signed statements;
|
|
@@ -168,8 +201,8 @@ No. 45 of 2021 and DIFC Law No. 5 of 2020, and lists only its own laws, so other
|
|
|
168
201
|
apply. Only want the list of laws? `POST /api/v1/resolve { context }` returns it without asking
|
|
169
202
|
them, and records nothing.
|
|
170
203
|
|
|
171
|
-
|
|
172
|
-
|
|
204
|
+
The system that commits the change can resolve too, from registrars it pins and a context it supplies
|
|
205
|
+
itself: see `accept({ resolution })` above (2.7.0).
|
|
173
206
|
|
|
174
207
|
### 1. Public authority
|
|
175
208
|
|
package/dist/index.cjs
CHANGED
|
@@ -36,6 +36,7 @@ __export(index_exports, {
|
|
|
36
36
|
NomosRateLimitError: () => NomosRateLimitError,
|
|
37
37
|
NomosValidationError: () => NomosValidationError,
|
|
38
38
|
NomosWebhookSignatureError: () => NomosWebhookSignatureError,
|
|
39
|
+
RegistrarUnavailable: () => RegistrarUnavailable,
|
|
39
40
|
RegistryError: () => RegistryError,
|
|
40
41
|
RevocationUnavailable: () => RevocationUnavailable,
|
|
41
42
|
WELL_KNOWN: () => WELL_KNOWN,
|
|
@@ -66,6 +67,7 @@ __export(index_exports, {
|
|
|
66
67
|
postgresStore: () => postgresStore,
|
|
67
68
|
presentedCertificates: () => presentedCertificates,
|
|
68
69
|
readAttestation: () => readAttestation,
|
|
70
|
+
registrarSource: () => registrarSource,
|
|
69
71
|
reliedKids: () => reliedKids,
|
|
70
72
|
revocationListCertificates: () => revocationListCertificates,
|
|
71
73
|
revocationSource: () => revocationSource,
|
|
@@ -191,7 +193,7 @@ function isRetryable(status) {
|
|
|
191
193
|
}
|
|
192
194
|
|
|
193
195
|
// src/http-client.ts
|
|
194
|
-
var SDK_VERSION = "2.
|
|
196
|
+
var SDK_VERSION = "2.7.0";
|
|
195
197
|
var HttpClient = class {
|
|
196
198
|
constructor(opts) {
|
|
197
199
|
this.opts = opts;
|
|
@@ -5258,7 +5260,7 @@ function verifyUnsafe(a, o) {
|
|
|
5258
5260
|
const now = (o.now ?? /* @__PURE__ */ new Date()).getTime();
|
|
5259
5261
|
const nowDate = new Date(now);
|
|
5260
5262
|
const revokedKids = o.revokedKids ?? /* @__PURE__ */ new Set(), revokedCerts = o.revokedCerts ?? /* @__PURE__ */ new Set();
|
|
5261
|
-
const localFacts = o.localFacts ?? {},
|
|
5263
|
+
const localFacts = o.localFacts ?? {}, inForce2 = o.inForce ?? {};
|
|
5262
5264
|
const need = ["act_version", "act_id", "nonce", "issued_at", "expires_at", "relying_party", "action", "authority"];
|
|
5263
5265
|
if (!isObject(a) || need.some((k) => !(k in a)) || a.act_version !== "1" || !isObject(a.authority))
|
|
5264
5266
|
return { decision: "MALFORMED", reason_code: "bad_envelope" };
|
|
@@ -5302,9 +5304,9 @@ function verifyUnsafe(a, o) {
|
|
|
5302
5304
|
if (o.revokedArtifacts?.has(seal2.hash))
|
|
5303
5305
|
return { decision: "ARTIFACT_REVOKED", artifact_id: artId };
|
|
5304
5306
|
let inForceSource;
|
|
5305
|
-
if (typeof artId === "string" && Object.prototype.hasOwnProperty.call(
|
|
5306
|
-
if (
|
|
5307
|
-
return { decision: "SUPERSEDED", artifact_id: artId, in_force_seal_hash:
|
|
5307
|
+
if (typeof artId === "string" && Object.prototype.hasOwnProperty.call(inForce2, artId)) {
|
|
5308
|
+
if (inForce2[artId] !== seal2.hash)
|
|
5309
|
+
return { decision: "SUPERSEDED", artifact_id: artId, in_force_seal_hash: inForce2[artId] };
|
|
5308
5310
|
inForceSource = "record";
|
|
5309
5311
|
} else if (o.currencyPolicy === "record") {
|
|
5310
5312
|
return { decision: "VERSION_UNVERIFIED", artifact_id: artId, reason_code: "not_in_record" };
|
|
@@ -5521,6 +5523,288 @@ function verifyRevocationList(list, o) {
|
|
|
5521
5523
|
return out;
|
|
5522
5524
|
}
|
|
5523
5525
|
|
|
5526
|
+
// node_modules/@nomosprotocol/verify/dist/resolution.js
|
|
5527
|
+
var DEFAULT_CLOSURE_MAX_AGE_DAYS = 90;
|
|
5528
|
+
function evaluateScope(condition, context) {
|
|
5529
|
+
if (condition === "always" || condition === "all")
|
|
5530
|
+
return { value: "TRUE", openFacts: [] };
|
|
5531
|
+
let out;
|
|
5532
|
+
try {
|
|
5533
|
+
out = evaluate({
|
|
5534
|
+
logic: {
|
|
5535
|
+
resolution: { conflict_policy: "highest_priority", tie_breaker: "deny_wins", default_outcome: "escalate" },
|
|
5536
|
+
decisions: [
|
|
5537
|
+
{ id: "scope-when-true", when: condition, priority: 1, then: [{ type: "allow" }] },
|
|
5538
|
+
{ id: "scope", when: condition, priority: 2, then: [{ type: "block" }] }
|
|
5539
|
+
]
|
|
5540
|
+
}
|
|
5541
|
+
}, context);
|
|
5542
|
+
} catch {
|
|
5543
|
+
return { value: "UNDECIDED", openFacts: ["__scope_unparseable__"] };
|
|
5544
|
+
}
|
|
5545
|
+
if (out.outcome === "block")
|
|
5546
|
+
return { value: "TRUE", openFacts: [] };
|
|
5547
|
+
if (out.outcome === "undecided")
|
|
5548
|
+
return { value: "UNDECIDED", openFacts: [...out.open_facts] };
|
|
5549
|
+
return { value: "FALSE", openFacts: [] };
|
|
5550
|
+
}
|
|
5551
|
+
var inForce = (c, at) => (!c.from || c.from <= at) && (!c.until || c.until > at);
|
|
5552
|
+
var closureFresh = (c, at) => {
|
|
5553
|
+
const ageMs = Date.parse(at) - Date.parse(c.at);
|
|
5554
|
+
if (!Number.isFinite(ageMs) || ageMs < 0)
|
|
5555
|
+
return false;
|
|
5556
|
+
return ageMs <= (c.max_age_days ?? DEFAULT_CLOSURE_MAX_AGE_DAYS) * 864e5;
|
|
5557
|
+
};
|
|
5558
|
+
var closureReplaced = (c, all, at) => {
|
|
5559
|
+
const t = Date.parse(c.at), now = Date.parse(at);
|
|
5560
|
+
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));
|
|
5561
|
+
};
|
|
5562
|
+
function resolveApplicableSet(world, query, opts = {}) {
|
|
5563
|
+
const at = query.at;
|
|
5564
|
+
const ctx = query.context ?? {};
|
|
5565
|
+
const proof = { at, pins: [...world.pins].sort(), heads: {}, closures: [], conflicts: [], dropped: [] };
|
|
5566
|
+
if (opts.ablate === "C5") {
|
|
5567
|
+
const applicable2 = (opts.presented ?? []).map((a) => ({ authority: a, version: null, registrar: "carrier", scope: "always" })).sort((x, y) => x.authority < y.authority ? -1 : 1);
|
|
5568
|
+
return { status: "RESOLVED", applicable: applicable2, proof };
|
|
5569
|
+
}
|
|
5570
|
+
const openFacts = /* @__PURE__ */ new Set();
|
|
5571
|
+
const unreachable = [];
|
|
5572
|
+
const staleClosures = [];
|
|
5573
|
+
const disagreements = [];
|
|
5574
|
+
const staleHeads = [];
|
|
5575
|
+
const memberAt = opts.ablate === "C7" && opts.snapshot ? opts.snapshot : at;
|
|
5576
|
+
const candidates2 = [];
|
|
5577
|
+
const visited = /* @__PURE__ */ new Set();
|
|
5578
|
+
const queue = [...world.pins].sort();
|
|
5579
|
+
while (queue.length > 0) {
|
|
5580
|
+
const id = queue.shift();
|
|
5581
|
+
if (visited.has(id))
|
|
5582
|
+
continue;
|
|
5583
|
+
visited.add(id);
|
|
5584
|
+
const reg = world.registrars[id];
|
|
5585
|
+
if (!reg) {
|
|
5586
|
+
if (opts.ablate !== "C8")
|
|
5587
|
+
unreachable.push(id);
|
|
5588
|
+
continue;
|
|
5589
|
+
}
|
|
5590
|
+
proof.heads[id] = reg.head ?? null;
|
|
5591
|
+
if (opts.ablate !== "C8" && typeof world.max_head_age_seconds === "number") {
|
|
5592
|
+
const age = reg.head_at ? Date.parse(at) - Date.parse(reg.head_at) : NaN;
|
|
5593
|
+
if (!Number.isFinite(age) || age > world.max_head_age_seconds * 1e3)
|
|
5594
|
+
staleHeads.push(id);
|
|
5595
|
+
}
|
|
5596
|
+
const cert = evaluateScope(reg.certified_scope, ctx);
|
|
5597
|
+
for (const f of cert.openFacts)
|
|
5598
|
+
openFacts.add(f);
|
|
5599
|
+
for (const e of [...reg.recognitions ?? []].sort((a, b) => a.to < b.to ? -1 : 1)) {
|
|
5600
|
+
const s = evaluateScope(e.scope, ctx);
|
|
5601
|
+
if (s.value === "TRUE")
|
|
5602
|
+
queue.push(e.to);
|
|
5603
|
+
for (const f of s.openFacts)
|
|
5604
|
+
openFacts.add(f);
|
|
5605
|
+
}
|
|
5606
|
+
const closedLists = [];
|
|
5607
|
+
if (opts.ablate !== "C8") {
|
|
5608
|
+
let covered = false;
|
|
5609
|
+
for (const c of reg.closures) {
|
|
5610
|
+
if (closureReplaced(c, reg.closures, at))
|
|
5611
|
+
continue;
|
|
5612
|
+
const s = evaluateScope(c.scope, ctx);
|
|
5613
|
+
for (const f of s.openFacts)
|
|
5614
|
+
openFacts.add(f);
|
|
5615
|
+
if (s.value !== "TRUE")
|
|
5616
|
+
continue;
|
|
5617
|
+
const fresh = closureFresh(c, at);
|
|
5618
|
+
proof.closures.push({ registrar: id, scope: c.scope, at: c.at, fresh });
|
|
5619
|
+
if (fresh) {
|
|
5620
|
+
covered = true;
|
|
5621
|
+
closedLists.push(c.authorities);
|
|
5622
|
+
}
|
|
5623
|
+
}
|
|
5624
|
+
if (!covered)
|
|
5625
|
+
staleClosures.push(id);
|
|
5626
|
+
}
|
|
5627
|
+
const supersededNow = new Set((reg.supersessions ?? []).filter((s) => s.from <= memberAt).map((s) => s.superseded));
|
|
5628
|
+
const revoked = new Set(reg.revoked ?? []);
|
|
5629
|
+
const liveAuthorities = /* @__PURE__ */ new Set();
|
|
5630
|
+
const trueAuthorities = /* @__PURE__ */ new Set();
|
|
5631
|
+
for (const claim of reg.claims) {
|
|
5632
|
+
if (opts.ablate !== "C7" || opts.snapshot) {
|
|
5633
|
+
if (!inForce(claim, memberAt)) {
|
|
5634
|
+
proof.dropped.push({ authority: claim.authority, registrar: id, reason: "not_in_force" });
|
|
5635
|
+
continue;
|
|
5636
|
+
}
|
|
5637
|
+
if (supersededNow.has(claim.authority)) {
|
|
5638
|
+
proof.dropped.push({ authority: claim.authority, registrar: id, reason: "superseded" });
|
|
5639
|
+
continue;
|
|
5640
|
+
}
|
|
5641
|
+
if (revoked.has(claim.authority)) {
|
|
5642
|
+
proof.dropped.push({ authority: claim.authority, registrar: id, reason: "revoked" });
|
|
5643
|
+
continue;
|
|
5644
|
+
}
|
|
5645
|
+
}
|
|
5646
|
+
liveAuthorities.add(claim.authority);
|
|
5647
|
+
const s = evaluateScope(claim.scope, ctx);
|
|
5648
|
+
for (const f of s.openFacts)
|
|
5649
|
+
openFacts.add(f);
|
|
5650
|
+
if (s.value === "TRUE")
|
|
5651
|
+
trueAuthorities.add(claim.authority);
|
|
5652
|
+
if (s.value === "FALSE")
|
|
5653
|
+
continue;
|
|
5654
|
+
const within = opts.ablate === "C6" ? { value: "TRUE", openFacts: [] } : cert;
|
|
5655
|
+
if (s.value === "UNDECIDED" || within.value === "UNDECIDED")
|
|
5656
|
+
continue;
|
|
5657
|
+
if (within.value === "FALSE")
|
|
5658
|
+
continue;
|
|
5659
|
+
candidates2.push({ authority: claim.authority, version: claim.version ?? null, registrar: id, scope: claim.scope, exclusive: claim.exclusive === true });
|
|
5660
|
+
}
|
|
5661
|
+
if (opts.ablate !== "C8" && closedLists.length > 0) {
|
|
5662
|
+
for (const a of new Set(closedLists.flat())) {
|
|
5663
|
+
if (!liveAuthorities.has(a))
|
|
5664
|
+
disagreements.push({ registrar: id, authority: a, reason: "closed_but_not_claimed" });
|
|
5665
|
+
}
|
|
5666
|
+
for (const a of trueAuthorities) {
|
|
5667
|
+
if (!closedLists.some((l) => l.includes(a)))
|
|
5668
|
+
disagreements.push({ registrar: id, authority: a, reason: "claimed_but_not_closed" });
|
|
5669
|
+
}
|
|
5670
|
+
}
|
|
5671
|
+
}
|
|
5672
|
+
proof.dropped.sort((a, b) => a.authority + a.registrar < b.authority + b.registrar ? -1 : 1);
|
|
5673
|
+
proof.closures.sort((a, b) => a.registrar + a.scope + a.at < b.registrar + b.scope + b.at ? -1 : 1);
|
|
5674
|
+
if (openFacts.size > 0 || unreachable.length > 0 || staleClosures.length > 0 || disagreements.length > 0 || staleHeads.length > 0) {
|
|
5675
|
+
return {
|
|
5676
|
+
status: "INCOMPLETE",
|
|
5677
|
+
missing: {
|
|
5678
|
+
context_facts: [...openFacts].sort(),
|
|
5679
|
+
unreachable_registrars: unreachable.sort(),
|
|
5680
|
+
stale_closures: staleClosures.sort(),
|
|
5681
|
+
stale_heads: staleHeads.sort(),
|
|
5682
|
+
closure_disagreements: disagreements.sort((a, b) => a.registrar + "\0" + a.authority < b.registrar + "\0" + b.authority ? -1 : 1)
|
|
5683
|
+
},
|
|
5684
|
+
proof
|
|
5685
|
+
};
|
|
5686
|
+
}
|
|
5687
|
+
const exclusives = candidates2.filter((c) => c.exclusive);
|
|
5688
|
+
const conflictPairs = exclusives.filter((c) => exclusives.some((o) => o.registrar !== c.registrar));
|
|
5689
|
+
if (conflictPairs.length > 0) {
|
|
5690
|
+
proof.conflicts.push({
|
|
5691
|
+
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)
|
|
5692
|
+
});
|
|
5693
|
+
return { status: "CONFLICT", conflicts: proof.conflicts, proof };
|
|
5694
|
+
}
|
|
5695
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5696
|
+
const applicable = [];
|
|
5697
|
+
for (const c of [...candidates2].sort((a, b) => a.authority + "\0" + a.registrar < b.authority + "\0" + b.registrar ? -1 : 1)) {
|
|
5698
|
+
const key = c.authority + "\0" + c.registrar;
|
|
5699
|
+
if (seen.has(key))
|
|
5700
|
+
continue;
|
|
5701
|
+
seen.add(key);
|
|
5702
|
+
applicable.push({ authority: c.authority, version: c.version, registrar: c.registrar, scope: c.scope });
|
|
5703
|
+
}
|
|
5704
|
+
return { status: "RESOLVED", applicable, proof };
|
|
5705
|
+
}
|
|
5706
|
+
|
|
5707
|
+
// node_modules/@nomosprotocol/verify/dist/registrar.js
|
|
5708
|
+
var LOG_GENESIS = "0".repeat(64);
|
|
5709
|
+
var registrarHeadPayload = (h, log = "nomos-exchange-log") => jcs({ log, size: h.size, hash: h.hash, at: h.at });
|
|
5710
|
+
function verifyLogChain(entries) {
|
|
5711
|
+
let prev = LOG_GENESIS;
|
|
5712
|
+
const hashAt = /* @__PURE__ */ new Map([[0, LOG_GENESIS]]);
|
|
5713
|
+
for (const [i, e] of entries.entries()) {
|
|
5714
|
+
if (e.seq !== i + 1)
|
|
5715
|
+
return { ok: false, detail: `entry ${i + 1}: its seq is ${e.seq}` };
|
|
5716
|
+
if (e.prev_hash !== prev)
|
|
5717
|
+
return { ok: false, detail: `entry ${e.seq}: prev_hash does not match entry ${e.seq - 1}` };
|
|
5718
|
+
const { hash, ...body } = e;
|
|
5719
|
+
if (sha256Hex(jcs(body)) !== hash)
|
|
5720
|
+
return { ok: false, detail: `entry ${e.seq}: its hash does not match its contents` };
|
|
5721
|
+
prev = hash;
|
|
5722
|
+
hashAt.set(e.seq, hash);
|
|
5723
|
+
}
|
|
5724
|
+
return { ok: true, hashAt };
|
|
5725
|
+
}
|
|
5726
|
+
function verifyRegistrarLog(o) {
|
|
5727
|
+
const fail2 = (reason, detail) => ({ ok: false, reason, detail });
|
|
5728
|
+
const chain = verifyLogChain(o.entries);
|
|
5729
|
+
if (!chain.ok)
|
|
5730
|
+
return fail2("chain_broken", chain.detail);
|
|
5731
|
+
const h = o.head;
|
|
5732
|
+
if (!h || typeof h.size !== "number" || h.size > o.entries.length || chain.hashAt.get(h.size) !== h.hash) {
|
|
5733
|
+
return fail2("head_mismatch", `the head (size ${h?.size}) does not name an entry of this chain`);
|
|
5734
|
+
}
|
|
5735
|
+
if (!h.signature || !h.kid)
|
|
5736
|
+
return fail2("head_unsigned", "the head is not signed");
|
|
5737
|
+
const pinnedKid = kidOf(o.publicKeyPem);
|
|
5738
|
+
if (!pinnedKid || h.kid !== pinnedKid)
|
|
5739
|
+
return fail2("wrong_key", `the head is signed by ${h.kid}, not the pinned key ${pinnedKid ?? "(unreadable)"}`);
|
|
5740
|
+
if (!verifySuite("Ed25519", o.publicKeyPem, registrarHeadPayload(h, o.log), { signature: h.signature }).ok)
|
|
5741
|
+
return fail2("bad_signature", "the head signature does not verify under the pinned key");
|
|
5742
|
+
const age = o.now.getTime() - Date.parse(h.at);
|
|
5743
|
+
if (!Number.isFinite(age))
|
|
5744
|
+
return fail2("head_stale", "the head carries no time");
|
|
5745
|
+
if (age < -6e4)
|
|
5746
|
+
return fail2("head_in_future", `the head is dated ${h.at}, after now`);
|
|
5747
|
+
if (age > o.maxHeadAgeSeconds * 1e3)
|
|
5748
|
+
return fail2("head_stale", `the head is ${Math.round(age / 1e3)} s old; the bound is ${o.maxHeadAgeSeconds} s`);
|
|
5749
|
+
if (o.previous && chain.hashAt.get(o.previous.size) !== o.previous.hash) {
|
|
5750
|
+
return fail2("history_rewritten", `the head kept earlier (${o.previous.size}:${o.previous.hash.slice(0, 12)}\u2026) is not part of this log`);
|
|
5751
|
+
}
|
|
5752
|
+
return { ok: true, size: o.entries.length, head: { size: h.size, hash: h.hash, at: h.at, kid: h.kid } };
|
|
5753
|
+
}
|
|
5754
|
+
function foldRegistrarLog(entries, o) {
|
|
5755
|
+
const open = /* @__PURE__ */ new Map();
|
|
5756
|
+
const closedClaims = [];
|
|
5757
|
+
const closures = [];
|
|
5758
|
+
const recognitions = [];
|
|
5759
|
+
const supersessions = [];
|
|
5760
|
+
const revoked = /* @__PURE__ */ new Set();
|
|
5761
|
+
let head = null;
|
|
5762
|
+
for (const e of entries) {
|
|
5763
|
+
head = e.hash;
|
|
5764
|
+
if (e.kind === "applies") {
|
|
5765
|
+
const prior = open.get(e.name);
|
|
5766
|
+
if (prior)
|
|
5767
|
+
closedClaims.push({ ...prior, until: e.at });
|
|
5768
|
+
open.set(e.name, {
|
|
5769
|
+
authority: e.name,
|
|
5770
|
+
version: e.version ?? null,
|
|
5771
|
+
scope: typeof e.entry?.scope === "string" ? e.entry.scope : "always",
|
|
5772
|
+
exclusive: e.entry?.exclusive === true,
|
|
5773
|
+
from: e.at,
|
|
5774
|
+
until: typeof e.entry?.until === "string" ? e.entry.until : null,
|
|
5775
|
+
...typeof e.entry?.basis === "string" ? { basis: e.entry.basis } : {}
|
|
5776
|
+
});
|
|
5777
|
+
} else if (e.kind === "withdrawn") {
|
|
5778
|
+
const prior = open.get(e.name);
|
|
5779
|
+
if (prior) {
|
|
5780
|
+
closedClaims.push({ ...prior, until: e.at });
|
|
5781
|
+
open.delete(e.name);
|
|
5782
|
+
}
|
|
5783
|
+
} else if (e.kind === "revoked") {
|
|
5784
|
+
revoked.add(e.name);
|
|
5785
|
+
} else if (e.kind === "closed") {
|
|
5786
|
+
const scope = typeof e.entry?.scope === "string" ? e.entry.scope : "always";
|
|
5787
|
+
const authorities = Array.isArray(e.entry?.authorities) ? e.entry.authorities.filter((a) => typeof a === "string") : [];
|
|
5788
|
+
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 } : {} });
|
|
5789
|
+
} else if (e.kind === "recognizes") {
|
|
5790
|
+
recognitions.push({ to: e.name, scope: typeof e.entry?.scope === "string" ? e.entry.scope : "always" });
|
|
5791
|
+
} else if (e.kind === "superseded") {
|
|
5792
|
+
if (typeof e.entry?.by === "string")
|
|
5793
|
+
supersessions.push({ superseded: e.name, by: e.entry.by, from: e.at });
|
|
5794
|
+
}
|
|
5795
|
+
}
|
|
5796
|
+
return {
|
|
5797
|
+
id: o.id,
|
|
5798
|
+
certified_scope: o.certifiedScope ?? "always",
|
|
5799
|
+
claims: [...closedClaims, ...open.values()],
|
|
5800
|
+
closures,
|
|
5801
|
+
recognitions,
|
|
5802
|
+
supersessions,
|
|
5803
|
+
revoked: [...revoked].sort(),
|
|
5804
|
+
head
|
|
5805
|
+
};
|
|
5806
|
+
}
|
|
5807
|
+
|
|
5524
5808
|
// src/verify-chain.ts
|
|
5525
5809
|
var CHAIN_PROTOCOL_VERSION2 = "1";
|
|
5526
5810
|
var DEFAULT_BASE = "https://www.nomosprotocol.com";
|
|
@@ -5697,6 +5981,68 @@ ${tool}`);
|
|
|
5697
5981
|
return lines.join("\n");
|
|
5698
5982
|
}
|
|
5699
5983
|
|
|
5984
|
+
// src/act/registrars.ts
|
|
5985
|
+
var RegistrarUnavailable = class extends Error {
|
|
5986
|
+
constructor(message, retryAfterSeconds, registrar) {
|
|
5987
|
+
super(message);
|
|
5988
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
5989
|
+
this.registrar = registrar;
|
|
5990
|
+
this.name = "RegistrarUnavailable";
|
|
5991
|
+
}
|
|
5992
|
+
};
|
|
5993
|
+
var PAGE = 1e3;
|
|
5994
|
+
function registrarSource(o) {
|
|
5995
|
+
const f = o.fetch ?? fetch, now = o.now ?? (() => /* @__PURE__ */ new Date());
|
|
5996
|
+
const base = o.url.replace(/\/$/, "");
|
|
5997
|
+
const maxHeadAge = o.maxHeadAgeSeconds ?? 600, refresh = (o.refreshSeconds ?? 60) * 1e3;
|
|
5998
|
+
let view = null, kept = o.initialHead ?? null;
|
|
5999
|
+
let fetchedAt = 0, inFlight = null, lastError = "";
|
|
6000
|
+
async function getJson2(path) {
|
|
6001
|
+
const res = await f(`${base}${path}`, { headers: { accept: "application/json" } });
|
|
6002
|
+
if (!res.ok) throw new Error(`${base}${path} answered ${res.status}`);
|
|
6003
|
+
return res.json();
|
|
6004
|
+
}
|
|
6005
|
+
async function load() {
|
|
6006
|
+
fetchedAt = now().getTime();
|
|
6007
|
+
try {
|
|
6008
|
+
const head = await getJson2("/.well-known/nomos-exchange-log/head");
|
|
6009
|
+
const entries = [];
|
|
6010
|
+
for (let after = 0; ; ) {
|
|
6011
|
+
const page2 = await getJson2(`/.well-known/nomos-exchange-log?after=${after}&limit=${PAGE}`);
|
|
6012
|
+
const got = Array.isArray(page2?.entries) ? page2.entries : [];
|
|
6013
|
+
entries.push(...got);
|
|
6014
|
+
if (got.length < PAGE || !page2.next) break;
|
|
6015
|
+
after = got[got.length - 1].seq;
|
|
6016
|
+
}
|
|
6017
|
+
const vouched = entries.slice(0, Math.min(entries.length, Number(head?.size) || 0));
|
|
6018
|
+
const check = verifyRegistrarLog({ entries: vouched, head, publicKeyPem: o.publicKeyPem, now: now(), maxHeadAgeSeconds: maxHeadAge, previous: kept });
|
|
6019
|
+
if (!check.ok) {
|
|
6020
|
+
lastError = `${check.reason}: ${check.detail}`;
|
|
6021
|
+
if (check.reason === "history_rewritten" || check.reason === "wrong_key" || check.reason === "bad_signature" || check.reason === "chain_broken") view = null;
|
|
6022
|
+
return;
|
|
6023
|
+
}
|
|
6024
|
+
view = { record: foldRegistrarLog(vouched, { id: o.id, certifiedScope: o.certifiedScope }), head: check.head };
|
|
6025
|
+
kept = { size: check.head.size, hash: check.head.hash };
|
|
6026
|
+
lastError = "";
|
|
6027
|
+
} catch (e) {
|
|
6028
|
+
lastError = String(e?.message ?? e);
|
|
6029
|
+
}
|
|
6030
|
+
}
|
|
6031
|
+
const source = (async () => {
|
|
6032
|
+
const t = now().getTime();
|
|
6033
|
+
if (!inFlight && t - fetchedAt >= refresh) inFlight = load().finally(() => {
|
|
6034
|
+
inFlight = null;
|
|
6035
|
+
});
|
|
6036
|
+
if (inFlight) await inFlight;
|
|
6037
|
+
if (!view || t - Date.parse(view.head.at) > maxHeadAge * 1e3) {
|
|
6038
|
+
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);
|
|
6039
|
+
}
|
|
6040
|
+
return view;
|
|
6041
|
+
});
|
|
6042
|
+
source.id = o.id;
|
|
6043
|
+
return source;
|
|
6044
|
+
}
|
|
6045
|
+
|
|
5700
6046
|
// src/act/bytes.ts
|
|
5701
6047
|
var utf82 = (s) => new TextEncoder().encode(s);
|
|
5702
6048
|
var fromUtf82 = (b) => new TextDecoder().decode(b);
|
|
@@ -5977,7 +6323,7 @@ var STATUS = { AUTHORIZED: 201, ESCALATED: 202, INCOMPLETE: 422, DENIED: 403, RE
|
|
|
5977
6323
|
var statusFor = (decision) => STATUS[decision] ?? 400;
|
|
5978
6324
|
var ACT_MEDIA_TYPE = "application/nomos-act+json";
|
|
5979
6325
|
var HINTS = {
|
|
5980
|
-
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.`,
|
|
6326
|
+
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.`,
|
|
5981
6327
|
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}.`,
|
|
5982
6328
|
DENIED: (v) => `Rule ${v.rule ?? "(default)"} refuses this action. No approval can change a denial.`,
|
|
5983
6329
|
WRONG_RELYING_PARTY: () => "The Act is addressed to another system. Build it with this relying_party_id.",
|
|
@@ -6090,6 +6436,24 @@ function accept(o) {
|
|
|
6090
6436
|
maxWindowSeconds
|
|
6091
6437
|
});
|
|
6092
6438
|
}
|
|
6439
|
+
async function resolveFor(act2, authority, facts, at, reuse) {
|
|
6440
|
+
if (!o.resolution) return null;
|
|
6441
|
+
const views = reuse ?? await Promise.all(o.resolution.registrars.map(async (src) => [src.id, await src()]));
|
|
6442
|
+
const context = { ...await o.resolution.context(act2, facts) };
|
|
6443
|
+
const r = resolveApplicableSet(
|
|
6444
|
+
{ pins: views.map(([id]) => id), registrars: Object.fromEntries(views.map(([id, v]) => [id, v.record])) },
|
|
6445
|
+
{ context, at: at.toISOString() }
|
|
6446
|
+
);
|
|
6447
|
+
const heads = Object.fromEntries(views.map(([id, v]) => [id, v.head.hash]));
|
|
6448
|
+
const applicable = r.status === "RESOLVED" ? [...new Set(r.applicable.map((m) => m.authority))].sort() : [];
|
|
6449
|
+
const info = { status: r.status, context, applicable, heads };
|
|
6450
|
+
if (r.status === "INCOMPLETE") return { ok: false, info, views, verdict: { decision: "INCOMPLETE", reason_code: "resolution_incomplete", missing: r.missing } };
|
|
6451
|
+
if (r.status === "CONFLICT") return { ok: false, info, views, verdict: { decision: "INCOMPLETE", reason_code: "resolution_conflict", conflicts: r.conflicts } };
|
|
6452
|
+
const carried = new Set([authority.registrarName, authority.artifactId].filter((x) => !!x));
|
|
6453
|
+
const notCarried = applicable.filter((a) => !carried.has(a));
|
|
6454
|
+
if (notCarried.length) return { ok: false, info: { ...info, laws_not_presented: notCarried }, views, verdict: { decision: "INCOMPLETE", reason_code: "laws_not_presented", laws: notCarried } };
|
|
6455
|
+
return { ok: true, info, views };
|
|
6456
|
+
}
|
|
6093
6457
|
async function handle(req) {
|
|
6094
6458
|
const ctype = String(req.headers["content-type"] ?? "").split(";")[0].trim().toLowerCase();
|
|
6095
6459
|
if ((req.method ?? "POST").toUpperCase() !== "POST") return { status: 405, headers: { allow: "POST", "content-type": "application/json" }, body: { error: "POST an Act here." } };
|
|
@@ -6136,8 +6500,31 @@ function accept(o) {
|
|
|
6136
6500
|
if (verdict.decision === "REPLAYED") {
|
|
6137
6501
|
return answer(verdict, { act_id: act2.act_id, receipt: await store.receiptFor(act2.nonce) });
|
|
6138
6502
|
}
|
|
6503
|
+
let resolved = null;
|
|
6504
|
+
if (verdict.decision === "AUTHORIZED" && o.resolution) {
|
|
6505
|
+
try {
|
|
6506
|
+
resolved = await resolveFor(act2, authority, facts, verifiedAt);
|
|
6507
|
+
} catch (e) {
|
|
6508
|
+
if (!(e instanceof RegistrarUnavailable)) throw e;
|
|
6509
|
+
return {
|
|
6510
|
+
status: 503,
|
|
6511
|
+
headers: { "content-type": "application/json", "retry-after": String(e.retryAfterSeconds) },
|
|
6512
|
+
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 }
|
|
6513
|
+
};
|
|
6514
|
+
}
|
|
6515
|
+
if (resolved && !resolved.ok) {
|
|
6516
|
+
const held = { ...resolved.verdict, act_id: act2.act_id, act_digest: verdict.act_digest, resolution: resolved.info };
|
|
6517
|
+
await o.onVerdict?.(held, { ...receiptOf(held, facts), resolution: resolved.info });
|
|
6518
|
+
if (o.mode === "shadow") {
|
|
6519
|
+
const a = answer(held);
|
|
6520
|
+
return { ...a, status: 200, body: { ...a.body, shadow: true } };
|
|
6521
|
+
}
|
|
6522
|
+
return answer(held);
|
|
6523
|
+
}
|
|
6524
|
+
}
|
|
6525
|
+
const withResolution = (r) => resolved?.ok ? { ...r, resolution: resolved.info } : r;
|
|
6139
6526
|
if (verdict.decision !== "AUTHORIZED" || o.mode === "shadow") {
|
|
6140
|
-
await o.onVerdict?.(verdict, receiptOf(verdict, facts));
|
|
6527
|
+
await o.onVerdict?.(verdict, withResolution(receiptOf(verdict, facts)));
|
|
6141
6528
|
if (verdict.decision === "ESCALATED") {
|
|
6142
6529
|
const esc3 = (act2.authority.artifact.governance?.escalations ?? []).find((e) => e?.id === verdict.escalation_id);
|
|
6143
6530
|
return answer(verdict, { role: esc3?.role_required ?? null, retry_until: act2.expires_at });
|
|
@@ -6155,17 +6542,23 @@ function accept(o) {
|
|
|
6155
6542
|
finalFacts = fresh;
|
|
6156
6543
|
final = verifyWith(act2, authority, fresh, verifiedAt, false, rev);
|
|
6157
6544
|
if (final.decision !== "AUTHORIZED") throw new Rollback(answer(final));
|
|
6545
|
+
if (o.resolution) {
|
|
6546
|
+
const again = await resolveFor(act2, authority, fresh, verifiedAt, resolved?.views);
|
|
6547
|
+
if (again && !again.ok) throw new Rollback(answer({ ...again.verdict, act_id: act2.act_id, resolution: again.info }));
|
|
6548
|
+
resolved = again;
|
|
6549
|
+
}
|
|
6158
6550
|
}
|
|
6159
6551
|
}
|
|
6160
6552
|
const action = structuredClone({ type: act2.action.type, ...act2.action.subject !== void 0 ? { subject: act2.action.subject } : {}, params: act2.action.params ?? {} });
|
|
6161
6553
|
const result = await o.commit(action, { tx: t.tx, actId: act2.act_id });
|
|
6162
|
-
const receipt = receiptOf(final, finalFacts, now());
|
|
6554
|
+
const receipt = withResolution(receiptOf(final, finalFacts, now()));
|
|
6163
6555
|
await t.putReceipt(act2.nonce, receipt, act2);
|
|
6164
6556
|
await o.onVerdict?.(final, receipt);
|
|
6165
6557
|
return answer(final, { receipt, ...result !== void 0 ? { result } : {} });
|
|
6166
6558
|
});
|
|
6167
6559
|
} catch (e) {
|
|
6168
6560
|
if (e instanceof Rollback) return e.answer;
|
|
6561
|
+
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 } };
|
|
6169
6562
|
if (e instanceof LostRace) return answer({ decision: "REPLAYED" }, { act_id: act2.act_id, receipt: await store.receiptFor(act2.nonce) });
|
|
6170
6563
|
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) } };
|
|
6171
6564
|
}
|
|
@@ -6930,6 +7323,7 @@ var scriptHash = (js) => `'sha256-${btoa(String.fromCharCode(...sha256(utf82(js)
|
|
|
6930
7323
|
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'`;
|
|
6931
7324
|
|
|
6932
7325
|
// src/act/carrier.ts
|
|
7326
|
+
var RESOLUTION_HOLDS = /* @__PURE__ */ new Set(["laws_not_presented", "resolution_incomplete", "resolution_conflict"]);
|
|
6933
7327
|
var STATUS_OF = { AUTHORIZED: "committed", ESCALATED: "escalated", INCOMPLETE: "incomplete", DENIED: "denied", REPLAYED: "replayed" };
|
|
6934
7328
|
var NomosActError = class extends Error {
|
|
6935
7329
|
constructor(message) {
|
|
@@ -6949,9 +7343,9 @@ async function act(o) {
|
|
|
6949
7343
|
const entry = issuer2.authorities.find((a) => a.artifact_id === accepted.artifact_id);
|
|
6950
7344
|
if (!entry) throw new NomosActError(`The issuer at ${accepted.issuer} does not publish ${accepted.artifact_id}.`);
|
|
6951
7345
|
const artifact = await getJson(f, entry.artifact_url, "the authority");
|
|
6952
|
-
let
|
|
7346
|
+
let inForce2;
|
|
6953
7347
|
if (accepted.currency_policy === "record_or_statement" && !accepted.in_force_seal_hash && entry.in_force_url) {
|
|
6954
|
-
|
|
7348
|
+
inForce2 = await getJson(f, entry.in_force_url, "the in-force statement").catch(() => void 0);
|
|
6955
7349
|
}
|
|
6956
7350
|
const inputs = artifact?.data_contract?.inputs ?? {};
|
|
6957
7351
|
const witnessed = Object.entries(inputs).filter(([, i]) => i?.source === "testimony").map(([n]) => n);
|
|
@@ -6991,7 +7385,7 @@ async function act(o) {
|
|
|
6991
7385
|
testimony.push(t);
|
|
6992
7386
|
if (Array.isArray(body2.key_certs)) available.push(...body2.key_certs);
|
|
6993
7387
|
}
|
|
6994
|
-
const theAct = buildAct({ relyingParty: rp.relying_party_id, action: o.action, artifact, keyCerts: [], testimony, presenterFacts: o.presenterFacts, inForce, windowSeconds });
|
|
7388
|
+
const theAct = buildAct({ relyingParty: rp.relying_party_id, action: o.action, artifact, keyCerts: [], testimony, presenterFacts: o.presenterFacts, inForce: inForce2, windowSeconds });
|
|
6995
7389
|
present(theAct, available);
|
|
6996
7390
|
let { status, body } = await submit(f, rp.act_endpoint, theAct);
|
|
6997
7391
|
let approvalUrl;
|
|
@@ -7019,6 +7413,10 @@ async function act(o) {
|
|
|
7019
7413
|
}
|
|
7020
7414
|
}
|
|
7021
7415
|
const kind = STATUS_OF[body.decision] ?? "refused";
|
|
7416
|
+
if (kind === "incomplete" && RESOLUTION_HOLDS.has(body.reason_code)) {
|
|
7417
|
+
const laws = Array.isArray(body.laws) ? body.laws : [];
|
|
7418
|
+
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}).`}` });
|
|
7419
|
+
}
|
|
7022
7420
|
if (kind === "incomplete") {
|
|
7023
7421
|
const missing = (body.open_facts ?? []).map((fact) => {
|
|
7024
7422
|
const why = unavailable.find((u) => u.fact === fact || fact.startsWith(u.fact + "."));
|
|
@@ -7050,6 +7448,7 @@ async function getJson(f, url, what) {
|
|
|
7050
7448
|
async function submit(f, endpoint, theAct) {
|
|
7051
7449
|
const res = await f(endpoint, { method: "POST", headers: { "content-type": ACT_MEDIA_TYPE }, body: JSON.stringify(theAct) });
|
|
7052
7450
|
const body = await res.json().catch(() => ({ decision: "MALFORMED", reason_code: "unreadable_response" }));
|
|
7451
|
+
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.` : ""}`);
|
|
7053
7452
|
if (res.status >= 500) throw new NomosActError(`The relying party failed to commit (${res.status}): ${body?.error ?? ""}`);
|
|
7054
7453
|
return { status: res.status, body };
|
|
7055
7454
|
}
|
|
@@ -7253,6 +7652,7 @@ ${buttons}</body>`;
|
|
|
7253
7652
|
NomosRateLimitError,
|
|
7254
7653
|
NomosValidationError,
|
|
7255
7654
|
NomosWebhookSignatureError,
|
|
7655
|
+
RegistrarUnavailable,
|
|
7256
7656
|
RegistryError,
|
|
7257
7657
|
RevocationUnavailable,
|
|
7258
7658
|
WELL_KNOWN,
|
|
@@ -7283,6 +7683,7 @@ ${buttons}</body>`;
|
|
|
7283
7683
|
postgresStore,
|
|
7284
7684
|
presentedCertificates,
|
|
7285
7685
|
readAttestation,
|
|
7686
|
+
registrarSource,
|
|
7286
7687
|
reliedKids,
|
|
7287
7688
|
revocationListCertificates,
|
|
7288
7689
|
revocationSource,
|