@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.
- package/dist/adapters/express.d.ts +322 -0
- package/dist/adapters/express.d.ts.map +1 -0
- package/dist/adapters/express.js +356 -0
- package/dist/adapters/express.js.map +1 -0
- package/dist/adapters/index.d.ts +15 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +15 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/langchain.d.ts +183 -0
- package/dist/adapters/langchain.d.ts.map +1 -0
- package/dist/adapters/langchain.js +203 -0
- package/dist/adapters/langchain.js.map +1 -0
- package/dist/adapters/vercel-ai.d.ts +122 -0
- package/dist/adapters/vercel-ai.d.ts.map +1 -0
- package/dist/adapters/vercel-ai.js +128 -0
- package/dist/adapters/vercel-ai.js.map +1 -0
- package/dist/benchmarks.d.ts +164 -0
- package/dist/benchmarks.d.ts.map +1 -0
- package/dist/benchmarks.js +327 -0
- package/dist/benchmarks.js.map +1 -0
- package/dist/benchmarks.test.d.ts +2 -0
- package/dist/benchmarks.test.d.ts.map +1 -0
- package/dist/benchmarks.test.js +71 -0
- package/dist/benchmarks.test.js.map +1 -0
- package/dist/conformance.d.ts +160 -0
- package/dist/conformance.d.ts.map +1 -0
- package/dist/conformance.js +1242 -0
- package/dist/conformance.js.map +1 -0
- package/dist/events.d.ts +176 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +208 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +384 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +695 -0
- package/dist/index.js.map +1 -0
- package/dist/index.test.d.ts +2 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/index.test.js +986 -0
- package/dist/index.test.js.map +1 -0
- package/dist/middleware.d.ts +104 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +222 -0
- package/dist/middleware.js.map +1 -0
- package/dist/middleware.test.d.ts +5 -0
- package/dist/middleware.test.d.ts.map +1 -0
- package/dist/middleware.test.js +735 -0
- package/dist/middleware.test.js.map +1 -0
- package/dist/plugins/auth.d.ts +49 -0
- package/dist/plugins/auth.d.ts.map +1 -0
- package/dist/plugins/auth.js +82 -0
- package/dist/plugins/auth.js.map +1 -0
- package/dist/plugins/cache.d.ts +40 -0
- package/dist/plugins/cache.d.ts.map +1 -0
- package/dist/plugins/cache.js +191 -0
- package/dist/plugins/cache.js.map +1 -0
- package/dist/plugins/index.d.ts +16 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +12 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/metrics-plugin.d.ts +32 -0
- package/dist/plugins/metrics-plugin.d.ts.map +1 -0
- package/dist/plugins/metrics-plugin.js +61 -0
- package/dist/plugins/metrics-plugin.js.map +1 -0
- package/dist/plugins/plugins.test.d.ts +8 -0
- package/dist/plugins/plugins.test.d.ts.map +1 -0
- package/dist/plugins/plugins.test.js +640 -0
- package/dist/plugins/plugins.test.js.map +1 -0
- package/dist/plugins/retry-plugin.d.ts +55 -0
- package/dist/plugins/retry-plugin.d.ts.map +1 -0
- package/dist/plugins/retry-plugin.js +133 -0
- package/dist/plugins/retry-plugin.js.map +1 -0
- package/dist/telemetry.d.ts +183 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +241 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/types.d.ts +200 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- 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
|