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

Viewing files at commit 8a15fe87b720Commit detailsView latest

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