> ## Documentation Index
> Fetch the complete documentation index at: https://jephalabs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Approval/Risk Policy Plane

> How QTG explains deny, manual review, refresh, and auto-approve decisions before funds move

# Approval/Risk Policy Plane

The approval/risk policy plane is QTG's decision model for answering one question:

> Can this movement execute automatically, should an operator review it, does it need fresher evidence, or must it be denied?

QTG already has many safety controls: address allowlists, signer/source checks, template auto-approve caps, Pro budget ledgers, bridge fee checks, executor guards, and Agent Wallet Top-Up envelopes. The policy plane turns those controls into a predictable trace instead of a pile of disconnected checks.

<Warning>
  The current implementation is the first tranche. It adds deterministic trace DTOs, preview evaluation, trusted EVM target classification, and persisted policy trace evidence on movement creation. Existing Free and Pro auto-approve hooks still decide whether a movement is actually auto-approved.
</Warning>

## Why this exists

Automation is useful only when the user can predict it. "This destination is trusted" is not enough. A safe automatic approval needs all of these to be true:

* the movement intent was normalized into canonical facts
* hard safety passed
* the target is allowed
* the target is trusted for auto-approval, if auto-approval is desired
* budget and limit checks can reserve capacity
* cost guards do not require review
* dispatch-time executor guards will still enforce the receiver and signer constraints

The policy plane provides a common language for these checks.

## Layered Evaluation Trace

QTG uses a fixed Layered Evaluation Trace as the top-level spine. Each layer can add evidence, but downstream policy cannot undo an upstream hard deny.

```mermaid theme={null}
flowchart TD
    NORMALIZE["1. Normalize intent"]
    HARD["2. Hard safety gates"]
    ALLOW["3. Target allowlist"]
    TRUST["4. Target trust"]
    ELIG["5. Auto-approval eligibility"]
    BUDGET["6. Budgets and limits"]
    COST["7. Cost guards"]
    REDUCE["8. Deterministic reducer"]

    NORMALIZE --> HARD --> ALLOW --> TRUST --> ELIG --> BUDGET --> COST --> REDUCE

    HARD -. "fail or structural unknown" .-> DENY["deny"]
    BUDGET -. "no reservation capacity" .-> MANUAL["manual_required"]
    COST -. "stale quote" .-> REFRESH["needs_refresh"]
    TRUST -. "trusted match" .-> AUTO["auto_eligible"]

    style HARD fill:#991b1b,stroke:#7f1d1d,color:#fff
    style REDUCE fill:#1d4ed8,stroke:#1e40af,color:#fff
```

This is deliberately not a generic top-level rule engine. A generic engine with global priority can make behavior surprising, especially when product policy grows. QTG keeps safety as deterministic code and allows rule-like flexibility only inside bounded leaf layers such as eligibility and cost guards.

## Trace entry shape

Every policy layer returns trace entries. The reducer decides from those entries alone.

| Field            | Meaning                                                                        |
| ---------------- | ------------------------------------------------------------------------------ |
| `layer`          | Semantic layer, such as `hard_safety`, `target_allow`, or `target_trust`       |
| `rule_key`       | Stable key for the specific check                                              |
| `outcome`        | `pass`, `fail`, `unknown`, or `not_applicable`                                 |
| `data_class`     | `structural` or `refreshable`                                                  |
| `severity`       | `hard_deny`, `refresh_required`, `manual_fallback`, `auto_eligible`, or `info` |
| `machine_reason` | Stable code for API, audit, tests, and dashboards                              |
| `operator_label` | Human-readable explanation                                                     |
| `evidence`       | Decision inputs that explain this entry                                        |

Example trusted-target evidence:

```json theme={null}
{
  "layer": "target_trust",
  "rule_key": "target_trust.registry",
  "outcome": "pass",
  "data_class": "structural",
  "severity": "auto_eligible",
  "machine_reason": "trusted_target_match",
  "operator_label": "Destination and token matched a trusted policy target",
  "evidence": {
    "chain_id": 84532,
    "destination_address": "0x...",
    "token_contract_or_native_id": "0x..."
  }
}
```

## Reducer outcomes

The reducer returns one of four preview outcomes:

| Outcome           | Meaning                                                        |
| ----------------- | -------------------------------------------------------------- |
| `deny`            | A hard safety failure or structural unknown prevents execution |
| `needs_refresh`   | Required refreshable evidence is stale or missing              |
| `manual_required` | Execution may be possible, but auto-approval is not justified  |
| `auto_eligible`   | The trace has an explicit auto-eligible path                   |

The current movement creation path persists this trace on the movement's auto-approve result as a `policy_trace`. That trace is evidence. It does not replace the existing approval hooks yet.

## Unknown data rule

Unknown data is split into two classes:

| Data class    | Examples                                                                                                             | Reducer behavior |
| ------------- | -------------------------------------------------------------------------------------------------------------------- | ---------------- |
| `structural`  | allowlist lookup, destination address, receiver equality, signer/source compatibility, executor coverage             | `deny`           |
| `refreshable` | balance freshness, exchange remaining limit, route availability, fee quote, slippage quote, venue maintenance status | `needs_refresh`  |

This distinction matters because not all unknowns are equal. If QTG cannot prove where funds are going, it must deny. If QTG only needs a fresher fee quote, the right outcome is to refresh evidence and evaluate again.

## Allowed vs trusted targets

Targets use a two-tier model:

| Tier           | Meaning                                                            | Typical result                                                  |
| -------------- | ------------------------------------------------------------------ | --------------------------------------------------------------- |
| Allowed target | The destination may receive funds if all hard safety checks pass   | Manual review may still be required                             |
| Trusted target | The allowed destination is also eligible for bounded auto-approval | Can become `auto_eligible` under caps, budgets, and cost guards |

Trusted targets are always a subset of allowed targets. A trusted row must never bypass the allowlist.

For EVM targets, the canonical key is:

* `chain_family`
* `chain_id`
* `destination_address`
* `token_contract_or_native_id`
* `target_level`
* `is_active`

Display symbols such as `USDC` are not policy keys. They are labels only.

## EVM target policy in the current tranche

The first tranche evaluates EVM target policy for action types that actually sign or dispatch to EVM receivers, including CCTP, CCIP, Stargate, Gateway, and native/ERC-20 transfers.

For non-EVM or CEX-only movements, EVM target policy is `not_applicable`. That avoids false deny traces for movements where an EVM destination is not part of the route.

## Good policy shape

Good policies compose layers in a way users can predict:

| Scenario                                                                                                    | Expected behavior                                         |
| ----------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
| Destination is allowlisted but not trusted                                                                  | Movement can proceed only through manual approval         |
| Destination is allowlisted and trusted, amount is within cap, budget capacity exists, and cost guard passes | Movement can be auto-eligible                             |
| Destination is trusted but not allowlisted                                                                  | Deny, because trust cannot override allowlist failure     |
| Destination is trusted but token key is missing                                                             | No trusted-target auto eligibility                        |
| Fee quote is missing                                                                                        | `needs_refresh` when the cost guard depends on that quote |
| Budget capacity is exhausted                                                                                | Manual review or deny, depending on budget policy         |

## Bad policy shape

These rules are intentionally rejected:

* "trusted destination always approves"
* "VIP strategy can skip address allowlist"
* "USDC symbol means safe"
* "low fee overrides unknown receiver validation"
* "priority 100 rule overrides a hard deny"

Those policies make safety depend on rule ordering or display metadata. QTG's safety model depends on resolved movement facts.

## Relationship to dispatch safety

The policy plane is an approval-time explanation layer. Dispatch-time executor guards remain mandatory. If an address is removed from the allowlist after a movement is created, dispatch must still fail before funds move.

For the operator-facing workflow, see [Approval/Risk Policy Guide](/guide/approval-risk-policy). For the older auto-approve hook reference, see [Auto-Approve](/reference/auto-approve).
