@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,136 @@
1
+ from fastapi import FastAPI, HTTPException, Request
2
+ from pydantic import BaseModel
3
+ from aport.middleware import require_policy
4
+ import asyncio
5
+ from typing import List, Optional
6
+
7
+ app = FastAPI(title="Refunds Service", version="1.0.0")
8
+
9
+ class RefundRequest(BaseModel):
10
+ amount_minor: int
11
+ currency: str
12
+ order_id: str
13
+ customer_id: str
14
+ reason_code: str
15
+ region: str
16
+ idempotency_key: str
17
+ order_currency: Optional[str] = None
18
+ order_total_minor: Optional[int] = None
19
+ already_refunded_minor: Optional[int] = None
20
+ note: Optional[str] = None
21
+ merchant_case_id: Optional[str] = None
22
+
23
+ class BatchRefundRequest(BaseModel):
24
+ refunds: List[RefundRequest]
25
+
26
+ @app.post("/refunds")
27
+ @require_policy("finance.payment.refund.v1")
28
+ async def process_refund(request: Request, refund_data: RefundRequest):
29
+ try:
30
+ passport = request.state.policy_result.passport
31
+
32
+ # Additional business logic validation
33
+ if refund_data.amount_minor <= 0:
34
+ raise HTTPException(status_code=400, detail="Invalid refund amount")
35
+
36
+ # Process refund using your payment processor
37
+ refund_id = await process_refund_payment({
38
+ "amount_minor": refund_data.amount_minor,
39
+ "currency": refund_data.currency,
40
+ "order_id": refund_data.order_id,
41
+ "customer_id": refund_data.customer_id,
42
+ "reason_code": refund_data.reason_code,
43
+ "region": refund_data.region,
44
+ "idempotency_key": refund_data.idempotency_key,
45
+ "order_currency": refund_data.order_currency,
46
+ "order_total_minor": refund_data.order_total_minor,
47
+ "already_refunded_minor": refund_data.already_refunded_minor,
48
+ "note": refund_data.note,
49
+ "merchant_case_id": refund_data.merchant_case_id,
50
+ "agent_id": passport.agent_id,
51
+ "agent_name": passport.name
52
+ })
53
+
54
+ # Log the transaction
55
+ print(f"Refund processed: {refund_id} for {refund_data.amount_minor} {refund_data.currency} by agent {passport.agent_id}")
56
+
57
+ return {
58
+ "success": True,
59
+ "refund_id": refund_id,
60
+ "amount_minor": refund_data.amount_minor,
61
+ "currency": refund_data.currency,
62
+ "status": "processed",
63
+ "decision_id": request.state.policy_result.decision_id
64
+ }
65
+
66
+ except Exception as e:
67
+ print(f"Refund processing error: {e}")
68
+ raise HTTPException(status_code=500, detail="Internal server error")
69
+
70
+ @app.post("/refunds/batch")
71
+ @require_policy("finance.payment.refund.v1")
72
+ async def process_batch_refunds(request: Request, batch_data: BatchRefundRequest):
73
+ try:
74
+ passport = request.state.policy_result.passport
75
+
76
+ # Group refunds by currency for daily cap checking
77
+ currency_totals = {}
78
+ for refund in batch_data.refunds:
79
+ currency = refund.currency
80
+ currency_totals[currency] = currency_totals.get(currency, 0) + refund.amount_minor
81
+
82
+ # Check daily caps per currency
83
+ for currency, total_amount in currency_totals.items():
84
+ currency_limits = passport.limits.get("currency_limits", {}).get(currency)
85
+ if currency_limits and currency_limits.get("daily_cap") and total_amount > currency_limits["daily_cap"]:
86
+ raise HTTPException(
87
+ status_code=403,
88
+ detail={
89
+ "error": "Batch total exceeds daily cap",
90
+ "currency": currency,
91
+ "total": total_amount,
92
+ "limit": currency_limits["daily_cap"]
93
+ }
94
+ )
95
+
96
+ # Process batch refunds
97
+ results = await asyncio.gather(*[
98
+ process_refund_payment({
99
+ "amount_minor": refund.amount_minor,
100
+ "currency": refund.currency,
101
+ "order_id": refund.order_id,
102
+ "customer_id": refund.customer_id,
103
+ "reason_code": refund.reason_code,
104
+ "region": refund.region,
105
+ "idempotency_key": refund.idempotency_key,
106
+ "agent_id": passport.agent_id
107
+ })
108
+ for refund in batch_data.refunds
109
+ ])
110
+
111
+ return {
112
+ "success": True,
113
+ "processed": len(results),
114
+ "currency_totals": currency_totals,
115
+ "decision_id": request.state.policy_result.decision_id
116
+ }
117
+
118
+ except Exception as e:
119
+ print(f"Batch refund error: {e}")
120
+ raise HTTPException(status_code=500, detail="Internal server error")
121
+
122
+ async def process_refund_payment(refund_data: dict) -> str:
123
+ """Mock refund processing function"""
124
+ # Simulate payment processor call
125
+ await asyncio.sleep(0.1)
126
+
127
+ # Log refund details for audit
128
+ print(f"Processing refund: {refund_data}")
129
+
130
+ return f"ref_{asyncio.get_event_loop().time()}_{hash(str(refund_data)) % 1000000}"
131
+
132
+ if __name__ == "__main__":
133
+ import uvicorn
134
+ print("Refunds service starting...")
135
+ print("Protected by APort finance.payment.refund.v1 policy pack")
136
+ uvicorn.run(app, host="0.0.0.0", port=8000)
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Minimal Refunds v1 Policy Example
3
+ *
4
+ * This is a simple, working example of how to use the finance.payment.refund.v1 policy
5
+ * with all required fields and proper error handling.
6
+ */
7
+
8
+ const express = require("express");
9
+ const { requirePolicy } = require("@aporthq/middleware-express"); // Assuming this exists
10
+
11
+ const app = express();
12
+ app.use(express.json());
13
+
14
+ // Mock Agent ID for demonstration
15
+ const AGENT_ID = "agents/ap_minimal_refund_agent";
16
+
17
+ // Minimal Refund Endpoint
18
+ app.post(
19
+ "/minimal-refund",
20
+ requirePolicy("finance.payment.refund.v1", AGENT_ID),
21
+ async (req, res) => {
22
+ try {
23
+ // Extract required fields from request
24
+ const {
25
+ amount_minor, // Amount in smallest currency unit (e.g., cents)
26
+ currency, // Currency code (e.g., "USD", "EUR", "JPY")
27
+ order_id, // Unique order identifier
28
+ customer_id, // Customer identifier
29
+ reason_code, // Reason for refund (e.g., "defective")
30
+ region, // Region code (e.g., "US", "EU", "APAC")
31
+ idempotency_key, // Unique key to prevent duplicate refunds
32
+ } = req.body;
33
+
34
+ // Optional fields
35
+ const {
36
+ order_currency, // Original order currency
37
+ order_total_minor, // Total order amount
38
+ already_refunded_minor, // Already refunded amount
39
+ note, // Additional notes
40
+ merchant_case_id, // Merchant's case ID
41
+ } = req.body;
42
+
43
+ // Policy is already verified by middleware
44
+ const policyResult = req.policyResult;
45
+
46
+ if (!policyResult.allow) {
47
+ return res.status(403).json({
48
+ success: false,
49
+ decision_id: policyResult.decision_id,
50
+ error: "Policy violation",
51
+ reasons: policyResult.reasons,
52
+ });
53
+ }
54
+
55
+ // Simulate processing the refund
56
+ const refund_id = `ref_${Date.now()}_${Math.random()
57
+ .toString(36)
58
+ .substr(2, 9)}`;
59
+ console.log(
60
+ `Minimal Refund Processed: ${refund_id} for ${amount_minor} ${currency} by agent ${policyResult.passport.agent_id}`
61
+ );
62
+
63
+ res.json({
64
+ success: true,
65
+ refund_id,
66
+ amount_minor,
67
+ currency,
68
+ status: "processed",
69
+ decision_id: policyResult.decision_id,
70
+ remaining_daily_cap: policyResult.remaining_daily_cap,
71
+ });
72
+ } catch (error) {
73
+ console.error("Minimal refund processing error:", error);
74
+ res.status(500).json({
75
+ error: "Internal server error",
76
+ message: "Failed to process refund",
77
+ });
78
+ }
79
+ }
80
+ );
81
+
82
+ // Batch Refund Endpoint (for multiple refunds)
83
+ app.post(
84
+ "/minimal-batch-refund",
85
+ requirePolicy("finance.payment.refund.v1", AGENT_ID),
86
+ async (req, res) => {
87
+ try {
88
+ const { refunds } = req.body; // Array of refund objects
89
+
90
+ if (!Array.isArray(refunds) || refunds.length === 0) {
91
+ return res.status(400).json({
92
+ error: "Invalid request",
93
+ message: "refunds array is required and must not be empty",
94
+ });
95
+ }
96
+
97
+ const policyResult = req.policyResult;
98
+
99
+ if (!policyResult.allow) {
100
+ return res.status(403).json({
101
+ success: false,
102
+ decision_id: policyResult.decision_id,
103
+ error: "Policy violation",
104
+ reasons: policyResult.reasons,
105
+ });
106
+ }
107
+
108
+ // Process each refund
109
+ const results = [];
110
+ for (const refund of refunds) {
111
+ const refund_id = `ref_${Date.now()}_${Math.random()
112
+ .toString(36)
113
+ .substr(2, 9)}`;
114
+ results.push({
115
+ refund_id,
116
+ amount_minor: refund.amount_minor,
117
+ currency: refund.currency,
118
+ status: "processed",
119
+ });
120
+ }
121
+
122
+ res.json({
123
+ success: true,
124
+ processed_count: results.length,
125
+ refunds: results,
126
+ decision_id: policyResult.decision_id,
127
+ remaining_daily_cap: policyResult.remaining_daily_cap,
128
+ });
129
+ } catch (error) {
130
+ console.error("Batch refund processing error:", error);
131
+ res.status(500).json({
132
+ error: "Internal server error",
133
+ message: "Failed to process batch refunds",
134
+ });
135
+ }
136
+ }
137
+ );
138
+
139
+ // Health check endpoint
140
+ app.get("/health", (req, res) => {
141
+ res.json({
142
+ status: "healthy",
143
+ service: "minimal-refunds",
144
+ policy: "finance.payment.refund.v1",
145
+ agent_id: AGENT_ID,
146
+ });
147
+ });
148
+
149
+ const PORT = process.env.PORT || 3001;
150
+ app.listen(PORT, () => {
151
+ console.log(`Minimal Refunds service running on port ${PORT}`);
152
+ console.log("Protected by APort finance.payment.refund.v1 policy pack");
153
+ console.log("\nTo test (example for allowed refund):");
154
+ console.log(`curl -X POST http://localhost:${PORT}/minimal-refund \\`);
155
+ console.log(` -H "Content-Type: application/json" \\`);
156
+ console.log(` -d '{`);
157
+ console.log(` "amount_minor": 7500,`);
158
+ console.log(` "currency": "USD",`);
159
+ console.log(` "order_id": "ORD-MIN-001",`);
160
+ console.log(` "customer_id": "CUST-MIN-001",`);
161
+ console.log(` "reason_code": "customer_request",`);
162
+ console.log(` "region": "US",`);
163
+ console.log(` "idempotency_key": "min_idempotency_key_123"`);
164
+ console.log(` }'`);
165
+ console.log("\nTo test batch refunds:");
166
+ console.log(`curl -X POST http://localhost:${PORT}/minimal-batch-refund \\`);
167
+ console.log(` -H "Content-Type: application/json" \\`);
168
+ console.log(` -d '{`);
169
+ console.log(` "refunds": [`);
170
+ console.log(` {`);
171
+ console.log(` "amount_minor": 5000,`);
172
+ console.log(` "currency": "USD",`);
173
+ console.log(` "order_id": "ORD-MIN-002",`);
174
+ console.log(` "customer_id": "CUST-MIN-002",`);
175
+ console.log(` "reason_code": "defective",`);
176
+ console.log(` "region": "US",`);
177
+ console.log(` "idempotency_key": "batch_key_001"`);
178
+ console.log(` }`);
179
+ console.log(` ]`);
180
+ console.log(` }'`);
181
+ });
182
+
183
+ module.exports = app;
@@ -0,0 +1,216 @@
1
+ {
2
+ "id": "finance.payment.refund.v1",
3
+ "name": "Refunds Protection Policy",
4
+ "description": "Pre-act governance for refund operations. Enforces per-currency caps, reason code validation, cross-currency restrictions, and idempotency.",
5
+ "version": "1.0.0",
6
+ "status": "active",
7
+ "requires_capabilities": ["finance.payment.refund"],
8
+ "min_assurance": "L2",
9
+ "limits_required": [
10
+ "supported_currencies",
11
+ "currency_limits",
12
+ "refund_reason_codes",
13
+ "regions",
14
+ "approval_required"
15
+ ],
16
+ "enforcement": {
17
+ "amount_lte": "limits.finance.payment.refund.currency_limits.{currency}.max_per_tx",
18
+ "currency_supported": "limits.finance.payment.refund.supported_currencies",
19
+ "region_in": "regions",
20
+ "reason_code_valid": "limits.finance.payment.refund.refund_reason_codes",
21
+ "assurance_tier_enforced": "true",
22
+ "idempotency_required": "true",
23
+ "order_id_required": "true",
24
+ "customer_id_required": "true",
25
+ "cross_currency_denied": "true"
26
+ },
27
+ "assurance_rules": [
28
+ { "lte_minor": 10000, "min_level": "L2" },
29
+ { "lte_minor": 50000, "min_level": "L3" }
30
+ ],
31
+ "required_fields": [
32
+ "order_id",
33
+ "customer_id",
34
+ "amount",
35
+ "currency",
36
+ "region",
37
+ "reason_code",
38
+ "idempotency_key"
39
+ ],
40
+ "optional_fields": [
41
+ "note",
42
+ "merchant_case_id",
43
+ "order_currency",
44
+ "order_total_minor",
45
+ "already_refunded_minor"
46
+ ],
47
+ "mcp": {
48
+ "require_allowlisted_if_present": true
49
+ },
50
+ "advice": [
51
+ "Cache /verify with ETag; 60s TTL",
52
+ "Subscribe to status webhooks for instant suspend",
53
+ "Log all refund attempts for Verifiable Attestation",
54
+ "Implement daily spend tracking with atomic counters",
55
+ "Use idempotency keys to prevent duplicate refunds",
56
+ "Validate remaining order balance before processing",
57
+ "Enforce reason code validation for compliance",
58
+ "Block cross-currency refunds to prevent abuse"
59
+ ],
60
+ "required_context": {
61
+ "$schema": "http://json-schema.org/draft-07/schema#",
62
+ "type": "object",
63
+ "required": [
64
+ "order_id",
65
+ "customer_id",
66
+ "amount",
67
+ "currency",
68
+ "region",
69
+ "reason_code",
70
+ "idempotency_key"
71
+ ],
72
+ "properties": {
73
+ "order_id": {
74
+ "type": "string",
75
+ "minLength": 1,
76
+ "description": "Original order identifier"
77
+ },
78
+ "customer_id": {
79
+ "type": "string",
80
+ "minLength": 1,
81
+ "description": "Customer identifier"
82
+ },
83
+ "amount": {
84
+ "type": "integer",
85
+ "minimum": 1,
86
+ "description": "Refund amount in minor units (e.g., cents)"
87
+ },
88
+ "currency": {
89
+ "type": "string",
90
+ "pattern": "^[A-Z]{3}$",
91
+ "description": "ISO 4217 currency code"
92
+ },
93
+ "region": {
94
+ "type": "string",
95
+ "description": "Geographic region"
96
+ },
97
+ "reason_code": {
98
+ "type": "string",
99
+ "description": "Refund reason code"
100
+ },
101
+ "idempotency_key": {
102
+ "type": "string",
103
+ "minLength": 8,
104
+ "description": "Idempotency key for duplicate prevention"
105
+ },
106
+ "note": {
107
+ "type": "string",
108
+ "description": "Optional refund note"
109
+ },
110
+ "merchant_case_id": {
111
+ "type": "string",
112
+ "description": "Merchant's internal case ID"
113
+ },
114
+ "order_currency": {
115
+ "type": "string",
116
+ "pattern": "^[A-Z]{3}$",
117
+ "description": "Original order currency"
118
+ },
119
+ "order_total_minor": {
120
+ "type": "integer",
121
+ "minimum": 0,
122
+ "description": "Original order total in minor units"
123
+ },
124
+ "already_refunded_minor": {
125
+ "type": "integer",
126
+ "minimum": 0,
127
+ "description": "Amount already refunded in minor units"
128
+ },
129
+ "mcp_servers": {
130
+ "type": "array",
131
+ "items": { "type": "string" },
132
+ "description": "MCP servers being used in this request (e.g., [\"https://mcp.stripe.com\"])"
133
+ },
134
+ "mcp_tools": {
135
+ "type": "array",
136
+ "items": { "type": "string" },
137
+ "description": "MCP tools being used in this request (e.g., [\"stripe.refunds.create\"])"
138
+ },
139
+ "mcp_server": {
140
+ "type": "string",
141
+ "description": "Single MCP server being used (backward compatibility - use mcp_servers array for multiple)"
142
+ },
143
+ "mcp_tool": {
144
+ "type": "string",
145
+ "description": "Single MCP tool being used (backward compatibility - use mcp_tools array for multiple)"
146
+ },
147
+ "mcp_session": {
148
+ "type": "string",
149
+ "description": "MCP session identifier for audit trail (optional)"
150
+ }
151
+ }
152
+ },
153
+ "evaluation_rules": [
154
+ {
155
+ "name": "passport_status_active",
156
+ "condition": "passport.status == 'active'",
157
+ "deny_code": "oap.passport_suspended",
158
+ "description": "Passport must be active"
159
+ },
160
+ {
161
+ "name": "assurance_minimum",
162
+ "condition": "passport.assurance_level >= getRequiredAssuranceLevel(amount, currency)",
163
+ "deny_code": "oap.assurance_insufficient",
164
+ "description": "Assurance level must meet minimum requirement based on amount"
165
+ },
166
+ {
167
+ "name": "currency_supported",
168
+ "condition": "currency in limits.supported_currencies",
169
+ "deny_code": "oap.currency_unsupported",
170
+ "description": "Currency must be supported"
171
+ },
172
+ {
173
+ "name": "per_tx_amount_cap",
174
+ "condition": "amount <= limits.currency_limits[currency].max_per_tx",
175
+ "deny_code": "oap.limit_exceeded",
176
+ "description": "Amount must not exceed per-transaction limit"
177
+ },
178
+ {
179
+ "name": "reason_code_valid",
180
+ "condition": "reason_code in limits.refund_reason_codes",
181
+ "deny_code": "oap.invalid_reason_code",
182
+ "description": "Reason code must be valid"
183
+ },
184
+ {
185
+ "name": "cross_currency_check",
186
+ "condition": "NOT (order_currency AND currency != order_currency)",
187
+ "deny_code": "oap.cross_currency_denied",
188
+ "description": "Cross-currency refunds are not allowed"
189
+ },
190
+ {
191
+ "name": "daily_cap_check",
192
+ "condition": "daily_total + amount <= limits.currency_limits[currency].daily_cap",
193
+ "deny_code": "oap.limit_exceeded",
194
+ "description": "Daily cap must not be exceeded"
195
+ },
196
+ {
197
+ "name": "idempotency_check",
198
+ "condition": "idempotency_key not in recent_keys",
199
+ "deny_code": "oap.idempotency_conflict",
200
+ "description": "Idempotency key must be unique"
201
+ },
202
+ {
203
+ "name": "region_authorization",
204
+ "condition": "region in passport.regions",
205
+ "deny_code": "oap.region_blocked",
206
+ "description": "Region must be authorized"
207
+ }
208
+ ],
209
+ "cache": {
210
+ "default_ttl_seconds": 60,
211
+ "suspend_invalidate_seconds": 30
212
+ },
213
+ "deprecation": null,
214
+ "created_at": "2025-01-16T00:00:00Z",
215
+ "updated_at": "2025-01-30T00:00:00Z"
216
+ }