Skip to main content

combining algorithms

deny-overrides, allow-overrides, first-match, and highest-priority — pick the right strategy for each policy.

Pick an algorithm

Loading diagram...

AlgorithmDefault?Use case
deny-overridesyesRestriction policies
allow-overridesnoRBAC, permissive grants
first-matchnoFirewall-style ordered lists
highest-prioritynoEmergency overrides, tiered rules

deny-overrides

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

const p = policy('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()
const p = policy('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.

const p = policy('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()
const p = policy('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.

const p = policy('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()
const p = policy('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.

const p = policy('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()
const p = policy('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.