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

Viewing files at commit 39e235ac59f4Commit detailsView latest

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