@weave_protocol/domere 1.0.13 → 1.0.14

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.
@@ -0,0 +1,428 @@
1
+ /**
2
+ * Dōmere - Multi-Agent Handoff Verification
3
+ *
4
+ * Secure delegation and verification between AI agents.
5
+ * Ensures chain of custody and authorization in multi-agent systems.
6
+ */
7
+
8
+ import * as crypto from 'crypto';
9
+
10
+ // =============================================================================
11
+ // Types
12
+ // =============================================================================
13
+
14
+ export interface HandoffToken {
15
+ id: string;
16
+ thread_id: string;
17
+
18
+ // Delegation chain
19
+ from_agent: string;
20
+ to_agent: string;
21
+ delegation_depth: number;
22
+ parent_handoff_id?: string;
23
+
24
+ // Intent & constraints
25
+ delegated_intent: string;
26
+ intent_hash: string;
27
+ constraints: string[];
28
+ permissions: Permission[];
29
+
30
+ // Temporal bounds
31
+ created_at: Date;
32
+ expires_at: Date;
33
+ max_actions?: number;
34
+
35
+ // Verification
36
+ token: string;
37
+ signature: string;
38
+ status: 'active' | 'used' | 'expired' | 'revoked';
39
+
40
+ // Tracking
41
+ actions_taken: number;
42
+ verification_count: number;
43
+ }
44
+
45
+ export interface Permission {
46
+ resource: string;
47
+ actions: ('read' | 'write' | 'execute' | 'delegate')[];
48
+ conditions?: Record<string, any>;
49
+ }
50
+
51
+ export interface HandoffVerification {
52
+ valid: boolean;
53
+ handoff_id: string;
54
+ reason?: string;
55
+ remaining_actions?: number;
56
+ expires_in_ms?: number;
57
+ permissions: Permission[];
58
+ constraints: string[];
59
+ }
60
+
61
+ export interface DelegationChain {
62
+ thread_id: string;
63
+ origin_agent: string;
64
+ current_agent: string;
65
+ depth: number;
66
+ handoffs: HandoffToken[];
67
+ total_permissions: Permission[];
68
+ active_constraints: string[];
69
+ integrity_valid: boolean;
70
+ }
71
+
72
+ export interface HandoffPolicy {
73
+ max_delegation_depth: number;
74
+ max_handoff_duration_ms: number;
75
+ require_explicit_permissions: boolean;
76
+ allowed_agents?: string[];
77
+ blocked_agents?: string[];
78
+ default_permissions: Permission[];
79
+ constraint_inheritance: 'strict' | 'additive' | 'none';
80
+ }
81
+
82
+ // =============================================================================
83
+ // Handoff Manager
84
+ // =============================================================================
85
+
86
+ export class HandoffManager {
87
+ private handoffs: Map<string, HandoffToken> = new Map();
88
+ private chainsByThread: Map<string, string[]> = new Map();
89
+ private signingKey: Buffer;
90
+ private policy: HandoffPolicy;
91
+
92
+ constructor(signingKey: string, policy?: Partial<HandoffPolicy>) {
93
+ this.signingKey = crypto.scryptSync(signingKey, 'domere-handoff', 32);
94
+ this.policy = {
95
+ max_delegation_depth: 5,
96
+ max_handoff_duration_ms: 3600000, // 1 hour
97
+ require_explicit_permissions: true,
98
+ default_permissions: [],
99
+ constraint_inheritance: 'strict',
100
+ ...policy,
101
+ };
102
+ }
103
+
104
+ /**
105
+ * Create a handoff token for delegation
106
+ */
107
+ async createHandoff(params: {
108
+ thread_id: string;
109
+ from_agent: string;
110
+ to_agent: string;
111
+ delegated_intent: string;
112
+ constraints?: string[];
113
+ permissions?: Permission[];
114
+ expires_in_ms?: number;
115
+ max_actions?: number;
116
+ parent_handoff_id?: string;
117
+ }): Promise<HandoffToken> {
118
+ // Validate policy
119
+ if (this.policy.blocked_agents?.includes(params.to_agent)) {
120
+ throw new Error(`Agent ${params.to_agent} is blocked by policy`);
121
+ }
122
+
123
+ if (this.policy.allowed_agents && !this.policy.allowed_agents.includes(params.to_agent)) {
124
+ throw new Error(`Agent ${params.to_agent} is not in allowed list`);
125
+ }
126
+
127
+ // Check delegation depth
128
+ let depth = 0;
129
+ let inheritedConstraints: string[] = [];
130
+ let inheritedPermissions: Permission[] = [];
131
+
132
+ if (params.parent_handoff_id) {
133
+ const parent = this.handoffs.get(params.parent_handoff_id);
134
+ if (!parent) {
135
+ throw new Error(`Parent handoff ${params.parent_handoff_id} not found`);
136
+ }
137
+ if (parent.status !== 'active') {
138
+ throw new Error(`Parent handoff is ${parent.status}`);
139
+ }
140
+
141
+ depth = parent.delegation_depth + 1;
142
+
143
+ if (depth > this.policy.max_delegation_depth) {
144
+ throw new Error(`Delegation depth ${depth} exceeds maximum ${this.policy.max_delegation_depth}`);
145
+ }
146
+
147
+ // Inherit constraints based on policy
148
+ if (this.policy.constraint_inheritance === 'strict') {
149
+ inheritedConstraints = [...parent.constraints];
150
+ } else if (this.policy.constraint_inheritance === 'additive') {
151
+ inheritedConstraints = [...parent.constraints];
152
+ }
153
+
154
+ // Permissions can only be subset of parent
155
+ inheritedPermissions = parent.permissions;
156
+ }
157
+
158
+ // Generate token
159
+ const id = `hoff_${crypto.randomUUID()}`;
160
+ const intentHash = crypto.createHash('sha256').update(params.delegated_intent).digest('hex');
161
+ const tokenData = `${id}:${params.from_agent}:${params.to_agent}:${intentHash}:${Date.now()}`;
162
+ const token = crypto.createHash('sha256').update(tokenData).digest('hex');
163
+
164
+ // Sign token
165
+ const signature = this.sign(token);
166
+
167
+ // Merge constraints
168
+ const finalConstraints = [...inheritedConstraints, ...(params.constraints || [])];
169
+
170
+ // Validate permissions against parent
171
+ let finalPermissions = params.permissions || this.policy.default_permissions;
172
+ if (inheritedPermissions.length > 0) {
173
+ finalPermissions = this.intersectPermissions(inheritedPermissions, finalPermissions);
174
+ }
175
+
176
+ const handoff: HandoffToken = {
177
+ id,
178
+ thread_id: params.thread_id,
179
+
180
+ from_agent: params.from_agent,
181
+ to_agent: params.to_agent,
182
+ delegation_depth: depth,
183
+ parent_handoff_id: params.parent_handoff_id,
184
+
185
+ delegated_intent: params.delegated_intent,
186
+ intent_hash: intentHash,
187
+ constraints: finalConstraints,
188
+ permissions: finalPermissions,
189
+
190
+ created_at: new Date(),
191
+ expires_at: new Date(Date.now() + (params.expires_in_ms || this.policy.max_handoff_duration_ms)),
192
+ max_actions: params.max_actions,
193
+
194
+ token,
195
+ signature,
196
+ status: 'active',
197
+
198
+ actions_taken: 0,
199
+ verification_count: 0,
200
+ };
201
+
202
+ // Store
203
+ this.handoffs.set(id, handoff);
204
+
205
+ // Track chain
206
+ const chain = this.chainsByThread.get(params.thread_id) || [];
207
+ chain.push(id);
208
+ this.chainsByThread.set(params.thread_id, chain);
209
+
210
+ return handoff;
211
+ }
212
+
213
+ /**
214
+ * Verify a handoff token before agent acts
215
+ */
216
+ async verifyHandoff(token: string, agent_id: string): Promise<HandoffVerification> {
217
+ // Find handoff by token
218
+ let handoff: HandoffToken | undefined;
219
+ for (const h of this.handoffs.values()) {
220
+ if (h.token === token) {
221
+ handoff = h;
222
+ break;
223
+ }
224
+ }
225
+
226
+ if (!handoff) {
227
+ return { valid: false, handoff_id: '', reason: 'Token not found', permissions: [], constraints: [] };
228
+ }
229
+
230
+ // Verify signature
231
+ if (!this.verifySignature(token, handoff.signature)) {
232
+ return { valid: false, handoff_id: handoff.id, reason: 'Invalid signature', permissions: [], constraints: [] };
233
+ }
234
+
235
+ // Check status
236
+ if (handoff.status !== 'active') {
237
+ return { valid: false, handoff_id: handoff.id, reason: `Handoff is ${handoff.status}`, permissions: [], constraints: [] };
238
+ }
239
+
240
+ // Check agent
241
+ if (handoff.to_agent !== agent_id) {
242
+ return { valid: false, handoff_id: handoff.id, reason: `Token issued to ${handoff.to_agent}, not ${agent_id}`, permissions: [], constraints: [] };
243
+ }
244
+
245
+ // Check expiry
246
+ if (new Date() > handoff.expires_at) {
247
+ handoff.status = 'expired';
248
+ return { valid: false, handoff_id: handoff.id, reason: 'Token expired', permissions: [], constraints: [] };
249
+ }
250
+
251
+ // Check action limit
252
+ if (handoff.max_actions && handoff.actions_taken >= handoff.max_actions) {
253
+ handoff.status = 'used';
254
+ return { valid: false, handoff_id: handoff.id, reason: 'Action limit reached', permissions: [], constraints: [] };
255
+ }
256
+
257
+ // Update verification count
258
+ handoff.verification_count++;
259
+
260
+ return {
261
+ valid: true,
262
+ handoff_id: handoff.id,
263
+ remaining_actions: handoff.max_actions ? handoff.max_actions - handoff.actions_taken : undefined,
264
+ expires_in_ms: handoff.expires_at.getTime() - Date.now(),
265
+ permissions: handoff.permissions,
266
+ constraints: handoff.constraints,
267
+ };
268
+ }
269
+
270
+ /**
271
+ * Record an action taken under a handoff
272
+ */
273
+ async recordAction(handoffId: string, action: {
274
+ action_type: string;
275
+ action_name: string;
276
+ success: boolean;
277
+ }): Promise<{ allowed: boolean; remaining_actions?: number }> {
278
+ const handoff = this.handoffs.get(handoffId);
279
+ if (!handoff || handoff.status !== 'active') {
280
+ return { allowed: false };
281
+ }
282
+
283
+ handoff.actions_taken++;
284
+
285
+ if (handoff.max_actions && handoff.actions_taken >= handoff.max_actions) {
286
+ handoff.status = 'used';
287
+ }
288
+
289
+ return {
290
+ allowed: true,
291
+ remaining_actions: handoff.max_actions ? handoff.max_actions - handoff.actions_taken : undefined,
292
+ };
293
+ }
294
+
295
+ /**
296
+ * Revoke a handoff (and all child handoffs)
297
+ */
298
+ async revokeHandoff(handoffId: string, reason?: string): Promise<{ revoked: string[]; reason?: string }> {
299
+ const revoked: string[] = [];
300
+
301
+ const revoke = (id: string) => {
302
+ const handoff = this.handoffs.get(id);
303
+ if (handoff && handoff.status === 'active') {
304
+ handoff.status = 'revoked';
305
+ revoked.push(id);
306
+
307
+ // Revoke children
308
+ for (const h of this.handoffs.values()) {
309
+ if (h.parent_handoff_id === id) {
310
+ revoke(h.id);
311
+ }
312
+ }
313
+ }
314
+ };
315
+
316
+ revoke(handoffId);
317
+
318
+ return { revoked, reason };
319
+ }
320
+
321
+ /**
322
+ * Get delegation chain for a thread
323
+ */
324
+ async getDelegationChain(threadId: string): Promise<DelegationChain | null> {
325
+ const handoffIds = this.chainsByThread.get(threadId);
326
+ if (!handoffIds || handoffIds.length === 0) return null;
327
+
328
+ const handoffs = handoffIds.map(id => this.handoffs.get(id)!).filter(Boolean);
329
+ if (handoffs.length === 0) return null;
330
+
331
+ // Find origin and current
332
+ const origin = handoffs[0];
333
+ const current = handoffs[handoffs.length - 1];
334
+
335
+ // Collect active constraints and permissions
336
+ const activeConstraints = [...new Set(handoffs.flatMap(h => h.constraints))];
337
+ const totalPermissions = this.intersectPermissions(
338
+ ...handoffs.map(h => h.permissions)
339
+ );
340
+
341
+ // Verify chain integrity
342
+ let integrityValid = true;
343
+ for (let i = 1; i < handoffs.length; i++) {
344
+ if (handoffs[i].parent_handoff_id !== handoffs[i - 1].id) {
345
+ integrityValid = false;
346
+ break;
347
+ }
348
+ if (!this.verifySignature(handoffs[i].token, handoffs[i].signature)) {
349
+ integrityValid = false;
350
+ break;
351
+ }
352
+ }
353
+
354
+ return {
355
+ thread_id: threadId,
356
+ origin_agent: origin.from_agent,
357
+ current_agent: current.to_agent,
358
+ depth: current.delegation_depth,
359
+ handoffs,
360
+ total_permissions: totalPermissions,
361
+ active_constraints: activeConstraints,
362
+ integrity_valid: integrityValid,
363
+ };
364
+ }
365
+
366
+ /**
367
+ * Check if an action is permitted
368
+ */
369
+ checkPermission(handoffId: string, resource: string, action: 'read' | 'write' | 'execute' | 'delegate'): boolean {
370
+ const handoff = this.handoffs.get(handoffId);
371
+ if (!handoff || handoff.status !== 'active') return false;
372
+
373
+ for (const perm of handoff.permissions) {
374
+ if (perm.resource === resource || perm.resource === '*') {
375
+ if (perm.actions.includes(action)) {
376
+ return true;
377
+ }
378
+ }
379
+ }
380
+
381
+ return false;
382
+ }
383
+
384
+ // ===========================================================================
385
+ // Private Methods
386
+ // ===========================================================================
387
+
388
+ private sign(data: string): string {
389
+ const hmac = crypto.createHmac('sha256', this.signingKey);
390
+ hmac.update(data);
391
+ return hmac.digest('hex');
392
+ }
393
+
394
+ private verifySignature(data: string, signature: string): boolean {
395
+ const expected = this.sign(data);
396
+ return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
397
+ }
398
+
399
+ private intersectPermissions(...permissionSets: Permission[][]): Permission[] {
400
+ if (permissionSets.length === 0) return [];
401
+ if (permissionSets.length === 1) return permissionSets[0];
402
+
403
+ const result: Permission[] = [];
404
+ const first = permissionSets[0];
405
+
406
+ for (const perm of first) {
407
+ let allowed = true;
408
+ let intersectedActions = [...perm.actions];
409
+
410
+ for (let i = 1; i < permissionSets.length; i++) {
411
+ const matching = permissionSets[i].find(p => p.resource === perm.resource);
412
+ if (!matching) {
413
+ allowed = false;
414
+ break;
415
+ }
416
+ intersectedActions = intersectedActions.filter(a => matching.actions.includes(a));
417
+ }
418
+
419
+ if (allowed && intersectedActions.length > 0) {
420
+ result.push({ ...perm, actions: intersectedActions as Permission['actions'] });
421
+ }
422
+ }
423
+
424
+ return result;
425
+ }
426
+ }
427
+
428
+ export default HandoffManager;
package/src/index.ts CHANGED
@@ -41,3 +41,8 @@ export * from './thread/index.js';
41
41
  export * from './language/index.js';
42
42
  export * from './anchoring/index.js';
43
43
  export * from './storage/index.js';
44
+
45
+ // Enterprise Modules
46
+ export * from "./audit/index.js";
47
+ export * from "./handoff/index.js";
48
+ export * from "./compliance/index.js";