Skip to content
Open
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
52 changes: 40 additions & 12 deletions src/services/a2aService.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}

/**
Expand All @@ -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)
Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/services/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
14 changes: 7 additions & 7 deletions src/services/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/agent.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});
});
Loading