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

Viewing files at commit 89b58109a0e6Commit detailsView latest

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