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

Viewing files at commit 132dee380109Commit detailsView latest

tests/onboarding.test.ts 119 lines · 4.7 KB · typescript
1
import { describe, expect, it } from 'vitest';
2
import {
3
  approveAgentSetup,
4
  getOnboardingState,
5
  requestAgentSetup,
6
  resolveInstallation,
7
  updateAgentProfile,
8
  type OnboardingKind,
9
  type OnboardingStore,
10
} from '../packages/persistence/src/onboarding.js';
11
12
class MemoryOnboardingStore implements OnboardingStore {
13
  private rows = new Map<string, { kind: OnboardingKind; recordId: string; payload: unknown }>();
14
15
  async list(operatorId: string) {
16
    return [...this.rows.entries()]
17
      .filter(([key]) => key.startsWith(`${operatorId}|`))
18
      .map(([, row]) => row);
19
  }
20
21
  async upsert(operatorId: string, kind: OnboardingKind, recordId: string, payload: unknown) {
22
    this.rows.set(`${operatorId}|${kind}|${recordId}`, { kind, recordId, payload });
23
  }
24
}
25
26
describe('agent self-onboarding', () => {
27
  it('requires owner confirmation before binding an installation', async () => {
28
    const store = new MemoryOnboardingStore();
29
    const request = await requestAgentSetup(store, 'owner-a', {
30
      clientId: 'codex-installation-a',
31
      agentName: 'Research agent',
32
      projectName: 'Market research',
33
      framework: 'Codex',
34
      capabilities: ['research'],
35
      limitations: ['No purchases'],
36
      description: 'Browses public sources',
37
      agentVersion: '2.0.0',
38
      a2aEndpoint: 'https://research.example/a2a',
39
    });
40
41
    expect(request.status).toBe('pending');
42
    await expect(resolveInstallation(store, 'owner-a', 'codex-installation-a')).resolves.toEqual({
43
      status: 'unbound',
44
    });
45
46
    const approved = await approveAgentSetup(store, 'owner-a', request.requestId);
47
    expect(approved).toMatchObject({
48
      status: 'connected',
49
      agent: {
50
        name: 'Research agent',
51
        capabilities: ['research'],
52
        agentVersion: '2.0.0',
53
        a2aEndpoint: 'https://research.example/a2a',
54
      },
55
      project: { name: 'Market research' },
56
    });
57
    await expect(
58
      resolveInstallation(store, 'owner-a', 'codex-installation-a'),
59
    ).resolves.toMatchObject({ status: 'connected', agent: { name: 'Research agent' } });
60
    await expect(resolveInstallation(store, 'owner-b', 'codex-installation-a')).resolves.toEqual({
61
      status: 'unbound',
62
    });
63
  });
64
65
  it('supports unrelated agents and confirmed installation switching', async () => {
66
    const store = new MemoryOnboardingStore();
67
    const first = await requestAgentSetup(store, 'owner', {
68
      clientId: 'client-a',
69
      agentName: 'Coding agent',
70
      projectName: 'Product A',
71
    });
72
    const second = await requestAgentSetup(store, 'owner', {
73
      clientId: 'client-b',
74
      agentName: 'Support agent',
75
      projectName: 'Product B',
76
    });
77
    const firstBinding = await approveAgentSetup(store, 'owner', first.requestId);
78
    const secondBinding = await approveAgentSetup(store, 'owner', second.requestId);
79
    if (firstBinding.status !== 'connected' || secondBinding.status !== 'connected')
80
      throw new Error('Expected connected agents');
81
82
    const switchRequest = await requestAgentSetup(store, 'owner', {
83
      clientId: 'client-a',
84
      action: 'switch',
85
      existingAgentId: secondBinding.agent.agentId,
86
    });
87
    const beforeSwitch = await resolveInstallation(store, 'owner', 'client-a');
88
    expect(beforeSwitch.status).toBe('connected');
89
    if (beforeSwitch.status !== 'connected') throw new Error('Expected connected installation');
90
    expect(beforeSwitch.agent.agentId).toBe(firstBinding.agent.agentId);
91
    await approveAgentSetup(store, 'owner', switchRequest.requestId);
92
    const afterSwitch = await resolveInstallation(store, 'owner', 'client-a');
93
    expect(afterSwitch.status).toBe('connected');
94
    if (afterSwitch.status !== 'connected') throw new Error('Expected connected installation');
95
    expect(afterSwitch.agent.agentId).toBe(secondBinding.agent.agentId);
96
    const state = await getOnboardingState(store, 'owner');
97
    expect(state.projects).toHaveLength(2);
98
    expect(state.agentProfiles).toHaveLength(2);
99
  });
100
101
  it('updates only the profile bound to the authenticated installation', async () => {
102
    const store = new MemoryOnboardingStore();
103
    const request = await requestAgentSetup(store, 'owner', {
104
      clientId: 'client-a',
105
      agentName: 'Planner',
106
      projectName: 'Launch',
107
    });
108
    await approveAgentSetup(store, 'owner', request.requestId);
109
    const updated = await updateAgentProfile(store, 'owner', 'client-a', {
110
      capabilities: ['planning', 'coordination'],
111
      a2aEndpoint: 'https://planner.example/a2a',
112
    });
113
    expect(updated.capabilities).toEqual(['planning', 'coordination']);
114
    expect(updated.a2aEndpoint).toBe('https://planner.example/a2a');
115
    await expect(
116
      updateAgentProfile(store, 'owner', 'different-client', { name: 'Hijacked' }),
117
    ).rejects.toThrow('not connected');
118
  });
119
});