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

Viewing files at commit fd993cf6d9f8Commit detailsView latest

apps/demo/src/scenario.ts 299 lines · 10.9 KB · typescript
1
import { randomUUID } from 'node:crypto';
2
import {
3
  canonicalHash,
4
  signNamed,
5
  signObject,
6
  type Feedback,
7
  type InteractionContract,
8
  type Receipt,
9
} from '../../../packages/protocol/src/index.js';
10
import {
11
  createIdentity,
12
  FixtureFactCheckProvider,
13
  TrustEngine,
14
} from '../../../packages/core/src/index.js';
15
import { createSignedEnvelope, createSignedEvent } from '../../../packages/sdk/src/index.js';
16
import { OpenClaspSidecar, openClaspA2AExtension } from '../../../packages/sidecar/src/index.js';
17
18
export async function runDemo(log: (line: string) => void = () => undefined) {
19
  const engine = new TrustEngine();
20
  const requester = createIdentity({
21
    agentId: 'agent:requester',
22
    operatorRef: 'operator:acme',
23
    capabilities: ['research.request'],
24
  });
25
  const provider = createIdentity({
26
    agentId: 'agent:provider',
27
    operatorRef: 'operator:labs',
28
    capabilities: ['research.answer', 'research.delegate'],
29
  });
30
  const subagent = createIdentity({
31
    agentId: 'agent:provider:subagent',
32
    operatorRef: 'operator:labs',
33
    capabilities: ['research.answer'],
34
    parentAgentId: provider.identity.agentId,
35
    rootControllerId: provider.identity.rootControllerId,
36
  });
37
  for (const agent of [requester, provider, subagent]) engine.registerAgent(agent.identity);
38
  log('1. Created and verified requester, provider, and subagent identities');
39
40
  const delegation = engine.createDelegation(
41
    provider.identity.agentId,
42
    subagent.identity.agentId,
43
    ['research.answer'],
44
    new Date(Date.now() + 60_000).toISOString(),
45
    provider.keyPair,
46
  );
47
  if (!engine.verifyDelegation(delegation.delegationId, 'research.answer'))
48
    throw new Error('Delegation verification failed');
49
  log('2. Verified scoped parent/subagent delegation');
50
51
  const interactionId = randomUUID();
52
  const baseContract: InteractionContract = {
53
    protocolVersion: '0.1',
54
    interactionId,
55
    purpose: 'Summarize a public research fixture',
56
    parties: [requester.identity.agentId, provider.identity.agentId],
57
    taskCategory: 'research.summary',
58
    requestedOutcome: 'Evidence-backed summary',
59
    successCriteria: ['Citations included'],
60
    allowedActions: ['research.answer'],
61
    prohibitedActions: ['publish.private_data'],
62
    allowedData: ['public'],
63
    prohibitedData: ['private'],
64
    evidenceRequirements: ['authoritative_source'],
65
    delegationRules: ['research.answer'],
66
    humanApprovalRequirements: ['publish'],
67
    factCheckingPolicy: 'important_claims',
68
    mediationPolicy: 'mutual_consent',
69
    retentionDays: 30,
70
    completionConditions: ['summary_delivered'],
71
    cancellationConditions: ['either_party'],
72
    signatures: {},
73
  };
74
  const requesterSigned = signNamed(
75
    baseContract as unknown as Record<string, unknown>,
76
    requester.identity.agentId,
77
    requester.keyPair,
78
  ) as unknown as InteractionContract;
79
  const contract = signNamed(
80
    requesterSigned as unknown as Record<string, unknown>,
81
    provider.identity.agentId,
82
    provider.keyPair,
83
  ) as unknown as InteractionContract;
84
  engine.saveContract(contract);
85
  log('3. Exchanged expectations and signed a minimal interaction contract');
86
87
  engine.setContributionConsent(provider.identity.agentId, true);
88
  const contractHash = canonicalHash({ ...contract, signatures: {} });
89
  const envelope = createSignedEnvelope(
90
    {
91
      protocolVersion: '0.1',
92
      interactionId,
93
      requestingAgentId: requester.identity.agentId,
94
      respondingAgentId: provider.identity.agentId,
95
      rootControllerId: requester.identity.rootControllerId,
96
      agentVersion: requester.identity.agentVersion,
97
      requestedCapability: 'research.answer',
98
      taskCategory: 'research.summary',
99
      contractHash,
100
      dataSharingMode: 'structured_only',
101
      evidenceRequirements: ['authoritative_source'],
102
      timestamp: new Date().toISOString(),
103
      expiresAt: new Date(Date.now() + 60_000).toISOString(),
104
      nonce: randomUUID(),
105
    },
106
    requester.keyPair,
107
  );
108
  const claim = 'OpenClasp uses a blockchain';
109
  const claimEvent = createSignedEvent(
110
    {
111
      protocolVersion: '0.1',
112
      eventId: randomUUID(),
113
      interactionId,
114
      eventType: 'claim',
115
      agentId: provider.identity.agentId,
116
      agentVersion: provider.identity.agentVersion,
117
      timestamp: new Date().toISOString(),
118
      visibility: 'network_aggregate',
119
      provenance: 'observed',
120
      payload: { claim },
121
      evidenceRefs: [],
122
    },
123
    provider.keyPair,
124
  );
125
  const sidecar = new OpenClaspSidecar(engine);
126
  const extension = openClaspA2AExtension();
127
  const forwarding = await sidecar.forward({
128
    message: {
129
      role: 'ROLE_USER',
130
      parts: [{ text: 'Provide the summary' }],
131
      extensions: [extension.uri ?? ''],
132
      metadata: { [extension.uri ?? '']: envelope, rawMessage: 'must stay local' },
133
    },
134
    envelope,
135
    policy: { action: 'research.answer', dataClasses: ['public'] },
136
    structuredEvents: [claimEvent],
137
    send: async () => ({ kind: 'message', text: claim }),
138
  });
139
  if (
140
    !forwarding.forwarded ||
141
    JSON.stringify(forwarding.networkPayloads).includes('rawMessage') ||
142
    JSON.stringify(forwarding.networkPayloads).includes(claim)
143
  )
144
    throw new Error('Structured-only privacy boundary failed');
145
  log('4. Forwarded an A2A message with trust metadata; raw content stayed local');
146
147
  const factChecker = new FixtureFactCheckProvider({
148
    [claim]: { status: 'contradicted', evidence: ['docs:data-boundary#no-blockchain'] },
149
  });
150
  const check = await factChecker.check(claim);
151
  if (check.status !== 'contradicted') throw new Error('Fact check did not contradict fixture');
152
  log('5. Issued a private evidence-backed contradiction warning');
153
154
  const blockedEnvelope = createSignedEnvelope(
155
    { ...envelope, nonce: randomUUID(), timestamp: new Date().toISOString() },
156
    requester.keyPair,
157
  );
158
  const blocked = engine.assess({
159
    envelope: blockedEnvelope,
160
    action: 'publish.private_data',
161
    dataClasses: ['private'],
162
  });
163
  if (blocked.decision !== 'DENY') throw new Error('Scope violation was not blocked');
164
  log('6. Blocked deterministic out-of-scope and prohibited-data action');
165
166
  const conflict = engine.createConflict({
167
    interactionId,
168
    issue: 'Unsupported factual claim',
169
    participants: [requester.identity.agentId, provider.identity.agentId],
170
    positions: {
171
      [requester.identity.agentId]: 'Claim is contradicted',
172
      [provider.identity.agentId]: 'Claim should be corrected',
173
    },
174
    evidence: check.evidenceReferences,
175
    contractClauses: ['evidenceRequirements'],
176
    missingInformation: [],
177
    possibleResolutions: ['Correct the claim and cite the architecture'],
178
  });
179
  engine.permitMediation(conflict.conflictId, requester.identity.agentId);
180
  engine.permitMediation(conflict.conflictId, provider.identity.agentId);
181
  engine.resolveConflict(conflict.conflictId, 'Provider corrected the claim');
182
  log('7. Opened and resolved a mutually consented conflict');
183
184
  const baseReceipt: Receipt = {
185
    receiptId: randomUUID(),
186
    interactionId,
187
    participants: [requester.identity.agentId, provider.identity.agentId],
188
    agentVersions: { [requester.identity.agentId]: '1.0.0', [provider.identity.agentId]: '1.0.0' },
189
    contractHash,
190
    startedAt: contract.signatures[requester.identity.agentId]
191
      ? new Date(Date.now() - 5000).toISOString()
192
      : new Date().toISOString(),
193
    completedAt: new Date().toISOString(),
194
    outcome: 'success',
195
    commitmentsFulfilled: ['summary_delivered'],
196
    commitmentsMissed: [],
197
    evidenceHashes: [canonicalHash(check.evidenceReferences)],
198
    policyWarnings: ['contradicted_claim'],
199
    policyViolations: ['blocked_scope_attempt'],
200
    disputeStatus: 'resolved',
201
    delegationChainHash: canonicalHash(delegation),
202
    unilateral: false,
203
    signatures: {},
204
  };
205
  const receiptOne = signNamed(
206
    baseReceipt as unknown as Record<string, unknown>,
207
    requester.identity.agentId,
208
    requester.keyPair,
209
  ) as unknown as Receipt;
210
  const receipt = signNamed(
211
    receiptOne as unknown as Record<string, unknown>,
212
    provider.identity.agentId,
213
    provider.keyPair,
214
  ) as unknown as Receipt;
215
  engine.submitReceipt(receipt);
216
  log('8. Independently verified a bilateral completion receipt');
217
218
  const feedbackBase = (
219
    reviewer: typeof requester,
220
    subject: typeof provider,
221
  ): Omit<Feedback, 'signature'> => ({
222
    feedbackId: randomUUID(),
223
    interactionId,
224
    receiptId: receipt.receiptId,
225
    reviewerAgentId: reviewer.identity.agentId,
226
    subjectAgentId: subject.identity.agentId,
227
    taskCompleted: true,
228
    outputAccepted: true,
229
    specificationMatched: true,
230
    deadlineMet: true,
231
    communicationQuality: 0.9,
232
    evidenceProvided: true,
233
    scopeRespected: true,
234
    disputeRaised: false,
235
    submittedAt: new Date().toISOString(),
236
  });
237
  const firstFeedback = signObject(
238
    feedbackBase(requester, provider),
239
    requester.keyPair,
240
  ) as Feedback;
241
  const secondFeedback = signObject(
242
    feedbackBase(provider, requester),
243
    provider.keyPair,
244
  ) as Feedback;
245
  if (engine.submitFeedback(firstFeedback).revealed) throw new Error('Feedback revealed too early');
246
  if (!engine.submitFeedback(secondFeedback).revealed)
247
    throw new Error('Bilateral feedback was not revealed');
248
  const learnedRisk = engine.getRisk(provider.identity.agentId, '1.0.0', 'research.summary');
249
  const newVersionRisk = engine.getRisk(provider.identity.agentId, '2.0.0', 'research.summary');
250
  if (learnedRisk.sampleSize !== 1 || newVersionRisk.confidence >= learnedRisk.confidence)
251
    throw new Error('Behavioural learning/version reduction failed');
252
  log('9. Updated contextual history; new versions inherit reduced confidence');
253
254
  const tampered = { ...receipt, outcome: 'failure' as const };
255
  let tamperRejected = false;
256
  try {
257
    engine.submitReceipt(tampered);
258
  } catch {
259
    tamperRejected = true;
260
  }
261
  const expired = engine.createDelegation(
262
    provider.identity.agentId,
263
    subagent.identity.agentId,
264
    ['research.answer'],
265
    new Date(Date.now() - 1000).toISOString(),
266
    provider.keyPair,
267
  );
268
  const replay = engine.assess({ envelope, action: 'research.answer' });
269
  const invalidIdentity = { ...requester.identity, operatorRef: 'operator:attacker' };
270
  let invalidRejected = false;
271
  try {
272
    engine.registerAgent(invalidIdentity);
273
  } catch {
274
    invalidRejected = true;
275
  }
276
  if (
277
    !tamperRejected ||
278
    engine.verifyDelegation(expired.delegationId) ||
279
    replay.decision !== 'DENY' ||
280
    !invalidRejected
281
  )
282
    throw new Error('Adversarial checks failed');
283
  log('10. Rejected tampered receipt, expired delegation, replay, and invalid signature');
284
285
  const unilateralBase = {
286
    ...baseReceipt,
287
    receiptId: randomUUID(),
288
    unilateral: true,
289
    signatures: {},
290
  };
291
  const unilateral = signNamed(
292
    unilateralBase as unknown as Record<string, unknown>,
293
    requester.identity.agentId,
294
    requester.keyPair,
295
  ) as unknown as Receipt;
296
  engine.submitReceipt(unilateral);
297
  log('11. Produced a clearly labelled unilateral receipt');
298
  return { engine, learnedRisk, newVersionRisk, forwarding, check, receipt };
299
}