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