@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,231 @@
|
|
|
1
|
+
from fastapi import FastAPI, HTTPException, Request
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
from aport.middleware import require_policy
|
|
4
|
+
import asyncio
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
app = FastAPI(title="Financial Transaction Service", version="1.0.0")
|
|
9
|
+
|
|
10
|
+
class TransactionRequest(BaseModel):
|
|
11
|
+
transaction_type: str
|
|
12
|
+
amount: int
|
|
13
|
+
currency: str
|
|
14
|
+
asset_class: str
|
|
15
|
+
source_account_id: str
|
|
16
|
+
destination_account_id: str
|
|
17
|
+
source_account_type: Optional[str] = None
|
|
18
|
+
destination_account_type: Optional[str] = None
|
|
19
|
+
counterparty_id: Optional[str] = None
|
|
20
|
+
idempotency_key: str
|
|
21
|
+
|
|
22
|
+
class BatchTransactionRequest(BaseModel):
|
|
23
|
+
transactions: List[TransactionRequest]
|
|
24
|
+
|
|
25
|
+
class CancelRequest(BaseModel):
|
|
26
|
+
reason: str
|
|
27
|
+
|
|
28
|
+
class TransactionResponse(BaseModel):
|
|
29
|
+
success: bool
|
|
30
|
+
transaction_id: str
|
|
31
|
+
transaction_type: str
|
|
32
|
+
amount: int
|
|
33
|
+
currency: str
|
|
34
|
+
asset_class: str
|
|
35
|
+
status: str
|
|
36
|
+
decision_id: str
|
|
37
|
+
|
|
38
|
+
class TransactionStatus(BaseModel):
|
|
39
|
+
transaction_id: str
|
|
40
|
+
status: str
|
|
41
|
+
created_at: str
|
|
42
|
+
transaction_type: str
|
|
43
|
+
amount: int
|
|
44
|
+
currency: str
|
|
45
|
+
asset_class: str
|
|
46
|
+
source_account_id: str
|
|
47
|
+
destination_account_id: str
|
|
48
|
+
|
|
49
|
+
@app.post("/finance/transaction")
|
|
50
|
+
@require_policy("finance.transaction.execute.v1")
|
|
51
|
+
async def execute_transaction(request: Request, transaction_data: TransactionRequest):
|
|
52
|
+
try:
|
|
53
|
+
passport = request.state.policy_result.passport
|
|
54
|
+
|
|
55
|
+
# Additional business logic validation
|
|
56
|
+
if transaction_data.amount <= 0:
|
|
57
|
+
raise HTTPException(status_code=400, detail="Invalid transaction amount")
|
|
58
|
+
|
|
59
|
+
# Check if required fields are provided
|
|
60
|
+
if not transaction_data.source_account_id or not transaction_data.destination_account_id:
|
|
61
|
+
raise HTTPException(status_code=400, detail="Source and destination accounts are required")
|
|
62
|
+
|
|
63
|
+
# Process transaction using your financial system
|
|
64
|
+
transaction_id = await process_transaction({
|
|
65
|
+
"transaction_type": transaction_data.transaction_type,
|
|
66
|
+
"amount": transaction_data.amount,
|
|
67
|
+
"currency": transaction_data.currency,
|
|
68
|
+
"asset_class": transaction_data.asset_class,
|
|
69
|
+
"source_account_id": transaction_data.source_account_id,
|
|
70
|
+
"destination_account_id": transaction_data.destination_account_id,
|
|
71
|
+
"source_account_type": transaction_data.source_account_type,
|
|
72
|
+
"destination_account_type": transaction_data.destination_account_type,
|
|
73
|
+
"counterparty_id": transaction_data.counterparty_id,
|
|
74
|
+
"idempotency_key": transaction_data.idempotency_key,
|
|
75
|
+
"agent_id": passport.passport_id,
|
|
76
|
+
"agent_name": passport.metadata.get("template_name", "Unknown Agent") if passport.metadata else "Unknown Agent"
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
# Log the transaction
|
|
80
|
+
print(f"Transaction processed: {transaction_id} for {transaction_data.amount} {transaction_data.currency} by agent {passport.passport_id}")
|
|
81
|
+
|
|
82
|
+
return TransactionResponse(
|
|
83
|
+
success=True,
|
|
84
|
+
transaction_id=transaction_id,
|
|
85
|
+
transaction_type=transaction_data.transaction_type,
|
|
86
|
+
amount=transaction_data.amount,
|
|
87
|
+
currency=transaction_data.currency,
|
|
88
|
+
asset_class=transaction_data.asset_class,
|
|
89
|
+
status="processed",
|
|
90
|
+
decision_id=request.state.policy_result.decision_id
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
except Exception as e:
|
|
94
|
+
print(f"Transaction processing error: {e}")
|
|
95
|
+
raise HTTPException(status_code=500, detail="Internal server error")
|
|
96
|
+
|
|
97
|
+
@app.post("/finance/transaction/batch")
|
|
98
|
+
@require_policy("finance.transaction.execute.v1")
|
|
99
|
+
async def execute_batch_transactions(request: Request, batch_data: BatchTransactionRequest):
|
|
100
|
+
try:
|
|
101
|
+
passport = request.state.policy_result.passport
|
|
102
|
+
|
|
103
|
+
# Group transactions by counterparty for exposure checking
|
|
104
|
+
counterparty_totals = {}
|
|
105
|
+
for transaction in batch_data.transactions:
|
|
106
|
+
counterparty = transaction.counterparty_id or "default"
|
|
107
|
+
counterparty_totals[counterparty] = counterparty_totals.get(counterparty, 0) + transaction.amount
|
|
108
|
+
|
|
109
|
+
# Check counterparty exposure limits
|
|
110
|
+
for counterparty, total_amount in counterparty_totals.items():
|
|
111
|
+
max_exposure = passport.limits.get("finance", {}).get("transaction", {}).get("max_exposure_per_counterparty_usd")
|
|
112
|
+
if max_exposure and total_amount > max_exposure:
|
|
113
|
+
raise HTTPException(
|
|
114
|
+
status_code=403,
|
|
115
|
+
detail={
|
|
116
|
+
"error": "Batch total exceeds counterparty exposure limit",
|
|
117
|
+
"counterparty": counterparty,
|
|
118
|
+
"total": total_amount,
|
|
119
|
+
"limit": max_exposure
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Process batch transactions
|
|
124
|
+
results = await asyncio.gather(*[
|
|
125
|
+
process_transaction({
|
|
126
|
+
"transaction_type": transaction.transaction_type,
|
|
127
|
+
"amount": transaction.amount,
|
|
128
|
+
"currency": transaction.currency,
|
|
129
|
+
"asset_class": transaction.asset_class,
|
|
130
|
+
"source_account_id": transaction.source_account_id,
|
|
131
|
+
"destination_account_id": transaction.destination_account_id,
|
|
132
|
+
"source_account_type": transaction.source_account_type,
|
|
133
|
+
"destination_account_type": transaction.destination_account_type,
|
|
134
|
+
"counterparty_id": transaction.counterparty_id,
|
|
135
|
+
"idempotency_key": transaction.idempotency_key,
|
|
136
|
+
"agent_id": passport.passport_id
|
|
137
|
+
})
|
|
138
|
+
for transaction in batch_data.transactions
|
|
139
|
+
])
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
"success": True,
|
|
143
|
+
"processed": len(results),
|
|
144
|
+
"counterparty_totals": counterparty_totals,
|
|
145
|
+
"decision_id": request.state.policy_result.decision_id
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
except Exception as e:
|
|
149
|
+
print(f"Batch transaction error: {e}")
|
|
150
|
+
raise HTTPException(status_code=500, detail="Internal server error")
|
|
151
|
+
|
|
152
|
+
@app.get("/finance/transaction/{transaction_id}")
|
|
153
|
+
@require_policy("finance.transaction.execute.v1")
|
|
154
|
+
async def get_transaction_status(request: Request, transaction_id: str):
|
|
155
|
+
try:
|
|
156
|
+
passport = request.state.policy_result.passport
|
|
157
|
+
|
|
158
|
+
transaction_info = await get_transaction_status(transaction_id, passport.passport_id)
|
|
159
|
+
|
|
160
|
+
if not transaction_info:
|
|
161
|
+
raise HTTPException(status_code=404, detail="Transaction not found")
|
|
162
|
+
|
|
163
|
+
return transaction_info
|
|
164
|
+
|
|
165
|
+
except Exception as e:
|
|
166
|
+
print(f"Transaction status error: {e}")
|
|
167
|
+
raise HTTPException(status_code=500, detail="Internal server error")
|
|
168
|
+
|
|
169
|
+
@app.post("/finance/transaction/{transaction_id}/cancel")
|
|
170
|
+
@require_policy("finance.transaction.execute.v1")
|
|
171
|
+
async def cancel_transaction(request: Request, transaction_id: str, cancel_data: CancelRequest):
|
|
172
|
+
try:
|
|
173
|
+
passport = request.state.policy_result.passport
|
|
174
|
+
|
|
175
|
+
cancel_id = await cancel_transaction_payment({
|
|
176
|
+
"transaction_id": transaction_id,
|
|
177
|
+
"reason": cancel_data.reason,
|
|
178
|
+
"agent_id": passport.passport_id
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
"success": True,
|
|
183
|
+
"cancel_id": cancel_id,
|
|
184
|
+
"transaction_id": transaction_id,
|
|
185
|
+
"status": "cancelled",
|
|
186
|
+
"decision_id": request.state.policy_result.decision_id
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
except Exception as e:
|
|
190
|
+
print(f"Transaction cancellation error: {e}")
|
|
191
|
+
raise HTTPException(status_code=500, detail="Internal server error")
|
|
192
|
+
|
|
193
|
+
async def process_transaction(transaction_data: dict) -> str:
|
|
194
|
+
"""Mock transaction processing function"""
|
|
195
|
+
# Simulate financial system call
|
|
196
|
+
await asyncio.sleep(0.15)
|
|
197
|
+
|
|
198
|
+
# Log transaction details for audit
|
|
199
|
+
print(f"Processing transaction: {transaction_data}")
|
|
200
|
+
|
|
201
|
+
return f"txn_{asyncio.get_event_loop().time()}_{hash(str(transaction_data)) % 1000000}"
|
|
202
|
+
|
|
203
|
+
async def get_transaction_status(transaction_id: str, agent_id: str) -> Optional[TransactionStatus]:
|
|
204
|
+
"""Mock transaction status lookup"""
|
|
205
|
+
await asyncio.sleep(0.05)
|
|
206
|
+
return TransactionStatus(
|
|
207
|
+
transaction_id=transaction_id,
|
|
208
|
+
status="completed",
|
|
209
|
+
created_at=datetime.now().isoformat(),
|
|
210
|
+
transaction_type="buy",
|
|
211
|
+
amount=10000,
|
|
212
|
+
currency="USD",
|
|
213
|
+
asset_class="equity",
|
|
214
|
+
source_account_id="acc_client_123",
|
|
215
|
+
destination_account_id="acc_trading_456"
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
async def cancel_transaction_payment(cancel_data: dict) -> str:
|
|
219
|
+
"""Mock transaction cancellation function"""
|
|
220
|
+
# Simulate transaction cancellation
|
|
221
|
+
await asyncio.sleep(0.1)
|
|
222
|
+
|
|
223
|
+
print(f"Cancelling transaction: {cancel_data}")
|
|
224
|
+
|
|
225
|
+
return f"cancel_{asyncio.get_event_loop().time()}_{hash(str(cancel_data)) % 1000000}"
|
|
226
|
+
|
|
227
|
+
if __name__ == "__main__":
|
|
228
|
+
import uvicorn
|
|
229
|
+
print("Financial transaction service starting...")
|
|
230
|
+
print("Protected by APort finance.transaction.execute.v1 policy pack")
|
|
231
|
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal Example: finance.transaction.execute.v1 Policy
|
|
3
|
+
*
|
|
4
|
+
* This is a quick-start example showing the basic usage of the finance.transaction.execute.v1 policy.
|
|
5
|
+
* For production use, see the full express.example.js file.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const express = require("express");
|
|
9
|
+
const { requirePolicy } = require("@aporthq/middleware-express");
|
|
10
|
+
|
|
11
|
+
const app = express();
|
|
12
|
+
app.use(express.json());
|
|
13
|
+
|
|
14
|
+
// Minimal transaction endpoint with policy protection
|
|
15
|
+
app.post(
|
|
16
|
+
"/transaction",
|
|
17
|
+
requirePolicy("finance.transaction.execute.v1"),
|
|
18
|
+
async (req, res) => {
|
|
19
|
+
try {
|
|
20
|
+
const {
|
|
21
|
+
transaction_type,
|
|
22
|
+
amount,
|
|
23
|
+
currency,
|
|
24
|
+
asset_class,
|
|
25
|
+
source_account_id,
|
|
26
|
+
destination_account_id,
|
|
27
|
+
} = req.body;
|
|
28
|
+
const passport = req.policyResult.passport;
|
|
29
|
+
|
|
30
|
+
// Process the transaction (your business logic here)
|
|
31
|
+
const transaction_id = `txn_${Date.now()}`;
|
|
32
|
+
|
|
33
|
+
console.log(
|
|
34
|
+
`Transaction processed: ${transaction_id} for ${amount} ${currency}`
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
res.json({
|
|
38
|
+
success: true,
|
|
39
|
+
transaction_id,
|
|
40
|
+
transaction_type,
|
|
41
|
+
amount,
|
|
42
|
+
currency,
|
|
43
|
+
asset_class,
|
|
44
|
+
decision_id: req.policyResult.decision_id,
|
|
45
|
+
});
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error("Transaction error:", error);
|
|
48
|
+
res.status(500).json({ error: "Internal server error" });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Example client request
|
|
54
|
+
const exampleRequest = {
|
|
55
|
+
transaction_type: "buy",
|
|
56
|
+
amount: 10000, // $100.00 in cents
|
|
57
|
+
currency: "USD",
|
|
58
|
+
asset_class: "equity",
|
|
59
|
+
source_account_id: "acc_client_123",
|
|
60
|
+
destination_account_id: "acc_trading_456",
|
|
61
|
+
source_account_type: "client_funds",
|
|
62
|
+
destination_account_type: "trading",
|
|
63
|
+
counterparty_id: "cpty_broker_789",
|
|
64
|
+
idempotency_key: "txn-buy-123",
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
console.log("Example request:", JSON.stringify(exampleRequest, null, 2));
|
|
68
|
+
|
|
69
|
+
const PORT = process.env.PORT || 3000;
|
|
70
|
+
app.listen(PORT, () => {
|
|
71
|
+
console.log(`Minimal transaction service running on port ${PORT}`);
|
|
72
|
+
console.log("Protected by APort finance.transaction.execute.v1 policy");
|
|
73
|
+
console.log(
|
|
74
|
+
`Try: curl -X POST http://localhost:${PORT}/transaction -H "Content-Type: application/json" -d '${JSON.stringify(
|
|
75
|
+
exampleRequest
|
|
76
|
+
)}'`
|
|
77
|
+
);
|
|
78
|
+
});
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "finance.transaction.execute.v1",
|
|
3
|
+
"name": "Financial Transaction Execution Policy",
|
|
4
|
+
"description": "Pre-action governance for agent-initiated financial transactions like trades or transfers. Enforces rules on asset classes, exposure limits, and account types.",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "active",
|
|
7
|
+
"requires_capabilities": ["finance.transaction"],
|
|
8
|
+
"min_assurance": "L3",
|
|
9
|
+
"limits_required": [
|
|
10
|
+
"allowed_transaction_types",
|
|
11
|
+
"allowed_asset_classes",
|
|
12
|
+
"max_exposure_per_tx_usd",
|
|
13
|
+
"allowed_source_account_types",
|
|
14
|
+
"restricted_source_account_types",
|
|
15
|
+
"max_exposure_per_counterparty_usd",
|
|
16
|
+
"approval_required"
|
|
17
|
+
],
|
|
18
|
+
"required_fields": [
|
|
19
|
+
"transaction_type",
|
|
20
|
+
"amount",
|
|
21
|
+
"currency",
|
|
22
|
+
"asset_class",
|
|
23
|
+
"source_account_id",
|
|
24
|
+
"destination_account_id"
|
|
25
|
+
],
|
|
26
|
+
"optional_fields": [
|
|
27
|
+
"source_account_type",
|
|
28
|
+
"idempotency_key",
|
|
29
|
+
"destination_account_type",
|
|
30
|
+
"counterparty_id"
|
|
31
|
+
],
|
|
32
|
+
"enforcement": {
|
|
33
|
+
"transaction_type_allowed": true,
|
|
34
|
+
"asset_class_allowed": true,
|
|
35
|
+
"exposure_limit_enforced": true,
|
|
36
|
+
"account_segregation_enforced": true,
|
|
37
|
+
"segregation_of_funds_enforced": true,
|
|
38
|
+
"counterparty_exposure_enforced": true,
|
|
39
|
+
"idempotency_required": true
|
|
40
|
+
},
|
|
41
|
+
"mcp": {
|
|
42
|
+
"require_allowlisted_if_present": true
|
|
43
|
+
},
|
|
44
|
+
"advice": [
|
|
45
|
+
"Cache /verify with ETag; 60s TTL",
|
|
46
|
+
"Subscribe to status webhooks for instant suspend",
|
|
47
|
+
"Log all transaction attempts for Verifiable Attestation",
|
|
48
|
+
"Implement daily exposure tracking per counterparty to prevent concentration risk",
|
|
49
|
+
"Always use unique idempotency keys to prevent duplicate transactions",
|
|
50
|
+
"Maintain strict segregation between client and proprietary funds",
|
|
51
|
+
"Monitor counterparty exposure limits to prevent over-concentration",
|
|
52
|
+
"Implement real-time balance checks before transaction execution",
|
|
53
|
+
"Use progressive limits for new counterparties",
|
|
54
|
+
"Maintain audit trails for all financial transactions"
|
|
55
|
+
],
|
|
56
|
+
"cache": {
|
|
57
|
+
"default_ttl_seconds": 60,
|
|
58
|
+
"suspend_invalidate_seconds": 30
|
|
59
|
+
},
|
|
60
|
+
"required_context": {
|
|
61
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
62
|
+
"type": "object",
|
|
63
|
+
"required": [
|
|
64
|
+
"transaction_type",
|
|
65
|
+
"amount",
|
|
66
|
+
"currency",
|
|
67
|
+
"asset_class",
|
|
68
|
+
"source_account_id",
|
|
69
|
+
"destination_account_id"
|
|
70
|
+
],
|
|
71
|
+
"properties": {
|
|
72
|
+
"transaction_type": {
|
|
73
|
+
"type": "string",
|
|
74
|
+
"enum": ["buy", "sell", "transfer", "short_sell"],
|
|
75
|
+
"description": "The type of financial transaction being executed."
|
|
76
|
+
},
|
|
77
|
+
"amount": {
|
|
78
|
+
"type": "integer",
|
|
79
|
+
"description": "Transaction amount in minor units (e.g., cents)."
|
|
80
|
+
},
|
|
81
|
+
"currency": {
|
|
82
|
+
"type": "string",
|
|
83
|
+
"pattern": "^[A-Z]{3}$",
|
|
84
|
+
"description": "ISO 4217 currency code."
|
|
85
|
+
},
|
|
86
|
+
"asset_class": {
|
|
87
|
+
"type": "string",
|
|
88
|
+
"description": "The class of the asset being transacted (e.g., 'equity', 'bond', 'crypto', 'cash')."
|
|
89
|
+
},
|
|
90
|
+
"source_account_id": {
|
|
91
|
+
"type": "string",
|
|
92
|
+
"description": "The ID of the account from which funds/assets are being moved."
|
|
93
|
+
},
|
|
94
|
+
"source_account_type": {
|
|
95
|
+
"type": "string",
|
|
96
|
+
"description": "The type of the source account (e.g., 'client_funds', 'trust_funds', 'proprietary')."
|
|
97
|
+
},
|
|
98
|
+
"destination_account_id": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"description": "The ID of the destination account."
|
|
101
|
+
},
|
|
102
|
+
"idempotency_key": {
|
|
103
|
+
"type": "string",
|
|
104
|
+
"description": "Idempotency key to prevent duplicate transactions."
|
|
105
|
+
},
|
|
106
|
+
"destination_account_type": {
|
|
107
|
+
"type": "string",
|
|
108
|
+
"description": "The type of the destination account (e.g., 'client_funds', 'proprietary'). Solves for Segregation of Funds."
|
|
109
|
+
},
|
|
110
|
+
"counterparty_id": {
|
|
111
|
+
"type": "string",
|
|
112
|
+
"description": "A unique identifier for the counterparty in a trade. Solves for Counterparty Exposure."
|
|
113
|
+
},
|
|
114
|
+
"mcp_servers": {
|
|
115
|
+
"type": "array",
|
|
116
|
+
"items": { "type": "string" },
|
|
117
|
+
"description": "MCP servers being used in this request (e.g., [\"https://mcp.plaid.com\"])"
|
|
118
|
+
},
|
|
119
|
+
"mcp_tools": {
|
|
120
|
+
"type": "array",
|
|
121
|
+
"items": { "type": "string" },
|
|
122
|
+
"description": "MCP tools being used in this request (e.g., [\"plaid.transactions.execute\"])"
|
|
123
|
+
},
|
|
124
|
+
"mcp_server": {
|
|
125
|
+
"type": "string",
|
|
126
|
+
"description": "Single MCP server being used (backward compatibility - use mcp_servers array for multiple)"
|
|
127
|
+
},
|
|
128
|
+
"mcp_tool": {
|
|
129
|
+
"type": "string",
|
|
130
|
+
"description": "Single MCP tool being used (backward compatibility - use mcp_tools array for multiple)"
|
|
131
|
+
},
|
|
132
|
+
"mcp_session": {
|
|
133
|
+
"type": "string",
|
|
134
|
+
"description": "MCP session identifier for audit trail (optional)"
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
"evaluation_rules": [
|
|
139
|
+
{
|
|
140
|
+
"name": "passport_status_active",
|
|
141
|
+
"condition": "passport.status == 'active'",
|
|
142
|
+
"deny_code": "oap.passport_suspended"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"name": "transaction_type_allowed",
|
|
146
|
+
"condition": "context.transaction_type in passport.limits.finance.transaction.allowed_transaction_types",
|
|
147
|
+
"deny_code": "oap.action_forbidden"
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"name": "asset_class_allowed",
|
|
151
|
+
"condition": "context.asset_class in passport.limits.finance.transaction.allowed_asset_classes",
|
|
152
|
+
"deny_code": "oap.asset_class_forbidden"
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"name": "exposure_limit_check",
|
|
156
|
+
"condition": "context.amount <= passport.limits.finance.transaction.max_exposure_per_tx_usd",
|
|
157
|
+
"deny_code": "oap.limit_exceeded"
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"name": "account_segregation_check",
|
|
161
|
+
"condition": "context.source_account_type not in passport.limits.finance.transaction.restricted_source_account_types",
|
|
162
|
+
"deny_code": "oap.account_type_restricted"
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"name": "account_type_check",
|
|
166
|
+
"condition": "context.source_account_type in passport.limits.finance.transaction.allowed_source_account_types",
|
|
167
|
+
"deny_code": "oap.account_type_restricted",
|
|
168
|
+
"description": "Restricts agent access to specific account classes (e.g., trust, retirement)."
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"name": "segregation_of_funds_check",
|
|
172
|
+
"condition": "not (context.source_account_type == 'client_funds' and context.destination_account_type == 'proprietary')",
|
|
173
|
+
"deny_code": "oap.commingling_of_funds_forbidden",
|
|
174
|
+
"description": "Enforces policies preventing the commingling of client and corporate funds."
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"name": "counterparty_exposure_check",
|
|
178
|
+
"condition": "daily_counterparty_total[context.counterparty_id] + context.amount <= passport.limits.finance.transaction.max_exposure_per_counterparty_usd",
|
|
179
|
+
"deny_code": "oap.counterparty_limit_exceeded",
|
|
180
|
+
"description": "Limits total daily exposure to any single counterparty."
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"name": "idempotency_check",
|
|
184
|
+
"condition": "context.idempotency_key not in recent_keys",
|
|
185
|
+
"deny_code": "oap.idempotency_conflict",
|
|
186
|
+
"description": "Idempotency key must be unique to prevent duplicate transactions."
|
|
187
|
+
}
|
|
188
|
+
]
|
|
189
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{"name":"allow_buy_equity_10k","context":{"transaction_type":"buy","amount":10000,"currency":"USD","asset_class":"equity","source_account_id":"acc_client_123","destination_account_id":"acc_trading_456","source_account_type":"client_funds","destination_account_type":"trading","counterparty_id":"cpty_broker_789","idempotency_key":"txn-buy-1001"}}
|
|
2
|
+
{"name":"deny_transaction_type_forbidden","context":{"transaction_type":"short_sell","amount":5000,"currency":"USD","asset_class":"equity","source_account_id":"acc_client_123","destination_account_id":"acc_trading_456","idempotency_key":"txn-short-1002"}}
|
|
3
|
+
{"name":"deny_asset_class_forbidden","context":{"transaction_type":"buy","amount":5000,"currency":"USD","asset_class":"commodity","source_account_id":"acc_client_123","destination_account_id":"acc_trading_456","idempotency_key":"txn-commodity-1003"}}
|
|
4
|
+
{"name":"deny_over_exposure_limit","context":{"transaction_type":"buy","amount":60000,"currency":"USD","asset_class":"equity","source_account_id":"acc_client_123","destination_account_id":"acc_trading_456","idempotency_key":"txn-over-limit-1004"}}
|
|
5
|
+
{"name":"deny_restricted_account_type","context":{"transaction_type":"buy","amount":5000,"currency":"USD","asset_class":"equity","source_account_id":"acc_trust_123","destination_account_id":"acc_trading_456","source_account_type":"trust_funds","idempotency_key":"txn-trust-1005"}}
|
|
6
|
+
{"name":"deny_account_type_not_allowed","context":{"transaction_type":"buy","amount":5000,"currency":"USD","asset_class":"equity","source_account_id":"acc_retirement_123","destination_account_id":"acc_trading_456","source_account_type":"retirement","idempotency_key":"txn-retirement-1006"}}
|
|
7
|
+
{"name":"deny_commingling_funds","context":{"transaction_type":"transfer","amount":5000,"currency":"USD","asset_class":"cash","source_account_id":"acc_client_123","destination_account_id":"acc_proprietary_456","source_account_type":"client_funds","destination_account_type":"proprietary","idempotency_key":"txn-commingling-1007"}}
|
|
8
|
+
{"name":"allow_sell_bond_5k","context":{"transaction_type":"sell","amount":5000,"currency":"USD","asset_class":"bond","source_account_id":"acc_trading_123","destination_account_id":"acc_client_456","source_account_type":"trading","destination_account_type":"client_funds","counterparty_id":"cpty_broker_789","idempotency_key":"txn-sell-1008"}}
|
|
9
|
+
{"name":"allow_transfer_crypto_2k","context":{"transaction_type":"transfer","amount":2000,"currency":"USD","asset_class":"crypto","source_account_id":"acc_trading_123","destination_account_id":"acc_cold_storage_456","source_account_type":"trading","destination_account_type":"custodial","counterparty_id":"cpty_exchange_789","idempotency_key":"txn-transfer-1009"}}
|
|
10
|
+
{"name":"deny_counterparty_exposure_exceeded","context":{"transaction_type":"buy","amount":250000,"currency":"USD","asset_class":"equity","source_account_id":"acc_client_123","destination_account_id":"acc_trading_456","counterparty_id":"cpty_broker_789","idempotency_key":"txn-counterparty-1010"}}
|
|
11
|
+
{"name":"deny_missing_required_fields","context":{"transaction_type":"buy","amount":5000,"currency":"USD","asset_class":"equity","idempotency_key":"txn-missing-1011"}}
|
|
12
|
+
{"name":"allow_cash_transfer_1k","context":{"transaction_type":"transfer","amount":1000,"currency":"USD","asset_class":"cash","source_account_id":"acc_client_123","destination_account_id":"acc_savings_456","source_account_type":"client_funds","destination_account_type":"client_funds","idempotency_key":"txn-cash-1012"}}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{"name":"allow_buy_equity_10k","expected":{"allow":true,"reasons":[{"code":"oap.allowed","message":"Transaction within limits and policy requirements","severity":"info"}]}}
|
|
2
|
+
{"name":"deny_transaction_type_forbidden","expected":{"allow":false,"reasons":[{"code":"oap.action_forbidden","message":"Transaction type short_sell is not allowed","severity":"error"}]}}
|
|
3
|
+
{"name":"deny_asset_class_forbidden","expected":{"allow":false,"reasons":[{"code":"oap.asset_class_forbidden","message":"Asset class commodity is not allowed","severity":"error"}]}}
|
|
4
|
+
{"name":"deny_over_exposure_limit","expected":{"allow":false,"reasons":[{"code":"oap.limit_exceeded","message":"Amount 60000 exceeds maximum exposure limit 50000","severity":"error"}]}}
|
|
5
|
+
{"name":"deny_restricted_account_type","expected":{"allow":false,"reasons":[{"code":"oap.account_type_restricted","message":"Source account type trust_funds is restricted","severity":"error"}]}}
|
|
6
|
+
{"name":"deny_account_type_not_allowed","expected":{"allow":false,"reasons":[{"code":"oap.account_type_restricted","message":"Source account type retirement is not allowed","severity":"error"}]}}
|
|
7
|
+
{"name":"deny_commingling_funds","expected":{"allow":false,"reasons":[{"code":"oap.commingling_of_funds_forbidden","message":"Cannot transfer from client funds to proprietary accounts","severity":"error"}]}}
|
|
8
|
+
{"name":"allow_sell_bond_5k","expected":{"allow":true,"reasons":[{"code":"oap.allowed","message":"Transaction within limits and policy requirements","severity":"info"}]}}
|
|
9
|
+
{"name":"allow_transfer_crypto_2k","expected":{"allow":true,"reasons":[{"code":"oap.allowed","message":"Transaction within limits and policy requirements","severity":"info"}]}}
|
|
10
|
+
{"name":"deny_counterparty_exposure_exceeded","expected":{"allow":false,"reasons":[{"code":"oap.counterparty_limit_exceeded","message":"Amount 250000 exceeds counterparty exposure limit 200000","severity":"error"}]}}
|
|
11
|
+
{"name":"deny_missing_required_fields","expected":{"allow":false,"reasons":[{"code":"oap.invalid_context","message":"Missing required fields: source_account_id, destination_account_id","severity":"error"}]}}
|
|
12
|
+
{"name":"allow_cash_transfer_1k","expected":{"allow":true,"reasons":[{"code":"oap.allowed","message":"Transaction within limits and policy requirements","severity":"info"}]}}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"passport_id": "ap_a2d10232c6534523812423eec8a1425c45678",
|
|
3
|
+
"kind": "instance",
|
|
4
|
+
"spec_version": "oap/1.0",
|
|
5
|
+
"owner_id": "org_demo_co",
|
|
6
|
+
"owner_type": "org",
|
|
7
|
+
"assurance_level": "L3",
|
|
8
|
+
"status": "active",
|
|
9
|
+
"capabilities": [
|
|
10
|
+
{
|
|
11
|
+
"id": "finance.transaction",
|
|
12
|
+
"params": {
|
|
13
|
+
"max_amount": 100000,
|
|
14
|
+
"currency": "USD"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"limits": {
|
|
19
|
+
"finance.transaction": {
|
|
20
|
+
"allowed_transaction_types": ["buy", "sell", "transfer"],
|
|
21
|
+
"allowed_asset_classes": ["equity", "bond", "crypto", "cash"],
|
|
22
|
+
"max_exposure_per_tx_usd": 50000,
|
|
23
|
+
"allowed_source_account_types": [
|
|
24
|
+
"client_funds",
|
|
25
|
+
"trading",
|
|
26
|
+
"proprietary"
|
|
27
|
+
],
|
|
28
|
+
"restricted_source_account_types": ["trust_funds", "retirement"],
|
|
29
|
+
"max_exposure_per_counterparty_usd": 200000,
|
|
30
|
+
"require_assurance_at_least": "L3",
|
|
31
|
+
"idempotency_required": true
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"regions": ["US", "CA", "EU"],
|
|
35
|
+
"metadata": {
|
|
36
|
+
"template_name": "Demo Transaction Agent",
|
|
37
|
+
"description": "Instance for financial transaction operations"
|
|
38
|
+
},
|
|
39
|
+
"created_at": "2025-01-30T00:00:00Z",
|
|
40
|
+
"updated_at": "2025-01-30T00:00:00Z",
|
|
41
|
+
"version": "1.0.0"
|
|
42
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"passport_id": "550e8400-e29b-41d4-a716-446655440001",
|
|
3
|
+
"kind": "template",
|
|
4
|
+
"spec_version": "oap/1.0",
|
|
5
|
+
"owner_id": "org_demo_co",
|
|
6
|
+
"owner_type": "org",
|
|
7
|
+
"assurance_level": "L3",
|
|
8
|
+
"status": "active",
|
|
9
|
+
"capabilities": [
|
|
10
|
+
{
|
|
11
|
+
"id": "finance.transaction",
|
|
12
|
+
"params": {
|
|
13
|
+
"max_amount": 100000,
|
|
14
|
+
"currency": "USD"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"limits": {
|
|
19
|
+
"finance.transaction": {
|
|
20
|
+
"allowed_transaction_types": ["buy", "sell", "transfer"],
|
|
21
|
+
"allowed_asset_classes": ["equity", "bond", "crypto", "cash"],
|
|
22
|
+
"max_exposure_per_tx_usd": 50000,
|
|
23
|
+
"allowed_source_account_types": [
|
|
24
|
+
"client_funds",
|
|
25
|
+
"trading",
|
|
26
|
+
"proprietary"
|
|
27
|
+
],
|
|
28
|
+
"restricted_source_account_types": ["trust_funds", "retirement"],
|
|
29
|
+
"max_exposure_per_counterparty_usd": 200000,
|
|
30
|
+
"require_assurance_at_least": "L3",
|
|
31
|
+
"idempotency_required": true
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"regions": ["US", "CA", "EU"],
|
|
35
|
+
"metadata": {
|
|
36
|
+
"template_name": "Demo Transaction Template",
|
|
37
|
+
"description": "Template for financial transaction operations"
|
|
38
|
+
},
|
|
39
|
+
"created_at": "2025-01-30T00:00:00Z",
|
|
40
|
+
"updated_at": "2025-01-30T00:00:00Z",
|
|
41
|
+
"version": "1.0.0"
|
|
42
|
+
}
|