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

Viewing files at commit 4818f40a6317Commit detailsView latest

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