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