## Pick an algorithm

| Algorithm | Default? | Use case |
| --- | --- | --- |
| `deny-overrides` | yes | Restriction policies |
| `allow-overrides` | no | RBAC, permissive grants |
| `first-match` | no | Firewall-style ordered lists |
| `highest-priority` | no | Emergency overrides, tiered rules |

***

## deny-overrides

The default and most conservative algorithm. **Any deny beats any allow.**

```typescript
const p = definePolicy('strict')
  .algorithm('deny-overrides')
  .rule('allow-read', (r) => r.allow().on('read').of('post'))
  .rule('deny-drafts', (r) =>
    r
      .deny()
      .on('read')
      .of('post')
      .when((w) => w.resourceAttr('status', 'eq', 'draft')),
  )
  .build()
```

Evaluation logic:

1. Find all matching rules
2. If any has `effect: 'deny'`, the policy result is **deny**
3. Else if any has `effect: 'allow'`, the policy result is **allow**
4. Else fall back to `defaultEffect`

Use this for restriction policies - a single deny blocks access regardless of how many allow rules match.

***

## allow-overrides

The inverse: **any allow beats any deny.** Used by the auto-generated RBAC policy.

```typescript
const p = definePolicy('permissive')
  .algorithm('allow-overrides')
  .rule('deny-default', (r) => r.deny().on('*').of('*'))
  .rule('vip-access', (r) =>
    r
      .allow()
      .on('*')
      .of('premium-content')
      .when((w) => w.attr('tier', 'in', ['pro', 'enterprise'])),
  )
  .build()
```

Evaluation logic:

1. Find all matching rules
2. If any has `effect: 'allow'`, the policy result is **allow**
3. Else if any has `effect: 'deny'`, the policy result is **deny**
4. Else fall back to `defaultEffect`

Use this for deny-by-default policies where specific allow rules grant access.

***

## first-match

The first matching rule wins. **Order matters.**

```typescript
const p = definePolicy('firewall')
  .algorithm('first-match')
  .rule('block-bad-ip', (r) =>
    r
      .deny()
      .on('*')
      .of('*')
      .when((w) => w.env('ip', 'in', ['10.0.0.99', '10.0.0.100'])),
  )
  .rule('allow-internal', (r) =>
    r
      .allow()
      .on('*')
      .of('*')
      .when((w) => w.env('ip', 'starts_with', '10.')),
  )
  .rule('deny-external', (r) => r.deny().on('*').of('*'))
  .build()
```

Evaluation logic:

1. Walk rules in order
2. Collect all matching rules
3. The first matching rule's effect is the policy result
4. If none match, fall back to `defaultEffect`

Use this for firewall-style ordered rule lists.

***

## highest-priority

The matching rule with the highest `priority` wins.

```typescript
const p = definePolicy('priority')
  .algorithm('highest-priority')
  .rule('normal-allow', (r) => r.allow().on('read').of('post').priority(10))
  .rule('elevated-deny', (r) =>
    r
      .deny()
      .on('read')
      .of('post')
      .when((w) => w.resourceAttr('classification', 'eq', 'top-secret'))
      .priority(50),
  )
  .rule('emergency-override', (r) =>
    r
      .allow()
      .on('*')
      .of('*')
      .when((w) => w.role('super-admin'))
      .priority(100),
  )
  .build()
```

Evaluation logic:

1. Find all matching rules
2. Sort by `priority` descending
3. The highest-priority rule's effect is the policy result
4. If tied, the first encountered among tied rules wins
5. If none match, fall back to `defaultEffect`

Use this when rules have clear priority tiers and definition order shouldn't matter.

***

## Cross-policy AND

Across policies, the engine **AND-combines** results. A deny from any policy is final regardless of what each policy's combining algorithm chose.

```
Policy A (allow-overrides) -> ALLOW
Policy B (deny-overrides)  -> DENY
Policy C (first-match)     -> ALLOW

Final result: DENY (Policy B's deny is final)
```

This is fixed engine-level behavior - you can't change it with a per-policy algorithm. To make a policy purely advisory, scope it tightly with [targets](/duck-iam/core/policies/targets).