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

Viewing files at commit fd993cf6d9f8Commit detailsView latest

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