> ## Documentation Index
> Fetch the complete documentation index at: https://docs.brane.membranelabs.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Refund Limit Policy

> Enforce a per-tenant refund limit with a before_capability policy.

Allow small refunds automatically. Block large refunds until approval is implemented.

## Problem

A support agent can issue refunds to customers. You want to:

* Allow refunds up to a tenant-specific limit automatically
* Block refunds above that limit until human approval exists
* Never allow refunds in non-production environments

## Complete Example

```python theme={null}
from brane import CapabilityDeniedError, Decision, Effect, Runtime

TENANT_LIMITS = {
    "tenant_acme": 100.0,
    "tenant_globex": 250.0,
    "tenant_initech": 50.0,
}

DEFAULT_LIMIT = 100.0

runtime = Runtime(
    agent_id="support-agent",
    environment="prod",
    tenant_id="tenant_acme",
)

@runtime.capability(
    name="refund_customer",
    type="tool",
    risk="high",
    effect=Effect(
        type="financial",
        reversible=False,
        external=True,
        description="Issues a payment refund",
    ),
    data_namespace="billing.refunds",
    owner="payments-team",
)
def refund_customer(customer_id: str, amount_usd: float, reason: str = ""):
    return {"refunded": True, "customer_id": customer_id, "amount": amount_usd}

@runtime.before_capability(
    "refund_customer",
    name="refund_require_prod",
    version="1.0",
    priority=200,
)
def require_prod_for_refunds(ctx):
    if not ctx.is_prod:
        return Decision(
            type="deny",
            reason="Refunds can only be issued in the production environment",
        )
    return Decision(type="allow")

@runtime.before_capability(
    "refund_customer",
    name="refund_amount_limit",
    version="1.0",
    priority=100,
)
def refund_amount_limit(ctx):
    amount = ctx.arg("amount_usd", 0)
    limit = TENANT_LIMITS.get(ctx.tenant_id, DEFAULT_LIMIT)

    if amount <= 0:
        return Decision(type="deny", reason="Refund amount must be positive")

    if amount > limit:
        return Decision(
            type="deny",
            reason=(
                f"Refund of ${amount:.2f} exceeds the ${limit:.2f} limit "
                f"for tenant {ctx.tenant_id}. Human approval is required."
            ),
        )

    return Decision(type="allow")

try:
    result = refund_customer("cust_123", 75.00, reason="Duplicate charge")
    print(result)

    refund_customer("cust_456", 349.00, reason="Defective product")
except CapabilityDeniedError as e:
    print(f"Blocked: {e.reason}")
    print(f"Action ID: {e.action_id}")
```

## How It Works

Two policies protect the `refund_customer` capability, running in priority order:

1. `require_prod_for_refunds` runs first and blocks the refund if the environment is not prod.
2. `refund_amount_limit` checks the refund amount against the tenant-specific limit.

Both policies must allow for the refund to proceed. Either can deny.

## Adding a Third Policy: Scope Check

```python theme={null}
@runtime.before_capability(
    "refund_customer",
    name="refund_scope_check",
    version="1.0",
    priority=150,
)
def require_refund_scope(ctx):
    if not ctx.agent_has_scope("refunds:create"):
        return Decision(
            type="deny",
            reason="Agent requires refunds:create scope to issue refunds",
        )
    return Decision(type="allow")
```

## Future: Approval Instead of Deny

Once `approval_required` decisions are implemented, the limit policy can pause rather than block:

```python theme={null}
if amount > limit:
    return Decision(
        type="approval_required",
        reason=f"Refund of ${amount:.2f} requires human approval",
        approval={
            "approver_group": "finance-ops",
            "expiration_minutes": 60,
            "context": {
                "customer_id": ctx.arg("customer_id"),
                "amount": amount,
                "tenant": ctx.tenant_id,
            },
        },
    )
```

The runtime will pause the action, send the approval request, and resume when a human approves or deny automatically when they reject or the request expires.

## Production Notes

* The policy runs before the refund API call. If the policy denies, no refund is issued.
* Store tenant limits in your configuration or database, not hardcoded.
* Use `action_id` from `CapabilityDeniedError` to correlate the denial with your request logs.
* When audit sinks are available, every allowed and denied refund attempt will produce an audit event.

## Related

* [Before Capability Policies](/policy/before-capability)
* [Decision Types](/concepts/decision)
* [SQL Read-Only Recipe](/recipes/sql-read-only)
