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

Viewing files at commit ce192002f08aCommit detailsView latest

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