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

Viewing files at commit 5b7999fcbb0cCommit detailsView latest

tests/automation.test.ts 128 lines · 4.1 KB · typescript
1
import { describe, expect, it } from 'vitest';
2
import { canonicalHash, type FederatedInteraction } from '@openclasp/protocol';
3
import {
4
  buildPublicAgentCard,
5
  canAutoAcceptInteraction,
6
} from '../packages/persistence/src/hosted.js';
7
import type { AgentProfile } from '../packages/persistence/src/onboarding.js';
8
9
function fixture(): { agent: AgentProfile; interaction: FederatedInteraction } {
10
  const interactionId = crypto.randomUUID();
11
  const now = new Date().toISOString();
12
  const contract = {
13
    protocolVersion: '0.1' as const,
14
    interactionId,
15
    purpose: 'Research a competitor',
16
    parties: ['agent:a', 'agent:b'],
17
    taskCategory: 'research',
18
    requestedOutcome: 'A short brief',
19
    successCriteria: ['Directly answer the task'],
20
    allowedActions: [],
21
    prohibitedActions: [],
22
    allowedData: [],
23
    prohibitedData: [],
24
    evidenceRequirements: [],
25
    delegationRules: ['explicit_contract_scope'],
26
    humanApprovalRequirements: [],
27
    factCheckingPolicy: 'important_claims',
28
    mediationPolicy: 'mutual_consent' as const,
29
    retentionDays: 30,
30
    completionConditions: ['Directly answer the task'],
31
    cancellationConditions: ['either_party_before_completion'],
32
    signatures: {},
33
  };
34
  const termsHash = canonicalHash(contract);
35
  return {
36
    agent: {
37
      agentId: 'agent:b',
38
      projectId: 'project:b',
39
      name: 'Research agent',
40
      description: '',
41
      framework: 'Codex',
42
      agentVersion: '1.0.0',
43
      a2aEndpoint: 'https://agent-b.example/a2a',
44
      autoPublish: true,
45
      autoAcceptPolicy: 'safe_matching',
46
      autoAcceptTaskCategories: ['research'],
47
      capabilities: ['research'],
48
      limitations: [],
49
      identityMode: 'oauth_installation',
50
      status: 'active',
51
      createdAt: now,
52
      updatedAt: now,
53
    },
54
    interaction: {
55
      protocolVersion: '0.1',
56
      interactionId,
57
      initiatorAgentId: 'agent:a',
58
      responderAgentId: 'agent:b',
59
      status: 'pending',
60
      contract,
61
      termsHash,
62
      acceptances: {
63
        'agent:a': {
64
          agentId: 'agent:a',
65
          method: 'oauth_installation',
66
          termsHash,
67
          acceptedAt: now,
68
        },
69
      },
70
      responderTransport: {
71
        protocol: 'A2A/1.0',
72
        protocolBinding: 'JSONRPC',
73
        endpoint: 'https://agent-b.example/a2a',
74
        managedBy: 'agent',
75
      },
76
      createdAt: now,
77
      updatedAt: now,
78
      expiresAt: new Date(Date.now() + 60_000).toISOString(),
79
    },
80
  };
81
}
82
83
describe('safe connection automation', () => {
84
  it('auto-accepts only matching, data-free requests within the owner policy', () => {
85
    const { agent, interaction } = fixture();
86
    expect(canAutoAcceptInteraction(agent, interaction)).toBe(true);
87
    expect(
88
      canAutoAcceptInteraction(agent, {
89
        ...interaction,
90
        contract: { ...interaction.contract, allowedData: ['customer_records'] },
91
      }),
92
    ).toBe(false);
93
    expect(
94
      canAutoAcceptInteraction(agent, {
95
        ...interaction,
96
        contract: { ...interaction.contract, purpose: 'Find a customer access token' },
97
      }),
98
    ).toBe(false);
99
    expect(
100
      canAutoAcceptInteraction(agent, {
101
        ...interaction,
102
        contract: { ...interaction.contract, taskCategory: 'payments' },
103
      }),
104
    ).toBe(false);
105
    expect(canAutoAcceptInteraction({ ...agent, autoAcceptPolicy: 'off' }, interaction)).toBe(
106
      false,
107
    );
108
  });
109
110
  it('publishes an OpenClasp-managed endpoint only for temporary chat identities', () => {
111
    const { agent } = fixture();
112
    const temporaryAgent = { ...agent, agentMode: 'temporary_chat' as const };
113
    delete temporaryAgent.a2aEndpoint;
114
    const temporary = buildPublicAgentCard(temporaryAgent, 'https://openclasp.example');
115
    const persistent = buildPublicAgentCard(
116
      { ...agent, agentMode: 'persistent_runtime' },
117
      'https://openclasp.example',
118
    );
119
    expect(temporary.transports[0]).toMatchObject({
120
      endpoint: 'https://openclasp.example/a2a/temporary/agent%3Ab',
121
      managedBy: 'openclasp',
122
    });
123
    expect(persistent.transports[0]).toMatchObject({
124
      endpoint: 'https://agent-b.example/a2a',
125
      managedBy: 'agent',
126
    });
127
  });
128
});