@riddledc/riddle-proof 0.8.82 → 0.8.84
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 +66 -10
- package/dist/advanced/index.d.cts +1 -1
- package/dist/advanced/index.d.ts +1 -1
- package/dist/advanced/proof-run-engine.d.cts +1 -1
- package/dist/advanced/proof-run-engine.d.ts +1 -1
- package/dist/chunk-AQTCU6W5.js +1086 -0
- package/dist/index.cjs +682 -58
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +16 -2
- package/dist/{proof-run-engine-MiKZt9oY.d.ts → proof-run-engine-BqRoA3Do.d.ts} +3 -3
- package/dist/{proof-run-engine-Baiv6l3A.d.cts → proof-run-engine-DpChFR5H.d.cts} +3 -3
- package/dist/proof-run-engine.d.cts +1 -1
- package/dist/proof-run-engine.d.ts +1 -1
- package/dist/semantic-certificate.cjs +684 -60
- package/dist/semantic-certificate.d.cts +170 -2
- package/dist/semantic-certificate.d.ts +170 -2
- package/dist/semantic-certificate.js +17 -3
- package/package.json +1 -1
- package/dist/chunk-ZZ6UNKJQ.js +0 -469
|
@@ -20,15 +20,24 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/semantic-certificate.ts
|
|
21
21
|
var semantic_certificate_exports = {};
|
|
22
22
|
__export(semantic_certificate_exports, {
|
|
23
|
+
RIDDLE_PROOF_SEMANTIC_CERTIFICATE_CLOSURE_MAX_CERTIFICATES: () => RIDDLE_PROOF_SEMANTIC_CERTIFICATE_CLOSURE_MAX_CERTIFICATES,
|
|
24
|
+
RIDDLE_PROOF_SEMANTIC_CERTIFICATE_CLOSURE_VERSION: () => RIDDLE_PROOF_SEMANTIC_CERTIFICATE_CLOSURE_VERSION,
|
|
23
25
|
RIDDLE_PROOF_SEMANTIC_CERTIFICATE_VERSION: () => RIDDLE_PROOF_SEMANTIC_CERTIFICATE_VERSION,
|
|
26
|
+
composeRiddleProofSemanticCertificateClosures: () => composeRiddleProofSemanticCertificateClosures,
|
|
24
27
|
composeRiddleProofSemanticCertificates: () => composeRiddleProofSemanticCertificates,
|
|
28
|
+
createRiddleProofSemanticAtomicCertificateClosure: () => createRiddleProofSemanticAtomicCertificateClosure,
|
|
25
29
|
createRiddleProofSemanticCertificate: () => createRiddleProofSemanticCertificate,
|
|
30
|
+
matchRiddleProofSemanticCertificate: () => matchRiddleProofSemanticCertificate,
|
|
31
|
+
matchRiddleProofSemanticCertificateClosure: () => matchRiddleProofSemanticCertificateClosure,
|
|
26
32
|
parseRiddleProofSemanticCertificate: () => parseRiddleProofSemanticCertificate,
|
|
27
|
-
riddleProofSemanticScopesEqual: () => riddleProofSemanticScopesEqual
|
|
33
|
+
riddleProofSemanticScopesEqual: () => riddleProofSemanticScopesEqual,
|
|
34
|
+
validateRiddleProofSemanticCertificateClosure: () => validateRiddleProofSemanticCertificateClosure
|
|
28
35
|
});
|
|
29
36
|
module.exports = __toCommonJS(semantic_certificate_exports);
|
|
30
37
|
var import_node_crypto = require("crypto");
|
|
31
38
|
var RIDDLE_PROOF_SEMANTIC_CERTIFICATE_VERSION = "riddle-proof.semantic-certificate.v0";
|
|
39
|
+
var RIDDLE_PROOF_SEMANTIC_CERTIFICATE_CLOSURE_VERSION = "riddle-proof.semantic-certificate-closure.v0";
|
|
40
|
+
var RIDDLE_PROOF_SEMANTIC_CERTIFICATE_CLOSURE_MAX_CERTIFICATES = 4096;
|
|
32
41
|
var SCOPE_FIELDS = [
|
|
33
42
|
"repository",
|
|
34
43
|
"revision",
|
|
@@ -60,55 +69,146 @@ var CERTIFICATE_FIELDS = /* @__PURE__ */ new Set([
|
|
|
60
69
|
"issued_at"
|
|
61
70
|
]);
|
|
62
71
|
function isRecord(value) {
|
|
63
|
-
|
|
72
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
73
|
+
const prototype = Object.getPrototypeOf(value);
|
|
74
|
+
return prototype === Object.prototype || prototype === null;
|
|
75
|
+
}
|
|
76
|
+
function safeErrorMessage(error) {
|
|
77
|
+
try {
|
|
78
|
+
if (error instanceof Error) return String(error.message);
|
|
79
|
+
} catch {
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
return String(error);
|
|
83
|
+
} catch {
|
|
84
|
+
return "unprintable thrown value";
|
|
85
|
+
}
|
|
64
86
|
}
|
|
65
87
|
function assertOnlyKeys(record, allowed, context) {
|
|
66
88
|
const allowedSet = new Set(allowed);
|
|
67
|
-
for (const key of
|
|
89
|
+
for (const key of Reflect.ownKeys(record)) {
|
|
90
|
+
if (typeof key !== "string") {
|
|
91
|
+
throw new Error(`${context} contains an unsupported symbol field.`);
|
|
92
|
+
}
|
|
68
93
|
if (!allowedSet.has(key)) {
|
|
69
94
|
throw new Error(`${context} contains unsupported field ${key}.`);
|
|
70
95
|
}
|
|
96
|
+
const descriptor = Object.getOwnPropertyDescriptor(record, key);
|
|
97
|
+
if (!descriptor || descriptor.enumerable !== true || descriptor.get !== void 0 || descriptor.set !== void 0) {
|
|
98
|
+
throw new Error(`${context}.${key} must be an enumerable data field.`);
|
|
99
|
+
}
|
|
71
100
|
}
|
|
72
101
|
}
|
|
102
|
+
function readDenseDataArray(value, context, maximumLength) {
|
|
103
|
+
if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) {
|
|
104
|
+
throw new Error(`${context} must be a plain array.`);
|
|
105
|
+
}
|
|
106
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
|
|
107
|
+
if (!lengthDescriptor || typeof lengthDescriptor.value !== "number" || !Number.isSafeInteger(lengthDescriptor.value) || lengthDescriptor.value < 0) {
|
|
108
|
+
throw new Error(`${context}.length must be an own data field.`);
|
|
109
|
+
}
|
|
110
|
+
const length = lengthDescriptor.value;
|
|
111
|
+
if (maximumLength !== void 0 && length > maximumLength) {
|
|
112
|
+
throw new Error(`${context} exceeds ${maximumLength} entries.`);
|
|
113
|
+
}
|
|
114
|
+
const indexedElements = [];
|
|
115
|
+
let elementCount = 0;
|
|
116
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
117
|
+
if (key === "length") continue;
|
|
118
|
+
if (typeof key !== "string") {
|
|
119
|
+
throw new Error(`${context} contains an unsupported symbol field.`);
|
|
120
|
+
}
|
|
121
|
+
const index = Number(key);
|
|
122
|
+
if (!Number.isInteger(index) || index < 0 || index >= length || index >= 2 ** 32 - 1 || String(index) !== key) {
|
|
123
|
+
throw new Error(`${context} contains unsupported array field ${key}.`);
|
|
124
|
+
}
|
|
125
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
126
|
+
if (!descriptor || descriptor.enumerable !== true || descriptor.get !== void 0 || descriptor.set !== void 0) {
|
|
127
|
+
throw new Error(`${context}[${key}] must be an enumerable data field.`);
|
|
128
|
+
}
|
|
129
|
+
indexedElements.push([index, descriptor.value]);
|
|
130
|
+
elementCount += 1;
|
|
131
|
+
}
|
|
132
|
+
if (elementCount !== length) {
|
|
133
|
+
throw new Error(`${context} must not contain sparse or inherited entries.`);
|
|
134
|
+
}
|
|
135
|
+
indexedElements.sort(([left], [right]) => left - right);
|
|
136
|
+
return indexedElements.map(([, entry]) => entry);
|
|
137
|
+
}
|
|
138
|
+
function requiredField(record, key, context) {
|
|
139
|
+
const descriptor = Object.getOwnPropertyDescriptor(record, key);
|
|
140
|
+
if (!descriptor) {
|
|
141
|
+
throw new Error(`${context}.${key} is required.`);
|
|
142
|
+
}
|
|
143
|
+
if (descriptor.enumerable !== true || descriptor.get !== void 0 || descriptor.set !== void 0) {
|
|
144
|
+
throw new Error(`${context}.${key} must be an enumerable data field.`);
|
|
145
|
+
}
|
|
146
|
+
return descriptor.value;
|
|
147
|
+
}
|
|
148
|
+
function optionalField(record, key) {
|
|
149
|
+
const descriptor = Object.getOwnPropertyDescriptor(record, key);
|
|
150
|
+
if (!descriptor) return void 0;
|
|
151
|
+
if (descriptor.enumerable !== true || descriptor.get !== void 0 || descriptor.set !== void 0) {
|
|
152
|
+
throw new Error(`${key} must be an enumerable data field.`);
|
|
153
|
+
}
|
|
154
|
+
return descriptor.value;
|
|
155
|
+
}
|
|
73
156
|
function requiredString(record, key, context) {
|
|
74
|
-
const value = record
|
|
157
|
+
const value = requiredField(record, key, context);
|
|
75
158
|
if (typeof value !== "string" || !value.trim()) {
|
|
76
159
|
throw new Error(`${context}.${key} must be a non-empty string.`);
|
|
77
160
|
}
|
|
78
161
|
return value.trim();
|
|
79
162
|
}
|
|
80
163
|
function optionalString(record, key, context) {
|
|
81
|
-
if (record
|
|
164
|
+
if (optionalField(record, key) === void 0) return void 0;
|
|
82
165
|
return requiredString(record, key, context);
|
|
83
166
|
}
|
|
84
|
-
function
|
|
167
|
+
function cloneJsonValue(value, context, ancestors = /* @__PURE__ */ new Set()) {
|
|
85
168
|
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
86
|
-
return
|
|
169
|
+
return value;
|
|
170
|
+
}
|
|
171
|
+
if (typeof value === "number") {
|
|
172
|
+
if (!Number.isFinite(value)) throw new Error(`${context} must contain only finite numbers.`);
|
|
173
|
+
return value;
|
|
87
174
|
}
|
|
88
|
-
if (typeof value === "number") return Number.isFinite(value);
|
|
89
175
|
if (Array.isArray(value)) {
|
|
90
|
-
|
|
176
|
+
const elements = readDenseDataArray(value, context);
|
|
177
|
+
if (ancestors.has(value)) throw new Error(`${context} must not be cyclic.`);
|
|
91
178
|
ancestors.add(value);
|
|
92
|
-
const
|
|
179
|
+
const cloned2 = elements.map((entry, index) => cloneJsonValue(entry, `${context}[${index}]`, ancestors));
|
|
93
180
|
ancestors.delete(value);
|
|
94
|
-
return
|
|
181
|
+
return cloned2;
|
|
95
182
|
}
|
|
96
|
-
if (!isRecord(value))
|
|
97
|
-
|
|
98
|
-
if (prototype !== Object.prototype && prototype !== null) return false;
|
|
99
|
-
if (ancestors.has(value)) return false;
|
|
183
|
+
if (!isRecord(value)) throw new Error(`${context} must contain only JSON values.`);
|
|
184
|
+
if (ancestors.has(value)) throw new Error(`${context} must not be cyclic.`);
|
|
100
185
|
ancestors.add(value);
|
|
101
|
-
const
|
|
186
|
+
const cloned = {};
|
|
187
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
188
|
+
if (typeof key !== "string") {
|
|
189
|
+
throw new Error(`${context} contains an unsupported symbol field.`);
|
|
190
|
+
}
|
|
191
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
192
|
+
if (!descriptor || descriptor.enumerable !== true || descriptor.get !== void 0 || descriptor.set !== void 0) {
|
|
193
|
+
throw new Error(`${context}.${key} must be an enumerable data field.`);
|
|
194
|
+
}
|
|
195
|
+
Object.defineProperty(cloned, key, {
|
|
196
|
+
value: cloneJsonValue(descriptor.value, `${context}.${key}`, ancestors),
|
|
197
|
+
enumerable: true,
|
|
198
|
+
configurable: true,
|
|
199
|
+
writable: true
|
|
200
|
+
});
|
|
201
|
+
}
|
|
102
202
|
ancestors.delete(value);
|
|
103
|
-
return
|
|
203
|
+
return cloned;
|
|
104
204
|
}
|
|
105
205
|
function parseJsonObject(value, context) {
|
|
106
206
|
if (value === void 0) return void 0;
|
|
107
|
-
if (!isRecord(value)
|
|
207
|
+
if (!isRecord(value)) {
|
|
108
208
|
throw new Error(`${context} must be a JSON object.`);
|
|
109
209
|
}
|
|
110
|
-
const cloned =
|
|
111
|
-
if (!isRecord(cloned)
|
|
210
|
+
const cloned = cloneJsonValue(value, context);
|
|
211
|
+
if (!isRecord(cloned)) {
|
|
112
212
|
throw new Error(`${context} must remain a JSON object when serialized.`);
|
|
113
213
|
}
|
|
114
214
|
return cloned;
|
|
@@ -133,7 +233,7 @@ function parseScope(value, context) {
|
|
|
133
233
|
function parseClaimRef(value, context, allowedExtras = []) {
|
|
134
234
|
if (!isRecord(value)) throw new Error(`${context} must be an object.`);
|
|
135
235
|
assertOnlyKeys(value, ["claim_id", "claim_version", "parameters", ...allowedExtras], context);
|
|
136
|
-
const parameters = parseJsonObject(value
|
|
236
|
+
const parameters = parseJsonObject(optionalField(value, "parameters"), `${context}.parameters`);
|
|
137
237
|
return {
|
|
138
238
|
claim_id: requiredString(value, "claim_id", context),
|
|
139
239
|
claim_version: requiredString(value, "claim_version", context),
|
|
@@ -169,10 +269,11 @@ function parseEvidenceRef(value, context) {
|
|
|
169
269
|
};
|
|
170
270
|
}
|
|
171
271
|
function parseEvidenceBundle(value, context) {
|
|
172
|
-
|
|
272
|
+
const entries = readDenseDataArray(value, context);
|
|
273
|
+
if (entries.length === 0) {
|
|
173
274
|
throw new Error(`${context} must contain at least one evidence reference.`);
|
|
174
275
|
}
|
|
175
|
-
return
|
|
276
|
+
return entries.map((entry, index) => parseEvidenceRef(entry, `${context}[${index}]`));
|
|
176
277
|
}
|
|
177
278
|
function parseContractRef(value, context) {
|
|
178
279
|
if (!isRecord(value)) throw new Error(`${context} must be an object.`);
|
|
@@ -189,30 +290,32 @@ function parseContract(value, context, allowRuntimePredicate = false) {
|
|
|
189
290
|
["contract_id", "contract_version", "label", "claim", ...allowRuntimePredicate ? ["accepts"] : []],
|
|
190
291
|
context
|
|
191
292
|
);
|
|
192
|
-
if (allowRuntimePredicate && typeof value
|
|
293
|
+
if (allowRuntimePredicate && typeof requiredField(value, "accepts", context) !== "function") {
|
|
193
294
|
throw new Error(`${context}.accepts must be a function.`);
|
|
194
295
|
}
|
|
195
296
|
return {
|
|
196
297
|
...parseContractRef(value, context),
|
|
197
|
-
claim: parseClaim(value
|
|
298
|
+
claim: parseClaim(requiredField(value, "claim", context), `${context}.claim`)
|
|
198
299
|
};
|
|
199
300
|
}
|
|
200
301
|
function parseRule(value, context, allowFullPremises = false) {
|
|
201
302
|
if (!isRecord(value)) throw new Error(`${context} must be an object.`);
|
|
202
303
|
assertOnlyKeys(value, ["rule_id", "rule_version", "label", "premises", "conclusion"], context);
|
|
203
|
-
|
|
304
|
+
const premises = requiredField(value, "premises", context);
|
|
305
|
+
const premiseValues = readDenseDataArray(premises, `${context}.premises`);
|
|
306
|
+
if (premiseValues.length === 0) {
|
|
204
307
|
throw new Error(`${context}.premises must contain at least one claim reference.`);
|
|
205
308
|
}
|
|
206
309
|
return {
|
|
207
310
|
rule_id: requiredString(value, "rule_id", context),
|
|
208
311
|
rule_version: requiredString(value, "rule_version", context),
|
|
209
312
|
label: requiredString(value, "label", context),
|
|
210
|
-
premises:
|
|
313
|
+
premises: premiseValues.map((premise, index) => parseClaimRef(
|
|
211
314
|
premise,
|
|
212
315
|
`${context}.premises[${index}]`,
|
|
213
316
|
allowFullPremises ? ["label"] : []
|
|
214
317
|
)),
|
|
215
|
-
conclusion: parseClaim(value
|
|
318
|
+
conclusion: parseClaim(requiredField(value, "conclusion", context), `${context}.conclusion`)
|
|
216
319
|
};
|
|
217
320
|
}
|
|
218
321
|
function parsePremise(value, context) {
|
|
@@ -232,9 +335,9 @@ function parsePremise(value, context) {
|
|
|
232
335
|
certificate_id: requiredString(value, "certificate_id", context),
|
|
233
336
|
derivation_kind: derivationKind,
|
|
234
337
|
assurance,
|
|
235
|
-
scope: parseScope(value
|
|
236
|
-
claim: parseClaim(value
|
|
237
|
-
evidence: parseEvidenceBundle(value
|
|
338
|
+
scope: parseScope(requiredField(value, "scope", context), `${context}.scope`),
|
|
339
|
+
claim: parseClaim(requiredField(value, "claim", context), `${context}.claim`),
|
|
340
|
+
evidence: parseEvidenceBundle(requiredField(value, "evidence", context), `${context}.evidence`)
|
|
238
341
|
};
|
|
239
342
|
}
|
|
240
343
|
function stableJson(value) {
|
|
@@ -264,28 +367,30 @@ function parseDerivation(value, context) {
|
|
|
264
367
|
const kind = requiredString(value, "kind", context);
|
|
265
368
|
if (kind === "contract") {
|
|
266
369
|
assertOnlyKeys(value, ["kind", "assurance", "contract"], context);
|
|
267
|
-
if (value
|
|
370
|
+
if (requiredField(value, "assurance", context) !== "runtime_contract_accepted") {
|
|
268
371
|
throw new Error(`${context}.assurance must be runtime_contract_accepted.`);
|
|
269
372
|
}
|
|
270
373
|
return {
|
|
271
374
|
kind,
|
|
272
375
|
assurance: "runtime_contract_accepted",
|
|
273
|
-
contract: parseContract(value
|
|
376
|
+
contract: parseContract(requiredField(value, "contract", context), `${context}.contract`)
|
|
274
377
|
};
|
|
275
378
|
}
|
|
276
379
|
if (kind === "composition") {
|
|
277
380
|
assertOnlyKeys(value, ["kind", "assurance", "rule", "premises"], context);
|
|
278
|
-
if (value
|
|
381
|
+
if (requiredField(value, "assurance", context) !== "declared_runtime_rule") {
|
|
279
382
|
throw new Error(`${context}.assurance must be declared_runtime_rule.`);
|
|
280
383
|
}
|
|
281
|
-
|
|
384
|
+
const premises = requiredField(value, "premises", context);
|
|
385
|
+
const premiseValues = readDenseDataArray(premises, `${context}.premises`);
|
|
386
|
+
if (premiseValues.length === 0) {
|
|
282
387
|
throw new Error(`${context}.premises must contain at least one certificate premise.`);
|
|
283
388
|
}
|
|
284
389
|
return {
|
|
285
390
|
kind,
|
|
286
391
|
assurance: "declared_runtime_rule",
|
|
287
|
-
rule: parseRule(value
|
|
288
|
-
premises:
|
|
392
|
+
rule: parseRule(requiredField(value, "rule", context), `${context}.rule`),
|
|
393
|
+
premises: premiseValues.map((premise, index) => parsePremise(premise, `${context}.premises[${index}]`))
|
|
289
394
|
};
|
|
290
395
|
}
|
|
291
396
|
throw new Error(`${context}.kind must be contract or composition.`);
|
|
@@ -300,9 +405,25 @@ function createRiddleProofSemanticCertificate(input) {
|
|
|
300
405
|
["scope", "evidence", "observation", "contract", "issued_at"],
|
|
301
406
|
"semantic certificate input"
|
|
302
407
|
);
|
|
303
|
-
const scope = parseScope(input
|
|
304
|
-
const evidence = parseEvidenceBundle(
|
|
305
|
-
|
|
408
|
+
const scope = parseScope(requiredField(input, "scope", "semantic certificate input"), "semantic certificate scope");
|
|
409
|
+
const evidence = parseEvidenceBundle(
|
|
410
|
+
requiredField(input, "evidence", "semantic certificate input"),
|
|
411
|
+
"semantic certificate evidence"
|
|
412
|
+
);
|
|
413
|
+
const runtimeContract = requiredField(
|
|
414
|
+
input,
|
|
415
|
+
"contract",
|
|
416
|
+
"semantic certificate input"
|
|
417
|
+
);
|
|
418
|
+
const contract = parseContract(runtimeContract, "semantic certificate contract", true);
|
|
419
|
+
const accepts = requiredField(
|
|
420
|
+
runtimeContract,
|
|
421
|
+
"accepts",
|
|
422
|
+
"semantic certificate contract"
|
|
423
|
+
);
|
|
424
|
+
if (typeof accepts !== "function") {
|
|
425
|
+
throw new Error("semantic certificate contract.accepts must be a function.");
|
|
426
|
+
}
|
|
306
427
|
const contractRef = {
|
|
307
428
|
contract_id: contract.contract_id,
|
|
308
429
|
contract_version: contract.contract_version,
|
|
@@ -311,14 +432,17 @@ function createRiddleProofSemanticCertificate(input) {
|
|
|
311
432
|
const claim = contract.claim;
|
|
312
433
|
let accepted;
|
|
313
434
|
try {
|
|
314
|
-
accepted =
|
|
435
|
+
accepted = accepts(
|
|
436
|
+
{ ...scope },
|
|
437
|
+
requiredField(input, "observation", "semantic certificate input")
|
|
438
|
+
) === true;
|
|
315
439
|
} catch (error) {
|
|
316
440
|
return {
|
|
317
441
|
ok: false,
|
|
318
442
|
error: {
|
|
319
443
|
code: "contract_error",
|
|
320
444
|
contract: contractRef,
|
|
321
|
-
message: `Semantic contract evaluation failed: ${
|
|
445
|
+
message: `Semantic contract evaluation failed: ${safeErrorMessage(error)}.`
|
|
322
446
|
}
|
|
323
447
|
};
|
|
324
448
|
}
|
|
@@ -338,29 +462,35 @@ function createRiddleProofSemanticCertificate(input) {
|
|
|
338
462
|
claim,
|
|
339
463
|
evidence,
|
|
340
464
|
derivation: { kind: "contract", assurance: "runtime_contract_accepted", contract },
|
|
341
|
-
issued_at: parseIssuedAt(
|
|
465
|
+
issued_at: parseIssuedAt(
|
|
466
|
+
optionalField(input, "issued_at") || (/* @__PURE__ */ new Date()).toISOString(),
|
|
467
|
+
"semantic certificate issued_at"
|
|
468
|
+
)
|
|
342
469
|
};
|
|
343
470
|
return { ok: true, certificate: withCertificateId(body) };
|
|
344
471
|
}
|
|
345
472
|
function parseRiddleProofSemanticCertificate(value) {
|
|
346
473
|
if (!isRecord(value)) throw new Error("Semantic certificate must be an object.");
|
|
347
|
-
|
|
348
|
-
|
|
474
|
+
const version = optionalField(value, "version");
|
|
475
|
+
if (version !== RIDDLE_PROOF_SEMANTIC_CERTIFICATE_VERSION) {
|
|
476
|
+
throw new Error(`Unsupported Semantic certificate version ${String(version || "missing")}.`);
|
|
349
477
|
}
|
|
350
478
|
for (const field of AUTHORITY_FIELDS) {
|
|
351
479
|
if (Object.prototype.hasOwnProperty.call(value, field)) {
|
|
352
480
|
throw new Error(`Semantic certificate must not contain authority field ${field}.`);
|
|
353
481
|
}
|
|
354
482
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
const
|
|
363
|
-
|
|
483
|
+
assertOnlyKeys(value, [...CERTIFICATE_FIELDS], "Semantic certificate");
|
|
484
|
+
const scope = parseScope(requiredField(value, "scope", "semantic certificate"), "semantic certificate scope");
|
|
485
|
+
const claim = parseClaim(requiredField(value, "claim", "semantic certificate"), "semantic certificate claim");
|
|
486
|
+
const evidence = parseEvidenceBundle(
|
|
487
|
+
requiredField(value, "evidence", "semantic certificate"),
|
|
488
|
+
"semantic certificate evidence"
|
|
489
|
+
);
|
|
490
|
+
const derivation = parseDerivation(
|
|
491
|
+
requiredField(value, "derivation", "semantic certificate"),
|
|
492
|
+
"semantic certificate derivation"
|
|
493
|
+
);
|
|
364
494
|
if (derivation.kind === "contract" && !sameClaimRef(claim, derivation.contract.claim)) {
|
|
365
495
|
throw new Error("Semantic contract-derived claim must match its contract claim.");
|
|
366
496
|
}
|
|
@@ -390,7 +520,10 @@ function parseRiddleProofSemanticCertificate(value) {
|
|
|
390
520
|
claim,
|
|
391
521
|
evidence,
|
|
392
522
|
derivation,
|
|
393
|
-
issued_at: parseIssuedAt(
|
|
523
|
+
issued_at: parseIssuedAt(
|
|
524
|
+
requiredField(value, "issued_at", "semantic certificate"),
|
|
525
|
+
"semantic certificate issued_at"
|
|
526
|
+
)
|
|
394
527
|
};
|
|
395
528
|
const observedId = requiredString(value, "certificate_id", "semantic certificate");
|
|
396
529
|
const expectedId = certificateId(body);
|
|
@@ -399,6 +532,112 @@ function parseRiddleProofSemanticCertificate(value) {
|
|
|
399
532
|
}
|
|
400
533
|
return { ...body, certificate_id: observedId };
|
|
401
534
|
}
|
|
535
|
+
function matchRiddleProofSemanticCertificate(input) {
|
|
536
|
+
if (!isRecord(input)) throw new Error("Semantic certificate match input must be a plain object.");
|
|
537
|
+
assertOnlyKeys(
|
|
538
|
+
input,
|
|
539
|
+
[
|
|
540
|
+
"certificate",
|
|
541
|
+
"expected_certificate_id",
|
|
542
|
+
"expected_scope",
|
|
543
|
+
"expected_claim",
|
|
544
|
+
"expected_assurance"
|
|
545
|
+
],
|
|
546
|
+
"semantic certificate match input"
|
|
547
|
+
);
|
|
548
|
+
const expectedCertificateId = requiredString(
|
|
549
|
+
input,
|
|
550
|
+
"expected_certificate_id",
|
|
551
|
+
"semantic certificate match input"
|
|
552
|
+
);
|
|
553
|
+
if (!/^rpsc_[0-9a-f]{64}$/u.test(expectedCertificateId)) {
|
|
554
|
+
throw new Error(
|
|
555
|
+
"Semantic certificate match input.expected_certificate_id must be a full rpsc content ID."
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
const expectedScope = parseScope(
|
|
559
|
+
requiredField(input, "expected_scope", "semantic certificate match input"),
|
|
560
|
+
"semantic certificate match expected_scope"
|
|
561
|
+
);
|
|
562
|
+
const expectedClaim = parseClaimRef(
|
|
563
|
+
requiredField(input, "expected_claim", "semantic certificate match input"),
|
|
564
|
+
"semantic certificate match expected_claim",
|
|
565
|
+
["label"]
|
|
566
|
+
);
|
|
567
|
+
const expectedAssurance = requiredString(
|
|
568
|
+
input,
|
|
569
|
+
"expected_assurance",
|
|
570
|
+
"semantic certificate match input"
|
|
571
|
+
);
|
|
572
|
+
if (expectedAssurance !== "runtime_contract_accepted" && expectedAssurance !== "declared_runtime_rule") {
|
|
573
|
+
throw new Error(
|
|
574
|
+
"Semantic certificate match input.expected_assurance must be runtime_contract_accepted or declared_runtime_rule."
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
let certificate;
|
|
578
|
+
try {
|
|
579
|
+
certificate = parseRiddleProofSemanticCertificate(
|
|
580
|
+
requiredField(input, "certificate", "semantic certificate match input")
|
|
581
|
+
);
|
|
582
|
+
} catch (error) {
|
|
583
|
+
return {
|
|
584
|
+
ok: false,
|
|
585
|
+
error: {
|
|
586
|
+
code: "invalid_certificate",
|
|
587
|
+
message: `Semantic certificate did not parse: ${safeErrorMessage(error)}`
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
if (certificate.certificate_id !== expectedCertificateId) {
|
|
592
|
+
return {
|
|
593
|
+
ok: false,
|
|
594
|
+
error: {
|
|
595
|
+
code: "certificate_id_mismatch",
|
|
596
|
+
expected: expectedCertificateId,
|
|
597
|
+
observed: certificate.certificate_id,
|
|
598
|
+
message: "Semantic certificate does not match the trusted expected content ID."
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
const scopeMismatch = firstScopeMismatch(expectedScope, certificate.scope);
|
|
603
|
+
if (scopeMismatch) {
|
|
604
|
+
return {
|
|
605
|
+
ok: false,
|
|
606
|
+
error: {
|
|
607
|
+
code: "scope_mismatch",
|
|
608
|
+
...scopeMismatch,
|
|
609
|
+
message: `Semantic certificate has a different ${scopeMismatch.field} than the consumer expected.`
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
if (!sameClaimRef(expectedClaim, certificate.claim)) {
|
|
614
|
+
return {
|
|
615
|
+
ok: false,
|
|
616
|
+
error: {
|
|
617
|
+
code: "claim_mismatch",
|
|
618
|
+
expected: expectedClaim,
|
|
619
|
+
observed: parseClaimRef(
|
|
620
|
+
certificate.claim,
|
|
621
|
+
"semantic certificate match observed claim",
|
|
622
|
+
["label"]
|
|
623
|
+
),
|
|
624
|
+
message: "Semantic certificate does not state the claim the consumer expected."
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
if (certificate.derivation.assurance !== expectedAssurance) {
|
|
629
|
+
return {
|
|
630
|
+
ok: false,
|
|
631
|
+
error: {
|
|
632
|
+
code: "assurance_mismatch",
|
|
633
|
+
expected: expectedAssurance,
|
|
634
|
+
observed: certificate.derivation.assurance,
|
|
635
|
+
message: "Semantic certificate does not have the assurance the consumer expected."
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
return { ok: true, certificate };
|
|
640
|
+
}
|
|
402
641
|
function firstScopeMismatch(expected, observed) {
|
|
403
642
|
for (const field of SCOPE_FIELDS) {
|
|
404
643
|
if (expected[field] !== observed[field]) {
|
|
@@ -424,11 +663,20 @@ function composeRiddleProofSemanticCertificates(input) {
|
|
|
424
663
|
["rule", "certificates", "issued_at"],
|
|
425
664
|
"semantic composition input"
|
|
426
665
|
);
|
|
427
|
-
const rule = parseRule(
|
|
428
|
-
|
|
666
|
+
const rule = parseRule(
|
|
667
|
+
requiredField(input, "rule", "semantic composition input"),
|
|
668
|
+
"semantic composition rule",
|
|
669
|
+
true
|
|
670
|
+
);
|
|
671
|
+
const inputCertificates = requiredField(input, "certificates", "semantic composition input");
|
|
672
|
+
const certificateValues = readDenseDataArray(
|
|
673
|
+
inputCertificates,
|
|
674
|
+
"semantic composition input.certificates"
|
|
675
|
+
);
|
|
676
|
+
if (certificateValues.length === 0) {
|
|
429
677
|
throw new Error("Semantic composition requires at least one certificate.");
|
|
430
678
|
}
|
|
431
|
-
const certificates =
|
|
679
|
+
const certificates = certificateValues.map((certificate) => parseRiddleProofSemanticCertificate(certificate));
|
|
432
680
|
if (certificates.length !== rule.premises.length) {
|
|
433
681
|
return {
|
|
434
682
|
ok: false,
|
|
@@ -483,15 +731,391 @@ function composeRiddleProofSemanticCertificates(input) {
|
|
|
483
731
|
rule,
|
|
484
732
|
premises: certificates.map(premiseFromCertificate)
|
|
485
733
|
},
|
|
486
|
-
issued_at: parseIssuedAt(
|
|
734
|
+
issued_at: parseIssuedAt(
|
|
735
|
+
optionalField(input, "issued_at") || (/* @__PURE__ */ new Date()).toISOString(),
|
|
736
|
+
"semantic certificate issued_at"
|
|
737
|
+
)
|
|
487
738
|
};
|
|
488
739
|
return { ok: true, certificate: withCertificateId(body) };
|
|
489
740
|
}
|
|
741
|
+
function invalidClosure(message) {
|
|
742
|
+
return { ok: false, error: { code: "invalid_closure", message } };
|
|
743
|
+
}
|
|
744
|
+
function firstPremiseSnapshotMismatch(snapshot, certificate) {
|
|
745
|
+
const observed = premiseFromCertificate(certificate);
|
|
746
|
+
if (snapshot.derivation_kind !== observed.derivation_kind) return "derivation_kind";
|
|
747
|
+
if (snapshot.assurance !== observed.assurance) return "assurance";
|
|
748
|
+
if (stableJson(snapshot.scope) !== stableJson(observed.scope)) return "scope";
|
|
749
|
+
if (stableJson(snapshot.claim) !== stableJson(observed.claim)) return "claim";
|
|
750
|
+
if (stableJson(snapshot.evidence) !== stableJson(observed.evidence)) return "evidence";
|
|
751
|
+
return void 0;
|
|
752
|
+
}
|
|
753
|
+
function validateSemanticCertificateClosureInternal(value) {
|
|
754
|
+
if (!isRecord(value)) {
|
|
755
|
+
return invalidClosure("Semantic certificate closure must be a plain object.");
|
|
756
|
+
}
|
|
757
|
+
assertOnlyKeys(
|
|
758
|
+
value,
|
|
759
|
+
["version", "root_certificate_id", "certificates"],
|
|
760
|
+
"Semantic certificate closure"
|
|
761
|
+
);
|
|
762
|
+
if (requiredField(value, "version", "Semantic certificate closure") !== RIDDLE_PROOF_SEMANTIC_CERTIFICATE_CLOSURE_VERSION) {
|
|
763
|
+
return invalidClosure("Unsupported Semantic certificate closure version.");
|
|
764
|
+
}
|
|
765
|
+
const rootCertificateId = requiredString(
|
|
766
|
+
value,
|
|
767
|
+
"root_certificate_id",
|
|
768
|
+
"Semantic certificate closure"
|
|
769
|
+
);
|
|
770
|
+
if (!/^rpsc_[0-9a-f]{64}$/u.test(rootCertificateId)) {
|
|
771
|
+
return invalidClosure(
|
|
772
|
+
"Semantic certificate closure.root_certificate_id must be a full rpsc content ID."
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
const inputCertificates = requiredField(
|
|
776
|
+
value,
|
|
777
|
+
"certificates",
|
|
778
|
+
"Semantic certificate closure"
|
|
779
|
+
);
|
|
780
|
+
const certificateValues = readDenseDataArray(
|
|
781
|
+
inputCertificates,
|
|
782
|
+
"Semantic certificate closure.certificates",
|
|
783
|
+
RIDDLE_PROOF_SEMANTIC_CERTIFICATE_CLOSURE_MAX_CERTIFICATES
|
|
784
|
+
);
|
|
785
|
+
if (certificateValues.length === 0) {
|
|
786
|
+
return invalidClosure("Semantic certificate closure must contain at least one certificate.");
|
|
787
|
+
}
|
|
788
|
+
const certificates = [];
|
|
789
|
+
for (let index = 0; index < certificateValues.length; index += 1) {
|
|
790
|
+
try {
|
|
791
|
+
certificates.push(parseRiddleProofSemanticCertificate(certificateValues[index]));
|
|
792
|
+
} catch (error) {
|
|
793
|
+
return {
|
|
794
|
+
ok: false,
|
|
795
|
+
error: {
|
|
796
|
+
code: "invalid_closure_certificate",
|
|
797
|
+
input_index: index,
|
|
798
|
+
message: `Semantic certificate closure certificate ${index} did not parse: ${safeErrorMessage(error)}`
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
const certificatesById = /* @__PURE__ */ new Map();
|
|
804
|
+
const firstIndexById = /* @__PURE__ */ new Map();
|
|
805
|
+
for (let index = 0; index < certificates.length; index += 1) {
|
|
806
|
+
const certificate = certificates[index];
|
|
807
|
+
const firstIndex = firstIndexById.get(certificate.certificate_id);
|
|
808
|
+
if (firstIndex !== void 0) {
|
|
809
|
+
return {
|
|
810
|
+
ok: false,
|
|
811
|
+
error: {
|
|
812
|
+
code: "duplicate_certificate_id",
|
|
813
|
+
certificate_id: certificate.certificate_id,
|
|
814
|
+
first_index: firstIndex,
|
|
815
|
+
duplicate_index: index,
|
|
816
|
+
message: `Semantic certificate closure repeats certificate ${certificate.certificate_id}.`
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
firstIndexById.set(certificate.certificate_id, index);
|
|
821
|
+
certificatesById.set(certificate.certificate_id, certificate);
|
|
822
|
+
}
|
|
823
|
+
const rootCertificate = certificatesById.get(rootCertificateId);
|
|
824
|
+
if (!rootCertificate) {
|
|
825
|
+
return {
|
|
826
|
+
ok: false,
|
|
827
|
+
error: {
|
|
828
|
+
code: "root_certificate_missing",
|
|
829
|
+
root_certificate_id: rootCertificateId,
|
|
830
|
+
message: "Semantic certificate closure does not contain its declared root certificate."
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
const states = /* @__PURE__ */ new Map();
|
|
835
|
+
const stack = [{ certificate: rootCertificate, next_premise_index: 0 }];
|
|
836
|
+
const dependencyFirst = [];
|
|
837
|
+
states.set(rootCertificateId, "visiting");
|
|
838
|
+
while (stack.length > 0) {
|
|
839
|
+
const frame = stack[stack.length - 1];
|
|
840
|
+
const premises = frame.certificate.derivation.kind === "composition" ? frame.certificate.derivation.premises : [];
|
|
841
|
+
if (frame.next_premise_index < premises.length) {
|
|
842
|
+
const premiseIndex = frame.next_premise_index;
|
|
843
|
+
frame.next_premise_index += 1;
|
|
844
|
+
const snapshot = premises[premiseIndex];
|
|
845
|
+
const child = certificatesById.get(snapshot.certificate_id);
|
|
846
|
+
if (!child) {
|
|
847
|
+
return {
|
|
848
|
+
ok: false,
|
|
849
|
+
error: {
|
|
850
|
+
code: "dangling_premise",
|
|
851
|
+
parent_certificate_id: frame.certificate.certificate_id,
|
|
852
|
+
premise_index: premiseIndex,
|
|
853
|
+
premise_certificate_id: snapshot.certificate_id,
|
|
854
|
+
message: `Semantic certificate ${frame.certificate.certificate_id} premise ${premiseIndex} has no full certificate body.`
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
const mismatch = firstPremiseSnapshotMismatch(snapshot, child);
|
|
859
|
+
if (mismatch) {
|
|
860
|
+
return {
|
|
861
|
+
ok: false,
|
|
862
|
+
error: {
|
|
863
|
+
code: "premise_snapshot_mismatch",
|
|
864
|
+
parent_certificate_id: frame.certificate.certificate_id,
|
|
865
|
+
premise_index: premiseIndex,
|
|
866
|
+
premise_certificate_id: snapshot.certificate_id,
|
|
867
|
+
field: mismatch,
|
|
868
|
+
message: `Semantic certificate ${frame.certificate.certificate_id} premise ${premiseIndex} has a different ${mismatch} than its full certificate body.`
|
|
869
|
+
}
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
const childState = states.get(child.certificate_id);
|
|
873
|
+
if (childState === "visiting") {
|
|
874
|
+
const cycleStart = stack.findIndex(
|
|
875
|
+
(candidate) => candidate.certificate.certificate_id === child.certificate_id
|
|
876
|
+
);
|
|
877
|
+
const certificateIds = stack.slice(Math.max(0, cycleStart)).map((candidate) => candidate.certificate.certificate_id);
|
|
878
|
+
certificateIds.push(child.certificate_id);
|
|
879
|
+
return {
|
|
880
|
+
ok: false,
|
|
881
|
+
error: {
|
|
882
|
+
code: "certificate_cycle",
|
|
883
|
+
certificate_ids: certificateIds,
|
|
884
|
+
message: "Semantic certificate closure contains a certificate cycle."
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
if (childState !== "visited") {
|
|
889
|
+
states.set(child.certificate_id, "visiting");
|
|
890
|
+
stack.push({ certificate: child, next_premise_index: 0 });
|
|
891
|
+
}
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
stack.pop();
|
|
895
|
+
states.set(frame.certificate.certificate_id, "visited");
|
|
896
|
+
dependencyFirst.push(frame.certificate);
|
|
897
|
+
}
|
|
898
|
+
if (dependencyFirst.length !== certificates.length) {
|
|
899
|
+
const reachable = new Set(dependencyFirst.map((certificate) => certificate.certificate_id));
|
|
900
|
+
const unreachable = certificates.filter((certificate) => !reachable.has(certificate.certificate_id)).map((certificate) => certificate.certificate_id).sort();
|
|
901
|
+
return {
|
|
902
|
+
ok: false,
|
|
903
|
+
error: {
|
|
904
|
+
code: "unreachable_certificates",
|
|
905
|
+
certificate_ids: unreachable,
|
|
906
|
+
message: "Semantic certificate closure contains certificates unreachable from its root."
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
const closure = {
|
|
911
|
+
version: RIDDLE_PROOF_SEMANTIC_CERTIFICATE_CLOSURE_VERSION,
|
|
912
|
+
root_certificate_id: rootCertificateId,
|
|
913
|
+
certificates: dependencyFirst
|
|
914
|
+
};
|
|
915
|
+
return { ok: true, closure, root_certificate: rootCertificate };
|
|
916
|
+
}
|
|
917
|
+
function validateRiddleProofSemanticCertificateClosure(value) {
|
|
918
|
+
try {
|
|
919
|
+
return validateSemanticCertificateClosureInternal(value);
|
|
920
|
+
} catch (error) {
|
|
921
|
+
return invalidClosure(
|
|
922
|
+
`Semantic certificate closure did not parse: ${safeErrorMessage(error)}`
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
function createRiddleProofSemanticAtomicCertificateClosure(input) {
|
|
927
|
+
if (!isRecord(input)) throw new Error("Semantic certificate closure input must be a plain object.");
|
|
928
|
+
assertOnlyKeys(input, ["certificate"], "Semantic certificate closure input");
|
|
929
|
+
const certificate = requiredField(input, "certificate", "Semantic certificate closure input");
|
|
930
|
+
let parsedCertificate;
|
|
931
|
+
try {
|
|
932
|
+
parsedCertificate = parseRiddleProofSemanticCertificate(certificate);
|
|
933
|
+
} catch (error) {
|
|
934
|
+
return {
|
|
935
|
+
ok: false,
|
|
936
|
+
error: {
|
|
937
|
+
code: "invalid_closure_certificate",
|
|
938
|
+
input_index: 0,
|
|
939
|
+
message: `Semantic certificate closure certificate 0 did not parse: ${safeErrorMessage(error)}`
|
|
940
|
+
}
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
if (parsedCertificate.derivation.kind !== "contract") {
|
|
944
|
+
return invalidClosure(
|
|
945
|
+
"An atomic Semantic certificate closure requires a contract-derived certificate."
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
return validateRiddleProofSemanticCertificateClosure({
|
|
949
|
+
version: RIDDLE_PROOF_SEMANTIC_CERTIFICATE_CLOSURE_VERSION,
|
|
950
|
+
root_certificate_id: parsedCertificate.certificate_id,
|
|
951
|
+
certificates: [certificate]
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
function composeRiddleProofSemanticCertificateClosures(input) {
|
|
955
|
+
if (!isRecord(input)) throw new Error("Semantic closure composition input must be a plain object.");
|
|
956
|
+
assertOnlyKeys(
|
|
957
|
+
input,
|
|
958
|
+
["rule", "closures", "issued_at"],
|
|
959
|
+
"Semantic closure composition input"
|
|
960
|
+
);
|
|
961
|
+
const inputClosures = requiredField(
|
|
962
|
+
input,
|
|
963
|
+
"closures",
|
|
964
|
+
"Semantic closure composition input"
|
|
965
|
+
);
|
|
966
|
+
const closureValues = readDenseDataArray(
|
|
967
|
+
inputClosures,
|
|
968
|
+
"Semantic closure composition input.closures"
|
|
969
|
+
);
|
|
970
|
+
if (closureValues.length === 0) {
|
|
971
|
+
throw new Error("Semantic closure composition requires at least one closure.");
|
|
972
|
+
}
|
|
973
|
+
const validatedClosures = [];
|
|
974
|
+
for (let index = 0; index < closureValues.length; index += 1) {
|
|
975
|
+
const result = validateRiddleProofSemanticCertificateClosure(closureValues[index]);
|
|
976
|
+
if (!result.ok) {
|
|
977
|
+
return {
|
|
978
|
+
ok: false,
|
|
979
|
+
error: {
|
|
980
|
+
code: "input_closure_invalid",
|
|
981
|
+
input_index: index,
|
|
982
|
+
cause: result.error,
|
|
983
|
+
message: `Semantic input closure ${index} is invalid: ${result.error.message}`
|
|
984
|
+
}
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
validatedClosures.push(result);
|
|
988
|
+
}
|
|
989
|
+
const roots = validatedClosures.map((result) => {
|
|
990
|
+
if (!result.ok) throw new Error("Validated Semantic closure unexpectedly became invalid.");
|
|
991
|
+
return result.root_certificate;
|
|
992
|
+
});
|
|
993
|
+
const composition = composeRiddleProofSemanticCertificates({
|
|
994
|
+
rule: requiredField(input, "rule", "Semantic closure composition input"),
|
|
995
|
+
certificates: roots,
|
|
996
|
+
issued_at: optionalField(input, "issued_at")
|
|
997
|
+
});
|
|
998
|
+
if (!composition.ok) return composition;
|
|
999
|
+
const merged = [];
|
|
1000
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1001
|
+
for (const result of validatedClosures) {
|
|
1002
|
+
if (!result.ok) continue;
|
|
1003
|
+
for (const certificate of result.closure.certificates) {
|
|
1004
|
+
const existing = byId.get(certificate.certificate_id);
|
|
1005
|
+
if (existing) {
|
|
1006
|
+
if (stableJson(existing) !== stableJson(certificate)) {
|
|
1007
|
+
return {
|
|
1008
|
+
ok: false,
|
|
1009
|
+
error: {
|
|
1010
|
+
code: "certificate_id_collision",
|
|
1011
|
+
certificate_id: certificate.certificate_id,
|
|
1012
|
+
message: `Semantic closures contain unequal bodies for ${certificate.certificate_id}.`
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
continue;
|
|
1017
|
+
}
|
|
1018
|
+
byId.set(certificate.certificate_id, certificate);
|
|
1019
|
+
merged.push(certificate);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
if (byId.has(composition.certificate.certificate_id)) {
|
|
1023
|
+
return {
|
|
1024
|
+
ok: false,
|
|
1025
|
+
error: {
|
|
1026
|
+
code: "certificate_id_collision",
|
|
1027
|
+
certificate_id: composition.certificate.certificate_id,
|
|
1028
|
+
message: "Semantic composition produced a root ID already present in its premise closures."
|
|
1029
|
+
}
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
merged.push(composition.certificate);
|
|
1033
|
+
const validation = validateRiddleProofSemanticCertificateClosure({
|
|
1034
|
+
version: RIDDLE_PROOF_SEMANTIC_CERTIFICATE_CLOSURE_VERSION,
|
|
1035
|
+
root_certificate_id: composition.certificate.certificate_id,
|
|
1036
|
+
certificates: merged
|
|
1037
|
+
});
|
|
1038
|
+
if (!validation.ok) {
|
|
1039
|
+
return {
|
|
1040
|
+
ok: false,
|
|
1041
|
+
error: {
|
|
1042
|
+
code: "closure_construction_failed",
|
|
1043
|
+
cause: validation.error,
|
|
1044
|
+
message: `Semantic closure construction failed: ${validation.error.message}`
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
return {
|
|
1049
|
+
ok: true,
|
|
1050
|
+
certificate: composition.certificate,
|
|
1051
|
+
closure: validation.closure
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
function matchRiddleProofSemanticCertificateClosure(input) {
|
|
1055
|
+
if (!isRecord(input)) throw new Error("Semantic certificate closure match input must be a plain object.");
|
|
1056
|
+
assertOnlyKeys(
|
|
1057
|
+
input,
|
|
1058
|
+
[
|
|
1059
|
+
"closure",
|
|
1060
|
+
"expected_root_certificate_id",
|
|
1061
|
+
"expected_scope",
|
|
1062
|
+
"expected_claim",
|
|
1063
|
+
"expected_assurance"
|
|
1064
|
+
],
|
|
1065
|
+
"Semantic certificate closure match input"
|
|
1066
|
+
);
|
|
1067
|
+
const validation = validateRiddleProofSemanticCertificateClosure(
|
|
1068
|
+
requiredField(input, "closure", "Semantic certificate closure match input")
|
|
1069
|
+
);
|
|
1070
|
+
if (!validation.ok) return validation;
|
|
1071
|
+
const expectedRootCertificateId = requiredString(
|
|
1072
|
+
input,
|
|
1073
|
+
"expected_root_certificate_id",
|
|
1074
|
+
"Semantic certificate closure match input"
|
|
1075
|
+
);
|
|
1076
|
+
if (!/^rpsc_[0-9a-f]{64}$/u.test(expectedRootCertificateId)) {
|
|
1077
|
+
throw new Error(
|
|
1078
|
+
"Semantic certificate closure match input.expected_root_certificate_id must be a full rpsc content ID."
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
const rootMatch = matchRiddleProofSemanticCertificate({
|
|
1082
|
+
certificate: validation.root_certificate,
|
|
1083
|
+
expected_certificate_id: expectedRootCertificateId,
|
|
1084
|
+
expected_scope: requiredField(
|
|
1085
|
+
input,
|
|
1086
|
+
"expected_scope",
|
|
1087
|
+
"Semantic certificate closure match input"
|
|
1088
|
+
),
|
|
1089
|
+
expected_claim: requiredField(
|
|
1090
|
+
input,
|
|
1091
|
+
"expected_claim",
|
|
1092
|
+
"Semantic certificate closure match input"
|
|
1093
|
+
),
|
|
1094
|
+
expected_assurance: requiredField(
|
|
1095
|
+
input,
|
|
1096
|
+
"expected_assurance",
|
|
1097
|
+
"Semantic certificate closure match input"
|
|
1098
|
+
)
|
|
1099
|
+
});
|
|
1100
|
+
if (!rootMatch.ok) return rootMatch;
|
|
1101
|
+
return {
|
|
1102
|
+
ok: true,
|
|
1103
|
+
closure: validation.closure,
|
|
1104
|
+
root_certificate: rootMatch.certificate
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
490
1107
|
// Annotate the CommonJS export names for ESM import in node:
|
|
491
1108
|
0 && (module.exports = {
|
|
1109
|
+
RIDDLE_PROOF_SEMANTIC_CERTIFICATE_CLOSURE_MAX_CERTIFICATES,
|
|
1110
|
+
RIDDLE_PROOF_SEMANTIC_CERTIFICATE_CLOSURE_VERSION,
|
|
492
1111
|
RIDDLE_PROOF_SEMANTIC_CERTIFICATE_VERSION,
|
|
1112
|
+
composeRiddleProofSemanticCertificateClosures,
|
|
493
1113
|
composeRiddleProofSemanticCertificates,
|
|
1114
|
+
createRiddleProofSemanticAtomicCertificateClosure,
|
|
494
1115
|
createRiddleProofSemanticCertificate,
|
|
1116
|
+
matchRiddleProofSemanticCertificate,
|
|
1117
|
+
matchRiddleProofSemanticCertificateClosure,
|
|
495
1118
|
parseRiddleProofSemanticCertificate,
|
|
496
|
-
riddleProofSemanticScopesEqual
|
|
1119
|
+
riddleProofSemanticScopesEqual,
|
|
1120
|
+
validateRiddleProofSemanticCertificateClosure
|
|
497
1121
|
});
|