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

Viewing files at commit 5b7999fcbb0cCommit detailsView latest

tests/core.test.ts 229 lines · 8.0 KB · typescript
1
import { describe, expect, it } from 'vitest';
2
import {
3
  buildCounterpartyBrief,
4
  createIdentity,
5
  FixtureFactCheckProvider,
6
  TrustEngine,
7
} from '@openclasp/core';
8
import {
9
  canonicalHash,
10
  PublicAgentCardSchema,
11
  signNamed,
12
  type InteractionContract,
13
  type Receipt,
14
} from '@openclasp/protocol';
15
import { createSignedEvent } from '@openclasp/sdk';
16
17
describe('general assurance behavior', () => {
18
  it('requires every contract party to sign before the agreement is active', () => {
19
    const engine = new TrustEngine();
20
    const first = createIdentity({
21
      agentId: 'agent:first',
22
      operatorRef: 'operator:a',
23
      capabilities: ['coordinate'],
24
    });
25
    const second = createIdentity({
26
      agentId: 'agent:second',
27
      operatorRef: 'operator:b',
28
      capabilities: ['coordinate'],
29
    });
30
    engine.registerAgent(first.identity);
31
    engine.registerAgent(second.identity);
32
    const unsigned: InteractionContract = {
33
      protocolVersion: '0.1',
34
      interactionId: crypto.randomUUID(),
35
      purpose: 'Coordinate a task',
36
      parties: [first.identity.agentId, second.identity.agentId],
37
      taskCategory: 'coordination',
38
      requestedOutcome: 'Agreed plan',
39
      successCriteria: ['Plan accepted'],
40
      allowedActions: ['coordinate'],
41
      prohibitedActions: [],
42
      allowedData: ['public'],
43
      prohibitedData: ['private'],
44
      evidenceRequirements: [],
45
      delegationRules: [],
46
      humanApprovalRequirements: [],
47
      factCheckingPolicy: 'important_claims',
48
      mediationPolicy: 'mutual_consent',
49
      retentionDays: 30,
50
      completionConditions: ['plan_delivered'],
51
      cancellationConditions: ['either_party'],
52
      signatures: {},
53
    };
54
    const oneSignature = signNamed(
55
      unsigned as unknown as Record<string, unknown>,
56
      first.identity.agentId,
57
      first.keyPair,
58
    ) as unknown as InteractionContract;
59
    expect(() => engine.saveContract(oneSignature)).toThrow('every party');
60
    const complete = signNamed(
61
      oneSignature as unknown as Record<string, unknown>,
62
      second.identity.agentId,
63
      second.keyPair,
64
    ) as unknown as InteractionContract;
65
    expect(engine.saveContract(complete).interactionId).toBe(unsigned.interactionId);
66
  });
67
68
  it('does not fact-check opinions as objective truth', async () => {
69
    const provider = new FixtureFactCheckProvider();
70
    expect((await provider.check('I think green is the best color')).status).toBe(
71
      'not_fact_checkable',
72
    );
73
  });
74
75
  it('verifies a receipt without recording it', () => {
76
    const engine = new TrustEngine();
77
    const agent = createIdentity({
78
      agentId: 'agent:receipt',
79
      operatorRef: 'operator:a',
80
      capabilities: ['work'],
81
    });
82
    engine.registerAgent(agent.identity);
83
    const base: Receipt = {
84
      receiptId: crypto.randomUUID(),
85
      interactionId: crypto.randomUUID(),
86
      participants: [agent.identity.agentId],
87
      agentVersions: { [agent.identity.agentId]: '1.0.0' },
88
      contractHash: 'contract',
89
      startedAt: new Date(Date.now() - 1000).toISOString(),
90
      completedAt: new Date().toISOString(),
91
      outcome: 'success',
92
      commitmentsFulfilled: ['done'],
93
      commitmentsMissed: [],
94
      evidenceHashes: [],
95
      policyWarnings: [],
96
      policyViolations: [],
97
      disputeStatus: 'none',
98
      delegationChainHash: 'none',
99
      unilateral: false,
100
      signatures: {},
101
    };
102
    const signed = signNamed(
103
      base as unknown as Record<string, unknown>,
104
      agent.identity.agentId,
105
      agent.keyPair,
106
    ) as unknown as Receipt;
107
    expect(engine.verifyReceipt(signed).receiptId).toBe(base.receiptId);
108
    expect(engine.receipts.size).toBe(0);
109
  });
110
111
  it('requires opt-in and strips payloads from network contributions', () => {
112
    const engine = new TrustEngine();
113
    const agent = createIdentity({
114
      agentId: 'agent:a',
115
      operatorRef: 'operator:a',
116
      capabilities: ['chat'],
117
    });
118
    engine.registerAgent(agent.identity);
119
    const event = createSignedEvent(
120
      {
121
        protocolVersion: '0.1',
122
        eventId: crypto.randomUUID(),
123
        interactionId: crypto.randomUUID(),
124
        eventType: 'claim',
125
        agentId: agent.identity.agentId,
126
        agentVersion: '1.0.0',
127
        timestamp: new Date().toISOString(),
128
        visibility: 'network_aggregate',
129
        provenance: 'observed',
130
        payload: { rawMessage: 'private conversation', claim: 'secret' },
131
      },
132
      agent.keyPair,
133
    );
134
    expect(engine.networkContribution(event)).toBeNull();
135
    engine.setContributionConsent(agent.identity.agentId, true);
136
    const contribution = engine.networkContribution(event);
137
    expect(contribution).not.toHaveProperty('payload');
138
    expect(JSON.stringify(contribution)).not.toContain('private conversation');
139
  });
140
141
  it('requires mutual mediation consent', () => {
142
    const engine = new TrustEngine();
143
    const conflict = engine.createConflict({
144
      interactionId: crypto.randomUUID(),
145
      issue: 'ambiguous requirement',
146
      participants: ['a', 'b'],
147
      positions: {},
148
      evidence: [],
149
      contractClauses: [],
150
      missingInformation: [],
151
      possibleResolutions: ['clarify'],
152
    });
153
    engine.permitMediation(conflict.conflictId, 'a');
154
    expect(() => engine.resolveConflict(conflict.conflictId, 'clarified')).toThrow('Mutual');
155
    engine.permitMediation(conflict.conflictId, 'b');
156
    expect(engine.resolveConflict(conflict.conflictId, 'clarified').status).toBe('resolved');
157
  });
158
159
  it('builds a private brief against the actual contract requirements', () => {
160
    const interactionId = crypto.randomUUID();
161
    const contract: InteractionContract = {
162
      protocolVersion: '0.1',
163
      interactionId,
164
      purpose: 'Find an engineering role',
165
      parties: ['agent:candidate', 'agent:recruiter'],
166
      taskCategory: 'recruiting',
167
      requestedOutcome: 'Available backend software engineering positions',
168
      successCriteria: ['At least one matching backend role'],
169
      allowedActions: ['recruiting'],
170
      prohibitedActions: [],
171
      allowedData: ['public'],
172
      prohibitedData: ['private'],
173
      evidenceRequirements: ['Job description link'],
174
      delegationRules: [],
175
      humanApprovalRequirements: [],
176
      factCheckingPolicy: 'important_claims',
177
      mediationPolicy: 'mutual_consent',
178
      retentionDays: 30,
179
      completionConditions: ['positions returned'],
180
      cancellationConditions: ['either party'],
181
      signatures: {},
182
    };
183
    const subject = PublicAgentCardSchema.parse({
184
      protocolVersion: '0.1',
185
      agentId: 'agent:recruiter',
186
      name: 'Recruiter',
187
      description: 'Recruiting assistant for marketing roles',
188
      framework: 'Botpress',
189
      agentVersion: '1.0.0',
190
      capabilities: ['recruiting'],
191
      limitations: ['no backend recruitment'],
192
      assurance: 'oauth_authenticated',
193
      transports: [],
194
      cardUrl: 'https://openclasp.example/agents/recruiter/card.json',
195
      a2aAgentCardUrl: 'https://openclasp.example/agents/recruiter/a2a-agent-card.json',
196
      extensionUri: 'https://openclasp.example/extensions/trust/v0.1',
197
      publishedAt: '2026-08-29T00:00:00.000Z',
198
      updatedAt: '2026-08-29T00:00:00.000Z',
199
    });
200
    const brief = buildCounterpartyBrief({
201
      interactionId,
202
      contractHash: canonicalHash(contract),
203
      contract,
204
      recipientAgentId: 'agent:candidate',
205
      subject,
206
      historyInsights: [
207
        {
208
          code: 'limited_verified_history',
209
          severity: 'caution',
210
          message: 'No eligible history exists.',
211
          evidenceReferences: [],
212
          requirementReferences: [],
213
        },
214
      ],
215
      relevantSampleSize: 0,
216
      historyConfidence: 0,
217
      generatedAt: '2026-08-29T00:00:00.000Z',
218
      expiresAt: '2026-08-29T01:00:00.000Z',
219
    });
220
    expect(brief.decision).toBe('CHALLENGE');
221
    expect(brief.requirements).toEqual(
222
      expect.arrayContaining([
223
        expect.objectContaining({ requirementId: 'task-category', status: 'match' }),
224
        expect.objectContaining({ requirementId: 'requested-outcome', status: 'mismatch' }),
225
      ]),
226
    );
227
    expect(brief.insights[0]?.requirementReferences.length).toBeGreaterThan(0);
228
  });
229
});