diff --git a/src/services/a2aService.js b/src/services/a2aService.js index 4b8c885..73c4443 100644 --- a/src/services/a2aService.js +++ b/src/services/a2aService.js @@ -5,13 +5,18 @@ * policy compliance, limit checks, and authorized counterparty validation. */ -const { Agent } = require("../models/agent"); +const MandateService = require("./mandate"); const logger = require("../utils/logger"); class A2AService { - constructor(walletService, db) { + constructor(walletService, db, config = {}) { this.walletService = walletService; this.db = db; + this.mandateService = config.mandateService || new MandateService(config.mandateConfig); + this.strictMandateMode = + config.strictMandateMode !== undefined + ? config.strictMandateMode + : process.env.STRICT_MANDATE_MODE === "true"; } /** @@ -20,19 +25,45 @@ class A2AService { * @param {string} params.fromAgentId - Sender Agent ID * @param {string} params.toAgentId - Recipient Agent ID * @param {number} params.amount - Amount to transfer + * @param {string} params.mandate - Optional signed Mandate (AP2) for Zero Trust validation * @param {Object} params.ucpPayload - The original UCP intent/payload */ - async executeTransfer({ fromAgentId, toAgentId, amount, ucpPayload = {} }) { + async executeTransfer({ + fromAgentId, + toAgentId, + amount, + mandate, + ucpPayload = {}, + }) { try { - // 1. Validate Agents - const fromAgent = await Agent.findById(fromAgentId); + // 0. Zero Trust Mandate Validation + if (mandate) { + try { + await this.mandateService.verifyMandate(mandate); + } catch (error) { + throw new Error( + `Zero Trust Validation Failed: Mandate verification failed: ${error.message}`, + ); + } + } else if (this.strictMandateMode) { + throw new Error( + "Zero Trust Validation Failed: Mandate required for A2A transfer in strict mode", + ); + } + + // 1. Validate Agents using Repository Pattern + const fromAgent = await this.db.findAgentById(fromAgentId); if (!fromAgent || fromAgent.status !== "active") { - throw new Error(`Sender agent ${fromAgentId} not found or inactive`); + throw new Error( + `Zero Trust Validation Failed: Sender agent ${fromAgentId} not found or inactive`, + ); } - const toAgent = await Agent.findById(toAgentId); + const toAgent = await this.db.findAgentById(toAgentId); if (!toAgent || toAgent.status !== "active") { - throw new Error(`Recipient agent ${toAgentId} not found or inactive`); + throw new Error( + `Zero Trust Validation Failed: Recipient agent ${toAgentId} not found or inactive`, + ); } // 2. Policy Checks (Sender) @@ -50,13 +81,10 @@ class A2AService { counterpartyAgentId: toAgentId, ucpPayload, type: "a2a_transfer", + mandate, }, }); - // 4. Update Agent Usage (if we were tracking daily usage in db, we'd do it here) - // For now, limits are stateless checks against config. - // In a real implementation, we would query daily volume or update a usage record. - return { success: true, transferId: transferResult.transferId, diff --git a/src/services/agent.js b/src/services/agent.js index 25e918f..c1be719 100644 --- a/src/services/agent.js +++ b/src/services/agent.js @@ -72,7 +72,7 @@ class AgentService { try { const agent = await this.db.findAgentById(agentId); if (!agent) { - throw new Error("Agent not found"); + throw new Error("Zero Trust Validation Failed: Agent not found"); } return agent; } catch (error) { diff --git a/src/services/wallet.js b/src/services/wallet.js index 3c7afe9..c35aa90 100644 --- a/src/services/wallet.js +++ b/src/services/wallet.js @@ -36,7 +36,7 @@ class WalletService { // Check if user already has a wallet const existingWallet = await this.db.findWalletByUserId(userId); if (existingWallet) { - throw new Error("User already has a wallet"); + throw new Error("Zero Trust Validation Failed: User already has a wallet"); } // Validate initial balance @@ -91,7 +91,7 @@ class WalletService { try { const wallet = await this.db.findWalletById(walletId); if (!wallet) { - throw new Error("Wallet not found"); + throw new Error("Zero Trust Validation Failed: Wallet not found"); } return wallet; } catch (error) { @@ -108,7 +108,7 @@ class WalletService { try { const wallet = await this.db.findWalletByUserId(userId); if (!wallet) { - throw new Error("Wallet not found for user"); + throw new Error("Zero Trust Validation Failed: Wallet not found for user"); } return wallet; } catch (error) { @@ -142,7 +142,7 @@ class WalletService { // Get wallet const wallet = await this.getWallet(walletId); if (wallet.status !== "active") { - throw new Error("Wallet is not active"); + throw new Error("Zero Trust Validation Failed: Wallet is not active"); } // Check if new balance would exceed max @@ -215,12 +215,12 @@ class WalletService { // Get wallet const wallet = await this.getWallet(walletId); if (wallet.status !== "active") { - throw new Error("Wallet is not active"); + throw new Error("Zero Trust Validation Failed: Wallet is not active"); } // Check sufficient balance if (wallet.balance < amount) { - throw new Error("Insufficient balance"); + throw new Error("Zero Trust Validation Failed: Insufficient balance"); } const newBalance = wallet.balance - amount; @@ -297,7 +297,7 @@ class WalletService { } if (fromWalletId === toWalletId) { - throw new Error("Cannot transfer to same wallet"); + throw new Error("Zero Trust Validation Failed: Cannot transfer to same wallet"); } // Start transaction diff --git a/tests/unit/agent.spec.js b/tests/unit/agent.spec.js index 015a2e4..d4ea2da 100644 --- a/tests/unit/agent.spec.js +++ b/tests/unit/agent.spec.js @@ -35,7 +35,7 @@ describe('AgentService', () => { describe('getAgent', () => { it('should throw if agent not found', async () => { mockDb.findAgentById.mockResolvedValue(null); - await expect(agentService.getAgent('nonexistent')).rejects.toThrow('Agent not found'); + await expect(agentService.getAgent('nonexistent')).rejects.toThrow('Zero Trust Validation Failed: Agent not found'); }); }); });