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

Viewing files at commit 2c749578ec22Commit detailsView latest

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