@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.
- package/LICENSE +217 -0
- package/README.md +481 -0
- package/bin/agent-guardrails +133 -0
- package/bin/aport-create-passport.sh +444 -0
- package/bin/aport-cursor-hook.sh +90 -0
- package/bin/aport-guardrail-api.sh +108 -0
- package/bin/aport-guardrail-bash.sh +394 -0
- package/bin/aport-guardrail-v2.sh +5 -0
- package/bin/aport-guardrail.sh +5 -0
- package/bin/aport-resolve-paths.sh +71 -0
- package/bin/aport-status.sh +276 -0
- package/bin/frameworks/crewai.sh +49 -0
- package/bin/frameworks/cursor.sh +95 -0
- package/bin/frameworks/langchain.sh +48 -0
- package/bin/frameworks/n8n.sh +36 -0
- package/bin/frameworks/openclaw.sh +19 -0
- package/bin/lib/allowlist.sh +18 -0
- package/bin/lib/common.sh +28 -0
- package/bin/lib/config.sh +46 -0
- package/bin/lib/constants.sh +232 -0
- package/bin/lib/detect.sh +65 -0
- package/bin/lib/error.sh +269 -0
- package/bin/lib/passport.sh +19 -0
- package/bin/lib/templates/.gitkeep +1 -0
- package/bin/lib/templates/config.yaml +6 -0
- package/bin/lib/validation.sh +206 -0
- package/bin/openclaw +660 -0
- package/docs/ADDING_A_FRAMEWORK.md +87 -0
- package/docs/AGENTS.md.example +40 -0
- package/docs/CODE_REVIEW.md +192 -0
- package/docs/DEPLOYMENT_READINESS.md +81 -0
- package/docs/FAQ_SECURITY_SCANNERS.md +373 -0
- package/docs/FRAMEWORK_ROADMAP.md +41 -0
- package/docs/HOSTED_PASSPORT_SETUP.md +362 -0
- package/docs/IMPLEMENTING_YOUR_OWN_EVALUATOR.md +433 -0
- package/docs/OPENCLAW_COMPATIBILITY.md +73 -0
- package/docs/OPENCLAW_LOCAL_INTEGRATION.md +596 -0
- package/docs/OPENCLAW_TOOLS_AND_POLICIES.md +54 -0
- package/docs/QUICKSTART.md +470 -0
- package/docs/QUICKSTART_OPENCLAW_PLUGIN.md +470 -0
- package/docs/README.md +28 -0
- package/docs/RELEASE.md +87 -0
- package/docs/REPO_LAYOUT.md +47 -0
- package/docs/SKILLS_ECOSYSTEM_ANALYSIS_FEB17.md +1260 -0
- package/docs/TOOL_POLICY_MAPPING.md +46 -0
- package/docs/UPGRADE.md +46 -0
- package/docs/VERIFICATION_METHODS.md +97 -0
- package/docs/assets/README.md +8 -0
- package/docs/assets/porter.svg +54 -0
- package/docs/development/ERROR_CODES.md +616 -0
- package/docs/frameworks/GITHUB_ISSUE_PROPOSALS.md +1105 -0
- package/docs/frameworks/crewai.md +114 -0
- package/docs/frameworks/cursor.md +159 -0
- package/docs/frameworks/langchain.md +72 -0
- package/docs/frameworks/n8n.md +40 -0
- package/docs/frameworks/openclaw.md +40 -0
- package/docs/launch/ADD_APORT_AWESOME_LISTS_INSTRUCTIONS.md +146 -0
- package/docs/launch/ANNOUNCEMENT_GUIDE.md +266 -0
- package/docs/launch/AWESOME_REPOS.md +53 -0
- package/docs/launch/CURSOR_VSCODE_HOOKS_RESEARCH.md +77 -0
- package/docs/launch/DEMO_TERMINAL_OUTPUT.txt +48 -0
- package/docs/launch/DRY_AND_PLAN_CHECKLIST.md +47 -0
- package/docs/launch/EVIDENCE_README.md +61 -0
- package/docs/launch/EVIDENCE_TERMINAL_CAPTURE.txt +10 -0
- package/docs/launch/FRAMEWORK_SUPPORT_PLAN.md +1640 -0
- package/docs/launch/LAUNCH_READINESS_CHECKLIST.md +237 -0
- package/docs/launch/LAUNCH_STRATEGY_SUMMARY.md +464 -0
- package/docs/launch/OPENCLAW_FEEDBACK_AND_FIXES.md +85 -0
- package/docs/launch/POST_1_VALENTINE_IMPROVED.md +233 -0
- package/docs/launch/POST_2_GUARDRAIL_IMPROVED.md +369 -0
- package/docs/launch/PRE_LAUNCH_FIXES.md +766 -0
- package/docs/launch/QUICK_LAUNCH_CHECKLIST.md +400 -0
- package/docs/launch/READINESS_SUMMARY.md +262 -0
- package/docs/launch/README.md +68 -0
- package/docs/launch/USER_STORIES.md +327 -0
- package/docs/launch/scripts/add-aport-awesome-pr.sh +69 -0
- package/docs/operations/MONITORING.md +588 -0
- package/docs/reviews/2026-02-18-staff-review.md +268 -0
- package/extensions/openclaw-aport/README.md +415 -0
- package/extensions/openclaw-aport/index.js +625 -0
- package/extensions/openclaw-aport/openclaw-aport.js +7 -0
- package/extensions/openclaw-aport/openclaw.plugin.json +46 -0
- package/extensions/openclaw-aport/package.json +36 -0
- package/extensions/openclaw-aport/test.js +307 -0
- package/external/aport-policies/README.md +363 -0
- package/external/aport-policies/agent.session.create.v1/README.md +345 -0
- package/external/aport-policies/agent.session.create.v1/policy.json +162 -0
- package/external/aport-policies/agent.tool.register.v1/README.md +361 -0
- package/external/aport-policies/agent.tool.register.v1/policy.json +172 -0
- package/external/aport-policies/code.release.publish.v1/README.md +51 -0
- package/external/aport-policies/code.release.publish.v1/policy.json +121 -0
- package/external/aport-policies/code.repository.merge.v1/README.md +287 -0
- package/external/aport-policies/code.repository.merge.v1/express.example.js +332 -0
- package/external/aport-policies/code.repository.merge.v1/fastapi.example.py +370 -0
- package/external/aport-policies/code.repository.merge.v1/policy.json +162 -0
- package/external/aport-policies/data.export.create.v1/README.md +226 -0
- package/external/aport-policies/data.export.create.v1/express.example.js +172 -0
- package/external/aport-policies/data.export.create.v1/fastapi.example.py +165 -0
- package/external/aport-policies/data.export.create.v1/policy.json +133 -0
- package/external/aport-policies/data.report.ingest.v1/README.md +134 -0
- package/external/aport-policies/data.report.ingest.v1/express.example.js +105 -0
- package/external/aport-policies/data.report.ingest.v1/minimal-example.js +68 -0
- package/external/aport-policies/data.report.ingest.v1/policy.json +174 -0
- package/external/aport-policies/finance.crypto.trade.v1/README.md +146 -0
- package/external/aport-policies/finance.crypto.trade.v1/express.example.js +109 -0
- package/external/aport-policies/finance.crypto.trade.v1/minimal-example.js +65 -0
- package/external/aport-policies/finance.crypto.trade.v1/policy.json +176 -0
- package/external/aport-policies/finance.payment.charge.v1/README.md +326 -0
- package/external/aport-policies/finance.payment.charge.v1/express.example.js +250 -0
- package/external/aport-policies/finance.payment.charge.v1/fastapi.example.py +227 -0
- package/external/aport-policies/finance.payment.charge.v1/minimal-example.js +64 -0
- package/external/aport-policies/finance.payment.charge.v1/policy.json +224 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/contexts.jsonl +12 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/expected.jsonl +12 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/passport.instance.json +42 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/passport.template.json +40 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/payments-charge-policy.test.js +817 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/test_payments_charge_policy.py +486 -0
- package/external/aport-policies/finance.payment.payout.v1/README.md +78 -0
- package/external/aport-policies/finance.payment.payout.v1/policy.json +181 -0
- package/external/aport-policies/finance.payment.refund.v1/README.md +275 -0
- package/external/aport-policies/finance.payment.refund.v1/express.example.js +167 -0
- package/external/aport-policies/finance.payment.refund.v1/fastapi.example.py +136 -0
- package/external/aport-policies/finance.payment.refund.v1/minimal-example.js +183 -0
- package/external/aport-policies/finance.payment.refund.v1/policy.json +216 -0
- package/external/aport-policies/finance.payment.refund.v1/tests/refunds-policy.test.js +924 -0
- package/external/aport-policies/finance.payment.refund.v1/tests/test_refunds_policy.py +778 -0
- package/external/aport-policies/finance.transaction.execute.v1/README.md +309 -0
- package/external/aport-policies/finance.transaction.execute.v1/express.example.js +261 -0
- package/external/aport-policies/finance.transaction.execute.v1/fastapi.example.py +231 -0
- package/external/aport-policies/finance.transaction.execute.v1/minimal-example.js +78 -0
- package/external/aport-policies/finance.transaction.execute.v1/policy.json +189 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/contexts.jsonl +12 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/expected.jsonl +12 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/passport.instance.json +42 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/passport.template.json +42 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/test_transactions_policy.py +214 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/transactions-policy.test.js +306 -0
- package/external/aport-policies/governance.data.access.v1/README.md +292 -0
- package/external/aport-policies/governance.data.access.v1/express.example.js +321 -0
- package/external/aport-policies/governance.data.access.v1/fastapi.example.py +279 -0
- package/external/aport-policies/governance.data.access.v1/minimal-example.js +65 -0
- package/external/aport-policies/governance.data.access.v1/policy.json +208 -0
- package/external/aport-policies/governance.data.access.v1/tests/contexts.jsonl +12 -0
- package/external/aport-policies/governance.data.access.v1/tests/data-access-policy.test.js +308 -0
- package/external/aport-policies/governance.data.access.v1/tests/expected.jsonl +12 -0
- package/external/aport-policies/governance.data.access.v1/tests/passport.instance.json +56 -0
- package/external/aport-policies/governance.data.access.v1/tests/passport.template.json +56 -0
- package/external/aport-policies/governance.data.access.v1/tests/test_data_access_policy.py +214 -0
- package/external/aport-policies/legal.contract.review.v1/README.md +109 -0
- package/external/aport-policies/legal.contract.review.v1/policy.json +378 -0
- package/external/aport-policies/legal.contract.review.v1/tests/legal-contract-review-policy.test.js +609 -0
- package/external/aport-policies/legal.contract.review.v1/tests/passport.template.json +49 -0
- package/external/aport-policies/mcp.tool.execute.v1/README.md +301 -0
- package/external/aport-policies/mcp.tool.execute.v1/policy.json +141 -0
- package/external/aport-policies/messaging.message.send.v1/README.md +230 -0
- package/external/aport-policies/messaging.message.send.v1/express.example.js +183 -0
- package/external/aport-policies/messaging.message.send.v1/fastapi.example.py +193 -0
- package/external/aport-policies/messaging.message.send.v1/policy.json +144 -0
- package/external/aport-policies/policy-template.json +107 -0
- package/external/aport-policies/system.command.execute.v1/README.md +275 -0
- package/external/aport-policies/system.command.execute.v1/policy.json +146 -0
- package/external/aport-spec/CONTRIBUTING.md +273 -0
- package/external/aport-spec/LICENSE +21 -0
- package/external/aport-spec/README.md +168 -0
- package/external/aport-spec/conformance/README.md +294 -0
- package/external/aport-spec/conformance/cases/data.export.v1/contexts/allow_users.json +6 -0
- package/external/aport-spec/conformance/cases/data.export.v1/contexts/deny_pii.json +6 -0
- package/external/aport-spec/conformance/cases/data.export.v1/expected/allow_users.decision.json +19 -0
- package/external/aport-spec/conformance/cases/data.export.v1/expected/deny_pii.decision.json +19 -0
- package/external/aport-spec/conformance/cases/data.export.v1/passports/template.json +29 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/allow_50usd.json +9 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/deny_150usd.json +9 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/deny_currency.json +9 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/allow_50usd.decision.json +19 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/deny_150usd.decision.json +19 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/deny_currency.decision.json +19 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/passports/template.json +42 -0
- package/external/aport-spec/conformance/package.json +44 -0
- package/external/aport-spec/conformance/pnpm-lock.yaml +642 -0
- package/external/aport-spec/conformance/src/cases.ts +371 -0
- package/external/aport-spec/conformance/src/ed25519.ts +167 -0
- package/external/aport-spec/conformance/src/jcs.ts +85 -0
- package/external/aport-spec/conformance/src/runner.ts +533 -0
- package/external/aport-spec/conformance/src/validators.ts +185 -0
- package/external/aport-spec/conformance/test-runner.js +315 -0
- package/external/aport-spec/conformance/tsconfig.json +21 -0
- package/external/aport-spec/error-schema.json +192 -0
- package/external/aport-spec/index.json +12 -0
- package/external/aport-spec/integrations/clawmoat/README.md +12 -0
- package/external/aport-spec/integrations/shield/README.md +245 -0
- package/external/aport-spec/integrations/shield/adapters/index.js +116 -0
- package/external/aport-spec/integrations/shield/adapters/system-command-execute.js +133 -0
- package/external/aport-spec/integrations/shield/test/README.md +58 -0
- package/external/aport-spec/integrations/shield/test/shield.md +40 -0
- package/external/aport-spec/integrations/shield/test/test-shield-to-verify.js +274 -0
- package/external/aport-spec/metrics-schema.json +504 -0
- package/external/aport-spec/oap/CHANGELOG.md +54 -0
- package/external/aport-spec/oap/VERSION.md +40 -0
- package/external/aport-spec/oap/capability-registry.md +229 -0
- package/external/aport-spec/oap/conformance.md +257 -0
- package/external/aport-spec/oap/decision-schema.json +114 -0
- package/external/aport-spec/oap/examples/context.refund.usd.50.json +9 -0
- package/external/aport-spec/oap/examples/decision.allow.sample.json +20 -0
- package/external/aport-spec/oap/examples/decision.deny.sample.json +23 -0
- package/external/aport-spec/oap/examples/passport.instance.v1.json +50 -0
- package/external/aport-spec/oap/examples/passport.template.v1.json +71 -0
- package/external/aport-spec/oap/oap-spec.md +426 -0
- package/external/aport-spec/oap/passport-schema.json +396 -0
- package/external/aport-spec/oap/security.md +213 -0
- package/external/aport-spec/oap/vc/context-oap-v1.jsonld +137 -0
- package/external/aport-spec/oap/vc/examples/oap-decision-vc.json +37 -0
- package/external/aport-spec/oap/vc/examples/oap-passport-vc.json +68 -0
- package/external/aport-spec/oap/vc/tools/INTEGRATION.md +375 -0
- package/external/aport-spec/oap/vc/tools/README.md +278 -0
- package/external/aport-spec/oap/vc/tools/examples/decision-to-vc.js +66 -0
- package/external/aport-spec/oap/vc/tools/examples/passport-to-vc.js +83 -0
- package/external/aport-spec/oap/vc/tools/examples/vc-to-decision.js +77 -0
- package/external/aport-spec/oap/vc/tools/examples/vc-to-passport.js +94 -0
- package/external/aport-spec/oap/vc/tools/package.json +38 -0
- package/external/aport-spec/oap/vc/tools/pnpm-lock.yaml +472 -0
- package/external/aport-spec/oap/vc/tools/src/cli.ts +226 -0
- package/external/aport-spec/oap/vc/tools/src/crypto-utils.ts +427 -0
- package/external/aport-spec/oap/vc/tools/src/index.ts +653 -0
- package/external/aport-spec/oap/vc/tools/src/test.ts +148 -0
- package/external/aport-spec/oap/vc/tools/src/vp.ts +382 -0
- package/external/aport-spec/oap/vc/tools/test-simple.js +214 -0
- package/external/aport-spec/oap/vc/tools/tsconfig.json +19 -0
- package/external/aport-spec/oap/vc/vc-mapping.md +443 -0
- package/external/aport-spec/passport-schema.json +586 -0
- package/external/aport-spec/rate-limiting.md +136 -0
- package/external/aport-spec/transport-profile.md +325 -0
- package/external/aport-spec/webhook-spec.md +314 -0
- package/package.json +70 -0
- package/skills/aport-agent-guardrail/SKILL.md +314 -0
- package/src/evaluator.js +252 -0
- 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
|
+
[](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
|
+
});
|