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

Viewing files at commit b506ef3834f7Commit detailsView latest

packages/protocol/src/index.ts 439 lines · 14.0 KB · typescript
1
import { createHash, generateKeyPairSync, randomUUID, sign, verify } from 'node:crypto';
2
import { canonicalize } from 'json-canonicalize';
3
import { z } from 'zod';
4
5
export const PROTOCOL_VERSION = '0.1' as const;
6
export const DEFAULT_EXTENSION_URI = 'https://openclasp.vercel.app/extensions/trust/v0.1';
7
8
export const ProvenanceSchema = z.enum([
9
  'self_declared',
10
  'operator_attested',
11
  'cryptographically_verified',
12
  'domain_verified',
13
  'third_party_verified',
14
  'observed',
15
  'disputed',
16
]);
17
export const VisibilitySchema = z.enum([
18
  'local_only',
19
  'private_requester',
20
  'private_responder',
21
  'shared_participants',
22
  'network_aggregate',
23
]);
24
export const DataSharingModeSchema = z.enum([
25
  'local_only',
26
  'structured_only',
27
  'permitted_evidence',
28
]);
29
export const SignatureSchema = z.object({
30
  algorithm: z.literal('Ed25519'),
31
  keyId: z.string().min(1),
32
  value: z.string().min(1),
33
});
34
35
export const ExpectationManifestSchema = z.object({
36
  requiredInputs: z.array(z.string()).default([]),
37
  supportedTaskCategories: z.array(z.string()).min(1),
38
  responseTimeMs: z.number().int().positive().optional(),
39
  dataRequirements: z.array(z.string()).default([]),
40
  prohibitedData: z.array(z.string()).default([]),
41
  humanApprovalThresholds: z.array(z.string()).default([]),
42
  delegationPolicy: z.string().default('explicit'),
43
  evidenceRequirements: z.array(z.string()).default([]),
44
  factCheckingPreference: z
45
    .enum(['none', 'important_claims', 'all_objective_claims'])
46
    .default('important_claims'),
47
  mediationPreference: z.enum(['never', 'mutual_consent']).default('mutual_consent'),
48
  retentionDays: z.number().int().nonnegative().default(30),
49
  communicationFormats: z.array(z.string()).default(['text/plain']),
50
  cancellationPolicy: z.string().default('either_party'),
51
  provenance: ProvenanceSchema,
52
});
53
54
export const AgentIdentitySchema = z.object({
55
  protocolVersion: z.literal(PROTOCOL_VERSION),
56
  agentId: z.string().min(1),
57
  publicKey: z.string().min(1),
58
  keyId: z.string().min(1),
59
  operatorRef: z.string().min(1),
60
  assurance: z.enum(['pseudonymous', 'domain_associated', 'organization_associated']),
61
  agentVersion: z.string().min(1),
62
  capabilities: z.array(z.string()),
63
  supportedProtocols: z.array(z.string()),
64
  parentAgentId: z.string().optional(),
65
  rootControllerId: z.string().min(1),
66
  revoked: z.boolean().default(false),
67
  provenance: ProvenanceSchema,
68
  createdAt: z.string().datetime(),
69
  signature: SignatureSchema.optional(),
70
});
71
72
export const DelegationCredentialSchema = z.object({
73
  protocolVersion: z.literal(PROTOCOL_VERSION),
74
  delegationId: z.string().uuid(),
75
  parentAgentId: z.string(),
76
  childAgentId: z.string(),
77
  rootControllerId: z.string(),
78
  capabilities: z.array(z.string()).min(1),
79
  issuedAt: z.string().datetime(),
80
  expiresAt: z.string().datetime(),
81
  revoked: z.boolean().default(false),
82
  signature: SignatureSchema.optional(),
83
});
84
85
export const InteractionContractSchema = z.object({
86
  protocolVersion: z.literal(PROTOCOL_VERSION),
87
  interactionId: z.string().uuid(),
88
  purpose: z.string(),
89
  parties: z.array(z.string()).min(1),
90
  taskCategory: z.string(),
91
  requestedOutcome: z.string(),
92
  successCriteria: z.array(z.string()),
93
  allowedActions: z.array(z.string()),
94
  prohibitedActions: z.array(z.string()),
95
  allowedData: z.array(z.string()),
96
  prohibitedData: z.array(z.string()),
97
  deadline: z.string().datetime().optional(),
98
  evidenceRequirements: z.array(z.string()),
99
  delegationRules: z.array(z.string()),
100
  humanApprovalRequirements: z.array(z.string()),
101
  factCheckingPolicy: z.string(),
102
  mediationPolicy: z.enum(['none', 'mutual_consent']),
103
  retentionDays: z.number().int().nonnegative(),
104
  completionConditions: z.array(z.string()),
105
  cancellationConditions: z.array(z.string()),
106
  signatures: z.record(z.string(), SignatureSchema).default({}),
107
});
108
109
export const AgentTransportSchema = z.object({
110
  protocol: z.literal('A2A/1.0'),
111
  protocolBinding: z.string().min(1).default('JSONRPC'),
112
  endpoint: z.string().url(),
113
  managedBy: z.literal('openclasp').default('openclasp'),
114
});
115
116
export const AgentPresenceSchema = z.object({
117
  status: z.enum(['online', 'offline']),
118
  lastSeenAt: z.string().datetime().optional(),
119
  checkedAt: z.string().datetime(),
120
});
121
122
export const RuntimeDeliverySchema = z.object({
123
  type: z.literal('openclasp.a2a.delivery'),
124
  version: z.literal('1'),
125
  deliveryId: z.string().uuid(),
126
  agentId: z.string().min(1),
127
  interactionId: z.string().uuid(),
128
  receivedAt: z.string().datetime(),
129
  message: z.object({
130
    messageId: z.string().uuid(),
131
    senderAgentId: z.string().min(1),
132
    contentType: z.string().min(1),
133
    createdAt: z.string().datetime(),
134
    expiresAt: z.string().datetime(),
135
    payload: z.unknown(),
136
  }),
137
  reply: z.object({
138
    endpoint: z.string().url(),
139
    bearerToken: z.string().min(1),
140
  }),
141
});
142
143
export const PublicAgentCardSchema = z.object({
144
  protocolVersion: z.literal(PROTOCOL_VERSION),
145
  agentId: z.string().min(1),
146
  name: z.string().min(1),
147
  description: z.string(),
148
  framework: z.string().min(1),
149
  agentVersion: z.string().min(1),
150
  capabilities: z.array(z.string()),
151
  limitations: z.array(z.string()),
152
  assurance: z.enum(['oauth_authenticated', 'cryptographically_verified']),
153
  transports: z.array(AgentTransportSchema),
154
  cardUrl: z.string().url(),
155
  a2aAgentCardUrl: z.string().url(),
156
  extensionUri: z.string().url(),
157
  presence: AgentPresenceSchema.optional(),
158
  publishedAt: z.string().datetime(),
159
  updatedAt: z.string().datetime(),
160
});
161
162
export const ContractAcceptanceSchema = z.object({
163
  agentId: z.string().min(1),
164
  method: z.enum(['oauth_installation', 'oauth_account', 'policy_auto_accept', 'ed25519']),
165
  termsHash: z.string().min(1),
166
  acceptedAt: z.string().datetime(),
167
  signature: SignatureSchema.optional(),
168
});
169
170
export const FederatedInteractionStatusSchema = z.enum([
171
  'pending',
172
  'active',
173
  'rejected',
174
  'expired',
175
  'cancelled',
176
  'completed',
177
]);
178
179
export const FederatedInteractionSchema = z.object({
180
  protocolVersion: z.literal(PROTOCOL_VERSION),
181
  interactionId: z.string().uuid(),
182
  initiatorAgentId: z.string().min(1),
183
  responderAgentId: z.string().min(1),
184
  status: FederatedInteractionStatusSchema,
185
  contract: InteractionContractSchema,
186
  termsHash: z.string().min(1),
187
  acceptances: z.record(z.string(), ContractAcceptanceSchema),
188
  initiatorTransport: AgentTransportSchema.optional(),
189
  responderTransport: AgentTransportSchema,
190
  createdAt: z.string().datetime(),
191
  updatedAt: z.string().datetime(),
192
  expiresAt: z.string().datetime(),
193
});
194
195
export const TrustEnvelopeSchema = z.object({
196
  protocolVersion: z.literal(PROTOCOL_VERSION),
197
  interactionId: z.string().uuid(),
198
  requestingAgentId: z.string(),
199
  respondingAgentId: z.string(),
200
  rootControllerId: z.string(),
201
  parentAgentId: z.string().optional(),
202
  agentVersion: z.string(),
203
  delegationId: z.string().uuid().optional(),
204
  requestedCapability: z.string(),
205
  taskCategory: z.string(),
206
  contractHash: z.string(),
207
  dataSharingMode: DataSharingModeSchema,
208
  evidenceRequirements: z.array(z.string()),
209
  timestamp: z.string().datetime(),
210
  expiresAt: z.string().datetime(),
211
  nonce: z.string().min(12),
212
  signature: SignatureSchema.optional(),
213
});
214
215
export const EventTypeSchema = z.enum([
216
  'claim',
217
  'evidence',
218
  'constraint',
219
  'commitment',
220
  'proposal',
221
  'objection',
222
  'policy_warning',
223
  'policy_violation',
224
  'private_suggestion',
225
  'shared_intervention',
226
  'delegation',
227
  'task_result',
228
  'resolution',
229
  'receipt',
230
  'feedback',
231
  'dispute',
232
]);
233
export const InteractionEventSchema = z.object({
234
  protocolVersion: z.literal(PROTOCOL_VERSION),
235
  eventId: z.string().uuid(),
236
  interactionId: z.string().uuid(),
237
  eventType: EventTypeSchema,
238
  agentId: z.string(),
239
  agentVersion: z.string(),
240
  timestamp: z.string().datetime(),
241
  visibility: VisibilitySchema,
242
  provenance: ProvenanceSchema,
243
  payloadHash: z.string(),
244
  payload: z.record(z.string(), z.unknown()),
245
  evidenceRefs: z.array(z.string()).optional(),
246
  signature: SignatureSchema.optional(),
247
});
248
249
export const FeedbackSchema = z.object({
250
  feedbackId: z.string().uuid(),
251
  interactionId: z.string().uuid(),
252
  receiptId: z.string().uuid(),
253
  reviewerAgentId: z.string(),
254
  subjectAgentId: z.string(),
255
  taskCompleted: z.boolean(),
256
  outputAccepted: z.boolean(),
257
  specificationMatched: z.boolean(),
258
  deadlineMet: z.boolean(),
259
  communicationQuality: z.number().min(0).max(1),
260
  evidenceProvided: z.boolean(),
261
  scopeRespected: z.boolean(),
262
  disputeRaised: z.boolean(),
263
  comment: z.string().max(1000).optional(),
264
  submittedAt: z.string().datetime(),
265
  signature: SignatureSchema.optional(),
266
});
267
268
export const ReceiptSchema = z.object({
269
  receiptId: z.string().uuid(),
270
  interactionId: z.string().uuid(),
271
  participants: z.array(z.string()),
272
  agentVersions: z.record(z.string(), z.string()),
273
  contractHash: z.string(),
274
  startedAt: z.string().datetime(),
275
  completedAt: z.string().datetime(),
276
  outcome: z.enum(['success', 'partial', 'failure', 'cancelled']),
277
  commitmentsFulfilled: z.array(z.string()),
278
  commitmentsMissed: z.array(z.string()),
279
  evidenceHashes: z.array(z.string()),
280
  policyWarnings: z.array(z.string()),
281
  policyViolations: z.array(z.string()),
282
  disputeStatus: z.enum(['none', 'open', 'resolved']),
283
  delegationChainHash: z.string(),
284
  unilateral: z.boolean(),
285
  signatures: z.record(z.string(), SignatureSchema).default({}),
286
});
287
288
export const RiskDecisionSchema = z.object({
289
  decision: z.enum(['ALLOW', 'CHALLENGE', 'DENY']),
290
  confidence: z.number().min(0).max(1),
291
  taskCategory: z.string(),
292
  sampleSize: z.number().int().nonnegative(),
293
  dimensions: z.record(z.string(), z.number()),
294
  reasons: z.array(z.string()),
295
  warnings: z.array(z.string()),
296
  dataFreshness: z.record(z.string(), z.string()),
297
  requiredChallenges: z.array(z.string()),
298
});
299
300
export const FactCheckResultSchema = z.object({
301
  claim: z.string(),
302
  claimType: z.enum(['objective', 'subjective', 'prediction']),
303
  status: z.enum([
304
    'verified',
305
    'supported',
306
    'contradicted',
307
    'unverified',
308
    'not_fact_checkable',
309
    'insufficient_permission',
310
  ]),
311
  confidence: z.number().min(0).max(1),
312
  evidenceReferences: z.array(z.string()),
313
  sourceAuthority: z.string(),
314
  sourceFreshness: z.string(),
315
  contradictingEvidence: z.array(z.string()),
316
  suggestedNextAction: z.string(),
317
});
318
319
export type AgentIdentity = z.infer<typeof AgentIdentitySchema>;
320
export type DelegationCredential = z.infer<typeof DelegationCredentialSchema>;
321
export type InteractionContract = z.infer<typeof InteractionContractSchema>;
322
export type TrustEnvelope = z.infer<typeof TrustEnvelopeSchema>;
323
export type InteractionEvent = z.infer<typeof InteractionEventSchema>;
324
export type Feedback = z.infer<typeof FeedbackSchema>;
325
export type Receipt = z.infer<typeof ReceiptSchema>;
326
export type RiskDecision = z.infer<typeof RiskDecisionSchema>;
327
export type FactCheckResult = z.infer<typeof FactCheckResultSchema>;
328
export type ExpectationManifest = z.infer<typeof ExpectationManifestSchema>;
329
export type AgentTransport = z.infer<typeof AgentTransportSchema>;
330
export type AgentPresence = z.infer<typeof AgentPresenceSchema>;
331
export type RuntimeDelivery = z.infer<typeof RuntimeDeliverySchema>;
332
export type PublicAgentCard = z.infer<typeof PublicAgentCardSchema>;
333
export type ContractAcceptance = z.infer<typeof ContractAcceptanceSchema>;
334
export type FederatedInteraction = z.infer<typeof FederatedInteractionSchema>;
335
336
export interface KeyPair {
337
  keyId: string;
338
  publicKey: string;
339
  privateKey: string;
340
}
341
342
export function createKeyPair(keyId: string = randomUUID()): KeyPair {
343
  const pair = generateKeyPairSync('ed25519');
344
  return {
345
    keyId,
346
    publicKey: pair.publicKey.export({ type: 'spki', format: 'der' }).toString('base64url'),
347
    privateKey: pair.privateKey.export({ type: 'pkcs8', format: 'der' }).toString('base64url'),
348
  };
349
}
350
351
export function canonicalHash(value: unknown): string {
352
  return createHash('sha256').update(canonicalize(value)).digest('base64url');
353
}
354
355
function unsigned(value: Record<string, unknown>): Record<string, unknown> {
356
  const copy = structuredClone(value);
357
  delete copy.signature;
358
  delete copy.signatures;
359
  return copy;
360
}
361
362
export function signObject<T extends Record<string, unknown>>(
363
  value: T,
364
  key: KeyPair,
365
): T & { signature: z.infer<typeof SignatureSchema> } {
366
  const payload = Buffer.from(canonicalize(unsigned(value)));
367
  const privateKey = {
368
    key: Buffer.from(key.privateKey, 'base64url'),
369
    type: 'pkcs8' as const,
370
    format: 'der' as const,
371
  };
372
  return {
373
    ...value,
374
    signature: {
375
      algorithm: 'Ed25519',
376
      keyId: key.keyId,
377
      value: sign(null, payload, privateKey).toString('base64url'),
378
    },
379
  };
380
}
381
382
export function verifyObject(value: Record<string, unknown>, publicKey: string): boolean {
383
  const parsed = SignatureSchema.safeParse(value.signature);
384
  if (!parsed.success) return false;
385
  const key = {
386
    key: Buffer.from(publicKey, 'base64url'),
387
    type: 'spki' as const,
388
    format: 'der' as const,
389
  };
390
  return verify(
391
    null,
392
    Buffer.from(canonicalize(unsigned(value))),
393
    key,
394
    Buffer.from(parsed.data.value, 'base64url'),
395
  );
396
}
397
398
export function signNamed<T extends Record<string, unknown>>(
399
  value: T,
400
  signerId: string,
401
  key: KeyPair,
402
): T & { signatures: Record<string, z.infer<typeof SignatureSchema>> } {
403
  const payload = Buffer.from(canonicalize(unsigned(value)));
404
  const privateKey = {
405
    key: Buffer.from(key.privateKey, 'base64url'),
406
    type: 'pkcs8' as const,
407
    format: 'der' as const,
408
  };
409
  const signature = {
410
    algorithm: 'Ed25519' as const,
411
    keyId: key.keyId,
412
    value: sign(null, payload, privateKey).toString('base64url'),
413
  };
414
  return {
415
    ...value,
416
    signatures: { ...((value.signatures as object) ?? {}), [signerId]: signature },
417
  };
418
}
419
420
export function verifyNamed(
421
  value: Record<string, unknown>,
422
  signerId: string,
423
  publicKey: string,
424
): boolean {
425
  const signatures = value.signatures as Record<string, unknown> | undefined;
426
  const parsed = SignatureSchema.safeParse(signatures?.[signerId]);
427
  if (!parsed.success) return false;
428
  const key = {
429
    key: Buffer.from(publicKey, 'base64url'),
430
    type: 'spki' as const,
431
    format: 'der' as const,
432
  };
433
  return verify(
434
    null,
435
    Buffer.from(canonicalize(unsigned(value))),
436
    key,
437
    Buffer.from(parsed.data.value, 'base64url'),
438
  );
439
}