@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,309 @@
1
+ # Financial Transaction Execution Policy Pack v1
2
+
3
+ Protect your financial transaction endpoints with APort's standardized policy pack. This pack ensures only verified agents with proper capabilities, assurance levels, and transaction limits can execute financial transactions like trades, transfers, and asset movements.
4
+
5
+ ## What This Pack Protects
6
+
7
+ - **Route**: `/finance/transaction/*` (POST)
8
+ - **Risk**: Financial transactions, fraud prevention, regulatory compliance, fund segregation
9
+ - **Impact**: Direct monetary loss, regulatory violations, audit findings, counterparty risk
10
+
11
+ ## Requirements
12
+
13
+ | Requirement | Value | Description |
14
+ |-------------|-------|-------------|
15
+ | **Capability** | `finance.transaction` | Agent must have transaction capability |
16
+ | **Assurance** | `L3` or higher | Enhanced verification minimum |
17
+ | **Limits** | `allowed_transaction_types[]` | Allowed transaction types (buy, sell, transfer, short_sell) |
18
+ | **Limits** | `allowed_asset_classes[]` | Allowed asset classes (equity, bond, crypto, cash) |
19
+ | **Limits** | `max_exposure_per_tx_usd` | Maximum exposure per transaction |
20
+ | **Limits** | `allowed_source_account_types[]` | Allowed source account types |
21
+ | **Limits** | `restricted_source_account_types[]` | Restricted account types |
22
+ | **Limits** | `max_exposure_per_counterparty_usd` | Maximum exposure per counterparty |
23
+ | **Regions** | Must match | Agent must be authorized in caller's region |
24
+ | **Idempotency** | Required | Prevents duplicate transactions |
25
+
26
+ ## Implementation
27
+
28
+ ### Express.js
29
+
30
+ ```javascript
31
+ const { requirePolicy } = require('@aporthq/middleware-express');
32
+
33
+ // Option 1: Explicit agent ID (preferred)
34
+ app.post('/finance/transaction',
35
+ requirePolicy('finance.transaction.execute.v1', 'ap_a2d10232c6534523812423eec8a1425c45678'),
36
+ async (req, res) => {
37
+ // Your transaction logic here
38
+ // req.policyResult contains the verified passport
39
+ const { transaction_type, amount, currency, asset_class, source_account_id, destination_account_id } = req.body;
40
+ const passport = req.policyResult.passport;
41
+
42
+ // Process transaction...
43
+ res.json({ success: true, transaction_id: generateId() });
44
+ }
45
+ );
46
+
47
+ // Option 2: Header fallback (backward compatible)
48
+ app.post('/finance/transaction',
49
+ requirePolicy('finance.transaction.execute.v1'),
50
+ async (req, res) => {
51
+ // Your transaction logic here
52
+ // req.policyResult contains the verified passport
53
+ const { transaction_type, amount, currency, asset_class, source_account_id, destination_account_id } = req.body;
54
+ const passport = req.policyResult.passport;
55
+
56
+ // Process transaction...
57
+ res.json({ success: true, transaction_id: generateId() });
58
+ }
59
+ );
60
+ ```
61
+
62
+ **Client Request Example:**
63
+ ```javascript
64
+ // The client must include the agent ID in the header (for Option 2)
65
+ fetch('/finance/transaction', {
66
+ method: 'POST',
67
+ headers: {
68
+ 'Content-Type': 'application/json',
69
+ 'X-Agent-Passport-Id': 'ap_a2d10232c6534523812423eec8a1425c45678' // ← Agent ID passed here
70
+ },
71
+ body: JSON.stringify({
72
+ transaction_type: "buy",
73
+ amount: 10000,
74
+ currency: "USD",
75
+ asset_class: "equity",
76
+ source_account_id: "acc_client_123",
77
+ destination_account_id: "acc_trading_456",
78
+ source_account_type: "client_funds",
79
+ destination_account_type: "trading",
80
+ counterparty_id: "cpty_broker_789",
81
+ idempotency_key: "txn-buy-123"
82
+ })
83
+ });
84
+ ```
85
+
86
+ ### FastAPI
87
+
88
+ ```python
89
+ from aport.middleware import require_policy
90
+
91
+ @app.post("/finance/transaction")
92
+ @require_policy("finance.transaction.execute.v1")
93
+ async def execute_transaction(request: Request, transaction_data: TransactionRequest):
94
+ # Your transaction logic here
95
+ # request.state.policy_result contains the verified passport
96
+ passport = request.state.policy_result.passport
97
+
98
+ # Process transaction...
99
+ return {"success": True, "transaction_id": generate_id()}
100
+ ```
101
+
102
+ **Client Request Example:**
103
+ ```python
104
+ import requests
105
+
106
+ # The client must include the agent ID in the header
107
+ response = requests.post('/finance/transaction',
108
+ headers={
109
+ 'Content-Type': 'application/json',
110
+ 'X-Agent-Passport-Id': 'ap_a2d10232c6534523812423eec8a1425c45678' # ← Agent ID passed here
111
+ },
112
+ json={
113
+ 'transaction_type': 'buy',
114
+ 'amount': 10000,
115
+ 'currency': 'USD',
116
+ 'asset_class': 'equity',
117
+ 'source_account_id': 'acc_client_123',
118
+ 'destination_account_id': 'acc_trading_456',
119
+ 'source_account_type': 'client_funds',
120
+ 'destination_account_type': 'trading',
121
+ 'counterparty_id': 'cpty_broker_789',
122
+ 'idempotency_key': 'txn-buy-123'
123
+ }
124
+ )
125
+ ```
126
+
127
+ ## How It Works
128
+
129
+ The `requirePolicy('finance.transaction.execute.v1', agentId?)` middleware implements a flexible approach:
130
+
131
+ 1. **Agent ID Resolution** (in order of preference):
132
+ - **Function Parameter**: Uses explicit `agentId` if provided
133
+ - **Header Fallback**: Reads `X-Agent-Passport-Id` header from request
134
+ - **Validation**: Ensures agent ID format is valid (`ap_xxxxxxxxxxxxx`)
135
+ - **Failure**: Returns 400 error if neither provided
136
+
137
+ 2. **Policy Validation**:
138
+ - **Format Check**: Validates policy ID format (`finance.transaction.execute.v1`)
139
+ - **API Call**: Calls `/api/verify/policy/finance.transaction.execute.v1` with agent ID and context
140
+ - **Requirements Check**: Validates agent meets finance.transaction.execute.v1 requirements:
141
+ - Has `finance.transaction` capability
142
+ - Meets minimum assurance level (L3+)
143
+ - Transaction type is allowed
144
+ - Asset class is allowed
145
+ - Within exposure limits per transaction
146
+ - Source account type is allowed
147
+ - No restricted account types
148
+ - Segregation of funds enforced (no client→proprietary)
149
+ - Counterparty exposure within limits
150
+ - Idempotency key is unique
151
+ - Authorized in the request region
152
+
153
+ 3. **Result Handling**:
154
+ - **Success**: Adds `req.policyResult` with verified data and continues
155
+ - **Failure**: Returns 403 with detailed error information
156
+ - **Logging**: Logs violations for monitoring and debugging
157
+
158
+ ## Error Responses
159
+
160
+ When policy checks fail, you'll receive a `403 Forbidden` with detailed error information:
161
+
162
+ ```json
163
+ {
164
+ "error": "policy_violation",
165
+ "code": "oap.limit_exceeded",
166
+ "message": "Amount $100,000 exceeds per-transaction limit $50,000",
167
+ "policy_id": "finance.transaction.execute.v1",
168
+ "agent_id": "ap_a2d10232c6534523812423eec8a1425c45678",
169
+ "upgrade_instructions": "Request higher limits in your passport"
170
+ }
171
+ ```
172
+
173
+ ## Test Payloads
174
+
175
+ ### Valid Request
176
+ ```json
177
+ {
178
+ "transaction_type": "buy",
179
+ "amount": 10000,
180
+ "currency": "USD",
181
+ "asset_class": "equity",
182
+ "source_account_id": "acc_client_123",
183
+ "destination_account_id": "acc_trading_456",
184
+ "source_account_type": "client_funds",
185
+ "destination_account_type": "trading",
186
+ "counterparty_id": "cpty_broker_789",
187
+ "idempotency_key": "txn-buy-123"
188
+ }
189
+ ```
190
+
191
+ ### Invalid Request (exceeds limit)
192
+ ```json
193
+ {
194
+ "transaction_type": "buy",
195
+ "amount": 100000,
196
+ "currency": "USD",
197
+ "asset_class": "equity",
198
+ "source_account_id": "acc_client_123",
199
+ "destination_account_id": "acc_trading_456",
200
+ "idempotency_key": "txn-buy-124"
201
+ }
202
+ ```
203
+ *Returns 403: "Amount $100,000 exceeds per-transaction limit $50,000"*
204
+
205
+ ## Best Practices
206
+
207
+ 1. **Cache Verification**: Cache `/verify` responses with ETag for 60 seconds
208
+ 2. **Webhook Integration**: Subscribe to `status.changed` webhooks for instant suspension
209
+ 3. **Verifiable Attestation**: Log all transaction attempts for compliance
210
+ 4. **Daily Tracking**: Implement daily exposure tracking per counterparty to prevent concentration risk
211
+ 5. **Idempotency**: Always use unique idempotency keys to prevent duplicate transactions
212
+ 6. **Error Handling**: Provide clear error messages to help agents self-remediate
213
+ 7. **Fund Segregation**: Maintain strict segregation between client and proprietary funds
214
+ 8. **Counterparty Monitoring**: Monitor counterparty exposure limits to prevent over-concentration
215
+ 9. **Real-time Balance Checks**: Implement real-time balance checks before transaction execution
216
+ 10. **Progressive Limits**: Use progressive limits for new counterparties
217
+
218
+ ## Compliance Badge
219
+
220
+ Agents that meet this policy's requirements can display the "Transaction-Ready" badge:
221
+
222
+ ```markdown
223
+ [![Transaction-Ready](https://api.aport.io/badge/ap_a2d10232c6534523812423eec8a1425c45678.svg)](https://aport.io/agents/ap_a2d10232c6534523812423eec8a1425c45678)
224
+ ```
225
+
226
+ ## Support
227
+
228
+ - [Documentation](https://aport.io/docs/policies/finance.transaction.execute.v1)
229
+ - [Community](https://github.com/aporthq/community)
230
+ - [Support](https://aport.io/support)
231
+
232
+ ---
233
+ **Last Updated**: 2025-01-30 00:00:00 UTC
234
+
235
+
236
+ ## Required Context
237
+
238
+ This policy requires the following context (JSON Schema):
239
+
240
+ ```json
241
+ {
242
+ "$schema": "http://json-schema.org/draft-07/schema#",
243
+ "type": "object",
244
+ "required": [
245
+ "transaction_type",
246
+ "amount",
247
+ "currency",
248
+ "asset_class",
249
+ "source_account_id",
250
+ "destination_account_id"
251
+ ],
252
+ "properties": {
253
+ "transaction_type": {
254
+ "type": "string",
255
+ "enum": [
256
+ "buy",
257
+ "sell",
258
+ "transfer",
259
+ "short_sell"
260
+ ],
261
+ "description": "The type of financial transaction being executed."
262
+ },
263
+ "amount": {
264
+ "type": "integer",
265
+ "description": "Transaction amount in minor units (e.g., cents)."
266
+ },
267
+ "currency": {
268
+ "type": "string",
269
+ "pattern": "^[A-Z]{3}$",
270
+ "description": "ISO 4217 currency code."
271
+ },
272
+ "asset_class": {
273
+ "type": "string",
274
+ "description": "The class of the asset being transacted (e.g., 'equity', 'bond', 'crypto', 'cash')."
275
+ },
276
+ "source_account_id": {
277
+ "type": "string",
278
+ "description": "The ID of the account from which funds/assets are being moved."
279
+ },
280
+ "source_account_type": {
281
+ "type": "string",
282
+ "description": "The type of the source account (e.g., 'client_funds', 'trust_funds', 'proprietary')."
283
+ },
284
+ "destination_account_id": {
285
+ "type": "string",
286
+ "description": "The ID of the destination account."
287
+ },
288
+ "idempotency_key": {
289
+ "type": "string",
290
+ "description": "Idempotency key to prevent duplicate transactions."
291
+ },
292
+ "destination_account_type": {
293
+ "type": "string",
294
+ "description": "The type of the destination account (e.g., 'client_funds', 'proprietary'). Solves for Segregation of Funds."
295
+ },
296
+ "counterparty_id": {
297
+ "type": "string",
298
+ "description": "A unique identifier for the counterparty in a trade. Solves for Counterparty Exposure."
299
+ }
300
+ }
301
+ }
302
+ ```
303
+
304
+ You can also fetch this live via the discovery endpoint:
305
+
306
+ ```bash
307
+ curl -s "https://aport.io/api/policies/finance.transaction.execute.v1?format=schema"
308
+ ```
309
+
@@ -0,0 +1,261 @@
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 finance.transaction policy to all transaction routes
8
+ app.post(
9
+ "/finance/transaction",
10
+ requirePolicy("finance.transaction.execute.v1"),
11
+ async (req, res) => {
12
+ try {
13
+ const {
14
+ transaction_type,
15
+ amount,
16
+ currency,
17
+ asset_class,
18
+ source_account_id,
19
+ destination_account_id,
20
+ source_account_type,
21
+ destination_account_type,
22
+ counterparty_id,
23
+ idempotency_key,
24
+ } = req.body;
25
+
26
+ const passport = req.policyResult.passport;
27
+
28
+ // Additional business logic validation
29
+ if (amount <= 0) {
30
+ return res.status(400).json({ error: "Invalid transaction amount" });
31
+ }
32
+
33
+ // Check if required fields are provided
34
+ if (!source_account_id || !destination_account_id) {
35
+ return res
36
+ .status(400)
37
+ .json({ error: "Source and destination accounts are required" });
38
+ }
39
+
40
+ // Process transaction using your financial system
41
+ const transaction_id = await processTransaction({
42
+ transaction_type,
43
+ amount,
44
+ currency,
45
+ asset_class,
46
+ source_account_id,
47
+ destination_account_id,
48
+ source_account_type,
49
+ destination_account_type,
50
+ counterparty_id,
51
+ idempotency_key,
52
+ agent_id: passport.passport_id,
53
+ agent_name: passport.metadata?.template_name || "Unknown Agent",
54
+ });
55
+
56
+ // Log the transaction
57
+ console.log(
58
+ `Transaction processed: ${transaction_id} for ${amount} ${currency} by agent ${passport.passport_id}`
59
+ );
60
+
61
+ res.json({
62
+ success: true,
63
+ transaction_id,
64
+ transaction_type,
65
+ amount,
66
+ currency,
67
+ asset_class,
68
+ status: "processed",
69
+ decision_id: req.policyResult.decision_id,
70
+ });
71
+ } catch (error) {
72
+ console.error("Transaction processing error:", error);
73
+ res.status(500).json({ error: "Internal server error" });
74
+ }
75
+ }
76
+ );
77
+
78
+ // Batch transactions endpoint
79
+ app.post(
80
+ "/finance/transaction/batch",
81
+ requirePolicy("finance.transaction.execute.v1"),
82
+ async (req, res) => {
83
+ try {
84
+ const { transactions } = req.body;
85
+ const passport = req.policyResult.passport;
86
+
87
+ // Group transactions by counterparty for exposure checking
88
+ const counterpartyTotals = {};
89
+ for (const transaction of transactions) {
90
+ const counterparty = transaction.counterparty_id || "default";
91
+ counterpartyTotals[counterparty] =
92
+ (counterpartyTotals[counterparty] || 0) + (transaction.amount || 0);
93
+ }
94
+
95
+ // Check counterparty exposure limits
96
+ for (const [counterparty, totalAmount] of Object.entries(
97
+ counterpartyTotals
98
+ )) {
99
+ const maxExposure =
100
+ passport.limits?.finance?.transaction
101
+ ?.max_exposure_per_counterparty_usd;
102
+ if (maxExposure && totalAmount > maxExposure) {
103
+ return res.status(403).json({
104
+ error: "Batch total exceeds counterparty exposure limit",
105
+ counterparty,
106
+ total: totalAmount,
107
+ limit: maxExposure,
108
+ });
109
+ }
110
+ }
111
+
112
+ // Process batch transactions
113
+ const results = await Promise.all(
114
+ transactions.map((transaction) =>
115
+ processTransaction({
116
+ ...transaction,
117
+ agent_id: passport.passport_id,
118
+ })
119
+ )
120
+ );
121
+
122
+ res.json({
123
+ success: true,
124
+ processed: results.length,
125
+ counterparty_totals: counterpartyTotals,
126
+ decision_id: req.policyResult.decision_id,
127
+ });
128
+ } catch (error) {
129
+ console.error("Batch transaction error:", error);
130
+ res.status(500).json({ error: "Internal server error" });
131
+ }
132
+ }
133
+ );
134
+
135
+ // Get transaction status
136
+ app.get(
137
+ "/finance/transaction/:transaction_id",
138
+ requirePolicy("finance.transaction.execute.v1"),
139
+ async (req, res) => {
140
+ try {
141
+ const { transaction_id } = req.params;
142
+ const passport = req.policyResult.passport;
143
+
144
+ const transaction_info = await getTransactionStatus(
145
+ transaction_id,
146
+ passport.passport_id
147
+ );
148
+
149
+ if (!transaction_info) {
150
+ return res.status(404).json({ error: "Transaction not found" });
151
+ }
152
+
153
+ res.json(transaction_info);
154
+ } catch (error) {
155
+ console.error("Transaction status error:", error);
156
+ res.status(500).json({ error: "Internal server error" });
157
+ }
158
+ }
159
+ );
160
+
161
+ // Cancel transaction endpoint
162
+ app.post(
163
+ "/finance/transaction/:transaction_id/cancel",
164
+ requirePolicy("finance.transaction.execute.v1"),
165
+ async (req, res) => {
166
+ try {
167
+ const { transaction_id } = req.params;
168
+ const { reason } = req.body;
169
+ const passport = req.policyResult.passport;
170
+
171
+ const cancel_id = await cancelTransaction({
172
+ transaction_id,
173
+ reason,
174
+ agent_id: passport.passport_id,
175
+ });
176
+
177
+ res.json({
178
+ success: true,
179
+ cancel_id,
180
+ transaction_id,
181
+ status: "cancelled",
182
+ decision_id: req.policyResult.decision_id,
183
+ });
184
+ } catch (error) {
185
+ console.error("Transaction cancellation error:", error);
186
+ res.status(500).json({ error: "Internal server error" });
187
+ }
188
+ }
189
+ );
190
+
191
+ // Mock transaction processing function
192
+ async function processTransaction({
193
+ transaction_type,
194
+ amount,
195
+ currency,
196
+ asset_class,
197
+ source_account_id,
198
+ destination_account_id,
199
+ source_account_type,
200
+ destination_account_type,
201
+ counterparty_id,
202
+ idempotency_key,
203
+ agent_id,
204
+ }) {
205
+ // Simulate financial system call
206
+ await new Promise((resolve) => setTimeout(resolve, 150));
207
+
208
+ // Log transaction details for audit
209
+ console.log(`Processing transaction:`, {
210
+ transaction_type,
211
+ amount,
212
+ currency,
213
+ asset_class,
214
+ source_account_id,
215
+ destination_account_id,
216
+ source_account_type,
217
+ destination_account_type,
218
+ counterparty_id,
219
+ idempotency_key,
220
+ agent_id,
221
+ });
222
+
223
+ return `txn_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
224
+ }
225
+
226
+ // Mock transaction status lookup
227
+ async function getTransactionStatus(transaction_id, agent_id) {
228
+ // Simulate transaction status lookup
229
+ await new Promise((resolve) => setTimeout(resolve, 50));
230
+ return {
231
+ transaction_id,
232
+ status: "completed",
233
+ created_at: new Date().toISOString(),
234
+ transaction_type: "buy",
235
+ amount: 10000,
236
+ currency: "USD",
237
+ asset_class: "equity",
238
+ source_account_id: "acc_client_123",
239
+ destination_account_id: "acc_trading_456",
240
+ };
241
+ }
242
+
243
+ // Mock transaction cancellation function
244
+ async function cancelTransaction({ transaction_id, reason, agent_id }) {
245
+ // Simulate transaction cancellation
246
+ await new Promise((resolve) => setTimeout(resolve, 100));
247
+
248
+ console.log(`Cancelling transaction:`, {
249
+ transaction_id,
250
+ reason,
251
+ agent_id,
252
+ });
253
+
254
+ return `cancel_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
255
+ }
256
+
257
+ const PORT = process.env.PORT || 3000;
258
+ app.listen(PORT, () => {
259
+ console.log(`Financial transaction service running on port ${PORT}`);
260
+ console.log("Protected by APort finance.transaction.execute.v1 policy pack");
261
+ });