@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,326 @@
1
+ # Payment Charge Policy Pack v1
2
+
3
+ Protect your payment charge endpoints with APort's standardized policy pack. This pack ensures only verified agents with proper capabilities, assurance levels, and transaction limits can initiate payments.
4
+
5
+ ## What This Pack Protects
6
+
7
+ - **Route**: `/payments/charge/*` (POST)
8
+ - **Risk**: Financial transactions, fraud prevention, regulatory compliance
9
+ - **Impact**: Direct monetary loss, chargeback disputes, audit findings
10
+
11
+ ## Requirements
12
+
13
+ | Requirement | Value | Description |
14
+ |-------------|-------|-------------|
15
+ | **Capability** | `payments.charge` | Agent must have charge capability |
16
+ | **Assurance** | `L2` or higher | Email + GitHub verification minimum |
17
+ | **Limits** | `currency_limits.{ISO4217}.{max_per_tx,daily_cap}` | Per-currency transaction and daily limits |
18
+ | **Limits** | `allowed_merchant_ids[]` | Merchant allowlist (optional) |
19
+ | **Limits** | `allowed_countries[]` | Country allowlist (optional) |
20
+ | **Limits** | `blocked_categories[]` | Category blocklist (optional) |
21
+ | **Limits** | `max_items_per_tx` | Maximum items per transaction (optional) |
22
+ | **Regions** | Must match | Agent must be authorized in caller's region |
23
+ | **Idempotency** | Required | Prevents duplicate charges |
24
+
25
+ ## Implementation
26
+
27
+ ### Express.js
28
+
29
+ ```javascript
30
+ const { requirePolicy } = require('@aporthq/middleware-express');
31
+
32
+ // Option 1: Explicit agent ID (preferred)
33
+ app.post('/payments/charge',
34
+ requirePolicy('finance.payment.charge.v1', 'ap_a2d10232c6534523812423eec8a1425c45678'),
35
+ async (req, res) => {
36
+ // Your charge logic here
37
+ // req.policyResult contains the verified passport
38
+ const { amount, currency, merchant_id, items } = req.body;
39
+ const passport = req.policyResult.passport;
40
+
41
+ // Process charge...
42
+ res.json({ success: true, charge_id: generateId() });
43
+ }
44
+ );
45
+
46
+ // Option 2: Header fallback (backward compatible)
47
+ app.post('/payments/charge',
48
+ requirePolicy('finance.payment.charge.v1'),
49
+ async (req, res) => {
50
+ // Your charge logic here
51
+ // req.policyResult contains the verified passport
52
+ const { amount, currency, merchant_id, items } = req.body;
53
+ const passport = req.policyResult.passport;
54
+
55
+ // Process charge...
56
+ res.json({ success: true, charge_id: generateId() });
57
+ }
58
+ );
59
+ ```
60
+
61
+ **Client Request Example:**
62
+ ```javascript
63
+ // The client must include the agent ID in the header (for Option 2)
64
+ fetch('/payments/charge', {
65
+ method: 'POST',
66
+ headers: {
67
+ 'Content-Type': 'application/json',
68
+ 'X-Agent-Passport-Id': 'ap_a2d10232c6534523812423eec8a1425c45678' // ← Agent ID passed here
69
+ },
70
+ body: JSON.stringify({
71
+ amount: 1299,
72
+ currency: "USD",
73
+ merchant_id: "merch_abc",
74
+ region: "US",
75
+ shipping_country: "US",
76
+ items: [
77
+ { sku: "SKU-1", qty: 1, category: "electronics" }
78
+ ],
79
+ idempotency_key: "charge-ord-123"
80
+ })
81
+ });
82
+ ```
83
+
84
+ ### FastAPI
85
+
86
+ ```python
87
+ from aport.middleware import require_policy
88
+
89
+ @app.post("/payments/charge")
90
+ @require_policy("finance.payment.charge.v1")
91
+ async def process_charge(request: Request, charge_data: ChargeRequest):
92
+ # Your charge logic here
93
+ # request.state.policy_result contains the verified passport
94
+ passport = request.state.policy_result.passport
95
+
96
+ # Process charge...
97
+ return {"success": True, "charge_id": generate_id()}
98
+ ```
99
+
100
+ **Client Request Example:**
101
+ ```python
102
+ import requests
103
+
104
+ # The client must include the agent ID in the header
105
+ response = requests.post('/payments/charge',
106
+ headers={
107
+ 'Content-Type': 'application/json',
108
+ 'X-Agent-Passport-Id': 'ap_a2d10232c6534523812423eec8a1425c45678' # ← Agent ID passed here
109
+ },
110
+ json={
111
+ 'amount': 1299,
112
+ 'currency': 'USD',
113
+ 'merchant_id': 'merch_abc',
114
+ 'region': 'US',
115
+ 'shipping_country': 'US',
116
+ 'items': [
117
+ {'sku': 'SKU-1', 'qty': 1, 'category': 'electronics'}
118
+ ],
119
+ 'idempotency_key': 'charge-ord-123'
120
+ }
121
+ )
122
+ ```
123
+
124
+ ## How It Works
125
+
126
+ The `requirePolicy('finance.payment.charge.v1', agentId?)` middleware implements a flexible approach:
127
+
128
+ 1. **Agent ID Resolution** (in order of preference):
129
+ - **Function Parameter**: Uses explicit `agentId` if provided
130
+ - **Header Fallback**: Reads `X-Agent-Passport-Id` header from request
131
+ - **Validation**: Ensures agent ID format is valid (`ap_xxxxxxxxxxxxx`)
132
+ - **Failure**: Returns 400 error if neither provided
133
+
134
+ 2. **Policy Validation**:
135
+ - **Format Check**: Validates policy ID format (`finance.payment.charge.v1`)
136
+ - **API Call**: Calls `/api/verify/policy/finance.payment.charge.v1` with agent ID and context
137
+ - **Requirements Check**: Validates agent meets finance.payment.charge.v1 requirements:
138
+ - Has `payments.charge` capability
139
+ - Meets minimum assurance level (L2+)
140
+ - Within per-currency transaction and daily limits
141
+ - Merchant is allowed (if allowlist configured)
142
+ - Country is allowed (if allowlist configured)
143
+ - No blocked categories in items
144
+ - Item count within limits
145
+ - Idempotency key is unique
146
+ - Authorized in the request region
147
+
148
+ 3. **Result Handling**:
149
+ - **Success**: Adds `req.policyResult` with verified data and continues
150
+ - **Failure**: Returns 403 with detailed error information
151
+ - **Logging**: Logs violations for monitoring and debugging
152
+
153
+ ## Error Responses
154
+
155
+ When policy checks fail, you'll receive a `403 Forbidden` with detailed error information:
156
+
157
+ ```json
158
+ {
159
+ "error": "policy_violation",
160
+ "code": "oap.limit_exceeded",
161
+ "message": "Amount $25.00 exceeds per-transaction limit $20.00",
162
+ "policy_id": "finance.payment.charge.v1",
163
+ "agent_id": "ap_a2d10232c6534523812423eec8a1425c45678",
164
+ "upgrade_instructions": "Request higher limits in your passport"
165
+ }
166
+ ```
167
+
168
+ ## Test Payloads
169
+
170
+ ### Valid Request
171
+ ```json
172
+ {
173
+ "amount": 1299,
174
+ "currency": "USD",
175
+ "merchant_id": "merch_abc",
176
+ "region": "US",
177
+ "shipping_country": "US",
178
+ "items": [
179
+ { "sku": "SKU-1", "qty": 1, "category": "electronics" }
180
+ ],
181
+ "idempotency_key": "charge-ord-123"
182
+ }
183
+ ```
184
+
185
+ ### Invalid Request (exceeds limit)
186
+ ```json
187
+ {
188
+ "amount": 25000,
189
+ "currency": "USD",
190
+ "merchant_id": "merch_abc",
191
+ "region": "US",
192
+ "items": [
193
+ { "sku": "SKU-1", "qty": 1, "category": "electronics" }
194
+ ],
195
+ "idempotency_key": "charge-ord-124"
196
+ }
197
+ ```
198
+ *Returns 403: "Amount $250.00 exceeds per-transaction limit $200.00"*
199
+
200
+ ## Best Practices
201
+
202
+ 1. **Cache Verification**: Cache `/verify` responses with ETag for 60 seconds
203
+ 2. **Webhook Integration**: Subscribe to `status.changed` webhooks for instant suspension
204
+ 3. **Verifiable Attestation**: Log all charge attempts for compliance
205
+ 4. **Daily Tracking**: Implement daily spend tracking per currency to prevent abuse
206
+ 5. **Idempotency**: Always use unique idempotency keys to prevent duplicate charges
207
+ 6. **Error Handling**: Provide clear error messages to help agents self-remediate
208
+ 7. **Merchant Validation**: Maintain merchant allowlists for trusted partners
209
+ 8. **Category Filtering**: Block high-risk categories (weapons, illicit goods)
210
+
211
+ ## Compliance Badge
212
+
213
+ Agents that meet this policy's requirements can display the "Charge-Ready" badge:
214
+
215
+ ```markdown
216
+ [![Charge-Ready](https://api.aport.io/badge/ap_a2d10232c6534523812423eec8a1425c45678.svg)](https://aport.io/agents/ap_a2d10232c6534523812423eec8a1425c45678)
217
+ ```
218
+
219
+ ## Support
220
+
221
+ - [Documentation](https://aport.io/docs/policies/finance.payment.charge.v1)
222
+ - [Community](https://github.com/aporthq/community)
223
+ - [Support](https://aport.io/support)
224
+
225
+ ---
226
+ **Last Updated**: 2025-09-30 00:00:00 UTC
227
+
228
+
229
+ ## Required Context
230
+
231
+ This policy requires the following context (JSON Schema):
232
+
233
+ ```json
234
+ {
235
+ "$schema": "http://json-schema.org/draft-07/schema#",
236
+ "type": "object",
237
+ "required": [
238
+ "amount",
239
+ "currency",
240
+ "merchant_id",
241
+ "region",
242
+ "items",
243
+ "idempotency_key"
244
+ ],
245
+ "properties": {
246
+ "amount": {
247
+ "type": "integer",
248
+ "minimum": 1,
249
+ "description": "Minor units (e.g., cents)"
250
+ },
251
+ "currency": {
252
+ "type": "string",
253
+ "pattern": "^[A-Z]{3}$",
254
+ "description": "ISO 4217 currency code"
255
+ },
256
+ "merchant_id": {
257
+ "type": "string",
258
+ "description": "Merchant identifier"
259
+ },
260
+ "region": {
261
+ "type": "string",
262
+ "description": "Geographic region"
263
+ },
264
+ "shipping_country": {
265
+ "type": "string",
266
+ "description": "Shipping country code"
267
+ },
268
+ "items": {
269
+ "type": "array",
270
+ "minItems": 1,
271
+ "description": "Array of items being purchased",
272
+ "items": {
273
+ "type": "object",
274
+ "required": [
275
+ "sku",
276
+ "qty",
277
+ "price"
278
+ ],
279
+ "properties": {
280
+ "sku": {
281
+ "type": "string",
282
+ "description": "Stock keeping unit"
283
+ },
284
+ "qty": {
285
+ "type": "integer",
286
+ "minimum": 1,
287
+ "description": "Quantity"
288
+ },
289
+ "name": {
290
+ "type": "string",
291
+ "maxLength": 200,
292
+ "description": "Item name for audit and compliance"
293
+ },
294
+ "price": {
295
+ "type": "integer",
296
+ "minimum": 1,
297
+ "description": "Price in minor currency units (e.g., cents)"
298
+ },
299
+ "category": {
300
+ "type": "string",
301
+ "description": "Item category"
302
+ }
303
+ }
304
+ }
305
+ },
306
+ "risk_score": {
307
+ "type": "number",
308
+ "minimum": 0,
309
+ "maximum": 100,
310
+ "description": "Risk score (0-100)"
311
+ },
312
+ "idempotency_key": {
313
+ "type": "string",
314
+ "minLength": 8,
315
+ "description": "Idempotency key for duplicate prevention"
316
+ }
317
+ }
318
+ }
319
+ ```
320
+
321
+ You can also fetch this live via the discovery endpoint:
322
+
323
+ ```bash
324
+ curl -s "https://aport.io/api/policies/finance.payment.charge.v1?format=schema"
325
+ ```
326
+
@@ -0,0 +1,250 @@
1
+ const express = require("express");
2
+ const { requirePolicy } = require("@aporthq/middleware-express");
3
+
4
+ const app = express();
5
+ app.use(express.json());
6
+
7
+ // Apply payments.charge policy to all charge routes
8
+ app.post(
9
+ "/payments/charge",
10
+ requirePolicy("finance.payment.charge.v1"),
11
+ async (req, res) => {
12
+ try {
13
+ const {
14
+ amount,
15
+ currency,
16
+ merchant_id,
17
+ region,
18
+ shipping_country,
19
+ items,
20
+ risk_score,
21
+ idempotency_key,
22
+ } = req.body;
23
+
24
+ const passport = req.policyResult.passport;
25
+
26
+ // Additional business logic validation
27
+ if (amount <= 0) {
28
+ return res.status(400).json({ error: "Invalid charge amount" });
29
+ }
30
+
31
+ // Check if items are provided
32
+ if (!items || items.length === 0) {
33
+ return res.status(400).json({ error: "Items are required" });
34
+ }
35
+
36
+ // Process charge using your payment processor
37
+ const charge_id = await processCharge({
38
+ amount,
39
+ currency,
40
+ merchant_id,
41
+ region,
42
+ shipping_country,
43
+ items,
44
+ risk_score,
45
+ idempotency_key,
46
+ agent_id: passport.passport_id,
47
+ agent_name: passport.metadata?.template_name || "Unknown Agent",
48
+ });
49
+
50
+ // Log the transaction
51
+ console.log(
52
+ `Charge processed: ${charge_id} for ${amount} ${currency} by agent ${passport.passport_id}`
53
+ );
54
+
55
+ res.json({
56
+ success: true,
57
+ charge_id,
58
+ amount,
59
+ currency,
60
+ status: "processed",
61
+ decision_id: req.policyResult.decision_id,
62
+ });
63
+ } catch (error) {
64
+ console.error("Charge processing error:", error);
65
+ res.status(500).json({ error: "Internal server error" });
66
+ }
67
+ }
68
+ );
69
+
70
+ // Batch charges endpoint
71
+ app.post(
72
+ "/payments/charge/batch",
73
+ requirePolicy("finance.payment.charge.v1"),
74
+ async (req, res) => {
75
+ try {
76
+ const { charges } = req.body;
77
+ const passport = req.policyResult.passport;
78
+
79
+ // Group charges by currency for daily cap checking
80
+ const currencyTotals = {};
81
+ for (const charge of charges) {
82
+ const currency = charge.currency || "USD";
83
+ currencyTotals[currency] =
84
+ (currencyTotals[currency] || 0) + (charge.amount || 0);
85
+ }
86
+
87
+ // Check daily caps per currency
88
+ for (const [currency, totalAmount] of Object.entries(currencyTotals)) {
89
+ const currencyLimits =
90
+ passport.limits?.payments?.charge?.currency_limits?.[currency];
91
+ if (
92
+ currencyLimits?.daily_cap &&
93
+ totalAmount > currencyLimits.daily_cap
94
+ ) {
95
+ return res.status(403).json({
96
+ error: "Batch total exceeds daily cap",
97
+ currency,
98
+ total: totalAmount,
99
+ limit: currencyLimits.daily_cap,
100
+ });
101
+ }
102
+ }
103
+
104
+ // Process batch charges
105
+ const results = await Promise.all(
106
+ charges.map((charge) =>
107
+ processCharge({
108
+ ...charge,
109
+ agent_id: passport.passport_id,
110
+ })
111
+ )
112
+ );
113
+
114
+ res.json({
115
+ success: true,
116
+ processed: results.length,
117
+ currency_totals: currencyTotals,
118
+ decision_id: req.policyResult.decision_id,
119
+ });
120
+ } catch (error) {
121
+ console.error("Batch charge error:", error);
122
+ res.status(500).json({ error: "Internal server error" });
123
+ }
124
+ }
125
+ );
126
+
127
+ // Get charge status
128
+ app.get(
129
+ "/payments/charge/:charge_id",
130
+ requirePolicy("finance.payment.charge.v1"),
131
+ async (req, res) => {
132
+ try {
133
+ const { charge_id } = req.params;
134
+ const passport = req.policyResult.passport;
135
+
136
+ const charge_info = await getChargeStatus(
137
+ charge_id,
138
+ passport.passport_id
139
+ );
140
+
141
+ if (!charge_info) {
142
+ return res.status(404).json({ error: "Charge not found" });
143
+ }
144
+
145
+ res.json(charge_info);
146
+ } catch (error) {
147
+ console.error("Charge status error:", error);
148
+ res.status(500).json({ error: "Internal server error" });
149
+ }
150
+ }
151
+ );
152
+
153
+ // Refund charge endpoint
154
+ app.post(
155
+ "/payments/charge/:charge_id/refund",
156
+ requirePolicy("finance.payment.charge.v1"),
157
+ async (req, res) => {
158
+ try {
159
+ const { charge_id } = req.params;
160
+ const { amount, reason } = req.body;
161
+ const passport = req.policyResult.passport;
162
+
163
+ const refund_id = await processRefund({
164
+ charge_id,
165
+ amount,
166
+ reason,
167
+ agent_id: passport.passport_id,
168
+ });
169
+
170
+ res.json({
171
+ success: true,
172
+ refund_id,
173
+ charge_id,
174
+ amount,
175
+ status: "refunded",
176
+ decision_id: req.policyResult.decision_id,
177
+ });
178
+ } catch (error) {
179
+ console.error("Refund processing error:", error);
180
+ res.status(500).json({ error: "Internal server error" });
181
+ }
182
+ }
183
+ );
184
+
185
+ // Mock charge processing function
186
+ async function processCharge({
187
+ amount,
188
+ currency,
189
+ merchant_id,
190
+ region,
191
+ shipping_country,
192
+ items,
193
+ risk_score,
194
+ idempotency_key,
195
+ agent_id,
196
+ }) {
197
+ // Simulate payment processor call
198
+ await new Promise((resolve) => setTimeout(resolve, 100));
199
+
200
+ // Log charge details for audit
201
+ console.log(`Processing charge:`, {
202
+ amount,
203
+ currency,
204
+ merchant_id,
205
+ region,
206
+ shipping_country,
207
+ items: items?.length || 0,
208
+ risk_score,
209
+ idempotency_key,
210
+ agent_id,
211
+ });
212
+
213
+ return `chg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
214
+ }
215
+
216
+ // Mock charge status lookup
217
+ async function getChargeStatus(charge_id, agent_id) {
218
+ // Simulate charge status lookup
219
+ await new Promise((resolve) => setTimeout(resolve, 50));
220
+ return {
221
+ charge_id,
222
+ status: "completed",
223
+ created_at: new Date().toISOString(),
224
+ amount: 1299,
225
+ currency: "USD",
226
+ merchant_id: "merch_abc",
227
+ items: [{ sku: "SKU-1", qty: 1, category: "electronics" }],
228
+ };
229
+ }
230
+
231
+ // Mock refund processing function
232
+ async function processRefund({ charge_id, amount, reason, agent_id }) {
233
+ // Simulate refund processing
234
+ await new Promise((resolve) => setTimeout(resolve, 100));
235
+
236
+ console.log(`Processing refund:`, {
237
+ charge_id,
238
+ amount,
239
+ reason,
240
+ agent_id,
241
+ });
242
+
243
+ return `ref_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
244
+ }
245
+
246
+ const PORT = process.env.PORT || 3000;
247
+ app.listen(PORT, () => {
248
+ console.log(`Payment charge service running on port ${PORT}`);
249
+ console.log("Protected by APort finance.payment.charge.v1 policy pack");
250
+ });