Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions src/services/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ const MandateService = require("./mandate");
const logger = require("../utils/logger");

class AgentService {
constructor(database, config = {}) {
constructor(database, config = {}, a2aService = null) {
this.db = database;
this.config = {
defaultSpendingLimit: config.defaultSpendingLimit || 1000,
defaultAuthorizedCounterparties:
config.defaultAuthorizedCounterparties || [],
};
this.mandateService = new MandateService(config.mandateConfig);
this.a2aService = a2aService;
}

/**
Expand Down Expand Up @@ -164,7 +165,7 @@ class AgentService {
}

/**
* Perform an Agent-to-Agent (A2A) transfer (conceptual)
* Perform an Agent-to-Agent (A2A) transfer
* @param {Object} params - Transfer parameters
* @param {string} params.fromAgentId - Source agent ID
* @param {string} params.toAgentId - Destination agent ID
Expand All @@ -173,9 +174,16 @@ class AgentService {
* @returns {Promise<Object>} Transfer result
*/
async performA2ATransfer({ fromAgentId, toAgentId, amount, currency }) {
// This is a conceptual implementation.
// In a real system, this would involve interaction with the WalletService,
// and policy checks for both agents.
if (this.a2aService) {
return await this.a2aService.executeTransfer({
fromAgentId,
toAgentId,
amount,
ucpPayload: { currency },
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delegation omits mandate parameter

Medium Severity

When a2aService is injected, performA2ATransfer calls executeTransfer without a mandate. Under strict mandate mode, A2AService rejects every such call, so agent-initiated transfers cannot succeed through this entry point even though UCP can pass a mandate to the same service.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0d11f38. Configure here.

}

// Fallback for cases where a2aService is not provided
const fromAgent = await this.getAgent(fromAgentId);
await this.getAgent(toAgentId); // Verify toAgent exists

Expand Down
69 changes: 68 additions & 1 deletion tests/unit/agent.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const AgentService = require('../../src/services/agent');
describe('AgentService', () => {
let agentService;
let mockDb;
let mockA2AService;

beforeEach(() => {
mockDb = {
Expand All @@ -11,7 +12,10 @@ describe('AgentService', () => {
findAllAgents: jest.fn(),
updateAgent: jest.fn(),
};
agentService = new AgentService(mockDb);
mockA2AService = {
executeTransfer: jest.fn()
};
agentService = new AgentService(mockDb, {}, mockA2AService);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fallback tests hit delegation path

Medium Severity

The beforeEach block now always injects mockA2AService into AgentService. This causes existing performA2ATransfer tests, designed to validate local policy logic (e.g., limits), to delegate to the mocked a2aService. Consequently, these tests no longer exercise the local fallback path or validate its intended behavior.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0d11f38. Configure here.

});

describe('registerAgent', () => {
Expand Down Expand Up @@ -85,4 +89,67 @@ describe('AgentService', () => {
expect(result.success).toBe(true);
});
});

describe('performA2ATransfer', () => {
it('should delegate to a2aService if provided', async () => {
const transferParams = {
fromAgentId: 'agent1',
toAgentId: 'agent2',
amount: 100,
currency: 'USD'
};
const expectedResult = { success: true, transferId: 'tx123' };
mockA2AService.executeTransfer.mockResolvedValue(expectedResult);

const result = await agentService.performA2ATransfer(transferParams);

expect(mockA2AService.executeTransfer).toHaveBeenCalledWith({
fromAgentId: 'agent1',
toAgentId: 'agent2',
amount: 100,
ucpPayload: { currency: 'USD' }
});
expect(result).toEqual(expectedResult);
});

it('should fallback to local logic if a2aService is not provided', async () => {
const localAgentService = new AgentService(mockDb);
const fromAgent = {
id: 'agent1',
config: { limits: { perTransaction: 500 }, authorizedCounterparties: ['agent2'] }
};
const toAgent = { id: 'agent2' };

mockDb.findAgentById.mockResolvedValueOnce(fromAgent);
mockDb.findAgentById.mockResolvedValueOnce(toAgent);

const result = await localAgentService.performA2ATransfer({
fromAgentId: 'agent1',
toAgentId: 'agent2',
amount: 100,
currency: 'USD'
});

expect(result.success).toBe(true);
expect(result.amount).toBe(100);
});

it('should throw error in fallback if amount exceeds limit', async () => {
const localAgentService = new AgentService(mockDb);
const fromAgent = {
id: 'agent1',
config: { limits: { perTransaction: 50 } }
};

mockDb.findAgentById.mockResolvedValueOnce(fromAgent);
mockDb.findAgentById.mockResolvedValueOnce({ id: 'agent2' });

await expect(localAgentService.performA2ATransfer({
fromAgentId: 'agent1',
toAgentId: 'agent2',
amount: 100,
currency: 'USD'
})).rejects.toThrow('Zero Trust Validation Failed: Amount 100 exceeds agent per-transaction limit of 50');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fallback test wrong error text

Medium Severity

The performA2ATransfer fallback limit test fails because its rejects.toThrow assertion expects an error message that doesn't match the actual message thrown by the AgentService's local validation. The test expects 'Amount ... exceeds agent per-transaction limit', but the service throws 'Transfer amount ... exceeds per-transaction limit ... for agent'.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0d11f38. Configure here.

});
});
});
Loading