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

Viewing files at commit 132dee380109Commit detailsView latest

tests/protocol.test.ts 157 lines · 5.3 KB · typescript
1
import { describe, expect, it } from 'vitest';
2
import { createIdentity, TrustEngine } from '@openclasp/core';
3
import {
4
  FederatedInteractionSchema,
5
  PublicAgentCardSchema,
6
  canonicalHash,
7
  signObject,
8
  verifyObject,
9
} from '@openclasp/protocol';
10
import { toA2AAgentCard } from '@openclasp/sidecar';
11
12
describe('protocol cryptography and delegation', () => {
13
  it('canonicalizes object key order and rejects tampering', () => {
14
    expect(canonicalHash({ b: 2, a: 1 })).toBe(canonicalHash({ a: 1, b: 2 }));
15
    const agent = createIdentity({
16
      agentId: 'agent:a',
17
      operatorRef: 'operator:a',
18
      capabilities: ['write'],
19
    });
20
    const signed = signObject({ action: 'write', value: 1 }, agent.keyPair);
21
    expect(verifyObject(signed, agent.identity.publicKey)).toBe(true);
22
    expect(verifyObject({ ...signed, value: 2 }, agent.identity.publicKey)).toBe(false);
23
  });
24
25
  it('prevents authority escalation and rejects expired delegation', () => {
26
    const engine = new TrustEngine();
27
    const parent = createIdentity({
28
      agentId: 'agent:parent',
29
      operatorRef: 'operator:x',
30
      capabilities: ['read'],
31
    });
32
    const child = createIdentity({
33
      agentId: 'agent:child',
34
      operatorRef: 'operator:x',
35
      capabilities: ['read'],
36
      parentAgentId: parent.identity.agentId,
37
      rootControllerId: parent.identity.rootControllerId,
38
    });
39
    engine.registerAgent(parent.identity);
40
    engine.registerAgent(child.identity);
41
    expect(() =>
42
      engine.createDelegation(
43
        parent.identity.agentId,
44
        child.identity.agentId,
45
        ['write'],
46
        new Date(Date.now() + 1000).toISOString(),
47
        parent.keyPair,
48
      ),
49
    ).toThrow('exceeds parent authority');
50
    const expired = engine.createDelegation(
51
      parent.identity.agentId,
52
      child.identity.agentId,
53
      ['read'],
54
      new Date(Date.now() - 1).toISOString(),
55
      parent.keyPair,
56
    );
57
    expect(engine.verifyDelegation(expired.delegationId)).toBe(false);
58
  });
59
60
  it('publishes an internet Agent Card and official A2A extension without private owner data', () => {
61
    const card = PublicAgentCardSchema.parse({
62
      protocolVersion: '0.1',
63
      agentId: 'agent:research',
64
      name: 'Research agent',
65
      description: 'Finds primary sources',
66
      framework: 'Codex',
67
      agentVersion: '1.2.0',
68
      capabilities: ['research'],
69
      limitations: ['no purchases'],
70
      assurance: 'oauth_authenticated',
71
      transports: [
72
        {
73
          protocol: 'A2A/1.0',
74
          protocolBinding: 'JSONRPC',
75
          endpoint: 'https://agent.example/a2a',
76
        },
77
      ],
78
      cardUrl: 'https://openclasp.vercel.app/agents/agent%3Aresearch/card.json',
79
      a2aAgentCardUrl: 'https://openclasp.vercel.app/agents/agent%3Aresearch/a2a-agent-card.json',
80
      extensionUri: 'https://openclasp.vercel.app/extensions/trust/v0.1',
81
      publishedAt: '2026-08-29T00:00:00.000Z',
82
      updatedAt: '2026-08-29T00:00:00.000Z',
83
    });
84
    expect(card).not.toHaveProperty('operatorId');
85
    expect(card).not.toHaveProperty('projectId');
86
    const a2a = toA2AAgentCard(card);
87
    expect(a2a.supportedInterfaces[0]).toMatchObject({
88
      url: 'https://agent.example/a2a',
89
      protocolBinding: 'JSONRPC',
90
      protocolVersion: '1.0',
91
    });
92
    expect(a2a.capabilities?.extensions?.[0]?.uri).toBe(card.extensionUri);
93
  });
94
95
  it('binds bilateral acceptances to one immutable contract hash', () => {
96
    const interactionId = crypto.randomUUID();
97
    const contract = {
98
      protocolVersion: '0.1' as const,
99
      interactionId,
100
      purpose: 'Research a market',
101
      parties: ['agent:a', 'agent:b'],
102
      taskCategory: 'research',
103
      requestedOutcome: 'A sourced brief',
104
      successCriteria: ['Primary sources included'],
105
      allowedActions: ['browse'],
106
      prohibitedActions: ['purchase'],
107
      allowedData: ['public'],
108
      prohibitedData: ['secrets'],
109
      evidenceRequirements: ['links'],
110
      delegationRules: ['explicit'],
111
      humanApprovalRequirements: [],
112
      factCheckingPolicy: 'important_claims',
113
      mediationPolicy: 'mutual_consent' as const,
114
      retentionDays: 30,
115
      completionConditions: ['brief delivered'],
116
      cancellationConditions: ['either party'],
117
      signatures: {},
118
    };
119
    const termsHash = canonicalHash(contract);
120
    const now = '2026-08-29T00:00:00.000Z';
121
    const value = FederatedInteractionSchema.parse({
122
      protocolVersion: '0.1',
123
      interactionId,
124
      initiatorAgentId: 'agent:a',
125
      responderAgentId: 'agent:b',
126
      status: 'active',
127
      contract,
128
      termsHash,
129
      acceptances: {
130
        'agent:a': {
131
          agentId: 'agent:a',
132
          method: 'oauth_installation',
133
          termsHash,
134
          acceptedAt: now,
135
        },
136
        'agent:b': {
137
          agentId: 'agent:b',
138
          method: 'oauth_installation',
139
          termsHash,
140
          acceptedAt: now,
141
        },
142
      },
143
      responderTransport: {
144
        protocol: 'A2A/1.0',
145
        protocolBinding: 'JSONRPC',
146
        endpoint: 'https://agent-b.example/a2a',
147
      },
148
      createdAt: now,
149
      updatedAt: now,
150
      expiresAt: '2026-08-30T00:00:00.000Z',
151
    });
152
    expect(Object.values(value.acceptances).every((item) => item.termsHash === termsHash)).toBe(
153
      true,
154
    );
155
    expect(canonicalHash({ ...contract, purpose: 'Changed terms' })).not.toBe(termsHash);
156
  });
157
});