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

Viewing files at commit 791506c0dbc6Commit detailsView latest

tests/onboarding.test.ts 109 lines · 4.4 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
    });
37
38
    expect(request.status).toBe('pending');
39
    await expect(resolveInstallation(store, 'owner-a', 'codex-installation-a')).resolves.toEqual({
40
      status: 'unbound',
41
    });
42
43
    const approved = await approveAgentSetup(store, 'owner-a', request.requestId);
44
    expect(approved).toMatchObject({
45
      status: 'connected',
46
      agent: { name: 'Research agent', capabilities: ['research'] },
47
      project: { name: 'Market research' },
48
    });
49
    await expect(
50
      resolveInstallation(store, 'owner-a', 'codex-installation-a'),
51
    ).resolves.toMatchObject({ status: 'connected', agent: { name: 'Research agent' } });
52
    await expect(resolveInstallation(store, 'owner-b', 'codex-installation-a')).resolves.toEqual({
53
      status: 'unbound',
54
    });
55
  });
56
57
  it('supports unrelated agents and confirmed installation switching', async () => {
58
    const store = new MemoryOnboardingStore();
59
    const first = await requestAgentSetup(store, 'owner', {
60
      clientId: 'client-a',
61
      agentName: 'Coding agent',
62
      projectName: 'Product A',
63
    });
64
    const second = await requestAgentSetup(store, 'owner', {
65
      clientId: 'client-b',
66
      agentName: 'Support agent',
67
      projectName: 'Product B',
68
    });
69
    const firstBinding = await approveAgentSetup(store, 'owner', first.requestId);
70
    const secondBinding = await approveAgentSetup(store, 'owner', second.requestId);
71
    if (firstBinding.status !== 'connected' || secondBinding.status !== 'connected')
72
      throw new Error('Expected connected agents');
73
74
    const switchRequest = await requestAgentSetup(store, 'owner', {
75
      clientId: 'client-a',
76
      action: 'switch',
77
      existingAgentId: secondBinding.agent.agentId,
78
    });
79
    const beforeSwitch = await resolveInstallation(store, 'owner', 'client-a');
80
    expect(beforeSwitch.status).toBe('connected');
81
    if (beforeSwitch.status !== 'connected') throw new Error('Expected connected installation');
82
    expect(beforeSwitch.agent.agentId).toBe(firstBinding.agent.agentId);
83
    await approveAgentSetup(store, 'owner', switchRequest.requestId);
84
    const afterSwitch = await resolveInstallation(store, 'owner', 'client-a');
85
    expect(afterSwitch.status).toBe('connected');
86
    if (afterSwitch.status !== 'connected') throw new Error('Expected connected installation');
87
    expect(afterSwitch.agent.agentId).toBe(secondBinding.agent.agentId);
88
    const state = await getOnboardingState(store, 'owner');
89
    expect(state.projects).toHaveLength(2);
90
    expect(state.agentProfiles).toHaveLength(2);
91
  });
92
93
  it('updates only the profile bound to the authenticated installation', async () => {
94
    const store = new MemoryOnboardingStore();
95
    const request = await requestAgentSetup(store, 'owner', {
96
      clientId: 'client-a',
97
      agentName: 'Planner',
98
      projectName: 'Launch',
99
    });
100
    await approveAgentSetup(store, 'owner', request.requestId);
101
    const updated = await updateAgentProfile(store, 'owner', 'client-a', {
102
      capabilities: ['planning', 'coordination'],
103
    });
104
    expect(updated.capabilities).toEqual(['planning', 'coordination']);
105
    await expect(
106
      updateAgentProfile(store, 'owner', 'different-client', { name: 'Hijacked' }),
107
    ).rejects.toThrow('not connected');
108
  });
109
});