#!/usr/bin/env node // Validates agentic-commerce spend mandates against ALPHA guardrails. // Usage: node scripts/validate-commerce-policy.mjs [path/to/policy.json] import { readFileSync } from 'node:fs'; const file = process.argv[2] ?? 'docs/policies/commerce-policy.json'; const PERIODS = new Set(['day', 'week', 'month']); const MODES = new Set(['sandbox', 'live']); const DAY_MS = 86_400_000; let policy; try { policy = JSON.parse(readFileSync(file, 'utf8')); } catch (err) { console.error(`✖ Cannot read/parse ${file}: ${err.message}`); process.exit(1); } const errors = []; const g = policy.globalGuardrails ?? {}; const maxAuto = Number(g.maxAutoApprove); const maxDays = Number(g.maxMandateDays ?? 90); const prohibited = new Set(g.prohibitedCategories ?? []); if (!Number.isFinite(maxAuto) || maxAuto < 0) errors.push('globalGuardrails.maxAutoApprove must be a non-negative number'); if (!Array.isArray(policy.mandates) || policy.mandates.length === 0) errors.push('mandates must be a non-empty array'); const ids = new Set(); for (const [i, m] of (policy.mandates ?? []).entries()) { const at = `mandates[${i}]${m?.id ? ` (${m.id})` : ''}`; const req = ['id', 'agentId', 'currency', 'perTransactionLimit', 'autoApproveLimit', 'periodLimit', 'period', 'allowedCategories', 'requireHumanApprovalFor', 'issuedAt', 'expiresAt', 'mode']; for (const k of req) if (m?.[k] === undefined) errors.push(`${at}: missing "${k}"`); if (!m) continue; if (ids.has(m.id)) errors.push(`${at}: duplicate id`); ids.add(m.id); if (!/^[A-Z]{3}$/.test(m.currency ?? '')) errors.push(`${at}: currency must be an ISO 4217 code`); const [auto, per, period] = [m.autoApproveLimit, m.perTransactionLimit, m.periodLimit].map(Number); if ([auto, per, period].some((n) => !Number.isFinite(n) || n < 0)) errors.push(`${at}: limits must be non-negative numbers`); if (!(auto <= per && per <= period)) errors.push(`${at}: require autoApproveLimit <= perTransactionLimit <= periodLimit`); if (Number.isFinite(maxAuto) && auto > maxAuto) errors.push(`${at}: autoApproveLimit ${auto} exceeds global maxAutoApprove ${maxAuto}`); if (!PERIODS.has(m.period)) errors.push(`${at}: period must be day|week|month`); if (!MODES.has(m.mode)) errors.push(`${at}: mode must be sandbox|live`); if (m.mode === 'live' && !m.approvedBy) errors.push(`${at}: live mode requires "approvedBy" (human approver)`); const cats = Array.isArray(m.allowedCategories) ? m.allowedCategories : []; if (cats.length === 0) errors.push(`${at}: allowedCategories must be non-empty`); for (const c of cats) if (prohibited.has(c)) errors.push(`${at}: category "${c}" is prohibited`); const hil = Array.isArray(m.requireHumanApprovalFor) ? m.requireHumanApprovalFor : []; if (!hil.includes('new_merchant')) errors.push(`${at}: requireHumanApprovalFor must include "new_merchant"`); const issued = Date.parse(m.issuedAt); const expires = Date.parse(m.expiresAt); if (Number.isNaN(issued) || Number.isNaN(expires)) errors.push(`${at}: issuedAt/expiresAt must be ISO 8601 dates`); else if (expires <= issued) errors.push(`${at}: expiresAt must be after issuedAt`); else if ((expires - issued) / DAY_MS > maxDays) errors.push(`${at}: mandate lasts longer than ${maxDays} days`); } if (errors.length) { console.error(`✖ ${errors.length} policy violation(s) in ${file}:`); for (const e of errors) console.error(` - ${e}`); process.exit(1); } console.log(`✔ ${policy.mandates.length} mandate(s) in ${file} pass all guardrails`);