@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
package/dist/index.js ADDED
@@ -0,0 +1,695 @@
1
+ /**
2
+ * @nobulex/sdk -- High-level TypeScript SDK that unifies the entire Stele protocol.
3
+ *
4
+ * Provides a single entry point (SteleClient) for key management, covenant
5
+ * lifecycle, identity management, chain operations, and CCL utilities.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ // ─── Imports from underlying packages ───────────────────────────────────────
10
+ import { generateKeyPair as cryptoGenerateKeyPair, timestamp, KeyManager, } from '@nobulex/crypto';
11
+ import { buildCovenant, verifyCovenant as coreVerifyCovenant, countersignCovenant, resolveChain as coreResolveChain, validateChainNarrowing, MemoryChainResolver, CovenantVerificationError, } from '@nobulex/core';
12
+ import { parse as cclParse, evaluate as cclEvaluate, merge as cclMerge, serialize as cclSerialize, } from '@nobulex/ccl';
13
+ import { createIdentity as identityCreate, evolveIdentity as identityEvolve, } from '@nobulex/identity';
14
+ // ─── Re-exports ─────────────────────────────────────────────────────────────
15
+ // Re-export conformance suite
16
+ export { runConformanceSuite, cryptoConformance, cclConformance, covenantConformance, interopConformance, } from './conformance.js';
17
+ // Re-export middleware system
18
+ export { MiddlewarePipeline, loggingMiddleware, validationMiddleware, timingMiddleware, rateLimitMiddleware } from './middleware.js';
19
+ // Re-export plugins
20
+ export * from './plugins/index.js';
21
+ // Re-export telemetry
22
+ export { telemetryMiddleware, SteleMetrics, NoopTracer, NoopMeter, NoopSpan, NoopCounter, NoopHistogram, createTelemetry, SpanStatusCode, } from './telemetry.js';
23
+ export { PROTOCOL_VERSION, MAX_CONSTRAINTS, MAX_CHAIN_DEPTH, MAX_DOCUMENT_SIZE, CovenantBuildError, CovenantVerificationError, MemoryChainResolver, canonicalForm, computeId, buildCovenant, verifyCovenant as verifyCovenant_core, countersignCovenant, resignCovenant, serializeCovenant, deserializeCovenant, resolveChain as resolveChain_core, computeEffectiveConstraints, validateChainNarrowing, } from '@nobulex/core';
24
+ export { generateKeyPair, sign, signString, verify, sha256, sha256String, sha256Object, canonicalizeJson, toHex, fromHex, base64urlEncode, base64urlDecode, generateNonce, generateId, constantTimeEqual, timestamp, keyPairFromPrivateKey, keyPairFromPrivateKeyHex, KeyManager, } from '@nobulex/crypto';
25
+ export { parse as parseCCL, evaluate as evaluateCCL, matchAction, matchResource, specificity, evaluateCondition, checkRateLimit, merge as mergeCCL, validateNarrowing, serialize as serializeCCL, validateCCL, tokenize, parseTokens, CCLSyntaxError, CCLValidationError, } from '@nobulex/ccl';
26
+ export { createIdentity as createIdentity_core, evolveIdentity as evolveIdentity_core, verifyIdentity, computeCapabilityManifestHash, computeIdentityHash, computeCarryForward, getLineage, shareAncestor, serializeIdentity, deserializeIdentity, DEFAULT_EVOLUTION_POLICY, } from '@nobulex/identity';
27
+ // ─── SteleClient ────────────────────────────────────────────────────────────
28
+ /**
29
+ * The main entry point for the Stele SDK.
30
+ *
31
+ * Provides a unified, high-level API for the entire Stele protocol:
32
+ * key management, covenant lifecycle, identity management, chain
33
+ * operations, and CCL utilities.
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * const client = new SteleClient();
38
+ * await client.generateKeyPair();
39
+ *
40
+ * const doc = await client.createCovenant({
41
+ * issuer: { id: 'alice', publicKey: client.keyPair!.publicKeyHex, role: 'issuer' },
42
+ * beneficiary: { id: 'bob', publicKey: bobPubHex, role: 'beneficiary' },
43
+ * constraints: "permit read on '/data/**'",
44
+ * });
45
+ *
46
+ * const result = await client.verifyCovenant(doc);
47
+ * console.log(result.valid); // true
48
+ * ```
49
+ */
50
+ export class SteleClient {
51
+ _keyPair;
52
+ _agentId;
53
+ _strictMode;
54
+ _listeners;
55
+ _keyManager;
56
+ constructor(options = {}) {
57
+ this._keyPair = options.keyPair;
58
+ this._agentId = options.agentId;
59
+ this._strictMode = options.strictMode ?? false;
60
+ this._listeners = new Map();
61
+ if (options.keyRotation) {
62
+ this._keyManager = new KeyManager({
63
+ maxAgeMs: options.keyRotation.maxAgeMs,
64
+ overlapPeriodMs: options.keyRotation.overlapPeriodMs,
65
+ onRotation: options.keyRotation.onRotation,
66
+ });
67
+ }
68
+ }
69
+ // ── Accessors ───────────────────────────────────────────────────────────
70
+ /** The currently configured key pair, if any. */
71
+ get keyPair() {
72
+ return this._keyPair;
73
+ }
74
+ /** The currently configured agent ID, if any. */
75
+ get agentId() {
76
+ return this._agentId;
77
+ }
78
+ /** Whether strict mode is enabled. */
79
+ get strictMode() {
80
+ return this._strictMode;
81
+ }
82
+ /** Get the key manager instance, if key rotation is configured. */
83
+ get keyManager() {
84
+ return this._keyManager;
85
+ }
86
+ // ── Key management ──────────────────────────────────────────────────────
87
+ /**
88
+ * Generate a new Ed25519 key pair and set it as the client's active key pair.
89
+ *
90
+ * Subsequent operations (createCovenant, countersign, etc.) will use this
91
+ * key pair automatically unless overridden per-call.
92
+ *
93
+ * If key rotation is configured and initialized, returns the current
94
+ * managed key pair instead of generating a new one.
95
+ *
96
+ * @returns The generated key pair.
97
+ *
98
+ * @example
99
+ * ```typescript
100
+ * const kp = await client.generateKeyPair();
101
+ * console.log(kp.publicKeyHex); // 64-char hex
102
+ * ```
103
+ */
104
+ async generateKeyPair() {
105
+ if (this._keyManager) {
106
+ try {
107
+ const managed = this._keyManager.current();
108
+ this._keyPair = managed.keyPair;
109
+ return managed.keyPair;
110
+ }
111
+ catch {
112
+ // KeyManager not initialized yet, fall through to normal generation
113
+ }
114
+ }
115
+ const kp = await cryptoGenerateKeyPair();
116
+ this._keyPair = kp;
117
+ return kp;
118
+ }
119
+ /**
120
+ * Initialize key rotation. Generates the first key and starts lifecycle management.
121
+ *
122
+ * Requires that the client was constructed with a `keyRotation` policy.
123
+ * After initialization, the managed key becomes the client's active key pair.
124
+ *
125
+ * @throws {Error} When key rotation is not configured.
126
+ */
127
+ async initializeKeyRotation() {
128
+ if (!this._keyManager) {
129
+ throw new Error('Key rotation is not configured. Pass { keyRotation } in the client options.');
130
+ }
131
+ const managed = await this._keyManager.initialize();
132
+ this._keyPair = managed.keyPair;
133
+ }
134
+ /**
135
+ * Check if the current key needs rotation and rotate if necessary.
136
+ *
137
+ * If the key manager determines that the current key has exceeded its
138
+ * maximum age, a new key is generated and the old key enters the overlap
139
+ * period. Emits a `key:rotated` event on rotation.
140
+ *
141
+ * @returns `true` if rotation occurred, `false` otherwise.
142
+ * @throws {Error} When key rotation is not configured.
143
+ */
144
+ async rotateKeyIfNeeded() {
145
+ if (!this._keyManager) {
146
+ throw new Error('Key rotation is not configured. Pass { keyRotation } in the client options.');
147
+ }
148
+ if (!this._keyManager.needsRotation()) {
149
+ return false;
150
+ }
151
+ const { previous, current } = await this._keyManager.rotate();
152
+ this._keyPair = current.keyPair;
153
+ this._emit('key:rotated', {
154
+ type: 'key:rotated',
155
+ timestamp: timestamp(),
156
+ previousPublicKey: previous.keyPair.publicKeyHex,
157
+ currentPublicKey: current.keyPair.publicKeyHex,
158
+ });
159
+ return true;
160
+ }
161
+ // ── Covenant lifecycle ──────────────────────────────────────────────────
162
+ /**
163
+ * Create a new, signed covenant document.
164
+ *
165
+ * If `options.privateKey` is not provided, the client's key pair is used.
166
+ * Emits a `covenant:created` event on success.
167
+ *
168
+ * @param options - Covenant creation options including parties and constraints.
169
+ * @returns A complete, signed CovenantDocument.
170
+ * @throws {CovenantBuildError} When validation of inputs or CCL parsing fails.
171
+ * @throws {Error} When no private key is available.
172
+ *
173
+ * @example
174
+ * ```typescript
175
+ * const doc = await client.createCovenant({
176
+ * issuer: { id: 'alice', publicKey: pubHex, role: 'issuer' },
177
+ * beneficiary: { id: 'bob', publicKey: bobHex, role: 'beneficiary' },
178
+ * constraints: "permit read on '/data/**'",
179
+ * });
180
+ * ```
181
+ */
182
+ async createCovenant(options) {
183
+ // ── Input validation (Stripe-quality errors at the public API boundary) ──
184
+ if (!options.issuer || !options.issuer.id) {
185
+ throw new Error('issuer.id is required and must be a non-empty string');
186
+ }
187
+ if (!options.beneficiary || !options.beneficiary.id) {
188
+ throw new Error('beneficiary.id is required and must be a non-empty string');
189
+ }
190
+ if (!options.constraints || options.constraints.trim().length === 0) {
191
+ throw new Error("constraints must be a non-empty CCL string. Example: permit read on '/data/**'");
192
+ }
193
+ // Auto-rotate key if key rotation is configured and initialized
194
+ if (this._keyManager && !options.privateKey) {
195
+ try {
196
+ await this.rotateKeyIfNeeded();
197
+ }
198
+ catch {
199
+ // KeyManager may not be initialized; fall through to normal key resolution
200
+ }
201
+ }
202
+ const privateKey = options.privateKey ?? this._keyPair?.privateKey;
203
+ if (!privateKey) {
204
+ throw new Error('No private key available. Call client.generateKeyPair() first, or pass { privateKey } in the options.');
205
+ }
206
+ const builderOpts = {
207
+ issuer: options.issuer,
208
+ beneficiary: options.beneficiary,
209
+ constraints: options.constraints,
210
+ privateKey,
211
+ obligations: options.obligations,
212
+ chain: options.chain,
213
+ enforcement: options.enforcement,
214
+ proof: options.proof,
215
+ revocation: options.revocation,
216
+ metadata: options.metadata,
217
+ expiresAt: options.expiresAt,
218
+ activatesAt: options.activatesAt,
219
+ };
220
+ const doc = await buildCovenant(builderOpts);
221
+ this._emit('covenant:created', {
222
+ type: 'covenant:created',
223
+ timestamp: timestamp(),
224
+ document: doc,
225
+ });
226
+ return doc;
227
+ }
228
+ /**
229
+ * Verify a covenant document by running all specification checks.
230
+ *
231
+ * Checks include signature validity, ID integrity, protocol version,
232
+ * party roles, expiry, and constraint syntax. In strict mode, throws
233
+ * `CovenantVerificationError` on failure. Emits a `covenant:verified` event.
234
+ *
235
+ * @param doc - The covenant document to verify.
236
+ * @returns A VerificationResult with `valid` and detailed `checks` array.
237
+ * @throws {CovenantVerificationError} In strict mode when verification fails.
238
+ *
239
+ * @example
240
+ * ```typescript
241
+ * const result = await client.verifyCovenant(doc);
242
+ * if (!result.valid) {
243
+ * console.log(result.checks.filter(c => !c.passed));
244
+ * }
245
+ * ```
246
+ */
247
+ async verifyCovenant(doc) {
248
+ const result = await coreVerifyCovenant(doc);
249
+ this._emit('covenant:verified', {
250
+ type: 'covenant:verified',
251
+ timestamp: timestamp(),
252
+ result,
253
+ });
254
+ if (this._strictMode && !result.valid) {
255
+ throw new CovenantVerificationError(`Covenant verification failed: ${result.checks.filter((c) => !c.passed).map((c) => c.name).join(', ')}`, result.checks);
256
+ }
257
+ return result;
258
+ }
259
+ /**
260
+ * Add a countersignature to a covenant document.
261
+ *
262
+ * If no key pair is provided, the client's key pair is used.
263
+ * Emits a `covenant:countersigned` event on success. Returns a
264
+ * new document; the original is not mutated.
265
+ *
266
+ * @param doc - The covenant document to countersign.
267
+ * @param signerRole - Role of the countersigner (default: `'auditor'`).
268
+ * @param signerKeyPair - Optional key pair override.
269
+ * @returns A new CovenantDocument with the countersignature appended.
270
+ * @throws {Error} When no key pair is available.
271
+ *
272
+ * @example
273
+ * ```typescript
274
+ * const audited = await client.countersign(doc, 'auditor');
275
+ * ```
276
+ */
277
+ async countersign(doc, signerRole = 'auditor', signerKeyPair) {
278
+ // Auto-rotate key if key rotation is configured and initialized
279
+ if (this._keyManager && !signerKeyPair) {
280
+ try {
281
+ await this.rotateKeyIfNeeded();
282
+ }
283
+ catch {
284
+ // KeyManager may not be initialized; fall through to normal key resolution
285
+ }
286
+ }
287
+ const kp = signerKeyPair ?? this._keyPair;
288
+ if (!kp) {
289
+ throw new Error('No key pair available. Call client.generateKeyPair() first, or pass a KeyPair in the method options.');
290
+ }
291
+ const result = await countersignCovenant(doc, kp, signerRole);
292
+ this._emit('covenant:countersigned', {
293
+ type: 'covenant:countersigned',
294
+ timestamp: timestamp(),
295
+ document: result,
296
+ signerRole,
297
+ });
298
+ return result;
299
+ }
300
+ /**
301
+ * Evaluate an action/resource pair against a covenant's CCL constraints.
302
+ *
303
+ * Parses the covenant's constraints and runs the CCL evaluator.
304
+ * Uses default-deny semantics: if no rules match, the result is
305
+ * `permitted: false`. Emits an `evaluation:completed` event.
306
+ *
307
+ * @param doc - The covenant document containing CCL constraints.
308
+ * @param action - The action to evaluate (e.g. `"read"`, `"api.call"`).
309
+ * @param resource - The target resource (e.g. `"/data/users"`).
310
+ * @param context - Optional evaluation context for condition checking.
311
+ * @returns An EvaluationResult with the access decision.
312
+ *
313
+ * @example
314
+ * ```typescript
315
+ * const result = await client.evaluateAction(doc, 'read', '/data/users');
316
+ * console.log(result.permitted); // true or false
317
+ * ```
318
+ */
319
+ async evaluateAction(doc, action, resource, context) {
320
+ if (!action || action.trim().length === 0) {
321
+ throw new Error('action must be a non-empty string (e.g., "read", "write", "api.call")');
322
+ }
323
+ if (!resource || resource.trim().length === 0) {
324
+ throw new Error('resource must be a non-empty string (e.g., "/data/**", "/api/endpoint")');
325
+ }
326
+ const cclDoc = cclParse(doc.constraints);
327
+ const cclResult = cclEvaluate(cclDoc, action, resource, context);
328
+ const result = {
329
+ permitted: cclResult.permitted,
330
+ matchedRule: cclResult.matchedRule,
331
+ allMatches: cclResult.allMatches,
332
+ reason: cclResult.reason,
333
+ severity: cclResult.severity,
334
+ };
335
+ this._emit('evaluation:completed', {
336
+ type: 'evaluation:completed',
337
+ timestamp: timestamp(),
338
+ result,
339
+ action,
340
+ resource,
341
+ });
342
+ return result;
343
+ }
344
+ // ── Identity ────────────────────────────────────────────────────────────
345
+ /**
346
+ * Create a new agent identity.
347
+ *
348
+ * If `options.operatorKeyPair` is not provided, the client's key pair is used.
349
+ * Emits an `identity:created` event on success.
350
+ *
351
+ * @param options - Identity creation options including model and capabilities.
352
+ * @returns A signed AgentIdentity object.
353
+ * @throws {Error} When no key pair is available.
354
+ *
355
+ * @example
356
+ * ```typescript
357
+ * const identity = await client.createIdentity({
358
+ * model: { provider: 'anthropic', modelId: 'claude-3' },
359
+ * capabilities: ['read', 'write'],
360
+ * deployment: { runtime: 'container' },
361
+ * });
362
+ * ```
363
+ */
364
+ async createIdentity(options) {
365
+ const operatorKeyPair = options.operatorKeyPair ?? this._keyPair;
366
+ if (!operatorKeyPair) {
367
+ throw new Error('No key pair available. Call client.generateKeyPair() first, or pass a KeyPair in the method options.');
368
+ }
369
+ const identity = await identityCreate({
370
+ operatorKeyPair,
371
+ operatorIdentifier: options.operatorIdentifier,
372
+ model: options.model,
373
+ capabilities: options.capabilities,
374
+ deployment: options.deployment,
375
+ });
376
+ this._emit('identity:created', {
377
+ type: 'identity:created',
378
+ timestamp: timestamp(),
379
+ identity,
380
+ });
381
+ return identity;
382
+ }
383
+ /**
384
+ * Evolve an existing agent identity with updates.
385
+ *
386
+ * Creates a new lineage entry recording the change. If
387
+ * `options.operatorKeyPair` is not provided, the client's key pair
388
+ * is used. Emits an `identity:evolved` event on success.
389
+ *
390
+ * @param identity - The current agent identity to evolve.
391
+ * @param options - Evolution options describing the change.
392
+ * @returns The updated AgentIdentity with a new lineage entry.
393
+ * @throws {Error} When no key pair is available.
394
+ *
395
+ * @example
396
+ * ```typescript
397
+ * const evolved = await client.evolveIdentity(identity, {
398
+ * changeType: 'model_update',
399
+ * description: 'Upgraded to v2',
400
+ * updates: { model: { provider: 'anthropic', modelId: 'claude-4' } },
401
+ * });
402
+ * ```
403
+ */
404
+ async evolveIdentity(identity, options) {
405
+ const operatorKeyPair = options.operatorKeyPair ?? this._keyPair;
406
+ if (!operatorKeyPair) {
407
+ throw new Error('No key pair available. Call client.generateKeyPair() first, or pass a KeyPair in the method options.');
408
+ }
409
+ const evolved = await identityEvolve(identity, {
410
+ operatorKeyPair,
411
+ changeType: options.changeType,
412
+ description: options.description,
413
+ updates: options.updates,
414
+ reputationCarryForward: options.reputationCarryForward,
415
+ });
416
+ this._emit('identity:evolved', {
417
+ type: 'identity:evolved',
418
+ timestamp: timestamp(),
419
+ identity: evolved,
420
+ changeType: options.changeType,
421
+ });
422
+ return evolved;
423
+ }
424
+ // ── Chain ───────────────────────────────────────────────────────────────
425
+ /**
426
+ * Resolve the ancestor chain of a covenant document.
427
+ *
428
+ * Uses a MemoryChainResolver seeded with the provided documents.
429
+ * If no additional documents are provided, only the document's
430
+ * immediate chain reference is followed.
431
+ * Emits a `chain:resolved` event.
432
+ *
433
+ * @param doc - The covenant document whose chain to resolve.
434
+ * @param knownDocuments - Optional array of documents that may be ancestors.
435
+ * @returns An array of ancestor documents, parent-first.
436
+ *
437
+ * @example
438
+ * ```typescript
439
+ * const ancestors = await client.resolveChain(childDoc, [parentDoc]);
440
+ * ```
441
+ */
442
+ async resolveChain(doc, knownDocuments) {
443
+ const resolver = new MemoryChainResolver();
444
+ if (knownDocuments) {
445
+ for (const d of knownDocuments) {
446
+ resolver.add(d);
447
+ }
448
+ }
449
+ resolver.add(doc);
450
+ const ancestors = await coreResolveChain(doc, resolver);
451
+ this._emit('chain:resolved', {
452
+ type: 'chain:resolved',
453
+ timestamp: timestamp(),
454
+ documents: ancestors,
455
+ });
456
+ return ancestors;
457
+ }
458
+ /**
459
+ * Validate a chain of covenant documents.
460
+ *
461
+ * Verifies each document individually and checks that each child
462
+ * only narrows (restricts) its parent's constraints. Documents
463
+ * should be ordered from root (index 0) to leaf (last index).
464
+ * Emits a `chain:validated` event.
465
+ *
466
+ * @param docs - The chain of documents, ordered root-first.
467
+ * @returns A ChainValidationResult with per-document and narrowing results.
468
+ *
469
+ * @example
470
+ * ```typescript
471
+ * const result = await client.validateChain([rootDoc, childDoc]);
472
+ * console.log(result.valid); // true if all docs valid and narrowing holds
473
+ * ```
474
+ */
475
+ async validateChain(docs) {
476
+ const results = [];
477
+ const narrowingViolations = [];
478
+ // Verify each document individually
479
+ for (const doc of docs) {
480
+ const result = await coreVerifyCovenant(doc);
481
+ results.push(result);
482
+ }
483
+ // Check narrowing between consecutive parent-child pairs
484
+ for (let i = 1; i < docs.length; i++) {
485
+ const parent = docs[i - 1];
486
+ const child = docs[i];
487
+ const narrowing = await validateChainNarrowing(child, parent);
488
+ if (!narrowing.valid) {
489
+ narrowingViolations.push({
490
+ childIndex: i,
491
+ parentIndex: i - 1,
492
+ violations: narrowing.violations,
493
+ });
494
+ }
495
+ }
496
+ const allVerificationsValid = results.every((r) => r.valid);
497
+ const noNarrowingViolations = narrowingViolations.length === 0;
498
+ const chainResult = {
499
+ valid: allVerificationsValid && noNarrowingViolations,
500
+ results,
501
+ narrowingViolations,
502
+ };
503
+ this._emit('chain:validated', {
504
+ type: 'chain:validated',
505
+ timestamp: timestamp(),
506
+ result: chainResult,
507
+ });
508
+ return chainResult;
509
+ }
510
+ // ── CCL utilities ─────────────────────────────────────────────────────
511
+ /**
512
+ * Parse CCL source text into a CCLDocument.
513
+ *
514
+ * @param source - CCL source text.
515
+ * @returns A parsed CCLDocument.
516
+ * @throws {CCLSyntaxError} When the source has syntax errors.
517
+ *
518
+ * @example
519
+ * ```typescript
520
+ * const cclDoc = client.parseCCL("permit read on '/data/**'");
521
+ * ```
522
+ */
523
+ parseCCL(source) {
524
+ return cclParse(source);
525
+ }
526
+ /**
527
+ * Merge two CCL documents using deny-wins semantics.
528
+ *
529
+ * @param a - The first (parent) CCL document.
530
+ * @param b - The second (child) CCL document.
531
+ * @returns A new merged CCLDocument.
532
+ */
533
+ mergeCCL(a, b) {
534
+ return cclMerge(a, b);
535
+ }
536
+ /**
537
+ * Serialize a CCLDocument back to human-readable CCL source text.
538
+ *
539
+ * @param doc - The CCL document to serialize.
540
+ * @returns Multi-line CCL source string.
541
+ */
542
+ serializeCCL(doc) {
543
+ return cclSerialize(doc);
544
+ }
545
+ // ── Event system ──────────────────────────────────────────────────────
546
+ /**
547
+ * Register an event handler for a specific event type.
548
+ *
549
+ * @param event - The event type to listen for.
550
+ * @param handler - The callback function.
551
+ * @returns A disposal function that removes the handler when called.
552
+ *
553
+ * @example
554
+ * ```typescript
555
+ * const off = client.on('covenant:created', (e) => {
556
+ * console.log('Created:', e.document.id);
557
+ * });
558
+ * // later: off() to unsubscribe
559
+ * ```
560
+ */
561
+ on(event, handler) {
562
+ if (!this._listeners.has(event)) {
563
+ this._listeners.set(event, new Set());
564
+ }
565
+ const handlers = this._listeners.get(event);
566
+ handlers.add(handler);
567
+ return () => {
568
+ handlers.delete(handler);
569
+ };
570
+ }
571
+ /**
572
+ * Remove a previously registered event handler.
573
+ *
574
+ * @param event - The event type.
575
+ * @param handler - The handler function to remove.
576
+ */
577
+ off(event, handler) {
578
+ const handlers = this._listeners.get(event);
579
+ if (handlers) {
580
+ handlers.delete(handler);
581
+ }
582
+ }
583
+ /**
584
+ * Remove all event handlers for a specific event type, or all events if
585
+ * no type is specified.
586
+ *
587
+ * @param event - Optional event type. If omitted, removes all handlers.
588
+ */
589
+ removeAllListeners(event) {
590
+ if (event) {
591
+ this._listeners.delete(event);
592
+ }
593
+ else {
594
+ this._listeners.clear();
595
+ }
596
+ }
597
+ /** Emit an event to all registered handlers. */
598
+ _emit(event, payload) {
599
+ const handlers = this._listeners.get(event);
600
+ if (handlers) {
601
+ for (const handler of handlers) {
602
+ handler(payload);
603
+ }
604
+ }
605
+ }
606
+ }
607
+ // ─── QuickCovenant convenience builders ─────────────────────────────────────
608
+ /**
609
+ * Convenience builders for creating common covenant patterns quickly.
610
+ *
611
+ * These produce CovenantDocument instances with minimal configuration.
612
+ * All methods require an issuer key pair, issuer party, and beneficiary party.
613
+ */
614
+ export class QuickCovenant {
615
+ /**
616
+ * Create a simple permit covenant that allows a specific action on a resource.
617
+ *
618
+ * @param action - The action to permit (e.g., `"read"`, `"file.read"`).
619
+ * @param resource - The resource to permit on (e.g., `"/data"`, `"/files/**"`).
620
+ * @param issuer - The issuing party.
621
+ * @param beneficiary - The beneficiary party.
622
+ * @param privateKey - Issuer's private key for signing.
623
+ * @returns A signed CovenantDocument with a single permit rule.
624
+ *
625
+ * @example
626
+ * ```typescript
627
+ * const doc = await QuickCovenant.permit('read', '/data/**', issuer, beneficiary, kp.privateKey);
628
+ * ```
629
+ */
630
+ static async permit(action, resource, issuer, beneficiary, privateKey) {
631
+ const constraints = `permit ${action} on '${resource}'`;
632
+ return buildCovenant({
633
+ issuer,
634
+ beneficiary,
635
+ constraints,
636
+ privateKey,
637
+ });
638
+ }
639
+ /**
640
+ * Create a simple deny covenant that denies a specific action on a resource.
641
+ *
642
+ * @param action - The action to deny.
643
+ * @param resource - The resource to deny on.
644
+ * @param issuer - The issuing party.
645
+ * @param beneficiary - The beneficiary party.
646
+ * @param privateKey - Issuer's private key for signing.
647
+ * @returns A signed CovenantDocument with a single deny rule.
648
+ *
649
+ * @example
650
+ * ```typescript
651
+ * const doc = await QuickCovenant.deny('delete', '/system/**', issuer, beneficiary, kp.privateKey);
652
+ * ```
653
+ */
654
+ static async deny(action, resource, issuer, beneficiary, privateKey) {
655
+ const constraints = `deny ${action} on '${resource}'`;
656
+ return buildCovenant({
657
+ issuer,
658
+ beneficiary,
659
+ constraints,
660
+ privateKey,
661
+ });
662
+ }
663
+ /**
664
+ * Create a standard covenant with common constraints:
665
+ * - Permits read on all resources
666
+ * - Denies write on system resources
667
+ * - Limits API calls to 1000 per 1 hour
668
+ *
669
+ * @param issuer - The issuing party.
670
+ * @param beneficiary - The beneficiary party.
671
+ * @param privateKey - Issuer's private key for signing.
672
+ * @returns A signed CovenantDocument with standard constraints.
673
+ *
674
+ * @example
675
+ * ```typescript
676
+ * const doc = await QuickCovenant.standard(issuer, beneficiary, kp.privateKey);
677
+ * ```
678
+ */
679
+ static async standard(issuer, beneficiary, privateKey) {
680
+ const constraints = [
681
+ "permit read on '**'",
682
+ "deny write on '/system/**'",
683
+ 'limit api.call 1000 per 1 hours',
684
+ ].join('\n');
685
+ return buildCovenant({
686
+ issuer,
687
+ beneficiary,
688
+ constraints,
689
+ privateKey,
690
+ });
691
+ }
692
+ }
693
+ // Re-export adapters
694
+ export * from './adapters/index.js';
695
+ //# sourceMappingURL=index.js.map