@nobulex/sdk 0.1.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.
Files changed (81) hide show
  1. package/dist/adapters/express.d.ts +322 -0
  2. package/dist/adapters/express.d.ts.map +1 -0
  3. package/dist/adapters/express.js +356 -0
  4. package/dist/adapters/express.js.map +1 -0
  5. package/dist/adapters/index.d.ts +15 -0
  6. package/dist/adapters/index.d.ts.map +1 -0
  7. package/dist/adapters/index.js +15 -0
  8. package/dist/adapters/index.js.map +1 -0
  9. package/dist/adapters/langchain.d.ts +183 -0
  10. package/dist/adapters/langchain.d.ts.map +1 -0
  11. package/dist/adapters/langchain.js +203 -0
  12. package/dist/adapters/langchain.js.map +1 -0
  13. package/dist/adapters/vercel-ai.d.ts +122 -0
  14. package/dist/adapters/vercel-ai.d.ts.map +1 -0
  15. package/dist/adapters/vercel-ai.js +128 -0
  16. package/dist/adapters/vercel-ai.js.map +1 -0
  17. package/dist/benchmarks.d.ts +164 -0
  18. package/dist/benchmarks.d.ts.map +1 -0
  19. package/dist/benchmarks.js +327 -0
  20. package/dist/benchmarks.js.map +1 -0
  21. package/dist/benchmarks.test.d.ts +2 -0
  22. package/dist/benchmarks.test.d.ts.map +1 -0
  23. package/dist/benchmarks.test.js +71 -0
  24. package/dist/benchmarks.test.js.map +1 -0
  25. package/dist/conformance.d.ts +160 -0
  26. package/dist/conformance.d.ts.map +1 -0
  27. package/dist/conformance.js +1242 -0
  28. package/dist/conformance.js.map +1 -0
  29. package/dist/events.d.ts +176 -0
  30. package/dist/events.d.ts.map +1 -0
  31. package/dist/events.js +208 -0
  32. package/dist/events.js.map +1 -0
  33. package/dist/index.d.ts +384 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +695 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/index.test.d.ts +2 -0
  38. package/dist/index.test.d.ts.map +1 -0
  39. package/dist/index.test.js +986 -0
  40. package/dist/index.test.js.map +1 -0
  41. package/dist/middleware.d.ts +104 -0
  42. package/dist/middleware.d.ts.map +1 -0
  43. package/dist/middleware.js +222 -0
  44. package/dist/middleware.js.map +1 -0
  45. package/dist/middleware.test.d.ts +5 -0
  46. package/dist/middleware.test.d.ts.map +1 -0
  47. package/dist/middleware.test.js +735 -0
  48. package/dist/middleware.test.js.map +1 -0
  49. package/dist/plugins/auth.d.ts +49 -0
  50. package/dist/plugins/auth.d.ts.map +1 -0
  51. package/dist/plugins/auth.js +82 -0
  52. package/dist/plugins/auth.js.map +1 -0
  53. package/dist/plugins/cache.d.ts +40 -0
  54. package/dist/plugins/cache.d.ts.map +1 -0
  55. package/dist/plugins/cache.js +191 -0
  56. package/dist/plugins/cache.js.map +1 -0
  57. package/dist/plugins/index.d.ts +16 -0
  58. package/dist/plugins/index.d.ts.map +1 -0
  59. package/dist/plugins/index.js +12 -0
  60. package/dist/plugins/index.js.map +1 -0
  61. package/dist/plugins/metrics-plugin.d.ts +32 -0
  62. package/dist/plugins/metrics-plugin.d.ts.map +1 -0
  63. package/dist/plugins/metrics-plugin.js +61 -0
  64. package/dist/plugins/metrics-plugin.js.map +1 -0
  65. package/dist/plugins/plugins.test.d.ts +8 -0
  66. package/dist/plugins/plugins.test.d.ts.map +1 -0
  67. package/dist/plugins/plugins.test.js +640 -0
  68. package/dist/plugins/plugins.test.js.map +1 -0
  69. package/dist/plugins/retry-plugin.d.ts +55 -0
  70. package/dist/plugins/retry-plugin.d.ts.map +1 -0
  71. package/dist/plugins/retry-plugin.js +133 -0
  72. package/dist/plugins/retry-plugin.js.map +1 -0
  73. package/dist/telemetry.d.ts +183 -0
  74. package/dist/telemetry.d.ts.map +1 -0
  75. package/dist/telemetry.js +241 -0
  76. package/dist/telemetry.js.map +1 -0
  77. package/dist/types.d.ts +200 -0
  78. package/dist/types.d.ts.map +1 -0
  79. package/dist/types.js +8 -0
  80. package/dist/types.js.map +1 -0
  81. package/package.json +52 -0
@@ -0,0 +1,1242 @@
1
+ /**
2
+ * Stele Protocol Conformance Suite
3
+ *
4
+ * Provides test vectors and validation functions for any implementation
5
+ * of the Stele protocol. Implementations that pass all conformance checks
6
+ * are considered spec-compliant.
7
+ *
8
+ * Like the W3C Acid Tests for browsers or TLS conformance suites --
9
+ * a standardized set of test vectors that any Stele implementation must pass.
10
+ *
11
+ * This module is self-contained at runtime: it uses the provided
12
+ * {@link ConformanceTarget} interface to generate keys and test documents.
13
+ * Type imports from @stele packages are used only for interface definitions.
14
+ *
15
+ * @packageDocumentation
16
+ */
17
+ // ─── Helpers ────────────────────────────────────────────────────────────────
18
+ function textEncode(s) {
19
+ return new TextEncoder().encode(s);
20
+ }
21
+ function bytesToHex(data) {
22
+ let hex = '';
23
+ for (let i = 0; i < data.length; i++) {
24
+ hex += data[i].toString(16).padStart(2, '0');
25
+ }
26
+ return hex;
27
+ }
28
+ // ─── Reference canonical JSON implementation ────────────────────────────────
29
+ // Embedded here so the conformance suite is fully self-contained.
30
+ // This follows JCS (RFC 8785): recursive alphabetical key sorting.
31
+ function referenceSortKeys(value) {
32
+ if (value === null || value === undefined)
33
+ return value;
34
+ if (Array.isArray(value))
35
+ return value.map(referenceSortKeys);
36
+ if (typeof value === 'object') {
37
+ const sorted = {};
38
+ const keys = Object.keys(value).sort();
39
+ for (const key of keys) {
40
+ const v = value[key];
41
+ if (v !== undefined) {
42
+ sorted[key] = referenceSortKeys(v);
43
+ }
44
+ }
45
+ return sorted;
46
+ }
47
+ return value;
48
+ }
49
+ function referenceCanonicalizeJson(obj) {
50
+ return JSON.stringify(referenceSortKeys(obj));
51
+ }
52
+ /**
53
+ * Reference canonical form: strips `id`, `signature`, `countersignatures`
54
+ * and produces deterministic JSON.
55
+ */
56
+ function referenceCanonicalForm(doc) {
57
+ const { id: _id, signature: _sig, countersignatures: _cs, ...body } = doc;
58
+ return referenceCanonicalizeJson(body);
59
+ }
60
+ // ─── Known-answer test vectors ──────────────────────────────────────────────
61
+ /**
62
+ * NIST SHA-256 test vectors.
63
+ * Source: FIPS 180-4 / NIST CSRC.
64
+ */
65
+ const SHA256_VECTORS = [
66
+ {
67
+ label: 'empty string',
68
+ input: '',
69
+ expected: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
70
+ },
71
+ {
72
+ label: 'abc',
73
+ input: 'abc',
74
+ expected: 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad',
75
+ },
76
+ {
77
+ label: '448-bit message',
78
+ input: 'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq',
79
+ expected: '248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1',
80
+ },
81
+ ];
82
+ /**
83
+ * Canonical JSON (JCS) test vectors.
84
+ * Verifies that implementations sort keys alphabetically and produce
85
+ * identical deterministic output.
86
+ */
87
+ const CANONICAL_JSON_VECTORS = [
88
+ {
89
+ label: 'flat key ordering',
90
+ input: { z: 1, a: 2, m: [3, 1] },
91
+ expected: '{"a":2,"m":[3,1],"z":1}',
92
+ },
93
+ {
94
+ label: 'nested key ordering',
95
+ input: { b: { z: 1, a: 2 }, a: 1 },
96
+ expected: '{"a":1,"b":{"a":2,"z":1}}',
97
+ },
98
+ {
99
+ label: 'mixed types',
100
+ input: { c: true, b: null, a: 'hello' },
101
+ expected: '{"a":"hello","b":null,"c":true}',
102
+ },
103
+ ];
104
+ // ═══════════════════════════════════════════════════════════════════════════
105
+ // Category 1: Cryptographic primitives
106
+ // ═══════════════════════════════════════════════════════════════════════════
107
+ /**
108
+ * Verify that the target's cryptographic primitives conform to spec.
109
+ *
110
+ * Checks:
111
+ * - Ed25519 sign/verify round-trip
112
+ * - SHA-256 known-answer tests (NIST vectors)
113
+ * - Signature verification rejects tampered messages
114
+ * - Different keys produce different signatures
115
+ * - Wrong public key rejects valid signature
116
+ */
117
+ export async function cryptoConformance(target) {
118
+ const failures = [];
119
+ let total = 0;
120
+ const category = 'crypto';
121
+ // ── Ed25519 sign/verify round-trip ──────────────────────────────────────
122
+ total++;
123
+ try {
124
+ const kp = await target.generateKeyPair();
125
+ const message = textEncode('stele conformance test message');
126
+ const sig = await target.sign(message, kp.privateKey);
127
+ const valid = await target.verify(message, sig, kp.publicKey);
128
+ if (!valid) {
129
+ failures.push({
130
+ test: 'ed25519-roundtrip',
131
+ category,
132
+ expected: true,
133
+ actual: valid,
134
+ message: 'Ed25519 sign/verify round-trip failed: signature should be valid',
135
+ });
136
+ }
137
+ }
138
+ catch (err) {
139
+ failures.push({
140
+ test: 'ed25519-roundtrip',
141
+ category,
142
+ expected: 'no error',
143
+ actual: String(err),
144
+ message: `Ed25519 sign/verify round-trip threw: ${err}`,
145
+ });
146
+ }
147
+ // ── SHA-256 known-answer tests (NIST) ───────────────────────────────────
148
+ for (const vec of SHA256_VECTORS) {
149
+ total++;
150
+ try {
151
+ const hash = await target.sha256(textEncode(vec.input));
152
+ if (hash !== vec.expected) {
153
+ failures.push({
154
+ test: `sha256-nist-${vec.label}`,
155
+ category,
156
+ expected: vec.expected,
157
+ actual: hash,
158
+ message: `SHA-256 of "${vec.label}" did not match NIST expected value`,
159
+ });
160
+ }
161
+ }
162
+ catch (err) {
163
+ failures.push({
164
+ test: `sha256-nist-${vec.label}`,
165
+ category,
166
+ expected: vec.expected,
167
+ actual: String(err),
168
+ message: `SHA-256 of "${vec.label}" threw: ${err}`,
169
+ });
170
+ }
171
+ }
172
+ // ── Signature verification rejects tampered messages ────────────────────
173
+ total++;
174
+ try {
175
+ const kp = await target.generateKeyPair();
176
+ const message = textEncode('original message');
177
+ const sig = await target.sign(message, kp.privateKey);
178
+ const tampered = textEncode('tampered message');
179
+ const valid = await target.verify(tampered, sig, kp.publicKey);
180
+ if (valid) {
181
+ failures.push({
182
+ test: 'ed25519-tamper-reject',
183
+ category,
184
+ expected: false,
185
+ actual: valid,
186
+ message: 'Tampered message should not verify with original signature',
187
+ });
188
+ }
189
+ }
190
+ catch (err) {
191
+ failures.push({
192
+ test: 'ed25519-tamper-reject',
193
+ category,
194
+ expected: 'no error',
195
+ actual: String(err),
196
+ message: `Tamper rejection test threw: ${err}`,
197
+ });
198
+ }
199
+ // ── Different keys produce different signatures ─────────────────────────
200
+ total++;
201
+ try {
202
+ const kp1 = await target.generateKeyPair();
203
+ const kp2 = await target.generateKeyPair();
204
+ const message = textEncode('same message different keys');
205
+ const sig1 = await target.sign(message, kp1.privateKey);
206
+ const sig2 = await target.sign(message, kp2.privateKey);
207
+ const hex1 = bytesToHex(sig1);
208
+ const hex2 = bytesToHex(sig2);
209
+ if (hex1 === hex2) {
210
+ failures.push({
211
+ test: 'ed25519-different-keys-different-sigs',
212
+ category,
213
+ expected: 'different signatures',
214
+ actual: 'identical signatures',
215
+ message: 'Different keys must produce different signatures for the same message',
216
+ });
217
+ }
218
+ }
219
+ catch (err) {
220
+ failures.push({
221
+ test: 'ed25519-different-keys-different-sigs',
222
+ category,
223
+ expected: 'no error',
224
+ actual: String(err),
225
+ message: `Different-keys test threw: ${err}`,
226
+ });
227
+ }
228
+ // ── Wrong public key rejects valid signature ────────────────────────────
229
+ total++;
230
+ try {
231
+ const kpA = await target.generateKeyPair();
232
+ const kpB = await target.generateKeyPair();
233
+ const message = textEncode('cross-key verification test');
234
+ const sig = await target.sign(message, kpA.privateKey);
235
+ const valid = await target.verify(message, sig, kpB.publicKey);
236
+ if (valid) {
237
+ failures.push({
238
+ test: 'ed25519-wrong-key-reject',
239
+ category,
240
+ expected: false,
241
+ actual: valid,
242
+ message: "Signature from key A should not verify with key B's public key",
243
+ });
244
+ }
245
+ }
246
+ catch (_err) {
247
+ // Throwing is acceptable for invalid verification -- counts as rejection
248
+ }
249
+ // ── Signature is 64 bytes ──────────────────────────────────────────────
250
+ total++;
251
+ try {
252
+ const kp = await target.generateKeyPair();
253
+ const message = textEncode('signature length test');
254
+ const sig = await target.sign(message, kp.privateKey);
255
+ if (sig.length !== 64) {
256
+ failures.push({
257
+ test: 'ed25519-signature-length',
258
+ category,
259
+ expected: 64,
260
+ actual: sig.length,
261
+ message: 'Ed25519 signature must be exactly 64 bytes',
262
+ });
263
+ }
264
+ }
265
+ catch (err) {
266
+ failures.push({
267
+ test: 'ed25519-signature-length',
268
+ category,
269
+ expected: 'no error',
270
+ actual: String(err),
271
+ message: `Signature length test threw: ${err}`,
272
+ });
273
+ }
274
+ // ── Public key is 32 bytes ─────────────────────────────────────────────
275
+ total++;
276
+ try {
277
+ const kp = await target.generateKeyPair();
278
+ if (kp.publicKey.length !== 32) {
279
+ failures.push({
280
+ test: 'ed25519-pubkey-length',
281
+ category,
282
+ expected: 32,
283
+ actual: kp.publicKey.length,
284
+ message: 'Ed25519 public key must be exactly 32 bytes',
285
+ });
286
+ }
287
+ }
288
+ catch (err) {
289
+ failures.push({
290
+ test: 'ed25519-pubkey-length',
291
+ category,
292
+ expected: 'no error',
293
+ actual: String(err),
294
+ message: `Public key length test threw: ${err}`,
295
+ });
296
+ }
297
+ return { failures, total };
298
+ }
299
+ // ═══════════════════════════════════════════════════════════════════════════
300
+ // Category 2: CCL parsing and evaluation
301
+ // ═══════════════════════════════════════════════════════════════════════════
302
+ /**
303
+ * Verify that the target's CCL parser and evaluator conform to spec.
304
+ *
305
+ * Checks:
306
+ * - `permit read on '/data'` permits read on /data
307
+ * - `deny write on '/system/**'` denies write on /system/config
308
+ * - Default deny: no matching rule results in denied
309
+ * - Deny-wins: permit + deny on same resource results in denied
310
+ * - Wildcards: `**` matches nested paths
311
+ * - Rate limits parse correctly
312
+ * - Conditions evaluate correctly
313
+ * - Exact resource matching: `/secrets` does NOT match `/secrets/key`
314
+ */
315
+ export async function cclConformance(target) {
316
+ const failures = [];
317
+ let total = 0;
318
+ const category = 'ccl';
319
+ // Helper: build a covenant with given constraints, then evaluate an action.
320
+ async function evalConstraints(constraints, action, resource, context) {
321
+ const kp = await target.generateKeyPair();
322
+ const doc = await target.buildCovenant({
323
+ issuer: {
324
+ id: 'conformance-issuer',
325
+ publicKey: kp.publicKeyHex,
326
+ role: 'issuer',
327
+ },
328
+ beneficiary: {
329
+ id: 'conformance-beneficiary',
330
+ publicKey: kp.publicKeyHex,
331
+ role: 'beneficiary',
332
+ },
333
+ constraints,
334
+ privateKey: kp.privateKey,
335
+ });
336
+ return target.evaluateAction(doc, action, resource, context);
337
+ }
338
+ // ── permit read on '/data' permits read on /data ────────────────────────
339
+ total++;
340
+ try {
341
+ const result = await evalConstraints("permit read on '/data'", 'read', '/data');
342
+ if (!result.permitted) {
343
+ failures.push({
344
+ test: 'ccl-permit-basic',
345
+ category,
346
+ expected: true,
347
+ actual: result.permitted,
348
+ message: "permit read on '/data' must permit read on /data",
349
+ });
350
+ }
351
+ }
352
+ catch (err) {
353
+ failures.push({
354
+ test: 'ccl-permit-basic',
355
+ category,
356
+ expected: true,
357
+ actual: String(err),
358
+ message: `Basic permit test threw: ${err}`,
359
+ });
360
+ }
361
+ // ── deny write on '/system/**' denies write on /system/config ──────────
362
+ total++;
363
+ try {
364
+ const result = await evalConstraints("deny write on '/system/**'", 'write', '/system/config');
365
+ if (result.permitted) {
366
+ failures.push({
367
+ test: 'ccl-deny-wildcard',
368
+ category,
369
+ expected: false,
370
+ actual: result.permitted,
371
+ message: "deny write on '/system/**' must deny write on /system/config",
372
+ });
373
+ }
374
+ }
375
+ catch (err) {
376
+ failures.push({
377
+ test: 'ccl-deny-wildcard',
378
+ category,
379
+ expected: false,
380
+ actual: String(err),
381
+ message: `Deny wildcard test threw: ${err}`,
382
+ });
383
+ }
384
+ // ── Default deny: no matching rule -> denied ───────────────────────────
385
+ total++;
386
+ try {
387
+ const result = await evalConstraints("permit read on '/data'", 'write', '/other');
388
+ if (result.permitted) {
389
+ failures.push({
390
+ test: 'ccl-default-deny',
391
+ category,
392
+ expected: false,
393
+ actual: result.permitted,
394
+ message: 'When no rules match, result must be denied (default deny)',
395
+ });
396
+ }
397
+ }
398
+ catch (err) {
399
+ failures.push({
400
+ test: 'ccl-default-deny',
401
+ category,
402
+ expected: false,
403
+ actual: String(err),
404
+ message: `Default deny test threw: ${err}`,
405
+ });
406
+ }
407
+ // ── Deny-wins: permit + deny on same resource -> denied ────────────────
408
+ total++;
409
+ try {
410
+ const constraints = "permit read on '/data'\ndeny read on '/data'";
411
+ const result = await evalConstraints(constraints, 'read', '/data');
412
+ if (result.permitted) {
413
+ failures.push({
414
+ test: 'ccl-deny-wins',
415
+ category,
416
+ expected: false,
417
+ actual: result.permitted,
418
+ message: 'When permit and deny have equal specificity, deny must win',
419
+ });
420
+ }
421
+ }
422
+ catch (err) {
423
+ failures.push({
424
+ test: 'ccl-deny-wins',
425
+ category,
426
+ expected: false,
427
+ actual: String(err),
428
+ message: `Deny-wins test threw: ${err}`,
429
+ });
430
+ }
431
+ // ── Wildcards: ** matches nested paths ─────────────────────────────────
432
+ total++;
433
+ try {
434
+ const result = await evalConstraints("permit read on '/data/**'", 'read', '/data/users/123/profile');
435
+ if (!result.permitted) {
436
+ failures.push({
437
+ test: 'ccl-wildcard-nested',
438
+ category,
439
+ expected: true,
440
+ actual: result.permitted,
441
+ message: '** wildcard must match deeply nested paths',
442
+ });
443
+ }
444
+ }
445
+ catch (err) {
446
+ failures.push({
447
+ test: 'ccl-wildcard-nested',
448
+ category,
449
+ expected: true,
450
+ actual: String(err),
451
+ message: `Wildcard nested test threw: ${err}`,
452
+ });
453
+ }
454
+ // ── Rate limits parse correctly ────────────────────────────────────────
455
+ total++;
456
+ try {
457
+ const cclDoc = target.parseCCL('limit api.call 1000 per 1 hours');
458
+ if (!cclDoc.limits || cclDoc.limits.length !== 1) {
459
+ failures.push({
460
+ test: 'ccl-rate-limit-parse',
461
+ category,
462
+ expected: 1,
463
+ actual: cclDoc.limits?.length ?? 0,
464
+ message: 'Rate limit statement must parse into exactly 1 limit',
465
+ });
466
+ }
467
+ else {
468
+ const limit = cclDoc.limits[0];
469
+ if (!limit) {
470
+ failures.push({
471
+ test: 'ccl-rate-limit-count',
472
+ category,
473
+ expected: 1000,
474
+ actual: 0,
475
+ message: 'Rate limit statement not found',
476
+ });
477
+ }
478
+ else {
479
+ if (limit.count !== 1000) {
480
+ failures.push({
481
+ test: 'ccl-rate-limit-count',
482
+ category,
483
+ expected: 1000,
484
+ actual: limit.count,
485
+ message: 'Rate limit count must be 1000',
486
+ });
487
+ }
488
+ if (limit.periodSeconds !== 3600) {
489
+ failures.push({
490
+ test: 'ccl-rate-limit-period',
491
+ category,
492
+ expected: 3600,
493
+ actual: limit.periodSeconds,
494
+ message: 'Rate limit period must be 3600 seconds (1 hour)',
495
+ });
496
+ }
497
+ }
498
+ }
499
+ }
500
+ catch (err) {
501
+ failures.push({
502
+ test: 'ccl-rate-limit-parse',
503
+ category,
504
+ expected: 'successful parse',
505
+ actual: String(err),
506
+ message: `Rate limit parse test threw: ${err}`,
507
+ });
508
+ }
509
+ // ── Conditions evaluate correctly (match) ──────────────────────────────
510
+ total++;
511
+ try {
512
+ const constraints = "permit read on '/data' when user.role = 'admin'";
513
+ const result = await evalConstraints(constraints, 'read', '/data', {
514
+ user: { role: 'admin' },
515
+ });
516
+ if (!result.permitted) {
517
+ failures.push({
518
+ test: 'ccl-condition-match',
519
+ category,
520
+ expected: true,
521
+ actual: result.permitted,
522
+ message: "Condition user.role = 'admin' must permit when context has role=admin",
523
+ });
524
+ }
525
+ }
526
+ catch (err) {
527
+ failures.push({
528
+ test: 'ccl-condition-match',
529
+ category,
530
+ expected: true,
531
+ actual: String(err),
532
+ message: `Condition match test threw: ${err}`,
533
+ });
534
+ }
535
+ // ── Conditions evaluate correctly (no match) ──────────────────────────
536
+ total++;
537
+ try {
538
+ const constraints = "permit read on '/data' when user.role = 'admin'";
539
+ const result = await evalConstraints(constraints, 'read', '/data', {
540
+ user: { role: 'guest' },
541
+ });
542
+ if (result.permitted) {
543
+ failures.push({
544
+ test: 'ccl-condition-no-match',
545
+ category,
546
+ expected: false,
547
+ actual: result.permitted,
548
+ message: "Condition user.role = 'admin' must deny when context has role=guest",
549
+ });
550
+ }
551
+ }
552
+ catch (err) {
553
+ failures.push({
554
+ test: 'ccl-condition-no-match',
555
+ category,
556
+ expected: false,
557
+ actual: String(err),
558
+ message: `Condition no-match test threw: ${err}`,
559
+ });
560
+ }
561
+ // ── Exact resource matching ────────────────────────────────────────────
562
+ total++;
563
+ try {
564
+ const result = await evalConstraints("permit read on '/secrets'", 'read', '/secrets/key');
565
+ if (result.permitted) {
566
+ failures.push({
567
+ test: 'ccl-exact-resource',
568
+ category,
569
+ expected: false,
570
+ actual: result.permitted,
571
+ message: "'/secrets' must not match '/secrets/key' -- exact matching required without **",
572
+ });
573
+ }
574
+ }
575
+ catch (err) {
576
+ failures.push({
577
+ test: 'ccl-exact-resource',
578
+ category,
579
+ expected: false,
580
+ actual: String(err),
581
+ message: `Exact resource matching test threw: ${err}`,
582
+ });
583
+ }
584
+ // ── Empty/invalid CCL source throws ────────────────────────────────────
585
+ total++;
586
+ try {
587
+ target.parseCCL('');
588
+ failures.push({
589
+ test: 'ccl-empty-source-rejects',
590
+ category,
591
+ expected: 'error thrown',
592
+ actual: 'no error',
593
+ message: 'Parsing empty CCL source must throw',
594
+ });
595
+ }
596
+ catch (_err) {
597
+ // Expected: parsing empty source should throw
598
+ }
599
+ return { failures, total };
600
+ }
601
+ // ═══════════════════════════════════════════════════════════════════════════
602
+ // Category 3: Covenant lifecycle
603
+ // ═══════════════════════════════════════════════════════════════════════════
604
+ /**
605
+ * Verify that the target's covenant build/verify lifecycle conforms to spec.
606
+ *
607
+ * Checks:
608
+ * - Build -> verify round-trip
609
+ * - Tampered covenant fails verification
610
+ * - Expired covenant detected
611
+ * - ID integrity check (id matches content hash)
612
+ * - Constraints must be valid CCL
613
+ * - Nonce must be present
614
+ * - Signature is a hex string of correct length
615
+ */
616
+ export async function covenantConformance(target) {
617
+ const failures = [];
618
+ let total = 0;
619
+ const category = 'covenant';
620
+ // Helper: build a standard test covenant with optional overrides.
621
+ async function buildTestCovenant(overrides) {
622
+ const kp = await target.generateKeyPair();
623
+ const defaults = {
624
+ issuer: {
625
+ id: 'test-issuer',
626
+ publicKey: kp.publicKeyHex,
627
+ role: 'issuer',
628
+ },
629
+ beneficiary: {
630
+ id: 'test-beneficiary',
631
+ publicKey: kp.publicKeyHex,
632
+ role: 'beneficiary',
633
+ },
634
+ constraints: "permit read on '/data/**'",
635
+ privateKey: kp.privateKey,
636
+ };
637
+ return {
638
+ doc: await target.buildCovenant({ ...defaults, ...overrides }),
639
+ kp,
640
+ };
641
+ }
642
+ // ── Build -> verify round-trip ─────────────────────────────────────────
643
+ total++;
644
+ try {
645
+ const { doc } = await buildTestCovenant();
646
+ const result = await target.verifyCovenant(doc);
647
+ if (!result.valid) {
648
+ const failedChecks = result.checks
649
+ ?.filter((c) => !c.passed)
650
+ .map((c) => c.name)
651
+ .join(', ');
652
+ failures.push({
653
+ test: 'covenant-build-verify-roundtrip',
654
+ category,
655
+ expected: true,
656
+ actual: result.valid,
657
+ message: `Freshly built covenant must pass verification. Failed: ${failedChecks}`,
658
+ });
659
+ }
660
+ }
661
+ catch (err) {
662
+ failures.push({
663
+ test: 'covenant-build-verify-roundtrip',
664
+ category,
665
+ expected: 'no error',
666
+ actual: String(err),
667
+ message: `Build/verify round-trip threw: ${err}`,
668
+ });
669
+ }
670
+ // ── Tampered covenant fails verification ───────────────────────────────
671
+ total++;
672
+ try {
673
+ const { doc } = await buildTestCovenant();
674
+ const tampered = { ...doc, constraints: "deny write on '/all'" };
675
+ const result = await target.verifyCovenant(tampered);
676
+ if (result.valid) {
677
+ failures.push({
678
+ test: 'covenant-tamper-detection',
679
+ category,
680
+ expected: false,
681
+ actual: result.valid,
682
+ message: 'Tampered covenant (modified constraints) must fail verification',
683
+ });
684
+ }
685
+ }
686
+ catch (_err) {
687
+ // Throwing is acceptable for tampered documents
688
+ }
689
+ // ── Expired covenant detected ──────────────────────────────────────────
690
+ total++;
691
+ try {
692
+ const kp = await target.generateKeyPair();
693
+ const doc = await target.buildCovenant({
694
+ issuer: {
695
+ id: 'test-issuer',
696
+ publicKey: kp.publicKeyHex,
697
+ role: 'issuer',
698
+ },
699
+ beneficiary: {
700
+ id: 'test-beneficiary',
701
+ publicKey: kp.publicKeyHex,
702
+ role: 'beneficiary',
703
+ },
704
+ constraints: "permit read on '/data'",
705
+ privateKey: kp.privateKey,
706
+ expiresAt: '2000-01-01T00:00:00.000Z',
707
+ });
708
+ const result = await target.verifyCovenant(doc);
709
+ const expiryCheck = result.checks?.find((c) => c.name === 'not_expired');
710
+ if (!expiryCheck || expiryCheck.passed) {
711
+ failures.push({
712
+ test: 'covenant-expired-detection',
713
+ category,
714
+ expected: false,
715
+ actual: expiryCheck?.passed ?? 'check not found',
716
+ message: 'Expired covenant must be detected (not_expired check should fail)',
717
+ });
718
+ }
719
+ }
720
+ catch (err) {
721
+ failures.push({
722
+ test: 'covenant-expired-detection',
723
+ category,
724
+ expected: 'expiry detection',
725
+ actual: String(err),
726
+ message: `Expiry detection test threw: ${err}`,
727
+ });
728
+ }
729
+ // ── ID integrity check ─────────────────────────────────────────────────
730
+ total++;
731
+ try {
732
+ const { doc } = await buildTestCovenant();
733
+ const badId = {
734
+ ...doc,
735
+ id: '0000000000000000000000000000000000000000000000000000000000000000',
736
+ };
737
+ const result = await target.verifyCovenant(badId);
738
+ const idCheck = result.checks?.find((c) => c.name === 'id_match');
739
+ if (!idCheck || idCheck.passed) {
740
+ failures.push({
741
+ test: 'covenant-id-integrity',
742
+ category,
743
+ expected: false,
744
+ actual: idCheck?.passed ?? 'check not found',
745
+ message: 'Covenant with tampered ID must fail the id_match check',
746
+ });
747
+ }
748
+ }
749
+ catch (_err) {
750
+ // Throwing is acceptable for invalid documents
751
+ }
752
+ // ── Constraints must be valid CCL ──────────────────────────────────────
753
+ total++;
754
+ try {
755
+ const { doc } = await buildTestCovenant();
756
+ const badCCL = { ...doc, constraints: 'not valid ccl at all ###' };
757
+ const result = await target.verifyCovenant(badCCL);
758
+ const cclCheck = result.checks?.find((c) => c.name === 'ccl_parses');
759
+ if (!cclCheck || cclCheck.passed) {
760
+ failures.push({
761
+ test: 'covenant-invalid-ccl',
762
+ category,
763
+ expected: false,
764
+ actual: cclCheck?.passed ?? 'check not found',
765
+ message: 'Covenant with invalid CCL constraints must fail the ccl_parses check',
766
+ });
767
+ }
768
+ }
769
+ catch (_err) {
770
+ // Throwing is also acceptable
771
+ }
772
+ // ── Nonce must be present ──────────────────────────────────────────────
773
+ total++;
774
+ try {
775
+ const { doc } = await buildTestCovenant();
776
+ const badNonce = { ...doc, nonce: '' };
777
+ const result = await target.verifyCovenant(badNonce);
778
+ const nonceCheck = result.checks?.find((c) => c.name === 'nonce_present');
779
+ if (!nonceCheck || nonceCheck.passed) {
780
+ failures.push({
781
+ test: 'covenant-nonce-present',
782
+ category,
783
+ expected: false,
784
+ actual: nonceCheck?.passed ?? 'check not found',
785
+ message: 'Covenant with empty nonce must fail the nonce_present check',
786
+ });
787
+ }
788
+ }
789
+ catch (_err) {
790
+ // Throwing is also acceptable
791
+ }
792
+ // ── Signature is hex string of correct length ──────────────────────────
793
+ total++;
794
+ try {
795
+ const { doc } = await buildTestCovenant();
796
+ const sigHexRegex = /^[0-9a-f]{128}$/;
797
+ if (!sigHexRegex.test(doc.signature)) {
798
+ failures.push({
799
+ test: 'covenant-signature-format',
800
+ category,
801
+ expected: '128-char lowercase hex string (64-byte Ed25519 signature)',
802
+ actual: `${typeof doc.signature}, length=${doc.signature?.length}`,
803
+ message: 'Covenant signature must be a 128-character lowercase hex string',
804
+ });
805
+ }
806
+ }
807
+ catch (err) {
808
+ failures.push({
809
+ test: 'covenant-signature-format',
810
+ category,
811
+ expected: 'no error',
812
+ actual: String(err),
813
+ message: `Signature format test threw: ${err}`,
814
+ });
815
+ }
816
+ // ── Document has required fields ───────────────────────────────────────
817
+ total++;
818
+ try {
819
+ const { doc } = await buildTestCovenant();
820
+ const requiredFields = [
821
+ 'id',
822
+ 'version',
823
+ 'issuer',
824
+ 'beneficiary',
825
+ 'constraints',
826
+ 'nonce',
827
+ 'createdAt',
828
+ 'signature',
829
+ ];
830
+ const missing = requiredFields.filter((f) => doc[f] === undefined ||
831
+ doc[f] === null);
832
+ if (missing.length > 0) {
833
+ failures.push({
834
+ test: 'covenant-required-fields',
835
+ category,
836
+ expected: 'all required fields present',
837
+ actual: `missing: ${missing.join(', ')}`,
838
+ message: `Covenant document is missing required fields: ${missing.join(', ')}`,
839
+ });
840
+ }
841
+ }
842
+ catch (err) {
843
+ failures.push({
844
+ test: 'covenant-required-fields',
845
+ category,
846
+ expected: 'no error',
847
+ actual: String(err),
848
+ message: `Required fields test threw: ${err}`,
849
+ });
850
+ }
851
+ // ── Issuer and beneficiary have correct roles ──────────────────────────
852
+ total++;
853
+ try {
854
+ const { doc } = await buildTestCovenant();
855
+ if (doc.issuer?.role !== 'issuer') {
856
+ failures.push({
857
+ test: 'covenant-issuer-role',
858
+ category,
859
+ expected: 'issuer',
860
+ actual: doc.issuer?.role,
861
+ message: 'Issuer party must have role "issuer"',
862
+ });
863
+ }
864
+ if (doc.beneficiary?.role !== 'beneficiary') {
865
+ failures.push({
866
+ test: 'covenant-beneficiary-role',
867
+ category,
868
+ expected: 'beneficiary',
869
+ actual: doc.beneficiary?.role,
870
+ message: 'Beneficiary party must have role "beneficiary"',
871
+ });
872
+ }
873
+ }
874
+ catch (err) {
875
+ failures.push({
876
+ test: 'covenant-party-roles',
877
+ category,
878
+ expected: 'no error',
879
+ actual: String(err),
880
+ message: `Party roles test threw: ${err}`,
881
+ });
882
+ }
883
+ return { failures, total };
884
+ }
885
+ // ═══════════════════════════════════════════════════════════════════════════
886
+ // Category 4: Interoperability
887
+ // ═══════════════════════════════════════════════════════════════════════════
888
+ /**
889
+ * Verify cross-implementation interoperability.
890
+ *
891
+ * Checks:
892
+ * - Canonical JSON test vectors (JCS compatibility)
893
+ * - JSON serialize/deserialize round-trip
894
+ * - Document ID matches reference canonical form hash
895
+ * - ID format (64-char lowercase hex)
896
+ * - Protocol version is "1.0"
897
+ * - Nonce format (64-char hex)
898
+ */
899
+ export async function interopConformance(target) {
900
+ const failures = [];
901
+ let total = 0;
902
+ const category = 'interop';
903
+ // ── Canonical JSON test vectors ────────────────────────────────────────
904
+ // Verify that the implementation's canonical JSON matches reference output
905
+ // by hashing both the reference and the implementation's output.
906
+ for (const vec of CANONICAL_JSON_VECTORS) {
907
+ total++;
908
+ try {
909
+ // Hash the reference canonical form
910
+ const referenceHash = await target.sha256(textEncode(vec.expected));
911
+ // Hash the input after canonicalization by the reference implementation
912
+ const referenceCanonical = referenceCanonicalizeJson(vec.input);
913
+ const referenceCanonicalHash = await target.sha256(textEncode(referenceCanonical));
914
+ // Verify the reference itself is self-consistent
915
+ if (referenceHash !== referenceCanonicalHash) {
916
+ failures.push({
917
+ test: `interop-canonical-json-${vec.label}`,
918
+ category,
919
+ expected: referenceHash,
920
+ actual: referenceCanonicalHash,
921
+ message: `Canonical JSON reference self-check failed for "${vec.label}"`,
922
+ });
923
+ }
924
+ // Verify the canonical output matches the expected string
925
+ if (referenceCanonical !== vec.expected) {
926
+ failures.push({
927
+ test: `interop-canonical-json-${vec.label}`,
928
+ category,
929
+ expected: vec.expected,
930
+ actual: referenceCanonical,
931
+ message: `Reference canonical JSON mismatch for "${vec.label}"`,
932
+ });
933
+ }
934
+ }
935
+ catch (err) {
936
+ failures.push({
937
+ test: `interop-canonical-json-${vec.label}`,
938
+ category,
939
+ expected: 'no error',
940
+ actual: String(err),
941
+ message: `Canonical JSON test "${vec.label}" threw: ${err}`,
942
+ });
943
+ }
944
+ }
945
+ // ── Document ID matches reference canonical form hash ──────────────────
946
+ // This is the critical interop test: it verifies that the implementation's
947
+ // canonical JSON, SHA-256, and ID computation are all compatible with the
948
+ // reference implementation.
949
+ total++;
950
+ try {
951
+ const kp = await target.generateKeyPair();
952
+ const doc = await target.buildCovenant({
953
+ issuer: {
954
+ id: 'interop-issuer',
955
+ publicKey: kp.publicKeyHex,
956
+ role: 'issuer',
957
+ },
958
+ beneficiary: {
959
+ id: 'interop-beneficiary',
960
+ publicKey: kp.publicKeyHex,
961
+ role: 'beneficiary',
962
+ },
963
+ constraints: "permit read on '/interop/**'",
964
+ privateKey: kp.privateKey,
965
+ });
966
+ // Compute the ID using the reference canonical form + target's sha256
967
+ const canonical = referenceCanonicalForm(doc);
968
+ const expectedId = await target.sha256(textEncode(canonical));
969
+ if (doc.id !== expectedId) {
970
+ failures.push({
971
+ test: 'interop-id-matches-reference-canonical',
972
+ category,
973
+ expected: expectedId,
974
+ actual: doc.id,
975
+ message: 'Document ID must equal SHA-256 of the reference canonical form ' +
976
+ '(verifies JCS compatibility)',
977
+ });
978
+ }
979
+ }
980
+ catch (err) {
981
+ failures.push({
982
+ test: 'interop-id-matches-reference-canonical',
983
+ category,
984
+ expected: 'no error',
985
+ actual: String(err),
986
+ message: `Reference canonical ID test threw: ${err}`,
987
+ });
988
+ }
989
+ // ── JSON serialize/deserialize round-trip ──────────────────────────────
990
+ total++;
991
+ try {
992
+ const kp = await target.generateKeyPair();
993
+ const doc = await target.buildCovenant({
994
+ issuer: {
995
+ id: 'roundtrip-issuer',
996
+ publicKey: kp.publicKeyHex,
997
+ role: 'issuer',
998
+ },
999
+ beneficiary: {
1000
+ id: 'roundtrip-beneficiary',
1001
+ publicKey: kp.publicKeyHex,
1002
+ role: 'beneficiary',
1003
+ },
1004
+ constraints: "permit read on '/interop/**'\ndeny write on '/interop/restricted'",
1005
+ privateKey: kp.privateKey,
1006
+ });
1007
+ const json = JSON.stringify(doc);
1008
+ const restored = JSON.parse(json);
1009
+ const result = await target.verifyCovenant(restored);
1010
+ if (!result.valid) {
1011
+ failures.push({
1012
+ test: 'interop-serialize-roundtrip',
1013
+ category,
1014
+ expected: true,
1015
+ actual: result.valid,
1016
+ message: 'Covenant must remain valid after JSON serialize/deserialize round-trip',
1017
+ });
1018
+ }
1019
+ }
1020
+ catch (err) {
1021
+ failures.push({
1022
+ test: 'interop-serialize-roundtrip',
1023
+ category,
1024
+ expected: 'no error',
1025
+ actual: String(err),
1026
+ message: `Interop serialize round-trip threw: ${err}`,
1027
+ });
1028
+ }
1029
+ // ── Document ID format ─────────────────────────────────────────────────
1030
+ total++;
1031
+ try {
1032
+ const kp = await target.generateKeyPair();
1033
+ const doc = await target.buildCovenant({
1034
+ issuer: {
1035
+ id: 'format-issuer',
1036
+ publicKey: kp.publicKeyHex,
1037
+ role: 'issuer',
1038
+ },
1039
+ beneficiary: {
1040
+ id: 'format-beneficiary',
1041
+ publicKey: kp.publicKeyHex,
1042
+ role: 'beneficiary',
1043
+ },
1044
+ constraints: "permit read on '/format'",
1045
+ privateKey: kp.privateKey,
1046
+ });
1047
+ const idRegex = /^[0-9a-f]{64}$/;
1048
+ if (!idRegex.test(doc.id)) {
1049
+ failures.push({
1050
+ test: 'interop-id-format',
1051
+ category,
1052
+ expected: '64-char lowercase hex string',
1053
+ actual: doc.id,
1054
+ message: 'Document ID must be a 64-character lowercase hex string (SHA-256)',
1055
+ });
1056
+ }
1057
+ }
1058
+ catch (err) {
1059
+ failures.push({
1060
+ test: 'interop-id-format',
1061
+ category,
1062
+ expected: 'no error',
1063
+ actual: String(err),
1064
+ message: `ID format test threw: ${err}`,
1065
+ });
1066
+ }
1067
+ // ── Protocol version is "1.0" ──────────────────────────────────────────
1068
+ total++;
1069
+ try {
1070
+ const kp = await target.generateKeyPair();
1071
+ const doc = await target.buildCovenant({
1072
+ issuer: {
1073
+ id: 'version-issuer',
1074
+ publicKey: kp.publicKeyHex,
1075
+ role: 'issuer',
1076
+ },
1077
+ beneficiary: {
1078
+ id: 'version-beneficiary',
1079
+ publicKey: kp.publicKeyHex,
1080
+ role: 'beneficiary',
1081
+ },
1082
+ constraints: "permit read on '/version'",
1083
+ privateKey: kp.privateKey,
1084
+ });
1085
+ if (doc.version !== '1.0') {
1086
+ failures.push({
1087
+ test: 'interop-protocol-version',
1088
+ category,
1089
+ expected: '1.0',
1090
+ actual: doc.version,
1091
+ message: 'Protocol version must be "1.0"',
1092
+ });
1093
+ }
1094
+ }
1095
+ catch (err) {
1096
+ failures.push({
1097
+ test: 'interop-protocol-version',
1098
+ category,
1099
+ expected: 'no error',
1100
+ actual: String(err),
1101
+ message: `Protocol version test threw: ${err}`,
1102
+ });
1103
+ }
1104
+ // ── Nonce format (64-char hex) ─────────────────────────────────────────
1105
+ total++;
1106
+ try {
1107
+ const kp = await target.generateKeyPair();
1108
+ const doc = await target.buildCovenant({
1109
+ issuer: {
1110
+ id: 'nonce-issuer',
1111
+ publicKey: kp.publicKeyHex,
1112
+ role: 'issuer',
1113
+ },
1114
+ beneficiary: {
1115
+ id: 'nonce-beneficiary',
1116
+ publicKey: kp.publicKeyHex,
1117
+ role: 'beneficiary',
1118
+ },
1119
+ constraints: "permit read on '/nonce'",
1120
+ privateKey: kp.privateKey,
1121
+ });
1122
+ const nonceRegex = /^[0-9a-f]{64}$/i;
1123
+ if (!nonceRegex.test(doc.nonce)) {
1124
+ failures.push({
1125
+ test: 'interop-nonce-format',
1126
+ category,
1127
+ expected: '64-char hex string (32 bytes)',
1128
+ actual: doc.nonce,
1129
+ message: 'Nonce must be a 64-character hex string representing 32 random bytes',
1130
+ });
1131
+ }
1132
+ }
1133
+ catch (err) {
1134
+ failures.push({
1135
+ test: 'interop-nonce-format',
1136
+ category,
1137
+ expected: 'no error',
1138
+ actual: String(err),
1139
+ message: `Nonce format test threw: ${err}`,
1140
+ });
1141
+ }
1142
+ // ── createdAt is valid ISO 8601 ────────────────────────────────────────
1143
+ total++;
1144
+ try {
1145
+ const kp = await target.generateKeyPair();
1146
+ const doc = await target.buildCovenant({
1147
+ issuer: {
1148
+ id: 'timestamp-issuer',
1149
+ publicKey: kp.publicKeyHex,
1150
+ role: 'issuer',
1151
+ },
1152
+ beneficiary: {
1153
+ id: 'timestamp-beneficiary',
1154
+ publicKey: kp.publicKeyHex,
1155
+ role: 'beneficiary',
1156
+ },
1157
+ constraints: "permit read on '/timestamp'",
1158
+ privateKey: kp.privateKey,
1159
+ });
1160
+ const parsed = new Date(doc.createdAt);
1161
+ if (isNaN(parsed.getTime())) {
1162
+ failures.push({
1163
+ test: 'interop-timestamp-format',
1164
+ category,
1165
+ expected: 'valid ISO 8601 timestamp',
1166
+ actual: doc.createdAt,
1167
+ message: 'createdAt must be a valid ISO 8601 timestamp',
1168
+ });
1169
+ }
1170
+ }
1171
+ catch (err) {
1172
+ failures.push({
1173
+ test: 'interop-timestamp-format',
1174
+ category,
1175
+ expected: 'no error',
1176
+ actual: String(err),
1177
+ message: `Timestamp format test threw: ${err}`,
1178
+ });
1179
+ }
1180
+ return { failures, total };
1181
+ }
1182
+ // ═══════════════════════════════════════════════════════════════════════════
1183
+ // Full suite runner
1184
+ // ═══════════════════════════════════════════════════════════════════════════
1185
+ /**
1186
+ * Run the complete Stele Protocol Conformance Suite.
1187
+ *
1188
+ * Executes all four categories (crypto, CCL, covenant, interop) and
1189
+ * aggregates the results. An implementation that returns
1190
+ * `result.passed === true` is considered spec-compliant.
1191
+ *
1192
+ * @param target - The implementation under test.
1193
+ * @returns Aggregate conformance result.
1194
+ *
1195
+ * @example
1196
+ * ```typescript
1197
+ * import { runConformanceSuite } from '@nobulex/sdk/conformance';
1198
+ * import { buildCovenant, verifyCovenant } from '@nobulex/core';
1199
+ * import { generateKeyPair, sign, verify, sha256 } from '@nobulex/crypto';
1200
+ * import { parse, evaluate } from '@nobulex/ccl';
1201
+ *
1202
+ * const result = await runConformanceSuite({
1203
+ * buildCovenant,
1204
+ * verifyCovenant,
1205
+ * evaluateAction: async (doc, action, resource, ctx) => {
1206
+ * const cclDoc = parse(doc.constraints);
1207
+ * return evaluate(cclDoc, action, resource, ctx);
1208
+ * },
1209
+ * generateKeyPair,
1210
+ * sign: async (msg, key) => sign(msg, key),
1211
+ * verify: async (msg, sig, key) => verify(msg, sig, key),
1212
+ * sha256: (data) => sha256(data),
1213
+ * parseCCL: parse,
1214
+ * });
1215
+ *
1216
+ * console.log(result.passed); // true
1217
+ * console.log(result.total); // number of checks run
1218
+ * ```
1219
+ */
1220
+ export async function runConformanceSuite(target) {
1221
+ const start = Date.now();
1222
+ const [crypto, ccl, covenant, interop] = await Promise.all([
1223
+ cryptoConformance(target),
1224
+ cclConformance(target),
1225
+ covenantConformance(target),
1226
+ interopConformance(target),
1227
+ ]);
1228
+ const allFailures = [
1229
+ ...crypto.failures,
1230
+ ...ccl.failures,
1231
+ ...covenant.failures,
1232
+ ...interop.failures,
1233
+ ];
1234
+ const total = crypto.total + ccl.total + covenant.total + interop.total;
1235
+ return {
1236
+ passed: allFailures.length === 0,
1237
+ total,
1238
+ failures: allFailures,
1239
+ duration: Date.now() - start,
1240
+ };
1241
+ }
1242
+ //# sourceMappingURL=conformance.js.map