@aporthq/aport-agent-guardrails 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/LICENSE +217 -0
  2. package/README.md +481 -0
  3. package/bin/agent-guardrails +133 -0
  4. package/bin/aport-create-passport.sh +444 -0
  5. package/bin/aport-cursor-hook.sh +90 -0
  6. package/bin/aport-guardrail-api.sh +108 -0
  7. package/bin/aport-guardrail-bash.sh +394 -0
  8. package/bin/aport-guardrail-v2.sh +5 -0
  9. package/bin/aport-guardrail.sh +5 -0
  10. package/bin/aport-resolve-paths.sh +71 -0
  11. package/bin/aport-status.sh +276 -0
  12. package/bin/frameworks/crewai.sh +49 -0
  13. package/bin/frameworks/cursor.sh +95 -0
  14. package/bin/frameworks/langchain.sh +48 -0
  15. package/bin/frameworks/n8n.sh +36 -0
  16. package/bin/frameworks/openclaw.sh +19 -0
  17. package/bin/lib/allowlist.sh +18 -0
  18. package/bin/lib/common.sh +28 -0
  19. package/bin/lib/config.sh +46 -0
  20. package/bin/lib/constants.sh +232 -0
  21. package/bin/lib/detect.sh +65 -0
  22. package/bin/lib/error.sh +269 -0
  23. package/bin/lib/passport.sh +19 -0
  24. package/bin/lib/templates/.gitkeep +1 -0
  25. package/bin/lib/templates/config.yaml +6 -0
  26. package/bin/lib/validation.sh +206 -0
  27. package/bin/openclaw +660 -0
  28. package/docs/ADDING_A_FRAMEWORK.md +87 -0
  29. package/docs/AGENTS.md.example +40 -0
  30. package/docs/CODE_REVIEW.md +192 -0
  31. package/docs/DEPLOYMENT_READINESS.md +81 -0
  32. package/docs/FAQ_SECURITY_SCANNERS.md +373 -0
  33. package/docs/FRAMEWORK_ROADMAP.md +41 -0
  34. package/docs/HOSTED_PASSPORT_SETUP.md +362 -0
  35. package/docs/IMPLEMENTING_YOUR_OWN_EVALUATOR.md +433 -0
  36. package/docs/OPENCLAW_COMPATIBILITY.md +73 -0
  37. package/docs/OPENCLAW_LOCAL_INTEGRATION.md +596 -0
  38. package/docs/OPENCLAW_TOOLS_AND_POLICIES.md +54 -0
  39. package/docs/QUICKSTART.md +470 -0
  40. package/docs/QUICKSTART_OPENCLAW_PLUGIN.md +470 -0
  41. package/docs/README.md +28 -0
  42. package/docs/RELEASE.md +87 -0
  43. package/docs/REPO_LAYOUT.md +47 -0
  44. package/docs/SKILLS_ECOSYSTEM_ANALYSIS_FEB17.md +1260 -0
  45. package/docs/TOOL_POLICY_MAPPING.md +46 -0
  46. package/docs/UPGRADE.md +46 -0
  47. package/docs/VERIFICATION_METHODS.md +97 -0
  48. package/docs/assets/README.md +8 -0
  49. package/docs/assets/porter.svg +54 -0
  50. package/docs/development/ERROR_CODES.md +616 -0
  51. package/docs/frameworks/GITHUB_ISSUE_PROPOSALS.md +1105 -0
  52. package/docs/frameworks/crewai.md +114 -0
  53. package/docs/frameworks/cursor.md +159 -0
  54. package/docs/frameworks/langchain.md +72 -0
  55. package/docs/frameworks/n8n.md +40 -0
  56. package/docs/frameworks/openclaw.md +40 -0
  57. package/docs/launch/ADD_APORT_AWESOME_LISTS_INSTRUCTIONS.md +146 -0
  58. package/docs/launch/ANNOUNCEMENT_GUIDE.md +266 -0
  59. package/docs/launch/AWESOME_REPOS.md +53 -0
  60. package/docs/launch/CURSOR_VSCODE_HOOKS_RESEARCH.md +77 -0
  61. package/docs/launch/DEMO_TERMINAL_OUTPUT.txt +48 -0
  62. package/docs/launch/DRY_AND_PLAN_CHECKLIST.md +47 -0
  63. package/docs/launch/EVIDENCE_README.md +61 -0
  64. package/docs/launch/EVIDENCE_TERMINAL_CAPTURE.txt +10 -0
  65. package/docs/launch/FRAMEWORK_SUPPORT_PLAN.md +1640 -0
  66. package/docs/launch/LAUNCH_READINESS_CHECKLIST.md +237 -0
  67. package/docs/launch/LAUNCH_STRATEGY_SUMMARY.md +464 -0
  68. package/docs/launch/OPENCLAW_FEEDBACK_AND_FIXES.md +85 -0
  69. package/docs/launch/POST_1_VALENTINE_IMPROVED.md +233 -0
  70. package/docs/launch/POST_2_GUARDRAIL_IMPROVED.md +369 -0
  71. package/docs/launch/PRE_LAUNCH_FIXES.md +766 -0
  72. package/docs/launch/QUICK_LAUNCH_CHECKLIST.md +400 -0
  73. package/docs/launch/READINESS_SUMMARY.md +262 -0
  74. package/docs/launch/README.md +68 -0
  75. package/docs/launch/USER_STORIES.md +327 -0
  76. package/docs/launch/scripts/add-aport-awesome-pr.sh +69 -0
  77. package/docs/operations/MONITORING.md +588 -0
  78. package/docs/reviews/2026-02-18-staff-review.md +268 -0
  79. package/extensions/openclaw-aport/README.md +415 -0
  80. package/extensions/openclaw-aport/index.js +625 -0
  81. package/extensions/openclaw-aport/openclaw-aport.js +7 -0
  82. package/extensions/openclaw-aport/openclaw.plugin.json +46 -0
  83. package/extensions/openclaw-aport/package.json +36 -0
  84. package/extensions/openclaw-aport/test.js +307 -0
  85. package/external/aport-policies/README.md +363 -0
  86. package/external/aport-policies/agent.session.create.v1/README.md +345 -0
  87. package/external/aport-policies/agent.session.create.v1/policy.json +162 -0
  88. package/external/aport-policies/agent.tool.register.v1/README.md +361 -0
  89. package/external/aport-policies/agent.tool.register.v1/policy.json +172 -0
  90. package/external/aport-policies/code.release.publish.v1/README.md +51 -0
  91. package/external/aport-policies/code.release.publish.v1/policy.json +121 -0
  92. package/external/aport-policies/code.repository.merge.v1/README.md +287 -0
  93. package/external/aport-policies/code.repository.merge.v1/express.example.js +332 -0
  94. package/external/aport-policies/code.repository.merge.v1/fastapi.example.py +370 -0
  95. package/external/aport-policies/code.repository.merge.v1/policy.json +162 -0
  96. package/external/aport-policies/data.export.create.v1/README.md +226 -0
  97. package/external/aport-policies/data.export.create.v1/express.example.js +172 -0
  98. package/external/aport-policies/data.export.create.v1/fastapi.example.py +165 -0
  99. package/external/aport-policies/data.export.create.v1/policy.json +133 -0
  100. package/external/aport-policies/data.report.ingest.v1/README.md +134 -0
  101. package/external/aport-policies/data.report.ingest.v1/express.example.js +105 -0
  102. package/external/aport-policies/data.report.ingest.v1/minimal-example.js +68 -0
  103. package/external/aport-policies/data.report.ingest.v1/policy.json +174 -0
  104. package/external/aport-policies/finance.crypto.trade.v1/README.md +146 -0
  105. package/external/aport-policies/finance.crypto.trade.v1/express.example.js +109 -0
  106. package/external/aport-policies/finance.crypto.trade.v1/minimal-example.js +65 -0
  107. package/external/aport-policies/finance.crypto.trade.v1/policy.json +176 -0
  108. package/external/aport-policies/finance.payment.charge.v1/README.md +326 -0
  109. package/external/aport-policies/finance.payment.charge.v1/express.example.js +250 -0
  110. package/external/aport-policies/finance.payment.charge.v1/fastapi.example.py +227 -0
  111. package/external/aport-policies/finance.payment.charge.v1/minimal-example.js +64 -0
  112. package/external/aport-policies/finance.payment.charge.v1/policy.json +224 -0
  113. package/external/aport-policies/finance.payment.charge.v1/tests/contexts.jsonl +12 -0
  114. package/external/aport-policies/finance.payment.charge.v1/tests/expected.jsonl +12 -0
  115. package/external/aport-policies/finance.payment.charge.v1/tests/passport.instance.json +42 -0
  116. package/external/aport-policies/finance.payment.charge.v1/tests/passport.template.json +40 -0
  117. package/external/aport-policies/finance.payment.charge.v1/tests/payments-charge-policy.test.js +817 -0
  118. package/external/aport-policies/finance.payment.charge.v1/tests/test_payments_charge_policy.py +486 -0
  119. package/external/aport-policies/finance.payment.payout.v1/README.md +78 -0
  120. package/external/aport-policies/finance.payment.payout.v1/policy.json +181 -0
  121. package/external/aport-policies/finance.payment.refund.v1/README.md +275 -0
  122. package/external/aport-policies/finance.payment.refund.v1/express.example.js +167 -0
  123. package/external/aport-policies/finance.payment.refund.v1/fastapi.example.py +136 -0
  124. package/external/aport-policies/finance.payment.refund.v1/minimal-example.js +183 -0
  125. package/external/aport-policies/finance.payment.refund.v1/policy.json +216 -0
  126. package/external/aport-policies/finance.payment.refund.v1/tests/refunds-policy.test.js +924 -0
  127. package/external/aport-policies/finance.payment.refund.v1/tests/test_refunds_policy.py +778 -0
  128. package/external/aport-policies/finance.transaction.execute.v1/README.md +309 -0
  129. package/external/aport-policies/finance.transaction.execute.v1/express.example.js +261 -0
  130. package/external/aport-policies/finance.transaction.execute.v1/fastapi.example.py +231 -0
  131. package/external/aport-policies/finance.transaction.execute.v1/minimal-example.js +78 -0
  132. package/external/aport-policies/finance.transaction.execute.v1/policy.json +189 -0
  133. package/external/aport-policies/finance.transaction.execute.v1/tests/contexts.jsonl +12 -0
  134. package/external/aport-policies/finance.transaction.execute.v1/tests/expected.jsonl +12 -0
  135. package/external/aport-policies/finance.transaction.execute.v1/tests/passport.instance.json +42 -0
  136. package/external/aport-policies/finance.transaction.execute.v1/tests/passport.template.json +42 -0
  137. package/external/aport-policies/finance.transaction.execute.v1/tests/test_transactions_policy.py +214 -0
  138. package/external/aport-policies/finance.transaction.execute.v1/tests/transactions-policy.test.js +306 -0
  139. package/external/aport-policies/governance.data.access.v1/README.md +292 -0
  140. package/external/aport-policies/governance.data.access.v1/express.example.js +321 -0
  141. package/external/aport-policies/governance.data.access.v1/fastapi.example.py +279 -0
  142. package/external/aport-policies/governance.data.access.v1/minimal-example.js +65 -0
  143. package/external/aport-policies/governance.data.access.v1/policy.json +208 -0
  144. package/external/aport-policies/governance.data.access.v1/tests/contexts.jsonl +12 -0
  145. package/external/aport-policies/governance.data.access.v1/tests/data-access-policy.test.js +308 -0
  146. package/external/aport-policies/governance.data.access.v1/tests/expected.jsonl +12 -0
  147. package/external/aport-policies/governance.data.access.v1/tests/passport.instance.json +56 -0
  148. package/external/aport-policies/governance.data.access.v1/tests/passport.template.json +56 -0
  149. package/external/aport-policies/governance.data.access.v1/tests/test_data_access_policy.py +214 -0
  150. package/external/aport-policies/legal.contract.review.v1/README.md +109 -0
  151. package/external/aport-policies/legal.contract.review.v1/policy.json +378 -0
  152. package/external/aport-policies/legal.contract.review.v1/tests/legal-contract-review-policy.test.js +609 -0
  153. package/external/aport-policies/legal.contract.review.v1/tests/passport.template.json +49 -0
  154. package/external/aport-policies/mcp.tool.execute.v1/README.md +301 -0
  155. package/external/aport-policies/mcp.tool.execute.v1/policy.json +141 -0
  156. package/external/aport-policies/messaging.message.send.v1/README.md +230 -0
  157. package/external/aport-policies/messaging.message.send.v1/express.example.js +183 -0
  158. package/external/aport-policies/messaging.message.send.v1/fastapi.example.py +193 -0
  159. package/external/aport-policies/messaging.message.send.v1/policy.json +144 -0
  160. package/external/aport-policies/policy-template.json +107 -0
  161. package/external/aport-policies/system.command.execute.v1/README.md +275 -0
  162. package/external/aport-policies/system.command.execute.v1/policy.json +146 -0
  163. package/external/aport-spec/CONTRIBUTING.md +273 -0
  164. package/external/aport-spec/LICENSE +21 -0
  165. package/external/aport-spec/README.md +168 -0
  166. package/external/aport-spec/conformance/README.md +294 -0
  167. package/external/aport-spec/conformance/cases/data.export.v1/contexts/allow_users.json +6 -0
  168. package/external/aport-spec/conformance/cases/data.export.v1/contexts/deny_pii.json +6 -0
  169. package/external/aport-spec/conformance/cases/data.export.v1/expected/allow_users.decision.json +19 -0
  170. package/external/aport-spec/conformance/cases/data.export.v1/expected/deny_pii.decision.json +19 -0
  171. package/external/aport-spec/conformance/cases/data.export.v1/passports/template.json +29 -0
  172. package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/allow_50usd.json +9 -0
  173. package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/deny_150usd.json +9 -0
  174. package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/deny_currency.json +9 -0
  175. package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/allow_50usd.decision.json +19 -0
  176. package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/deny_150usd.decision.json +19 -0
  177. package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/deny_currency.decision.json +19 -0
  178. package/external/aport-spec/conformance/cases/payments.refunds.v1/passports/template.json +42 -0
  179. package/external/aport-spec/conformance/package.json +44 -0
  180. package/external/aport-spec/conformance/pnpm-lock.yaml +642 -0
  181. package/external/aport-spec/conformance/src/cases.ts +371 -0
  182. package/external/aport-spec/conformance/src/ed25519.ts +167 -0
  183. package/external/aport-spec/conformance/src/jcs.ts +85 -0
  184. package/external/aport-spec/conformance/src/runner.ts +533 -0
  185. package/external/aport-spec/conformance/src/validators.ts +185 -0
  186. package/external/aport-spec/conformance/test-runner.js +315 -0
  187. package/external/aport-spec/conformance/tsconfig.json +21 -0
  188. package/external/aport-spec/error-schema.json +192 -0
  189. package/external/aport-spec/index.json +12 -0
  190. package/external/aport-spec/integrations/clawmoat/README.md +12 -0
  191. package/external/aport-spec/integrations/shield/README.md +245 -0
  192. package/external/aport-spec/integrations/shield/adapters/index.js +116 -0
  193. package/external/aport-spec/integrations/shield/adapters/system-command-execute.js +133 -0
  194. package/external/aport-spec/integrations/shield/test/README.md +58 -0
  195. package/external/aport-spec/integrations/shield/test/shield.md +40 -0
  196. package/external/aport-spec/integrations/shield/test/test-shield-to-verify.js +274 -0
  197. package/external/aport-spec/metrics-schema.json +504 -0
  198. package/external/aport-spec/oap/CHANGELOG.md +54 -0
  199. package/external/aport-spec/oap/VERSION.md +40 -0
  200. package/external/aport-spec/oap/capability-registry.md +229 -0
  201. package/external/aport-spec/oap/conformance.md +257 -0
  202. package/external/aport-spec/oap/decision-schema.json +114 -0
  203. package/external/aport-spec/oap/examples/context.refund.usd.50.json +9 -0
  204. package/external/aport-spec/oap/examples/decision.allow.sample.json +20 -0
  205. package/external/aport-spec/oap/examples/decision.deny.sample.json +23 -0
  206. package/external/aport-spec/oap/examples/passport.instance.v1.json +50 -0
  207. package/external/aport-spec/oap/examples/passport.template.v1.json +71 -0
  208. package/external/aport-spec/oap/oap-spec.md +426 -0
  209. package/external/aport-spec/oap/passport-schema.json +396 -0
  210. package/external/aport-spec/oap/security.md +213 -0
  211. package/external/aport-spec/oap/vc/context-oap-v1.jsonld +137 -0
  212. package/external/aport-spec/oap/vc/examples/oap-decision-vc.json +37 -0
  213. package/external/aport-spec/oap/vc/examples/oap-passport-vc.json +68 -0
  214. package/external/aport-spec/oap/vc/tools/INTEGRATION.md +375 -0
  215. package/external/aport-spec/oap/vc/tools/README.md +278 -0
  216. package/external/aport-spec/oap/vc/tools/examples/decision-to-vc.js +66 -0
  217. package/external/aport-spec/oap/vc/tools/examples/passport-to-vc.js +83 -0
  218. package/external/aport-spec/oap/vc/tools/examples/vc-to-decision.js +77 -0
  219. package/external/aport-spec/oap/vc/tools/examples/vc-to-passport.js +94 -0
  220. package/external/aport-spec/oap/vc/tools/package.json +38 -0
  221. package/external/aport-spec/oap/vc/tools/pnpm-lock.yaml +472 -0
  222. package/external/aport-spec/oap/vc/tools/src/cli.ts +226 -0
  223. package/external/aport-spec/oap/vc/tools/src/crypto-utils.ts +427 -0
  224. package/external/aport-spec/oap/vc/tools/src/index.ts +653 -0
  225. package/external/aport-spec/oap/vc/tools/src/test.ts +148 -0
  226. package/external/aport-spec/oap/vc/tools/src/vp.ts +382 -0
  227. package/external/aport-spec/oap/vc/tools/test-simple.js +214 -0
  228. package/external/aport-spec/oap/vc/tools/tsconfig.json +19 -0
  229. package/external/aport-spec/oap/vc/vc-mapping.md +443 -0
  230. package/external/aport-spec/passport-schema.json +586 -0
  231. package/external/aport-spec/rate-limiting.md +136 -0
  232. package/external/aport-spec/transport-profile.md +325 -0
  233. package/external/aport-spec/webhook-spec.md +314 -0
  234. package/package.json +70 -0
  235. package/skills/aport-agent-guardrail/SKILL.md +314 -0
  236. package/src/evaluator.js +252 -0
  237. package/src/server/index.js +72 -0
@@ -0,0 +1,924 @@
1
+ /**
2
+ * Comprehensive tests for finance.payment.refund.v1 policy
3
+ * Tests all enforcement rules, edge cases, and fraud prevention
4
+ */
5
+
6
+ const {
7
+ test,
8
+ expect,
9
+ describe,
10
+ beforeEach,
11
+ afterEach,
12
+ } = require("@jest/globals");
13
+
14
+ // Mock the policy verification endpoint
15
+ const mockPolicyVerification = (response) => {
16
+ global.fetch = jest.fn().mockResolvedValue({
17
+ ok: true,
18
+ json: () => Promise.resolve(response),
19
+ });
20
+ };
21
+
22
+ const mockPolicyVerificationError = (status = 500) => {
23
+ global.fetch = jest.fn().mockResolvedValue({
24
+ ok: false,
25
+ status,
26
+ text: () => Promise.resolve("Policy verification failed"),
27
+ });
28
+ };
29
+
30
+ describe("Refunds v1 Policy - Core Functionality", () => {
31
+ beforeEach(() => {
32
+ // Reset mocks
33
+ jest.clearAllMocks();
34
+ });
35
+
36
+ describe("Required Fields Validation", () => {
37
+ test("should allow refund with all required fields", async () => {
38
+ const validContext = {
39
+ order_id: "ORD-12345",
40
+ customer_id: "CUST-67890",
41
+ amount_minor: 5000,
42
+ currency: "USD",
43
+ region: "US",
44
+ reason_code: "customer_request",
45
+ idempotency_key: "idempotency_key_123",
46
+ };
47
+
48
+ mockPolicyVerification({
49
+ allow: true,
50
+ decision_id: "dec_123",
51
+ remaining_daily_cap: { USD: 25000 },
52
+ expires_in: 60,
53
+ });
54
+
55
+ const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
56
+ const result = await verifyPolicy(
57
+ "agent_123",
58
+ "finance.payment.refund.v1",
59
+ validContext
60
+ );
61
+
62
+ expect(result.allowed).toBe(true);
63
+ expect(result.result).toBeDefined();
64
+ });
65
+
66
+ test("should deny refund missing required fields", async () => {
67
+ const invalidContext = {
68
+ order_id: "ORD-12345",
69
+ // Missing customer_id, amount_minor, currency, region, reason_code, idempotency_key
70
+ };
71
+
72
+ mockPolicyVerification({
73
+ allow: false,
74
+ reasons: [
75
+ {
76
+ code: "missing_required_field",
77
+ message: "customer_id is required",
78
+ },
79
+ {
80
+ code: "missing_required_field",
81
+ message: "amount_minor is required",
82
+ },
83
+ { code: "missing_required_field", message: "currency is required" },
84
+ { code: "missing_required_field", message: "region is required" },
85
+ {
86
+ code: "missing_required_field",
87
+ message: "reason_code is required",
88
+ },
89
+ {
90
+ code: "missing_required_field",
91
+ message: "idempotency_key is required",
92
+ },
93
+ ],
94
+ });
95
+
96
+ const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
97
+ const result = await verifyPolicy(
98
+ "agent_123",
99
+ "finance.payment.refund.v1",
100
+ invalidContext
101
+ );
102
+
103
+ expect(result.allowed).toBe(false);
104
+ expect(result.error?.violations).toHaveLength(6);
105
+ });
106
+ });
107
+
108
+ describe("Currency Support", () => {
109
+ test("should support multiple currencies", async () => {
110
+ const currencies = ["USD", "EUR", "GBP", "JPY", "CAD", "AUD"];
111
+
112
+ for (const currency of currencies) {
113
+ const context = {
114
+ order_id: "ORD-12345",
115
+ customer_id: "CUST-67890",
116
+ amount_minor: 1000,
117
+ currency,
118
+ region: "US",
119
+ reason_code: "customer_request",
120
+ idempotency_key: `idempotency_${currency}`,
121
+ };
122
+
123
+ mockPolicyVerification({
124
+ allow: true,
125
+ decision_id: `dec_${currency}`,
126
+ remaining_daily_cap: { [currency]: 25000 },
127
+ });
128
+
129
+ const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
130
+ const result = await verifyPolicy(
131
+ "agent_123",
132
+ "finance.payment.refund.v1",
133
+ context
134
+ );
135
+
136
+ expect(result.allowed).toBe(true);
137
+ }
138
+ });
139
+
140
+ test("should deny unsupported currency", async () => {
141
+ const context = {
142
+ order_id: "ORD-12345",
143
+ customer_id: "CUST-67890",
144
+ amount_minor: 1000,
145
+ currency: "XYZ", // Unsupported currency
146
+ region: "US",
147
+ reason_code: "customer_request",
148
+ idempotency_key: "idempotency_xyz",
149
+ };
150
+
151
+ mockPolicyVerification({
152
+ allow: false,
153
+ reasons: [
154
+ {
155
+ code: "currency_not_supported",
156
+ message: "Currency XYZ is not supported",
157
+ },
158
+ ],
159
+ });
160
+
161
+ const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
162
+ const result = await verifyPolicy(
163
+ "agent_123",
164
+ "finance.payment.refund.v1",
165
+ context
166
+ );
167
+
168
+ expect(result.allowed).toBe(false);
169
+ expect(result.error?.violations[0].code).toBe("currency_not_supported");
170
+ });
171
+ });
172
+
173
+ describe("Amount Validation", () => {
174
+ test("should validate amount precision for different currencies", async () => {
175
+ const testCases = [
176
+ { currency: "USD", amount: 1000, valid: true }, // $10.00 - valid
177
+ { currency: "USD", amount: 1001, valid: false }, // $10.01 - invalid precision
178
+ { currency: "JPY", amount: 1000, valid: true }, // ¥1000 - valid
179
+ { currency: "JPY", amount: 1001, valid: true }, // ¥1001 - valid (no decimals)
180
+ { currency: "EUR", amount: 1000, valid: true }, // €10.00 - valid
181
+ { currency: "EUR", amount: 1001, valid: false }, // €10.01 - invalid precision
182
+ ];
183
+
184
+ for (const testCase of testCases) {
185
+ const context = {
186
+ order_id: "ORD-12345",
187
+ customer_id: "CUST-67890",
188
+ amount_minor: testCase.amount,
189
+ currency: testCase.currency,
190
+ region: "US",
191
+ reason_code: "customer_request",
192
+ idempotency_key: `idempotency_${testCase.currency}_${testCase.amount}`,
193
+ };
194
+
195
+ mockPolicyVerification({
196
+ allow: testCase.valid,
197
+ reasons: testCase.valid
198
+ ? []
199
+ : [
200
+ {
201
+ code: "invalid_amount",
202
+ message: `Amount ${testCase.amount} has invalid precision for currency ${testCase.currency}`,
203
+ },
204
+ ],
205
+ });
206
+
207
+ const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
208
+ const result = await verifyPolicy(
209
+ "agent_123",
210
+ "finance.payment.refund.v1",
211
+ context
212
+ );
213
+
214
+ expect(result.allowed).toBe(testCase.valid);
215
+ }
216
+ });
217
+
218
+ test("should validate amount bounds", async () => {
219
+ const testCases = [
220
+ { amount: 0, valid: false, reason: "Amount must be positive" },
221
+ { amount: -100, valid: false, reason: "Amount must be positive" },
222
+ { amount: 1, valid: true, reason: "Minimum amount" },
223
+ { amount: 1000000000, valid: false, reason: "Amount exceeds maximum" },
224
+ { amount: 999999999, valid: true, reason: "Maximum valid amount" },
225
+ ];
226
+
227
+ for (const testCase of testCases) {
228
+ const context = {
229
+ order_id: "ORD-12345",
230
+ customer_id: "CUST-67890",
231
+ amount_minor: testCase.amount,
232
+ currency: "USD",
233
+ region: "US",
234
+ reason_code: "customer_request",
235
+ idempotency_key: `idempotency_${testCase.amount}`,
236
+ };
237
+
238
+ mockPolicyVerification({
239
+ allow: testCase.valid,
240
+ reasons: testCase.valid
241
+ ? []
242
+ : [{ code: "invalid_amount", message: testCase.reason }],
243
+ });
244
+
245
+ const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
246
+ const result = await verifyPolicy(
247
+ "agent_123",
248
+ "finance.payment.refund.v1",
249
+ context
250
+ );
251
+
252
+ expect(result.allowed).toBe(testCase.valid);
253
+ }
254
+ });
255
+ });
256
+
257
+ describe("Assurance Level Requirements", () => {
258
+ test("should require L2 for amounts <= $100", async () => {
259
+ const context = {
260
+ order_id: "ORD-12345",
261
+ customer_id: "CUST-67890",
262
+ amount_minor: 10000, // $100
263
+ currency: "USD",
264
+ region: "US",
265
+ reason_code: "customer_request",
266
+ idempotency_key: "idempotency_l2",
267
+ };
268
+
269
+ // Test with L2 assurance (should pass)
270
+ mockPolicyVerification({
271
+ allow: true,
272
+ decision_id: "dec_l2_pass",
273
+ remaining_daily_cap: { USD: 25000 },
274
+ });
275
+
276
+ const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
277
+ const result = await verifyPolicy(
278
+ "agent_123",
279
+ "finance.payment.refund.v1",
280
+ context
281
+ );
282
+
283
+ expect(result.allowed).toBe(true);
284
+ });
285
+
286
+ test("should require L3 for amounts $100-$500", async () => {
287
+ const context = {
288
+ order_id: "ORD-12345",
289
+ customer_id: "CUST-67890",
290
+ amount_minor: 25000, // $250
291
+ currency: "USD",
292
+ region: "US",
293
+ reason_code: "customer_request",
294
+ idempotency_key: "idempotency_l3",
295
+ };
296
+
297
+ // Test with L2 assurance (should fail)
298
+ mockPolicyVerification({
299
+ allow: false,
300
+ reasons: [
301
+ {
302
+ code: "assurance_too_low",
303
+ message:
304
+ "Refund amount 25000 USD requires L3 assurance level, but agent has L2",
305
+ },
306
+ ],
307
+ });
308
+
309
+ const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
310
+ const result = await verifyPolicy(
311
+ "agent_123",
312
+ "finance.payment.refund.v1",
313
+ context
314
+ );
315
+
316
+ expect(result.allowed).toBe(false);
317
+ expect(result.error?.violations[0].code).toBe("assurance_too_low");
318
+ });
319
+
320
+ test("should deny amounts > $500 in v1", async () => {
321
+ const context = {
322
+ order_id: "ORD-12345",
323
+ customer_id: "CUST-67890",
324
+ amount_minor: 60000, // $600
325
+ currency: "USD",
326
+ region: "US",
327
+ reason_code: "customer_request",
328
+ idempotency_key: "idempotency_deny",
329
+ };
330
+
331
+ mockPolicyVerification({
332
+ allow: false,
333
+ reasons: [
334
+ {
335
+ code: "assurance_too_low",
336
+ message:
337
+ "Refund amount 60000 USD requires L4 assurance level, but agent has L3",
338
+ },
339
+ ],
340
+ });
341
+
342
+ const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
343
+ const result = await verifyPolicy(
344
+ "agent_123",
345
+ "finance.payment.refund.v1",
346
+ context
347
+ );
348
+
349
+ expect(result.allowed).toBe(false);
350
+ });
351
+ });
352
+
353
+ describe("Idempotency Protection", () => {
354
+ test("should allow first request with idempotency key", async () => {
355
+ const context = {
356
+ order_id: "ORD-12345",
357
+ customer_id: "CUST-67890",
358
+ amount_minor: 5000,
359
+ currency: "USD",
360
+ region: "US",
361
+ reason_code: "customer_request",
362
+ idempotency_key: "unique_key_123",
363
+ };
364
+
365
+ mockPolicyVerification({
366
+ allow: true,
367
+ decision_id: "dec_first",
368
+ remaining_daily_cap: { USD: 25000 },
369
+ });
370
+
371
+ const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
372
+ const result = await verifyPolicy(
373
+ "agent_123",
374
+ "finance.payment.refund.v1",
375
+ context
376
+ );
377
+
378
+ expect(result.allowed).toBe(true);
379
+ });
380
+
381
+ test("should deny duplicate idempotency key", async () => {
382
+ const context = {
383
+ order_id: "ORD-12345",
384
+ customer_id: "CUST-67890",
385
+ amount_minor: 5000,
386
+ currency: "USD",
387
+ region: "US",
388
+ reason_code: "customer_request",
389
+ idempotency_key: "duplicate_key_123",
390
+ };
391
+
392
+ mockPolicyVerification({
393
+ allow: false,
394
+ reasons: [
395
+ {
396
+ code: "idempotency_replay",
397
+ message:
398
+ "Duplicate idempotency key detected. Previous decision: dec_duplicate",
399
+ },
400
+ ],
401
+ });
402
+
403
+ const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
404
+ const result = await verifyPolicy(
405
+ "agent_123",
406
+ "finance.payment.refund.v1",
407
+ context
408
+ );
409
+
410
+ expect(result.allowed).toBe(false);
411
+ expect(result.error?.violations[0].code).toBe("idempotency_replay");
412
+ });
413
+ });
414
+
415
+ describe("Daily Cap Enforcement", () => {
416
+ test("should allow refund within daily cap", async () => {
417
+ const context = {
418
+ order_id: "ORD-12345",
419
+ customer_id: "CUST-67890",
420
+ amount_minor: 5000,
421
+ currency: "USD",
422
+ region: "US",
423
+ reason_code: "customer_request",
424
+ idempotency_key: "daily_cap_test",
425
+ };
426
+
427
+ mockPolicyVerification({
428
+ allow: true,
429
+ decision_id: "dec_daily_cap",
430
+ remaining_daily_cap: { USD: 20000 },
431
+ });
432
+
433
+ const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
434
+ const result = await verifyPolicy(
435
+ "agent_123",
436
+ "finance.payment.refund.v1",
437
+ context
438
+ );
439
+
440
+ expect(result.allowed).toBe(true);
441
+ expect(result.result?.evaluation?.remaining_daily_cap?.USD).toBe(20000);
442
+ });
443
+
444
+ test("should deny refund exceeding daily cap", async () => {
445
+ const context = {
446
+ order_id: "ORD-12345",
447
+ customer_id: "CUST-67890",
448
+ amount_minor: 5000,
449
+ currency: "USD",
450
+ region: "US",
451
+ reason_code: "customer_request",
452
+ idempotency_key: "daily_cap_exceeded",
453
+ };
454
+
455
+ mockPolicyVerification({
456
+ allow: false,
457
+ reasons: [
458
+ {
459
+ code: "daily_cap_exceeded",
460
+ message:
461
+ "Daily cap 25000 USD exceeded for USD; current 23000 + 5000 > 25000",
462
+ },
463
+ ],
464
+ remaining_daily_cap: { USD: 2000 },
465
+ });
466
+
467
+ const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
468
+ const result = await verifyPolicy(
469
+ "agent_123",
470
+ "finance.payment.refund.v1",
471
+ context
472
+ );
473
+
474
+ expect(result.allowed).toBe(false);
475
+ expect(result.error?.violations[0].code).toBe("daily_cap_exceeded");
476
+ });
477
+ });
478
+
479
+ describe("Cross-Currency Protection", () => {
480
+ test("should deny cross-currency refunds", async () => {
481
+ const context = {
482
+ order_id: "ORD-12345",
483
+ customer_id: "CUST-67890",
484
+ amount_minor: 5000,
485
+ currency: "USD",
486
+ order_currency: "EUR", // Different from refund currency
487
+ region: "US",
488
+ reason_code: "customer_request",
489
+ idempotency_key: "cross_currency_test",
490
+ };
491
+
492
+ mockPolicyVerification({
493
+ allow: false,
494
+ reasons: [
495
+ {
496
+ code: "cross_currency_denied",
497
+ message: "Cross-currency refunds are not supported in v1",
498
+ },
499
+ ],
500
+ });
501
+
502
+ const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
503
+ const result = await verifyPolicy(
504
+ "agent_123",
505
+ "finance.payment.refund.v1",
506
+ context
507
+ );
508
+
509
+ expect(result.allowed).toBe(false);
510
+ expect(result.error?.violations[0].code).toBe("cross_currency_denied");
511
+ });
512
+ });
513
+
514
+ describe("Order Balance Validation", () => {
515
+ test("should allow refund within order balance", async () => {
516
+ const context = {
517
+ order_id: "ORD-12345",
518
+ customer_id: "CUST-67890",
519
+ amount_minor: 5000,
520
+ currency: "USD",
521
+ order_total_minor: 10000,
522
+ already_refunded_minor: 2000,
523
+ region: "US",
524
+ reason_code: "customer_request",
525
+ idempotency_key: "balance_valid",
526
+ };
527
+
528
+ mockPolicyVerification({
529
+ allow: true,
530
+ decision_id: "dec_balance_valid",
531
+ remaining_daily_cap: { USD: 25000 },
532
+ });
533
+
534
+ const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
535
+ const result = await verifyPolicy(
536
+ "agent_123",
537
+ "finance.payment.refund.v1",
538
+ context
539
+ );
540
+
541
+ expect(result.allowed).toBe(true);
542
+ });
543
+
544
+ test("should deny refund exceeding order balance", async () => {
545
+ const context = {
546
+ order_id: "ORD-12345",
547
+ customer_id: "CUST-67890",
548
+ amount_minor: 5000,
549
+ currency: "USD",
550
+ order_total_minor: 10000,
551
+ already_refunded_minor: 8000, // Only 2000 remaining
552
+ region: "US",
553
+ reason_code: "customer_request",
554
+ idempotency_key: "balance_exceeded",
555
+ };
556
+
557
+ mockPolicyVerification({
558
+ allow: false,
559
+ reasons: [
560
+ {
561
+ code: "order_balance_exceeded",
562
+ message: "Refund amount 5000 exceeds remaining order balance 2000",
563
+ },
564
+ ],
565
+ });
566
+
567
+ const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
568
+ const result = await verifyPolicy(
569
+ "agent_123",
570
+ "finance.payment.refund.v1",
571
+ context
572
+ );
573
+
574
+ expect(result.allowed).toBe(false);
575
+ expect(result.error?.violations[0].code).toBe("order_balance_exceeded");
576
+ });
577
+ });
578
+
579
+ describe("Region Validation", () => {
580
+ test("should allow refund in allowed region", async () => {
581
+ const context = {
582
+ order_id: "ORD-12345",
583
+ customer_id: "CUST-67890",
584
+ amount_minor: 5000,
585
+ currency: "USD",
586
+ region: "US",
587
+ reason_code: "customer_request",
588
+ idempotency_key: "region_valid",
589
+ };
590
+
591
+ mockPolicyVerification({
592
+ allow: true,
593
+ decision_id: "dec_region_valid",
594
+ remaining_daily_cap: { USD: 25000 },
595
+ });
596
+
597
+ const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
598
+ const result = await verifyPolicy(
599
+ "agent_123",
600
+ "finance.payment.refund.v1",
601
+ context
602
+ );
603
+
604
+ expect(result.allowed).toBe(true);
605
+ });
606
+
607
+ test("should deny refund in disallowed region", async () => {
608
+ const context = {
609
+ order_id: "ORD-12345",
610
+ customer_id: "CUST-67890",
611
+ amount_minor: 5000,
612
+ currency: "USD",
613
+ region: "RESTRICTED",
614
+ reason_code: "customer_request",
615
+ idempotency_key: "region_invalid",
616
+ };
617
+
618
+ mockPolicyVerification({
619
+ allow: false,
620
+ reasons: [
621
+ {
622
+ code: "region_not_allowed",
623
+ message: "Region RESTRICTED is not allowed for this agent",
624
+ },
625
+ ],
626
+ });
627
+
628
+ const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
629
+ const result = await verifyPolicy(
630
+ "agent_123",
631
+ "finance.payment.refund.v1",
632
+ context
633
+ );
634
+
635
+ expect(result.allowed).toBe(false);
636
+ expect(result.error?.violations[0].code).toBe("region_not_allowed");
637
+ });
638
+ });
639
+
640
+ describe("Reason Code Validation", () => {
641
+ test("should allow valid reason codes", async () => {
642
+ const validReasonCodes = [
643
+ "customer_request",
644
+ "defective",
645
+ "not_as_described",
646
+ "duplicate",
647
+ "fraud",
648
+ ];
649
+
650
+ for (const reasonCode of validReasonCodes) {
651
+ const context = {
652
+ order_id: "ORD-12345",
653
+ customer_id: "CUST-67890",
654
+ amount_minor: 5000,
655
+ currency: "USD",
656
+ region: "US",
657
+ reason_code: reasonCode,
658
+ idempotency_key: `reason_${reasonCode}`,
659
+ };
660
+
661
+ mockPolicyVerification({
662
+ allow: true,
663
+ decision_id: `dec_${reasonCode}`,
664
+ remaining_daily_cap: { USD: 25000 },
665
+ });
666
+
667
+ const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
668
+ const result = await verifyPolicy(
669
+ "agent_123",
670
+ "finance.payment.refund.v1",
671
+ context
672
+ );
673
+
674
+ expect(result.allowed).toBe(true);
675
+ }
676
+ });
677
+
678
+ test("should deny invalid reason codes", async () => {
679
+ const context = {
680
+ order_id: "ORD-12345",
681
+ customer_id: "CUST-67890",
682
+ amount_minor: 5000,
683
+ currency: "USD",
684
+ region: "US",
685
+ reason_code: "invalid_reason",
686
+ idempotency_key: "invalid_reason_test",
687
+ };
688
+
689
+ mockPolicyVerification({
690
+ allow: false,
691
+ reasons: [
692
+ {
693
+ code: "reason_code_invalid",
694
+ message: "Reason code invalid_reason is not supported",
695
+ },
696
+ ],
697
+ });
698
+
699
+ const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
700
+ const result = await verifyPolicy(
701
+ "agent_123",
702
+ "finance.payment.refund.v1",
703
+ context
704
+ );
705
+
706
+ expect(result.allowed).toBe(false);
707
+ expect(result.error?.violations[0].code).toBe("reason_code_invalid");
708
+ });
709
+ });
710
+
711
+ describe("Error Handling", () => {
712
+ test("should handle policy verification failures gracefully", async () => {
713
+ mockPolicyVerificationError(500);
714
+
715
+ const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
716
+ const result = await verifyPolicy(
717
+ "agent_123",
718
+ "finance.payment.refund.v1",
719
+ {}
720
+ );
721
+
722
+ expect(result.allowed).toBe(false);
723
+ expect(result.error?.code).toBe("policy_verification_failed");
724
+ });
725
+
726
+ test("should handle network errors", async () => {
727
+ global.fetch = jest.fn().mockRejectedValue(new Error("Network error"));
728
+
729
+ const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
730
+ const result = await verifyPolicy(
731
+ "agent_123",
732
+ "finance.payment.refund.v1",
733
+ {}
734
+ );
735
+
736
+ expect(result.allowed).toBe(false);
737
+ expect(result.error?.code).toBe("policy_verification_error");
738
+ });
739
+ });
740
+
741
+ describe("Edge Cases and Fraud Prevention", () => {
742
+ test("should prevent extremely large amounts", async () => {
743
+ const context = {
744
+ order_id: "ORD-12345",
745
+ customer_id: "CUST-67890",
746
+ amount_minor: Number.MAX_SAFE_INTEGER,
747
+ currency: "USD",
748
+ region: "US",
749
+ reason_code: "customer_request",
750
+ idempotency_key: "extreme_amount",
751
+ };
752
+
753
+ mockPolicyVerification({
754
+ allow: false,
755
+ reasons: [
756
+ {
757
+ code: "invalid_amount",
758
+ message: "Amount exceeds maximum allowed amount",
759
+ },
760
+ ],
761
+ });
762
+
763
+ const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
764
+ const result = await verifyPolicy(
765
+ "agent_123",
766
+ "finance.payment.refund.v1",
767
+ context
768
+ );
769
+
770
+ expect(result.allowed).toBe(false);
771
+ });
772
+
773
+ test("should prevent negative amounts", async () => {
774
+ const context = {
775
+ order_id: "ORD-12345",
776
+ customer_id: "CUST-67890",
777
+ amount_minor: -1000,
778
+ currency: "USD",
779
+ region: "US",
780
+ reason_code: "customer_request",
781
+ idempotency_key: "negative_amount",
782
+ };
783
+
784
+ mockPolicyVerification({
785
+ allow: false,
786
+ reasons: [
787
+ { code: "invalid_amount", message: "Amount must be positive" },
788
+ ],
789
+ });
790
+
791
+ const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
792
+ const result = await verifyPolicy(
793
+ "agent_123",
794
+ "finance.payment.refund.v1",
795
+ context
796
+ );
797
+
798
+ expect(result.allowed).toBe(false);
799
+ });
800
+
801
+ test("should prevent zero amounts", async () => {
802
+ const context = {
803
+ order_id: "ORD-12345",
804
+ customer_id: "CUST-67890",
805
+ amount_minor: 0,
806
+ currency: "USD",
807
+ region: "US",
808
+ reason_code: "customer_request",
809
+ idempotency_key: "zero_amount",
810
+ };
811
+
812
+ mockPolicyVerification({
813
+ allow: false,
814
+ reasons: [
815
+ { code: "invalid_amount", message: "Amount must be positive" },
816
+ ],
817
+ });
818
+
819
+ const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
820
+ const result = await verifyPolicy(
821
+ "agent_123",
822
+ "finance.payment.refund.v1",
823
+ context
824
+ );
825
+
826
+ expect(result.allowed).toBe(false);
827
+ });
828
+
829
+ test("should validate idempotency key format", async () => {
830
+ const invalidKeys = [
831
+ "",
832
+ "a",
833
+ "a".repeat(100),
834
+ "invalid@key",
835
+ "key with spaces",
836
+ ];
837
+
838
+ for (const key of invalidKeys) {
839
+ const context = {
840
+ order_id: "ORD-12345",
841
+ customer_id: "CUST-67890",
842
+ amount_minor: 5000,
843
+ currency: "USD",
844
+ region: "US",
845
+ reason_code: "customer_request",
846
+ idempotency_key: key,
847
+ };
848
+
849
+ mockPolicyVerification({
850
+ allow: false,
851
+ reasons: [
852
+ {
853
+ code: "invalid_idempotency_key",
854
+ message: "Invalid idempotency key format",
855
+ },
856
+ ],
857
+ });
858
+
859
+ const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
860
+ const result = await verifyPolicy(
861
+ "agent_123",
862
+ "finance.payment.refund.v1",
863
+ context
864
+ );
865
+
866
+ expect(result.allowed).toBe(false);
867
+ }
868
+ });
869
+ });
870
+ });
871
+
872
+ describe("Refunds v1 Policy - Integration Tests", () => {
873
+ describe("Express Middleware Integration", () => {
874
+ test("should work with Express middleware", async () => {
875
+ const express = require("express");
876
+ const {
877
+ requireRefundsPolicy,
878
+ } = require("../middleware/express/src/index");
879
+
880
+ const app = express();
881
+ app.use(express.json());
882
+
883
+ app.post("/refund", requireRefundsPolicy("agent_123"), (req, res) => {
884
+ res.json({ success: true, refund_id: "ref_123" });
885
+ });
886
+
887
+ // Mock the policy verification
888
+ mockPolicyVerification({
889
+ allow: true,
890
+ decision_id: "dec_123",
891
+ remaining_daily_cap: { USD: 25000 },
892
+ });
893
+
894
+ const request = require("supertest")(app);
895
+ const response = await request.post("/refund").send({
896
+ order_id: "ORD-12345",
897
+ customer_id: "CUST-67890",
898
+ amount_minor: 5000,
899
+ currency: "USD",
900
+ region: "US",
901
+ reason_code: "customer_request",
902
+ idempotency_key: "test_key_123",
903
+ });
904
+
905
+ expect(response.status).toBe(200);
906
+ expect(response.body.success).toBe(true);
907
+ });
908
+ });
909
+
910
+ describe("FastAPI Middleware Integration", () => {
911
+ test("should work with FastAPI middleware", async () => {
912
+ // This would require a more complex setup with FastAPI test client
913
+ // For now, we'll just verify the function exists and has the right signature
914
+ const {
915
+ require_refunds_policy,
916
+ } = require("../middleware/fastapi/src/aporthq_middleware_fastapi/middleware_v2");
917
+
918
+ expect(typeof require_refunds_policy).toBe("function");
919
+
920
+ const dependency = require_refunds_policy("agent_123", true, true);
921
+ expect(typeof dependency).toBe("function");
922
+ });
923
+ });
924
+ });