# Clearing Entitlements (/docs/auth/entitlements)



RBAC answers "may this *caller* do this?"; **entitlements** answer a different
question — "is this *tenant* provisioned for this capability?" They run as a
separate plane alongside authorization, and a request must satisfy **both**.

<Callout title="Two gates, not one">
  A caller can be a tenant `admin` (passes RBAC) and still be refused a workflow
  publish because the *tenant* is not entitled to that workflow type. RBAC gates
  the identity; entitlements gate the tenant's plan. This page covers the auth
  behavior of the entitlement plane; for the plan/quota model see
  [Concepts → Entitlements](/docs/concepts/entitlements).
</Callout>

## What the plane gates [#what-the-plane-gates]

Entitlement checks are wired at specific enforcement points where a tenant's plan
should constrain behavior:

| Surface                 | Gated operations                                                             | Requirement prefix                                          |
| ----------------------- | ---------------------------------------------------------------------------- | ----------------------------------------------------------- |
| **Workflow lifecycle**  | Start, publish, and schedule-activation of a workflow definition             | `workflow.start` / `workflow.publish` / `workflow.schedule` |
| **Routing strategy**    | Selecting a routing/pipeline strategy                                        | `strategy.*` / `strategy.pipeline.*`                        |
| **Queue admission**     | Queue-admission governance operations                                        | `routing.admission.<operation>`                             |
| **Connector execution** | Provider use; action, trigger, resolver, proxy, sync, and function execution | `connector.action.*` / `connector.trigger.*` …              |

Each check builds a subject (e.g. `{Kind: "workflow.definition", Key: <type>}`) and
a requirement key, then evaluates it against the tenant's entitlement snapshot.

## Enforcement postures [#enforcement-postures]

The behavior when a tenant's required entitlement is **missing, stale, or the
evaluator is unavailable** is controlled by one setting:

```bash
export DUCTOR_ENTITLEMENT_ENFORCEMENT=report   # report | strict | off
```

| Mode                           | Behavior                                                                                                          | When to use                                                     |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| `report&#x60; &#x2A;(default)* | Evaluate and **admit**, but emit a loud grace-admit event for every request that *would* be denied under strict.  | Safe migration default — no outage, but not a silent fail-open. |
| `strict`                       | **Fail closed** on missing/stale/unavailable facts for the enforced surfaces. Explicit denies always deny.        | Production, once snapshots are populated.                       |
| `off`                          | Legacy fail-open (deny-list only): explicit denies still deny, everything else admits. Announced once at startup. | Escape hatch / legacy compatibility.                            |

Read the table with one rule in mind: the posture only decides what happens when
a fact is *missing, stale, or unavailable*. An explicit deny denies in every
mode, including `off`:

```mermaid
flowchart TD
  req([request]) --> f{fact present?}
  f -->|explicit deny| den([denied, all modes])
  f -->|explicit allow| adm([admitted])
  f -->|missing / stale| p{enforcement}
  p -->|report| grace([admitted + grace-admit event])
  p -->|off| adm
  p -->|strict| u{unprovisioned grace?}
  u -->|yes, never onboarded| grace
  u -->|no| den
```

<Callout title="`report` is a migration ramp, not a destination">
  Report mode lets you turn entitlements on and watch exactly which requests
  *would* be denied — via the grace-admit events — before flipping to `strict`.
  An invalid enforcement value is treated as a visible misconfiguration: it is
  logged and falls back to the safe `report` posture rather than crashing boot or
  silently fail-opening.
</Callout>

## Unprovisioned grace [#unprovisioned-grace]

`strict` mode alone would instantly deny tenants that have never been onboarded to
any entitlement source. The **unprovisioned grace** softens exactly that case:

```bash
export DUCTOR_ENTITLEMENT_GRACE_UNPROVISIONED=true   # default
```

When `true` (the default), a tenant that has **no entitlement source and no
snapshot at all** is admitted under a loud, observable bootstrap grace — so
enabling `strict` does not instantly lock out un-onboarded tenants. Genuine gaps
still fail closed: a **stale** snapshot, a **present-but-incomplete** snapshot, or a
tenant that **has sources but no snapshot** are all denied. Set it to `false` to
remove the grace entirely.

## Tenant-scoped evaluation [#tenant-scoped-evaluation]

Every entitlement check is evaluated in the tenant's context — there is no global
entitlement decision. A check that cannot resolve a tenant is refused rather than
admitted: the enforced surfaces require tenant-scoped evaluation, and a missing
tenant context yields `ErrEntitlementDenied`. Environment-qualified checks resolve
the environment first; an **archived** or **locked** environment cannot satisfy an
entitlement check.

## Startup posture logging [#startup-posture-logging]

On boot the entitlement service logs its resolved enforcement posture, so an
operator can confirm from the logs whether the deployment is running in `report`,
`strict`, or `off` — and whether unprovisioned grace is on.

<Callout title="Where the plan itself lives">
  Entitlements gate against a tenant's snapshot; how that snapshot maps to plans
  and quotas — including [hot-path quota enforcement](/docs/concepts/entitlements) —
  is covered in Concepts → Entitlements, while the Stripe billing integration that
  populates plan tiers is documented in
  [Billing & Usage → Plans](/docs/billing/plans).
</Callout>
