@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,214 @@
1
+ """
2
+ Test Suite: finance.transaction.execute.v1 Policy
3
+
4
+ Tests the financial transaction execution policy with various scenarios
5
+ including valid transactions, limit violations, and security controls.
6
+ """
7
+
8
+ import json
9
+ import os
10
+ from typing import Dict, Any, List
11
+
12
+ def evaluate_finance_transaction_execute_v1(passport: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]:
13
+ """Mock implementation of the finance.transaction.execute.v1 policy evaluation"""
14
+ reasons = []
15
+ allow = True
16
+
17
+ # Check agent status
18
+ if passport.get("status") in ["suspended", "revoked"]:
19
+ return {
20
+ "allow": False,
21
+ "reasons": [{
22
+ "code": "oap.passport_suspended",
23
+ "message": f"Agent is {passport.get('status')} and cannot perform operations",
24
+ "severity": "error"
25
+ }]
26
+ }
27
+
28
+ # Check capabilities
29
+ capabilities = passport.get("capabilities", [])
30
+ has_transaction_capability = any(
31
+ cap.get("id") == "finance.transaction" for cap in capabilities
32
+ )
33
+ if not has_transaction_capability:
34
+ return {
35
+ "allow": False,
36
+ "reasons": [{
37
+ "code": "oap.unknown_capability",
38
+ "message": "Agent does not have finance.transaction capability",
39
+ "severity": "error"
40
+ }]
41
+ }
42
+
43
+ # Check assurance level
44
+ required_assurance = passport.get("limits", {}).get("finance", {}).get("transaction", {}).get("require_assurance_at_least", "L3")
45
+ assurance_level = passport.get("assurance_level")
46
+ if assurance_level not in [required_assurance, "L4KYC", "L4FIN"]:
47
+ return {
48
+ "allow": False,
49
+ "reasons": [{
50
+ "code": "oap.assurance_insufficient",
51
+ "message": f"Assurance level {assurance_level} is insufficient, requires {required_assurance}",
52
+ "severity": "error"
53
+ }]
54
+ }
55
+
56
+ # Check required fields
57
+ required_fields = ["transaction_type", "amount", "currency", "asset_class", "source_account_id", "destination_account_id"]
58
+ missing_fields = [field for field in required_fields if not context.get(field)]
59
+ if missing_fields:
60
+ return {
61
+ "allow": False,
62
+ "reasons": [{
63
+ "code": "oap.invalid_context",
64
+ "message": f"Missing required fields: {', '.join(missing_fields)}",
65
+ "severity": "error"
66
+ }]
67
+ }
68
+
69
+ # Check transaction type is allowed
70
+ allowed_transaction_types = passport.get("limits", {}).get("finance", {}).get("transaction", {}).get("allowed_transaction_types", [])
71
+ if allowed_transaction_types and context.get("transaction_type") not in allowed_transaction_types:
72
+ return {
73
+ "allow": False,
74
+ "reasons": [{
75
+ "code": "oap.action_forbidden",
76
+ "message": f"Transaction type {context.get('transaction_type')} is not allowed",
77
+ "severity": "error"
78
+ }]
79
+ }
80
+
81
+ # Check asset class is allowed
82
+ allowed_asset_classes = passport.get("limits", {}).get("finance", {}).get("transaction", {}).get("allowed_asset_classes", [])
83
+ if allowed_asset_classes and context.get("asset_class") not in allowed_asset_classes:
84
+ return {
85
+ "allow": False,
86
+ "reasons": [{
87
+ "code": "oap.asset_class_forbidden",
88
+ "message": f"Asset class {context.get('asset_class')} is not allowed",
89
+ "severity": "error"
90
+ }]
91
+ }
92
+
93
+ # Check exposure limit
94
+ max_exposure = passport.get("limits", {}).get("finance", {}).get("transaction", {}).get("max_exposure_per_tx_usd")
95
+ if max_exposure and context.get("amount", 0) > max_exposure:
96
+ return {
97
+ "allow": False,
98
+ "reasons": [{
99
+ "code": "oap.limit_exceeded",
100
+ "message": f"Amount {context.get('amount')} exceeds maximum exposure limit {max_exposure}",
101
+ "severity": "error"
102
+ }]
103
+ }
104
+
105
+ # Check source account type restrictions
106
+ restricted_account_types = passport.get("limits", {}).get("finance", {}).get("transaction", {}).get("restricted_source_account_types", [])
107
+ if context.get("source_account_type") in restricted_account_types:
108
+ return {
109
+ "allow": False,
110
+ "reasons": [{
111
+ "code": "oap.account_type_restricted",
112
+ "message": f"Source account type {context.get('source_account_type')} is restricted",
113
+ "severity": "error"
114
+ }]
115
+ }
116
+
117
+ # Check allowed source account types
118
+ allowed_source_account_types = passport.get("limits", {}).get("finance", {}).get("transaction", {}).get("allowed_source_account_types", [])
119
+ if allowed_source_account_types and context.get("source_account_type") and context.get("source_account_type") not in allowed_source_account_types:
120
+ return {
121
+ "allow": False,
122
+ "reasons": [{
123
+ "code": "oap.account_type_restricted",
124
+ "message": f"Source account type {context.get('source_account_type')} is not allowed",
125
+ "severity": "error"
126
+ }]
127
+ }
128
+
129
+ # Check segregation of funds (prevent commingling)
130
+ if context.get("source_account_type") == "client_funds" and context.get("destination_account_type") == "proprietary":
131
+ return {
132
+ "allow": False,
133
+ "reasons": [{
134
+ "code": "oap.commingling_of_funds_forbidden",
135
+ "message": "Cannot transfer from client funds to proprietary accounts",
136
+ "severity": "error"
137
+ }]
138
+ }
139
+
140
+ # Check counterparty exposure limit
141
+ max_counterparty_exposure = passport.get("limits", {}).get("finance", {}).get("transaction", {}).get("max_exposure_per_counterparty_usd")
142
+ if max_counterparty_exposure and context.get("counterparty_id") and context.get("amount", 0) > max_counterparty_exposure:
143
+ return {
144
+ "allow": False,
145
+ "reasons": [{
146
+ "code": "oap.counterparty_limit_exceeded",
147
+ "message": f"Amount {context.get('amount')} exceeds counterparty exposure limit {max_counterparty_exposure}",
148
+ "severity": "error"
149
+ }]
150
+ }
151
+
152
+ # If all checks pass, allow the transaction
153
+ return {
154
+ "allow": True,
155
+ "reasons": [{
156
+ "code": "oap.allowed",
157
+ "message": "Transaction within limits and policy requirements",
158
+ "severity": "info"
159
+ }]
160
+ }
161
+
162
+ def run_tests():
163
+ """Run the test suite"""
164
+ print("๐Ÿงช Running finance.transaction.execute.v1 Policy Tests\n")
165
+
166
+ # Load test data
167
+ test_dir = os.path.dirname(os.path.abspath(__file__))
168
+
169
+ with open(os.path.join(test_dir, "passport.instance.json"), "r") as f:
170
+ passport = json.load(f)
171
+
172
+ with open(os.path.join(test_dir, "contexts.jsonl"), "r") as f:
173
+ contexts = [json.loads(line) for line in f.read().strip().split("\n")]
174
+
175
+ with open(os.path.join(test_dir, "expected.jsonl"), "r") as f:
176
+ expected = [json.loads(line) for line in f.read().strip().split("\n")]
177
+
178
+ passed = 0
179
+ failed = 0
180
+
181
+ for i, test_case in enumerate(contexts):
182
+ expected_result = expected[i]
183
+
184
+ try:
185
+ result = evaluate_finance_transaction_execute_v1(passport, test_case["context"])
186
+
187
+ # Compare results
188
+ allow_match = result["allow"] == expected_result["expected"]["allow"]
189
+ reasons_match = json.dumps(result["reasons"]) == json.dumps(expected_result["expected"]["reasons"])
190
+
191
+ if allow_match and reasons_match:
192
+ print(f"โœ… {test_case['name']}: PASS")
193
+ passed += 1
194
+ else:
195
+ print(f"โŒ {test_case['name']}: FAIL")
196
+ print(f" Expected: {json.dumps(expected_result['expected'])}")
197
+ print(f" Got: {json.dumps(result)}")
198
+ failed += 1
199
+ except Exception as error:
200
+ print(f"โŒ {test_case['name']}: ERROR - {str(error)}")
201
+ failed += 1
202
+
203
+ print(f"\n๐Ÿ“Š Test Results: {passed} passed, {failed} failed")
204
+
205
+ if failed == 0:
206
+ print("๐ŸŽ‰ All tests passed!")
207
+ return True
208
+ else:
209
+ print("๐Ÿ’ฅ Some tests failed!")
210
+ return False
211
+
212
+ if __name__ == "__main__":
213
+ success = run_tests()
214
+ exit(0 if success else 1)
@@ -0,0 +1,306 @@
1
+ /**
2
+ * Test Suite: finance.transaction.execute.v1 Policy
3
+ *
4
+ * Tests the financial transaction execution policy with various scenarios
5
+ * including valid transactions, limit violations, and security controls.
6
+ */
7
+
8
+ const fs = require("fs");
9
+ const path = require("path");
10
+
11
+ // Mock the policy evaluation function
12
+ async function evaluateFinanceTransactionExecuteV1(passport, context) {
13
+ // Mock implementation based on the actual policy logic
14
+ const reasons = [];
15
+ let allow = true;
16
+
17
+ // Check agent status
18
+ if (passport.status === "suspended" || passport.status === "revoked") {
19
+ return {
20
+ allow: false,
21
+ reasons: [
22
+ {
23
+ code: "oap.passport_suspended",
24
+ message: `Agent is ${passport.status} and cannot perform operations`,
25
+ severity: "error",
26
+ },
27
+ ],
28
+ };
29
+ }
30
+
31
+ // Check capabilities
32
+ const hasTransactionCapability = passport.capabilities?.some(
33
+ (cap) => cap.id === "finance.transaction"
34
+ );
35
+ if (!hasTransactionCapability) {
36
+ return {
37
+ allow: false,
38
+ reasons: [
39
+ {
40
+ code: "oap.unknown_capability",
41
+ message: "Agent does not have finance.transaction capability",
42
+ severity: "error",
43
+ },
44
+ ],
45
+ };
46
+ }
47
+
48
+ // Check assurance level
49
+ const requiredAssurance =
50
+ passport.limits?.finance?.transaction?.require_assurance_at_least || "L3";
51
+ if (
52
+ passport.assurance_level !== requiredAssurance &&
53
+ passport.assurance_level !== "L4KYC" &&
54
+ passport.assurance_level !== "L4FIN"
55
+ ) {
56
+ return {
57
+ allow: false,
58
+ reasons: [
59
+ {
60
+ code: "oap.assurance_insufficient",
61
+ message: `Assurance level ${passport.assurance_level} is insufficient, requires ${requiredAssurance}`,
62
+ severity: "error",
63
+ },
64
+ ],
65
+ };
66
+ }
67
+
68
+ // Check required fields
69
+ const requiredFields = [
70
+ "transaction_type",
71
+ "amount",
72
+ "currency",
73
+ "asset_class",
74
+ "source_account_id",
75
+ "destination_account_id",
76
+ ];
77
+ const missingFields = requiredFields.filter((field) => !context[field]);
78
+ if (missingFields.length > 0) {
79
+ return {
80
+ allow: false,
81
+ reasons: [
82
+ {
83
+ code: "oap.invalid_context",
84
+ message: `Missing required fields: ${missingFields.join(", ")}`,
85
+ severity: "error",
86
+ },
87
+ ],
88
+ };
89
+ }
90
+
91
+ // Check transaction type is allowed
92
+ const allowedTransactionTypes =
93
+ passport.limits?.finance?.transaction?.allowed_transaction_types || [];
94
+ if (
95
+ allowedTransactionTypes.length > 0 &&
96
+ !allowedTransactionTypes.includes(context.transaction_type)
97
+ ) {
98
+ return {
99
+ allow: false,
100
+ reasons: [
101
+ {
102
+ code: "oap.action_forbidden",
103
+ message: `Transaction type ${context.transaction_type} is not allowed`,
104
+ severity: "error",
105
+ },
106
+ ],
107
+ };
108
+ }
109
+
110
+ // Check asset class is allowed
111
+ const allowedAssetClasses =
112
+ passport.limits?.finance?.transaction?.allowed_asset_classes || [];
113
+ if (
114
+ allowedAssetClasses.length > 0 &&
115
+ !allowedAssetClasses.includes(context.asset_class)
116
+ ) {
117
+ return {
118
+ allow: false,
119
+ reasons: [
120
+ {
121
+ code: "oap.asset_class_forbidden",
122
+ message: `Asset class ${context.asset_class} is not allowed`,
123
+ severity: "error",
124
+ },
125
+ ],
126
+ };
127
+ }
128
+
129
+ // Check exposure limit
130
+ const maxExposure =
131
+ passport.limits?.finance?.transaction?.max_exposure_per_tx_usd;
132
+ if (maxExposure && context.amount > maxExposure) {
133
+ return {
134
+ allow: false,
135
+ reasons: [
136
+ {
137
+ code: "oap.limit_exceeded",
138
+ message: `Amount ${context.amount} exceeds maximum exposure limit ${maxExposure}`,
139
+ severity: "error",
140
+ },
141
+ ],
142
+ };
143
+ }
144
+
145
+ // Check source account type restrictions
146
+ const restrictedAccountTypes =
147
+ passport.limits?.finance?.transaction?.restricted_source_account_types ||
148
+ [];
149
+ if (
150
+ context.source_account_type &&
151
+ restrictedAccountTypes.includes(context.source_account_type)
152
+ ) {
153
+ return {
154
+ allow: false,
155
+ reasons: [
156
+ {
157
+ code: "oap.account_type_restricted",
158
+ message: `Source account type ${context.source_account_type} is restricted`,
159
+ severity: "error",
160
+ },
161
+ ],
162
+ };
163
+ }
164
+
165
+ // Check allowed source account types
166
+ const allowedSourceAccountTypes =
167
+ passport.limits?.finance?.transaction?.allowed_source_account_types || [];
168
+ if (
169
+ allowedSourceAccountTypes.length > 0 &&
170
+ context.source_account_type &&
171
+ !allowedSourceAccountTypes.includes(context.source_account_type)
172
+ ) {
173
+ return {
174
+ allow: false,
175
+ reasons: [
176
+ {
177
+ code: "oap.account_type_restricted",
178
+ message: `Source account type ${context.source_account_type} is not allowed`,
179
+ severity: "error",
180
+ },
181
+ ],
182
+ };
183
+ }
184
+
185
+ // Check segregation of funds (prevent commingling)
186
+ if (
187
+ context.source_account_type === "client_funds" &&
188
+ context.destination_account_type === "proprietary"
189
+ ) {
190
+ return {
191
+ allow: false,
192
+ reasons: [
193
+ {
194
+ code: "oap.commingling_of_funds_forbidden",
195
+ message: "Cannot transfer from client funds to proprietary accounts",
196
+ severity: "error",
197
+ },
198
+ ],
199
+ };
200
+ }
201
+
202
+ // Check counterparty exposure limit
203
+ const maxCounterpartyExposure =
204
+ passport.limits?.finance?.transaction?.max_exposure_per_counterparty_usd;
205
+ if (
206
+ maxCounterpartyExposure &&
207
+ context.counterparty_id &&
208
+ context.amount > maxCounterpartyExposure
209
+ ) {
210
+ return {
211
+ allow: false,
212
+ reasons: [
213
+ {
214
+ code: "oap.counterparty_limit_exceeded",
215
+ message: `Amount ${context.amount} exceeds counterparty exposure limit ${maxCounterpartyExposure}`,
216
+ severity: "error",
217
+ },
218
+ ],
219
+ };
220
+ }
221
+
222
+ // If all checks pass, allow the transaction
223
+ return {
224
+ allow: true,
225
+ reasons: [
226
+ {
227
+ code: "oap.allowed",
228
+ message: "Transaction within limits and policy requirements",
229
+ severity: "info",
230
+ },
231
+ ],
232
+ };
233
+ }
234
+
235
+ // Test runner
236
+ async function runTests() {
237
+ console.log("๐Ÿงช Running finance.transaction.execute.v1 Policy Tests\n");
238
+
239
+ // Load test data
240
+ const passportPath = path.join(__dirname, "passport.instance.json");
241
+ const contextsPath = path.join(__dirname, "contexts.jsonl");
242
+ const expectedPath = path.join(__dirname, "expected.jsonl");
243
+
244
+ const passport = JSON.parse(fs.readFileSync(passportPath, "utf8"));
245
+ const contexts = fs
246
+ .readFileSync(contextsPath, "utf8")
247
+ .trim()
248
+ .split("\n")
249
+ .map((line) => JSON.parse(line));
250
+ const expected = fs
251
+ .readFileSync(expectedPath, "utf8")
252
+ .trim()
253
+ .split("\n")
254
+ .map((line) => JSON.parse(line));
255
+
256
+ let passed = 0;
257
+ let failed = 0;
258
+
259
+ for (let i = 0; i < contexts.length; i++) {
260
+ const testCase = contexts[i];
261
+ const expectedResult = expected[i];
262
+
263
+ try {
264
+ const result = await evaluateFinanceTransactionExecuteV1(
265
+ passport,
266
+ testCase.context
267
+ );
268
+
269
+ // Compare results
270
+ const allowMatch = result.allow === expectedResult.expected.allow;
271
+ const reasonsMatch =
272
+ JSON.stringify(result.reasons) ===
273
+ JSON.stringify(expectedResult.expected.reasons);
274
+
275
+ if (allowMatch && reasonsMatch) {
276
+ console.log(`โœ… ${testCase.name}: PASS`);
277
+ passed++;
278
+ } else {
279
+ console.log(`โŒ ${testCase.name}: FAIL`);
280
+ console.log(` Expected: ${JSON.stringify(expectedResult.expected)}`);
281
+ console.log(` Got: ${JSON.stringify(result)}`);
282
+ failed++;
283
+ }
284
+ } catch (error) {
285
+ console.log(`โŒ ${testCase.name}: ERROR - ${error.message}`);
286
+ failed++;
287
+ }
288
+ }
289
+
290
+ console.log(`\n๐Ÿ“Š Test Results: ${passed} passed, ${failed} failed`);
291
+
292
+ if (failed === 0) {
293
+ console.log("๐ŸŽ‰ All tests passed!");
294
+ process.exit(0);
295
+ } else {
296
+ console.log("๐Ÿ’ฅ Some tests failed!");
297
+ process.exit(1);
298
+ }
299
+ }
300
+
301
+ // Run tests if this file is executed directly
302
+ if (require.main === module) {
303
+ runTests().catch(console.error);
304
+ }
305
+
306
+ module.exports = { evaluateFinanceTransactionExecuteV1, runTests };