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

Viewing files at commit 66ff6831b5d2Commit detailsView latest

packages/protocol/src/index.ts 971 lines · 33.8 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
  'limitation_disclosure',
502
  'reliability',
503
]);
504
505
export const FeedbackRequestSchema = z
506
  .object({
507
    requestId: z.string().uuid(),
508
    interactionId: z.string().uuid(),
509
    reviewerAgentId: z.string().min(1),
510
    subjectAgentId: z.string().min(1),
511
    status: z.enum(['pending', 'submitted', 'expired', 'waived']),
512
    requestedDimensions: z.array(FeedbackDimensionSchema).min(1),
513
    requestedAt: z.string().datetime(),
514
    dueAt: z.string().datetime(),
515
    platformAttestation: RecordAttestationSchema.optional(),
516
  })
517
  .strict()
518
  .refine((request) => request.reviewerAgentId !== request.subjectAgentId, {
519
    message: 'Feedback must concern another agent',
520
    path: ['subjectAgentId'],
521
  })
522
  .refine((request) => Date.parse(request.dueAt) > Date.parse(request.requestedAt), {
523
    message: 'Feedback due date must follow its request date',
524
    path: ['dueAt'],
525
  });
526
527
export const InteractionFeedbackSchema = z
528
  .object({
529
    feedbackId: z.string().uuid(),
530
    requestId: z.string().uuid(),
531
    interactionId: z.string().uuid(),
532
    reviewerAgentId: z.string().min(1),
533
    subjectAgentId: z.string().min(1),
534
    reviewerAgentVersion: z.string().min(1),
535
    ratings: z.partialRecord(FeedbackDimensionSchema, z.number().min(0).max(1)),
536
    wouldWorkAgain: z.enum(['yes', 'no', 'unsure']),
537
    reasonCodes: z.array(z.string().min(1).max(128)).max(32).default([]),
538
    privateComment: z.string().max(1000).optional(),
539
    evidenceReferences: z.array(z.string().min(1).max(2048)).max(50).default([]),
540
    confidence: z.number().min(0).max(1),
541
    submittedAt: z.string().datetime(),
542
    signature: SignatureSchema.optional(),
543
    submissionMethod: z
544
      .enum(['agent_signature', 'oauth_installation', 'agent_access_token', 'runtime_session'])
545
      .optional(),
546
    platformAttestation: RecordAttestationSchema.optional(),
547
  })
548
  .strict()
549
  .refine((feedback) => feedback.reviewerAgentId !== feedback.subjectAgentId, {
550
    message: 'Feedback must concern another agent',
551
    path: ['subjectAgentId'],
552
  })
553
  .refine((feedback) => Object.keys(feedback.ratings).length > 0, {
554
    message: 'At least one feedback rating is required',
555
    path: ['ratings'],
556
  });
557
558
export const InteractionConclusionSchema = z
559
  .object({
560
    conclusionId: z.string().uuid(),
561
    interactionId: z.string().uuid(),
562
    contractHash: z.string().min(1),
563
    outcome: CompletionOutcomeSchema,
564
    consensus: z.enum([
565
      'bilateral_agreement',
566
      'bilateral_partial_agreement',
567
      'conflicting',
568
      'unilateral',
569
      'insufficient',
570
    ]),
571
    lifecycle: z.enum(['provisional', 'final']).optional(),
572
    confidence: z.number().min(0).max(1).optional(),
573
    missingReportAgentIds: z.array(z.string().min(1)).max(2).optional(),
574
    pendingFeedbackAgentIds: z.array(z.string().min(1)).max(2).optional(),
575
    peerReportStatus: z.enum(['awaiting', 'unreachable', 'received', 'timed_out']).optional(),
576
    summary: z.string().min(1).max(2000),
577
    criteria: z.array(SuccessCriterionAssessmentSchema).max(100),
578
    reportIds: z.array(z.string().uuid()).max(2),
579
    feedbackIds: z.array(z.string().uuid()).max(2),
580
    averageRatings: z.partialRecord(FeedbackDimensionSchema, z.number().min(0).max(1)).default({}),
581
    evidenceReferences: z.array(z.string().min(1).max(2048)).max(100).default([]),
582
    generatedAt: z.string().datetime(),
583
    platformAttestation: RecordAttestationSchema.optional(),
584
  })
585
  .strict();
586
587
export const LearningEligibilityDecisionSchema = z
588
  .object({
589
    decisionId: z.string().uuid(),
590
    interactionId: z.string().uuid(),
591
    eligible: z.boolean(),
592
    reasons: z.array(z.string().min(1).max(500)).min(1).max(50),
593
    sampleWeight: z.number().min(0).max(1),
594
    reportIds: z.array(z.string().uuid()).max(2),
595
    feedbackIds: z.array(z.string().uuid()).max(2),
596
    evidenceReferences: z.array(z.string().min(1).max(2048)).max(100).default([]),
597
    manipulationSignals: z.array(z.string().min(1).max(128)).max(20).default([]),
598
    contributionMode: z.enum(['local_only', 'network_aggregate']),
599
    structuredDataOnly: z.literal(true),
600
    decidedAt: z.string().datetime(),
601
    platformAttestation: RecordAttestationSchema.optional(),
602
  })
603
  .strict();
604
605
export const BehaviouralProfileDeltaSchema = z
606
  .object({
607
    deltaId: z.string().uuid(),
608
    interactionId: z.string().uuid(),
609
    agentId: z.string().min(1),
610
    agentVersion: z.string().min(1),
611
    taskCategory: z.string().min(1),
612
    sampleWeight: z.number().min(0).max(1),
613
    dimensionDeltas: z.record(z.string().min(1).max(128), z.number().min(-1).max(1)),
614
    explanation: z.string().min(1).max(2000),
615
    appliedAt: z.string().datetime(),
616
    platformAttestation: RecordAttestationSchema.optional(),
617
  })
618
  .strict();
619
620
export const BehaviouralDimensionNameSchema = z.enum([
621
  'completion',
622
  'acceptance',
623
  'specification',
624
  'deadline',
625
  'communication',
626
  'evidence',
627
  'scope',
628
  'correction',
629
  'limitations',
630
  'disputes',
631
]);
632
633
export const ContextualReliabilitySummarySchema = z
634
  .object({
635
    agentId: z.string().min(1),
636
    agentVersion: z.string().min(1),
637
    taskCategory: z.string().min(1),
638
    score: z.number().min(0).max(1),
639
    confidence: z.object({
640
      level: z.enum(['low', 'medium', 'high']),
641
      value: z.number().min(0).max(1),
642
      evidenceCount: z.number().int().nonnegative(),
643
      effectiveSampleSize: z.number().nonnegative(),
644
    }),
645
    trend: z.object({
646
      direction: z.enum(['improving', 'stable', 'declining']),
647
      delta: z.number().min(-1).max(1),
648
    }),
649
    strengths: z
650
      .array(
651
        z.object({
652
          dimension: BehaviouralDimensionNameSchema,
653
          score: z.number().min(0).max(1),
654
        }),
655
      )
656
      .max(3),
657
    risks: z
658
      .array(
659
        z.object({
660
          dimension: BehaviouralDimensionNameSchema,
661
          score: z.number().min(0).max(1),
662
          reason: z.string().min(1).max(300),
663
        }),
664
      )
665
      .max(3),
666
    versionStatus: z.object({
667
      currentVersion: z.string().min(1),
668
      evidenceVersion: z.string().min(1),
669
      status: z.enum(['current', 'reduced_confidence']),
670
    }),
671
    source: z.literal('private_verified_history'),
672
    updatedAt: z.string().datetime(),
673
  })
674
  .strict();
675
676
export const TrustEnvelopeSchema = z.object({
677
  protocolVersion: z.literal(PROTOCOL_VERSION),
678
  interactionId: z.string().uuid(),
679
  requestingAgentId: z.string(),
680
  respondingAgentId: z.string(),
681
  rootControllerId: z.string(),
682
  parentAgentId: z.string().optional(),
683
  agentVersion: z.string(),
684
  delegationId: z.string().uuid().optional(),
685
  requestedCapability: z.string(),
686
  taskCategory: z.string(),
687
  contractHash: z.string(),
688
  dataSharingMode: DataSharingModeSchema,
689
  evidenceRequirements: z.array(z.string()),
690
  timestamp: z.string().datetime(),
691
  expiresAt: z.string().datetime(),
692
  nonce: z.string().min(12),
693
  signature: SignatureSchema.optional(),
694
});
695
696
export const EventTypeSchema = z.enum([
697
  'claim',
698
  'evidence',
699
  'constraint',
700
  'commitment',
701
  'proposal',
702
  'objection',
703
  'policy_warning',
704
  'policy_violation',
705
  'private_suggestion',
706
  'shared_intervention',
707
  'delegation',
708
  'task_result',
709
  'resolution',
710
  'receipt',
711
  'feedback',
712
  'dispute',
713
]);
714
export const InteractionEventSchema = z.object({
715
  protocolVersion: z.literal(PROTOCOL_VERSION),
716
  eventId: z.string().uuid(),
717
  interactionId: z.string().uuid(),
718
  eventType: EventTypeSchema,
719
  agentId: z.string(),
720
  agentVersion: z.string(),
721
  timestamp: z.string().datetime(),
722
  visibility: VisibilitySchema,
723
  provenance: ProvenanceSchema,
724
  payloadHash: z.string(),
725
  payload: z.record(z.string(), z.unknown()),
726
  evidenceRefs: z.array(z.string()).optional(),
727
  signature: SignatureSchema.optional(),
728
});
729
730
export const FeedbackSchema = z.object({
731
  feedbackId: z.string().uuid(),
732
  interactionId: z.string().uuid(),
733
  receiptId: z.string().uuid(),
734
  reviewerAgentId: z.string(),
735
  subjectAgentId: z.string(),
736
  taskCompleted: z.boolean(),
737
  outputAccepted: z.boolean(),
738
  specificationMatched: z.boolean(),
739
  deadlineMet: z.boolean(),
740
  communicationQuality: z.number().min(0).max(1),
741
  evidenceProvided: z.boolean(),
742
  scopeRespected: z.boolean(),
743
  disputeRaised: z.boolean(),
744
  comment: z.string().max(1000).optional(),
745
  submittedAt: z.string().datetime(),
746
  signature: SignatureSchema.optional(),
747
});
748
749
export const ReceiptSchema = z.object({
750
  receiptId: z.string().uuid(),
751
  interactionId: z.string().uuid(),
752
  participants: z.array(z.string()),
753
  agentVersions: z.record(z.string(), z.string()),
754
  contractHash: z.string(),
755
  startedAt: z.string().datetime(),
756
  completedAt: z.string().datetime(),
757
  outcome: z.enum(['success', 'partial', 'failure', 'cancelled']),
758
  commitmentsFulfilled: z.array(z.string()),
759
  commitmentsMissed: z.array(z.string()),
760
  evidenceHashes: z.array(z.string()),
761
  policyWarnings: z.array(z.string()),
762
  policyViolations: z.array(z.string()),
763
  disputeStatus: z.enum(['none', 'open', 'resolved']),
764
  delegationChainHash: z.string(),
765
  unilateral: z.boolean(),
766
  provisional: z.boolean().optional(),
767
  confidence: z.number().min(0).max(1).optional(),
768
  signatures: z.record(z.string(), SignatureSchema).default({}),
769
  completionReportIds: z.array(z.string().uuid()).max(2).optional(),
770
  conclusionId: z.string().uuid().optional(),
771
  platformAttestation: RecordAttestationSchema.optional(),
772
});
773
774
export const RiskDecisionSchema = z.object({
775
  decision: z.enum(['ALLOW', 'CHALLENGE', 'DENY']),
776
  confidence: z.number().min(0).max(1),
777
  taskCategory: z.string(),
778
  sampleSize: z.number().int().nonnegative(),
779
  dimensions: z.record(z.string(), z.number()),
780
  reasons: z.array(z.string()),
781
  warnings: z.array(z.string()),
782
  dataFreshness: z.record(z.string(), z.string()),
783
  requiredChallenges: z.array(z.string()),
784
});
785
786
export const FactCheckResultSchema = z.object({
787
  claim: z.string(),
788
  claimType: z.enum(['objective', 'subjective', 'prediction']),
789
  status: z.enum([
790
    'verified',
791
    'supported',
792
    'contradicted',
793
    'unverified',
794
    'not_fact_checkable',
795
    'insufficient_permission',
796
  ]),
797
  confidence: z.number().min(0).max(1),
798
  evidenceReferences: z.array(z.string()),
799
  sourceAuthority: z.string(),
800
  sourceFreshness: z.string(),
801
  contradictingEvidence: z.array(z.string()),
802
  suggestedNextAction: z.string(),
803
});
804
805
export type AgentIdentity = z.infer<typeof AgentIdentitySchema>;
806
export type DelegationCredential = z.infer<typeof DelegationCredentialSchema>;
807
export type InteractionContract = z.infer<typeof InteractionContractSchema>;
808
export type TrustEnvelope = z.infer<typeof TrustEnvelopeSchema>;
809
export type InteractionEvent = z.infer<typeof InteractionEventSchema>;
810
export type Feedback = z.infer<typeof FeedbackSchema>;
811
export type Receipt = z.infer<typeof ReceiptSchema>;
812
export type RiskDecision = z.infer<typeof RiskDecisionSchema>;
813
export type FactCheckResult = z.infer<typeof FactCheckResultSchema>;
814
export type ExpectationManifest = z.infer<typeof ExpectationManifestSchema>;
815
export type AgentTransport = z.infer<typeof AgentTransportSchema>;
816
export type AgentMode = z.infer<typeof AgentModeSchema>;
817
export type AgentPresence = z.infer<typeof AgentPresenceSchema>;
818
export type PublicAgentVerification = z.infer<typeof PublicAgentVerificationSchema>;
819
export type PublicAgentCard = z.infer<typeof PublicAgentCardSchema>;
820
export type AgentResolution = z.infer<typeof AgentResolutionSchema>;
821
export type HostedMessage = z.infer<typeof HostedMessageSchema>;
822
export type HostedThread = z.infer<typeof HostedThreadSchema>;
823
export type ContractAcceptance = z.infer<typeof ContractAcceptanceSchema>;
824
export type ContractRevision = z.infer<typeof ContractRevisionSchema>;
825
export type FederatedInteraction = z.infer<typeof FederatedInteractionSchema>;
826
export type LiveSessionInsight = z.infer<typeof LiveSessionInsightSchema>;
827
export type LiveSessionOffer = z.infer<typeof LiveSessionOfferSchema>;
828
export type LiveSessionAcceptance = z.infer<typeof LiveSessionAcceptanceSchema>;
829
export type LiveSessionActivation = z.infer<typeof LiveSessionActivationSchema>;
830
export type LiveSessionEvent = z.infer<typeof LiveSessionEventSchema>;
831
export type RequirementAssessment = z.infer<typeof RequirementAssessmentSchema>;
832
export type CounterpartyBrief = z.infer<typeof CounterpartyBriefSchema>;
833
export type CompletionOutcome = z.infer<typeof CompletionOutcomeSchema>;
834
export type SuccessCriterionAssessment = z.infer<typeof SuccessCriterionAssessmentSchema>;
835
export type InteractionCompletionReport = z.infer<typeof InteractionCompletionReportSchema>;
836
export type FeedbackDimension = z.infer<typeof FeedbackDimensionSchema>;
837
export type FeedbackRequest = z.infer<typeof FeedbackRequestSchema>;
838
export type InteractionFeedback = z.infer<typeof InteractionFeedbackSchema>;
839
export type InteractionConclusion = z.infer<typeof InteractionConclusionSchema>;
840
export type LearningEligibilityDecision = z.infer<typeof LearningEligibilityDecisionSchema>;
841
export type BehaviouralProfileDelta = z.infer<typeof BehaviouralProfileDeltaSchema>;
842
export type BehaviouralDimensionName = z.infer<typeof BehaviouralDimensionNameSchema>;
843
export type ContextualReliabilitySummary = z.infer<typeof ContextualReliabilitySummarySchema>;
844
export type RecordAttestation = z.infer<typeof RecordAttestationSchema>;
845
846
export interface KeyPair {
847
  keyId: string;
848
  publicKey: string;
849
  privateKey: string;
850
}
851
852
export function createKeyPair(keyId: string = randomUUID()): KeyPair {
853
  const pair = generateKeyPairSync('ed25519');
854
  return {
855
    keyId,
856
    publicKey: pair.publicKey.export({ type: 'spki', format: 'der' }).toString('base64url'),
857
    privateKey: pair.privateKey.export({ type: 'pkcs8', format: 'der' }).toString('base64url'),
858
  };
859
}
860
861
export function canonicalHash(value: unknown): string {
862
  return createHash('sha256').update(canonicalize(value)).digest('base64url');
863
}
864
865
export function verifyRecordAttestation(
866
  value: Record<string, unknown>,
867
  publicKey: string,
868
): boolean {
869
  const attestation = RecordAttestationSchema.safeParse(value.platformAttestation);
870
  if (!attestation.success) return false;
871
  const unsignedValue = structuredClone(value);
872
  delete unsignedValue.platformAttestation;
873
  const digest = canonicalHash(unsignedValue);
874
  if (digest !== attestation.data.digest) return false;
875
  return verify(
876
    null,
877
    Buffer.from(digest),
878
    {
879
      key: Buffer.from(publicKey, 'base64url'),
880
      type: 'spki',
881
      format: 'der',
882
    },
883
    Buffer.from(attestation.data.value, 'base64url'),
884
  );
885
}
886
887
function unsigned(value: Record<string, unknown>): Record<string, unknown> {
888
  const copy = structuredClone(value);
889
  delete copy.signature;
890
  delete copy.signatures;
891
  return copy;
892
}
893
894
export function signObject<T extends Record<string, unknown>>(
895
  value: T,
896
  key: KeyPair,
897
): T & { signature: z.infer<typeof SignatureSchema> } {
898
  const payload = Buffer.from(canonicalize(unsigned(value)));
899
  const privateKey = {
900
    key: Buffer.from(key.privateKey, 'base64url'),
901
    type: 'pkcs8' as const,
902
    format: 'der' as const,
903
  };
904
  return {
905
    ...value,
906
    signature: {
907
      algorithm: 'Ed25519',
908
      keyId: key.keyId,
909
      value: sign(null, payload, privateKey).toString('base64url'),
910
    },
911
  };
912
}
913
914
export function verifyObject(value: Record<string, unknown>, publicKey: string): boolean {
915
  const parsed = SignatureSchema.safeParse(value.signature);
916
  if (!parsed.success) return false;
917
  const key = {
918
    key: Buffer.from(publicKey, 'base64url'),
919
    type: 'spki' as const,
920
    format: 'der' as const,
921
  };
922
  return verify(
923
    null,
924
    Buffer.from(canonicalize(unsigned(value))),
925
    key,
926
    Buffer.from(parsed.data.value, 'base64url'),
927
  );
928
}
929
930
export function signNamed<T extends Record<string, unknown>>(
931
  value: T,
932
  signerId: string,
933
  key: KeyPair,
934
): T & { signatures: Record<string, z.infer<typeof SignatureSchema>> } {
935
  const payload = Buffer.from(canonicalize(unsigned(value)));
936
  const privateKey = {
937
    key: Buffer.from(key.privateKey, 'base64url'),
938
    type: 'pkcs8' as const,
939
    format: 'der' as const,
940
  };
941
  const signature = {
942
    algorithm: 'Ed25519' as const,
943
    keyId: key.keyId,
944
    value: sign(null, payload, privateKey).toString('base64url'),
945
  };
946
  return {
947
    ...value,
948
    signatures: { ...((value.signatures as object) ?? {}), [signerId]: signature },
949
  };
950
}
951
952
export function verifyNamed(
953
  value: Record<string, unknown>,
954
  signerId: string,
955
  publicKey: string,
956
): boolean {
957
  const signatures = value.signatures as Record<string, unknown> | undefined;
958
  const parsed = SignatureSchema.safeParse(signatures?.[signerId]);
959
  if (!parsed.success) return false;
960
  const key = {
961
    key: Buffer.from(publicKey, 'base64url'),
962
    type: 'spki' as const,
963
    format: 'der' as const,
964
  };
965
  return verify(
966
    null,
967
    Buffer.from(canonicalize(unsigned(value))),
968
    key,
969
    Buffer.from(parsed.data.value, 'base64url'),
970
  );
971
}