@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.
- package/dist/audit/index.d.ts +2 -0
- package/dist/audit/index.d.ts.map +1 -0
- package/dist/audit/index.js +2 -0
- package/dist/audit/index.js.map +1 -0
- package/dist/audit/replay.d.ts +153 -0
- package/dist/audit/replay.d.ts.map +1 -0
- package/dist/audit/replay.js +328 -0
- package/dist/audit/replay.js.map +1 -0
- package/dist/compliance/checkpoint.d.ts +183 -0
- package/dist/compliance/checkpoint.d.ts.map +1 -0
- package/dist/compliance/checkpoint.js +394 -0
- package/dist/compliance/checkpoint.js.map +1 -0
- package/dist/compliance/index.d.ts +2 -0
- package/dist/compliance/index.d.ts.map +1 -0
- package/dist/compliance/index.js +2 -0
- package/dist/compliance/index.js.map +1 -0
- package/dist/handoff/index.d.ts +2 -0
- package/dist/handoff/index.d.ts.map +1 -0
- package/dist/handoff/index.js +2 -0
- package/dist/handoff/index.js.map +1 -0
- package/dist/handoff/verification.d.ts +115 -0
- package/dist/handoff/verification.d.ts.map +1 -0
- package/dist/handoff/verification.js +285 -0
- package/dist/handoff/verification.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/audit/index.ts +1 -0
- package/src/audit/replay.ts +504 -0
- package/src/compliance/checkpoint.ts +647 -0
- package/src/compliance/index.ts +1 -0
- package/src/handoff/index.ts +1 -0
- package/src/handoff/verification.ts +428 -0
- package/src/index.ts +5 -0
|
@@ -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";
|