The AI trust layer between agents: identity, agreements, safeguards, and outcome intelligence for A2A.

Viewing files at commit f87848a4fcb8Commit detailsView latest

packages/protocol/src/index.ts 786 lines · 27.0 KB · typescript
1
import { createHash, generateKeyPairSync, randomUUID, sign, verify } from 'node:crypto';
2
import { canonicalize } from 'json-canonicalize';
3
import { z } from 'zod';
4
5
export const PROTOCOL_VERSION = '0.1' as const;
6
export const DEFAULT_EXTENSION_URI = 'https://openclasp.vercel.app/extensions/trust/v0.1';
7
8
export const ProvenanceSchema = z.enum([
9
  'self_declared',
10
  'operator_attested',
11
  'cryptographically_verified',
12
  'domain_verified',
13
  'third_party_verified',
14
  'observed',
15
  'disputed',
16
]);
17
export const VisibilitySchema = z.enum([
18
  'local_only',
19
  'private_requester',
20
  'private_responder',
21
  'shared_participants',
22
  'network_aggregate',
23
]);
24
export const DataSharingModeSchema = z.enum([
25
  'local_only',
26
  'structured_only',
27
  'permitted_evidence',
28
]);
29
export const SignatureSchema = z.object({
30
  algorithm: z.literal('Ed25519'),
31
  keyId: z.string().min(1),
32
  value: z.string().min(1),
33
});
34
35
export const ExpectationManifestSchema = z.object({
36
  requiredInputs: z.array(z.string()).default([]),
37
  supportedTaskCategories: z.array(z.string()).min(1),
38
  responseTimeMs: z.number().int().positive().optional(),
39
  dataRequirements: z.array(z.string()).default([]),
40
  prohibitedData: z.array(z.string()).default([]),
41
  humanApprovalThresholds: z.array(z.string()).default([]),
42
  delegationPolicy: z.string().default('explicit'),
43
  evidenceRequirements: z.array(z.string()).default([]),
44
  factCheckingPreference: z
45
    .enum(['none', 'important_claims', 'all_objective_claims'])
46
    .default('important_claims'),
47
  mediationPreference: z.enum(['never', 'mutual_consent']).default('mutual_consent'),
48
  retentionDays: z.number().int().nonnegative().default(30),
49
  communicationFormats: z.array(z.string()).default(['text/plain']),
50
  cancellationPolicy: z.string().default('either_party'),
51
  provenance: ProvenanceSchema,
52
});
53
54
export const AgentIdentitySchema = z.object({
55
  protocolVersion: z.literal(PROTOCOL_VERSION),
56
  agentId: z.string().min(1),
57
  publicKey: z.string().min(1),
58
  keyId: z.string().min(1),
59
  operatorRef: z.string().min(1),
60
  assurance: z.enum(['pseudonymous', 'domain_associated', 'organization_associated']),
61
  agentVersion: z.string().min(1),
62
  capabilities: z.array(z.string()),
63
  supportedProtocols: z.array(z.string()),
64
  parentAgentId: z.string().optional(),
65
  rootControllerId: z.string().min(1),
66
  revoked: z.boolean().default(false),
67
  provenance: ProvenanceSchema,
68
  createdAt: z.string().datetime(),
69
  signature: SignatureSchema.optional(),
70
});
71
72
export const DelegationCredentialSchema = z.object({
73
  protocolVersion: z.literal(PROTOCOL_VERSION),
74
  delegationId: z.string().uuid(),
75
  parentAgentId: z.string(),
76
  childAgentId: z.string(),
77
  rootControllerId: z.string(),
78
  capabilities: z.array(z.string()).min(1),
79
  issuedAt: z.string().datetime(),
80
  expiresAt: z.string().datetime(),
81
  revoked: z.boolean().default(false),
82
  signature: SignatureSchema.optional(),
83
});
84
85
export const InteractionContractSchema = z.object({
86
  protocolVersion: z.literal(PROTOCOL_VERSION),
87
  interactionId: z.string().uuid(),
88
  purpose: z.string(),
89
  parties: z.array(z.string()).min(1),
90
  taskCategory: z.string(),
91
  requestedOutcome: z.string(),
92
  successCriteria: z.array(z.string()),
93
  allowedActions: z.array(z.string()),
94
  prohibitedActions: z.array(z.string()),
95
  allowedData: z.array(z.string()),
96
  prohibitedData: z.array(z.string()),
97
  deadline: z.string().datetime().optional(),
98
  evidenceRequirements: z.array(z.string()),
99
  delegationRules: z.array(z.string()),
100
  humanApprovalRequirements: z.array(z.string()),
101
  factCheckingPolicy: z.string(),
102
  mediationPolicy: z.enum(['none', 'mutual_consent']),
103
  retentionDays: z.number().int().nonnegative(),
104
  completionConditions: z.array(z.string()),
105
  cancellationConditions: z.array(z.string()),
106
  signatures: z.record(z.string(), SignatureSchema).default({}),
107
});
108
109
export const AgentTransportSchema = z.object({
110
  protocol: z.literal('A2A/1.0'),
111
  protocolBinding: z.string().min(1).default('JSONRPC'),
112
  endpoint: z.string().url(),
113
  managedBy: z.enum(['agent', 'openclasp']).default('agent'),
114
});
115
116
export const AgentModeSchema = z.enum(['persistent_runtime', 'temporary_chat']);
117
118
export const AgentPresenceSchema = z.object({
119
  status: z.enum(['online', 'offline']),
120
  lastSeenAt: z.string().datetime().optional(),
121
  checkedAt: z.string().datetime(),
122
});
123
124
export const PublicAgentCardSchema = z.object({
125
  protocolVersion: z.literal(PROTOCOL_VERSION),
126
  agentId: z.string().min(1),
127
  name: z.string().min(1),
128
  description: z.string(),
129
  framework: z.string().min(1),
130
  agentVersion: z.string().min(1),
131
  capabilities: z.array(z.string()),
132
  limitations: z.array(z.string()),
133
  assurance: z.enum(['oauth_authenticated', 'cryptographically_verified']),
134
  agentMode: AgentModeSchema.default('persistent_runtime'),
135
  transports: z.array(AgentTransportSchema),
136
  cardUrl: z.string().url(),
137
  a2aAgentCardUrl: z.string().url(),
138
  extensionUri: z.string().url(),
139
  presence: AgentPresenceSchema.optional(),
140
  publishedAt: z.string().datetime(),
141
  updatedAt: z.string().datetime(),
142
});
143
144
export const HostedThreadStatusSchema = z.enum(['open', 'closed']);
145
146
export const HostedMessageSchema = z.object({
147
  messageId: z.string().uuid(),
148
  threadId: z.string().uuid(),
149
  interactionId: z.string().uuid(),
150
  senderAgentId: z.string().min(1),
151
  recipientAgentId: z.string().min(1),
152
  contentType: z.literal('text/plain'),
153
  content: z.string().min(1).max(20_000),
154
  contentHash: z.string().regex(/^[a-zA-Z0-9_-]{43}$/),
155
  delivery: z.enum(['accepted', 'delivered', 'read']),
156
  createdAt: z.string().datetime(),
157
  readAt: z.string().datetime().optional(),
158
});
159
160
export const HostedThreadSchema = z.object({
161
  threadId: z.string().uuid(),
162
  interactionId: z.string().uuid(),
163
  participantAgentIds: z.tuple([z.string().min(1), z.string().min(1)]),
164
  status: HostedThreadStatusSchema,
165
  privacyMode: z.literal('openclasp_hosted_temporary'),
166
  unreadCount: z.number().int().nonnegative().default(0),
167
  createdAt: z.string().datetime(),
168
  updatedAt: z.string().datetime(),
169
  expiresAt: z.string().datetime(),
170
});
171
172
export const ContractAcceptanceSchema = z.object({
173
  agentId: z.string().min(1),
174
  method: z.enum(['oauth_installation', 'oauth_account', 'policy_auto_accept', 'ed25519']),
175
  termsHash: z.string().min(1),
176
  acceptedAt: z.string().datetime(),
177
  signature: SignatureSchema.optional(),
178
});
179
180
export const FederatedInteractionStatusSchema = z.enum([
181
  'pending',
182
  'active',
183
  'rejected',
184
  'expired',
185
  'cancelled',
186
  'completed',
187
]);
188
189
export const FederatedInteractionSchema = z.object({
190
  protocolVersion: z.literal(PROTOCOL_VERSION),
191
  interactionId: z.string().uuid(),
192
  initiatorAgentId: z.string().min(1),
193
  responderAgentId: z.string().min(1),
194
  status: FederatedInteractionStatusSchema,
195
  contract: InteractionContractSchema,
196
  termsHash: z.string().min(1),
197
  acceptances: z.record(z.string(), ContractAcceptanceSchema),
198
  initiatorTransport: AgentTransportSchema.optional(),
199
  responderTransport: AgentTransportSchema,
200
  createdAt: z.string().datetime(),
201
  updatedAt: z.string().datetime(),
202
  expiresAt: z.string().datetime(),
203
});
204
205
export const LiveSessionInsightSchema = z
206
  .object({
207
    code: z.string().min(1),
208
    severity: z.enum(['info', 'caution', 'high']),
209
    message: z.string().min(1),
210
    evidenceReferences: z.array(z.string()).default([]),
211
    requirementReferences: z.array(z.string()).default([]),
212
    confidence: z.number().min(0).max(1).optional(),
213
  })
214
  .strict();
215
216
export const RequirementAssessmentSchema = z
217
  .object({
218
    requirementId: z.string().min(1).max(128),
219
    requirement: z.string().min(1).max(1000),
220
    kind: z.enum([
221
      'capability',
222
      'limitation',
223
      'success_criterion',
224
      'deadline',
225
      'evidence',
226
      'scope',
227
      'data',
228
      'communication',
229
      'delegation',
230
    ]),
231
    status: z.enum(['match', 'partial', 'mismatch', 'unknown']),
232
    reason: z.string().min(1).max(1000),
233
    confidence: z.number().min(0).max(1),
234
    sources: z
235
      .array(
236
        z.enum(['contract', 'self_declared', 'eligible_history', 'evidence', 'version_change']),
237
      )
238
      .min(1),
239
    evidenceReferences: z.array(z.string().min(1).max(2048)).max(50).default([]),
240
  })
241
  .strict();
242
243
export const CounterpartyBriefSchema = z
244
  .object({
245
    briefId: z.string().uuid(),
246
    interactionId: z.string().uuid(),
247
    contractHash: z.string().min(1),
248
    recipientAgentId: z.string().min(1),
249
    subjectAgentId: z.string().min(1),
250
    taskCategory: z.string().min(1),
251
    decision: z.enum(['ALLOW', 'CHALLENGE', 'DENY']),
252
    requirements: z.array(RequirementAssessmentSchema).max(100),
253
    insights: z.array(LiveSessionInsightSchema).max(100),
254
    relevantSampleSize: z.number().int().nonnegative(),
255
    historyConfidence: z.number().min(0).max(1),
256
    subjectAgentVersion: z.string().min(1),
257
    recommendedContractChanges: z.array(z.string().min(1).max(1000)).max(50).default([]),
258
    generatedAt: z.string().datetime(),
259
    expiresAt: z.string().datetime(),
260
    signature: SignatureSchema.optional(),
261
  })
262
  .strict()
263
  .refine((brief) => brief.recipientAgentId !== brief.subjectAgentId, {
264
    message: 'A counterparty brief must describe another agent',
265
    path: ['subjectAgentId'],
266
  })
267
  .refine((brief) => Date.parse(brief.expiresAt) > Date.parse(brief.generatedAt), {
268
    message: 'Counterparty brief expiry must be after generation',
269
    path: ['expiresAt'],
270
  });
271
272
export const LiveSessionOfferSchema = z.object({
273
  type: z.literal('openclasp.session.offer'),
274
  version: z.literal('1'),
275
  offerId: z.string().uuid(),
276
  interactionId: z.string().uuid(),
277
  agentId: z.string().min(1),
278
  role: z.enum(['initiator', 'responder']),
279
  counterparty: z.object({
280
    agentId: z.string().min(1),
281
    name: z.string().min(1),
282
    agentVersion: z.string().min(1),
283
    capabilities: z.array(z.string()),
284
  }),
285
  contract: InteractionContractSchema,
286
  contractHash: z.string().min(1),
287
  privateInsights: z.array(LiveSessionInsightSchema).default([]),
288
  counterpartyBrief: CounterpartyBriefSchema.optional(),
289
  issuedAt: z.string().datetime(),
290
  expiresAt: z.string().datetime(),
291
});
292
293
export const LiveSessionAcceptanceSchema = z.object({
294
  type: z.literal('openclasp.session.accepted'),
295
  version: z.literal('1'),
296
  offerId: z.string().uuid(),
297
  interactionId: z.string().uuid(),
298
  agentId: z.string().min(1),
299
  sessionId: z.string().uuid(),
300
  a2aEndpoint: z.string().url(),
301
  expiresAt: z.string().datetime(),
302
});
303
304
export const LiveSessionActivationSchema = z.object({
305
  type: z.literal('openclasp.session.activation'),
306
  version: z.literal('1'),
307
  activationId: z.string().uuid(),
308
  interactionId: z.string().uuid(),
309
  agentId: z.string().min(1),
310
  sessionId: z.string().uuid(),
311
  role: z.enum(['initiator', 'responder']),
312
  peer: z.object({
313
    agentId: z.string().min(1),
314
    sessionId: z.string().uuid(),
315
    endpoint: z.string().url(),
316
    bearerToken: z.string().min(1),
317
    verificationKey: z.string().min(1),
318
  }),
319
  reporting: z.object({
320
    endpoint: z.string().url(),
321
    bearerToken: z.string().min(1),
322
  }),
323
  privateInsights: z.array(LiveSessionInsightSchema).optional(),
324
  counterpartyBrief: CounterpartyBriefSchema.optional(),
325
  contractHash: z.string().min(1),
326
  activatedAt: z.string().datetime(),
327
  expiresAt: z.string().datetime(),
328
});
329
330
const SessionDetailKeySchema = z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,63}$/);
331
332
export const StructuredSessionDetailsSchema = z
333
  .object({
334
    labels: z.array(SessionDetailKeySchema).max(32).default([]),
335
    metrics: z.record(SessionDetailKeySchema, z.number().finite()).default({}),
336
    flags: z.record(SessionDetailKeySchema, z.boolean()).default({}),
337
  })
338
  .strict()
339
  .default({ labels: [], metrics: {}, flags: {} });
340
341
export const LiveSessionEventSchema = z.object({
342
  eventId: z.string().uuid(),
343
  interactionId: z.string().uuid(),
344
  agentId: z.string().min(1),
345
  sequence: z.number().int().nonnegative(),
346
  type: z.enum([
347
    'session_started',
348
    'message_sent',
349
    'claim',
350
    'evidence',
351
    'correction',
352
    'constraint',
353
    'task_result',
354
    'session_completed',
355
    'session_failed',
356
  ]),
357
  occurredAt: z.string().datetime(),
358
  messageHash: z
359
    .string()
360
    .regex(/^[a-zA-Z0-9_-]{43}$/)
361
    .optional(),
362
  evidenceReferences: z.array(z.string()).default([]),
363
  outcome: z.enum(['success', 'failure', 'partial']).optional(),
364
  details: StructuredSessionDetailsSchema,
365
});
366
367
export const CompletionOutcomeSchema = z.enum(['success', 'partial', 'failure', 'cancelled']);
368
369
export const SuccessCriterionAssessmentSchema = z
370
  .object({
371
    criterion: z.string().min(1).max(1000),
372
    status: z.enum(['met', 'partially_met', 'missed', 'unknown']),
373
    explanation: z.string().max(1000).optional(),
374
    evidenceReferences: z.array(z.string().min(1).max(2048)).max(50).default([]),
375
  })
376
  .strict();
377
378
export const InteractionCompletionReportSchema = z
379
  .object({
380
    reportId: z.string().uuid(),
381
    interactionId: z.string().uuid(),
382
    contractHash: z.string().min(1),
383
    reportingAgentId: z.string().min(1),
384
    counterpartyAgentId: z.string().min(1),
385
    agentVersion: z.string().min(1),
386
    outcome: CompletionOutcomeSchema,
387
    summary: z.string().min(1).max(2000),
388
    requestedOutcome: z.string().min(1).max(1000),
389
    criteria: z.array(SuccessCriterionAssessmentSchema).max(100),
390
    deliverables: z.array(z.string().min(1).max(1000)).max(100).default([]),
391
    actionsTaken: z.array(z.string().min(1).max(1000)).max(100).default([]),
392
    blockers: z.array(z.string().min(1).max(1000)).max(100).default([]),
393
    scopeChanges: z.array(z.string().min(1).max(1000)).max(100).default([]),
394
    corrections: z.array(z.string().min(1).max(1000)).max(100).default([]),
395
    evidenceReferences: z.array(z.string().min(1).max(2048)).max(100).default([]),
396
    startedAt: z.string().datetime().optional(),
397
    completedAt: z.string().datetime(),
398
    confidence: z.number().min(0).max(1),
399
    dataSharingMode: DataSharingModeSchema.default('structured_only'),
400
    signature: SignatureSchema.optional(),
401
  })
402
  .strict()
403
  .refine((report) => report.reportingAgentId !== report.counterpartyAgentId, {
404
    message: 'A completion report must describe an interaction with another agent',
405
    path: ['counterpartyAgentId'],
406
  })
407
  .refine(
408
    (report) => !report.startedAt || Date.parse(report.completedAt) >= Date.parse(report.startedAt),
409
    { message: 'Completion cannot precede the start time', path: ['completedAt'] },
410
  );
411
412
export const FeedbackDimensionSchema = z.enum([
413
  'overall_satisfaction',
414
  'outcome_satisfaction',
415
  'communication',
416
  'timeliness',
417
  'scope_adherence',
418
  'evidence_quality',
419
  'correction_handling',
420
  'reliability',
421
]);
422
423
export const FeedbackRequestSchema = z
424
  .object({
425
    requestId: z.string().uuid(),
426
    interactionId: z.string().uuid(),
427
    reviewerAgentId: z.string().min(1),
428
    subjectAgentId: z.string().min(1),
429
    status: z.enum(['pending', 'submitted', 'expired', 'waived']),
430
    requestedDimensions: z.array(FeedbackDimensionSchema).min(1),
431
    requestedAt: z.string().datetime(),
432
    dueAt: z.string().datetime(),
433
  })
434
  .strict()
435
  .refine((request) => request.reviewerAgentId !== request.subjectAgentId, {
436
    message: 'Feedback must concern another agent',
437
    path: ['subjectAgentId'],
438
  })
439
  .refine((request) => Date.parse(request.dueAt) > Date.parse(request.requestedAt), {
440
    message: 'Feedback due date must follow its request date',
441
    path: ['dueAt'],
442
  });
443
444
export const InteractionFeedbackSchema = z
445
  .object({
446
    feedbackId: z.string().uuid(),
447
    requestId: z.string().uuid(),
448
    interactionId: z.string().uuid(),
449
    reviewerAgentId: z.string().min(1),
450
    subjectAgentId: z.string().min(1),
451
    reviewerAgentVersion: z.string().min(1),
452
    ratings: z.partialRecord(FeedbackDimensionSchema, z.number().min(0).max(1)),
453
    wouldWorkAgain: z.enum(['yes', 'no', 'unsure']),
454
    reasonCodes: z.array(z.string().min(1).max(128)).max(32).default([]),
455
    privateComment: z.string().max(1000).optional(),
456
    evidenceReferences: z.array(z.string().min(1).max(2048)).max(50).default([]),
457
    confidence: z.number().min(0).max(1),
458
    submittedAt: z.string().datetime(),
459
    signature: SignatureSchema.optional(),
460
  })
461
  .strict()
462
  .refine((feedback) => feedback.reviewerAgentId !== feedback.subjectAgentId, {
463
    message: 'Feedback must concern another agent',
464
    path: ['subjectAgentId'],
465
  })
466
  .refine((feedback) => Object.keys(feedback.ratings).length > 0, {
467
    message: 'At least one feedback rating is required',
468
    path: ['ratings'],
469
  });
470
471
export const InteractionConclusionSchema = z
472
  .object({
473
    conclusionId: z.string().uuid(),
474
    interactionId: z.string().uuid(),
475
    contractHash: z.string().min(1),
476
    outcome: CompletionOutcomeSchema,
477
    consensus: z.enum([
478
      'bilateral_agreement',
479
      'bilateral_partial_agreement',
480
      'conflicting',
481
      'unilateral',
482
      'insufficient',
483
    ]),
484
    summary: z.string().min(1).max(2000),
485
    criteria: z.array(SuccessCriterionAssessmentSchema).max(100),
486
    reportIds: z.array(z.string().uuid()).max(2),
487
    feedbackIds: z.array(z.string().uuid()).max(2),
488
    averageRatings: z.partialRecord(FeedbackDimensionSchema, z.number().min(0).max(1)).default({}),
489
    evidenceReferences: z.array(z.string().min(1).max(2048)).max(100).default([]),
490
    generatedAt: z.string().datetime(),
491
  })
492
  .strict();
493
494
export const LearningEligibilityDecisionSchema = z
495
  .object({
496
    decisionId: z.string().uuid(),
497
    interactionId: z.string().uuid(),
498
    eligible: z.boolean(),
499
    reasons: z.array(z.string().min(1).max(500)).min(1).max(50),
500
    sampleWeight: z.number().min(0).max(1),
501
    reportIds: z.array(z.string().uuid()).max(2),
502
    feedbackIds: z.array(z.string().uuid()).max(2),
503
    evidenceReferences: z.array(z.string().min(1).max(2048)).max(100).default([]),
504
    contributionMode: z.enum(['local_only', 'network_aggregate']),
505
    structuredDataOnly: z.literal(true),
506
    decidedAt: z.string().datetime(),
507
  })
508
  .strict();
509
510
export const BehaviouralProfileDeltaSchema = z
511
  .object({
512
    deltaId: z.string().uuid(),
513
    interactionId: z.string().uuid(),
514
    agentId: z.string().min(1),
515
    agentVersion: z.string().min(1),
516
    taskCategory: z.string().min(1),
517
    sampleWeight: z.number().min(0).max(1),
518
    dimensionDeltas: z.record(z.string().min(1).max(128), z.number().min(-1).max(1)),
519
    explanation: z.string().min(1).max(2000),
520
    appliedAt: z.string().datetime(),
521
  })
522
  .strict();
523
524
export const TrustEnvelopeSchema = z.object({
525
  protocolVersion: z.literal(PROTOCOL_VERSION),
526
  interactionId: z.string().uuid(),
527
  requestingAgentId: z.string(),
528
  respondingAgentId: z.string(),
529
  rootControllerId: z.string(),
530
  parentAgentId: z.string().optional(),
531
  agentVersion: z.string(),
532
  delegationId: z.string().uuid().optional(),
533
  requestedCapability: z.string(),
534
  taskCategory: z.string(),
535
  contractHash: z.string(),
536
  dataSharingMode: DataSharingModeSchema,
537
  evidenceRequirements: z.array(z.string()),
538
  timestamp: z.string().datetime(),
539
  expiresAt: z.string().datetime(),
540
  nonce: z.string().min(12),
541
  signature: SignatureSchema.optional(),
542
});
543
544
export const EventTypeSchema = z.enum([
545
  'claim',
546
  'evidence',
547
  'constraint',
548
  'commitment',
549
  'proposal',
550
  'objection',
551
  'policy_warning',
552
  'policy_violation',
553
  'private_suggestion',
554
  'shared_intervention',
555
  'delegation',
556
  'task_result',
557
  'resolution',
558
  'receipt',
559
  'feedback',
560
  'dispute',
561
]);
562
export const InteractionEventSchema = z.object({
563
  protocolVersion: z.literal(PROTOCOL_VERSION),
564
  eventId: z.string().uuid(),
565
  interactionId: z.string().uuid(),
566
  eventType: EventTypeSchema,
567
  agentId: z.string(),
568
  agentVersion: z.string(),
569
  timestamp: z.string().datetime(),
570
  visibility: VisibilitySchema,
571
  provenance: ProvenanceSchema,
572
  payloadHash: z.string(),
573
  payload: z.record(z.string(), z.unknown()),
574
  evidenceRefs: z.array(z.string()).optional(),
575
  signature: SignatureSchema.optional(),
576
});
577
578
export const FeedbackSchema = z.object({
579
  feedbackId: z.string().uuid(),
580
  interactionId: z.string().uuid(),
581
  receiptId: z.string().uuid(),
582
  reviewerAgentId: z.string(),
583
  subjectAgentId: z.string(),
584
  taskCompleted: z.boolean(),
585
  outputAccepted: z.boolean(),
586
  specificationMatched: z.boolean(),
587
  deadlineMet: z.boolean(),
588
  communicationQuality: z.number().min(0).max(1),
589
  evidenceProvided: z.boolean(),
590
  scopeRespected: z.boolean(),
591
  disputeRaised: z.boolean(),
592
  comment: z.string().max(1000).optional(),
593
  submittedAt: z.string().datetime(),
594
  signature: SignatureSchema.optional(),
595
});
596
597
export const ReceiptSchema = z.object({
598
  receiptId: z.string().uuid(),
599
  interactionId: z.string().uuid(),
600
  participants: z.array(z.string()),
601
  agentVersions: z.record(z.string(), z.string()),
602
  contractHash: z.string(),
603
  startedAt: z.string().datetime(),
604
  completedAt: z.string().datetime(),
605
  outcome: z.enum(['success', 'partial', 'failure', 'cancelled']),
606
  commitmentsFulfilled: z.array(z.string()),
607
  commitmentsMissed: z.array(z.string()),
608
  evidenceHashes: z.array(z.string()),
609
  policyWarnings: z.array(z.string()),
610
  policyViolations: z.array(z.string()),
611
  disputeStatus: z.enum(['none', 'open', 'resolved']),
612
  delegationChainHash: z.string(),
613
  unilateral: z.boolean(),
614
  signatures: z.record(z.string(), SignatureSchema).default({}),
615
});
616
617
export const RiskDecisionSchema = z.object({
618
  decision: z.enum(['ALLOW', 'CHALLENGE', 'DENY']),
619
  confidence: z.number().min(0).max(1),
620
  taskCategory: z.string(),
621
  sampleSize: z.number().int().nonnegative(),
622
  dimensions: z.record(z.string(), z.number()),
623
  reasons: z.array(z.string()),
624
  warnings: z.array(z.string()),
625
  dataFreshness: z.record(z.string(), z.string()),
626
  requiredChallenges: z.array(z.string()),
627
});
628
629
export const FactCheckResultSchema = z.object({
630
  claim: z.string(),
631
  claimType: z.enum(['objective', 'subjective', 'prediction']),
632
  status: z.enum([
633
    'verified',
634
    'supported',
635
    'contradicted',
636
    'unverified',
637
    'not_fact_checkable',
638
    'insufficient_permission',
639
  ]),
640
  confidence: z.number().min(0).max(1),
641
  evidenceReferences: z.array(z.string()),
642
  sourceAuthority: z.string(),
643
  sourceFreshness: z.string(),
644
  contradictingEvidence: z.array(z.string()),
645
  suggestedNextAction: z.string(),
646
});
647
648
export type AgentIdentity = z.infer<typeof AgentIdentitySchema>;
649
export type DelegationCredential = z.infer<typeof DelegationCredentialSchema>;
650
export type InteractionContract = z.infer<typeof InteractionContractSchema>;
651
export type TrustEnvelope = z.infer<typeof TrustEnvelopeSchema>;
652
export type InteractionEvent = z.infer<typeof InteractionEventSchema>;
653
export type Feedback = z.infer<typeof FeedbackSchema>;
654
export type Receipt = z.infer<typeof ReceiptSchema>;
655
export type RiskDecision = z.infer<typeof RiskDecisionSchema>;
656
export type FactCheckResult = z.infer<typeof FactCheckResultSchema>;
657
export type ExpectationManifest = z.infer<typeof ExpectationManifestSchema>;
658
export type AgentTransport = z.infer<typeof AgentTransportSchema>;
659
export type AgentMode = z.infer<typeof AgentModeSchema>;
660
export type AgentPresence = z.infer<typeof AgentPresenceSchema>;
661
export type PublicAgentCard = z.infer<typeof PublicAgentCardSchema>;
662
export type HostedMessage = z.infer<typeof HostedMessageSchema>;
663
export type HostedThread = z.infer<typeof HostedThreadSchema>;
664
export type ContractAcceptance = z.infer<typeof ContractAcceptanceSchema>;
665
export type FederatedInteraction = z.infer<typeof FederatedInteractionSchema>;
666
export type LiveSessionInsight = z.infer<typeof LiveSessionInsightSchema>;
667
export type LiveSessionOffer = z.infer<typeof LiveSessionOfferSchema>;
668
export type LiveSessionAcceptance = z.infer<typeof LiveSessionAcceptanceSchema>;
669
export type LiveSessionActivation = z.infer<typeof LiveSessionActivationSchema>;
670
export type LiveSessionEvent = z.infer<typeof LiveSessionEventSchema>;
671
export type RequirementAssessment = z.infer<typeof RequirementAssessmentSchema>;
672
export type CounterpartyBrief = z.infer<typeof CounterpartyBriefSchema>;
673
export type CompletionOutcome = z.infer<typeof CompletionOutcomeSchema>;
674
export type SuccessCriterionAssessment = z.infer<typeof SuccessCriterionAssessmentSchema>;
675
export type InteractionCompletionReport = z.infer<typeof InteractionCompletionReportSchema>;
676
export type FeedbackDimension = z.infer<typeof FeedbackDimensionSchema>;
677
export type FeedbackRequest = z.infer<typeof FeedbackRequestSchema>;
678
export type InteractionFeedback = z.infer<typeof InteractionFeedbackSchema>;
679
export type InteractionConclusion = z.infer<typeof InteractionConclusionSchema>;
680
export type LearningEligibilityDecision = z.infer<typeof LearningEligibilityDecisionSchema>;
681
export type BehaviouralProfileDelta = z.infer<typeof BehaviouralProfileDeltaSchema>;
682
683
export interface KeyPair {
684
  keyId: string;
685
  publicKey: string;
686
  privateKey: string;
687
}
688
689
export function createKeyPair(keyId: string = randomUUID()): KeyPair {
690
  const pair = generateKeyPairSync('ed25519');
691
  return {
692
    keyId,
693
    publicKey: pair.publicKey.export({ type: 'spki', format: 'der' }).toString('base64url'),
694
    privateKey: pair.privateKey.export({ type: 'pkcs8', format: 'der' }).toString('base64url'),
695
  };
696
}
697
698
export function canonicalHash(value: unknown): string {
699
  return createHash('sha256').update(canonicalize(value)).digest('base64url');
700
}
701
702
function unsigned(value: Record<string, unknown>): Record<string, unknown> {
703
  const copy = structuredClone(value);
704
  delete copy.signature;
705
  delete copy.signatures;
706
  return copy;
707
}
708
709
export function signObject<T extends Record<string, unknown>>(
710
  value: T,
711
  key: KeyPair,
712
): T & { signature: z.infer<typeof SignatureSchema> } {
713
  const payload = Buffer.from(canonicalize(unsigned(value)));
714
  const privateKey = {
715
    key: Buffer.from(key.privateKey, 'base64url'),
716
    type: 'pkcs8' as const,
717
    format: 'der' as const,
718
  };
719
  return {
720
    ...value,
721
    signature: {
722
      algorithm: 'Ed25519',
723
      keyId: key.keyId,
724
      value: sign(null, payload, privateKey).toString('base64url'),
725
    },
726
  };
727
}
728
729
export function verifyObject(value: Record<string, unknown>, publicKey: string): boolean {
730
  const parsed = SignatureSchema.safeParse(value.signature);
731
  if (!parsed.success) return false;
732
  const key = {
733
    key: Buffer.from(publicKey, 'base64url'),
734
    type: 'spki' as const,
735
    format: 'der' as const,
736
  };
737
  return verify(
738
    null,
739
    Buffer.from(canonicalize(unsigned(value))),
740
    key,
741
    Buffer.from(parsed.data.value, 'base64url'),
742
  );
743
}
744
745
export function signNamed<T extends Record<string, unknown>>(
746
  value: T,
747
  signerId: string,
748
  key: KeyPair,
749
): T & { signatures: Record<string, z.infer<typeof SignatureSchema>> } {
750
  const payload = Buffer.from(canonicalize(unsigned(value)));
751
  const privateKey = {
752
    key: Buffer.from(key.privateKey, 'base64url'),
753
    type: 'pkcs8' as const,
754
    format: 'der' as const,
755
  };
756
  const signature = {
757
    algorithm: 'Ed25519' as const,
758
    keyId: key.keyId,
759
    value: sign(null, payload, privateKey).toString('base64url'),
760
  };
761
  return {
762
    ...value,
763
    signatures: { ...((value.signatures as object) ?? {}), [signerId]: signature },
764
  };
765
}
766
767
export function verifyNamed(
768
  value: Record<string, unknown>,
769
  signerId: string,
770
  publicKey: string,
771
): boolean {
772
  const signatures = value.signatures as Record<string, unknown> | undefined;
773
  const parsed = SignatureSchema.safeParse(signatures?.[signerId]);
774
  if (!parsed.success) return false;
775
  const key = {
776
    key: Buffer.from(publicKey, 'base64url'),
777
    type: 'spki' as const,
778
    format: 'der' as const,
779
  };
780
  return verify(
781
    null,
782
    Buffer.from(canonicalize(unsigned(value))),
783
    key,
784
    Buffer.from(parsed.data.value, 'base64url'),
785
  );
786
}