# Plans, Entitlements & Clearing Limits (/docs/billing/plans)



A "plan" in Ductor is **not** a separate subsystem. It is an entitlement item.
The whole concept model — sources feeding snapshots feeding decisions, and the
capability projection those decisions drive — belongs to
[Concepts → Entitlements](/docs/concepts/entitlements) and
[Auth → Entitlements](/docs/auth/entitlements). This page covers only what
*plans and billing add* on top of that model.

## A plan tier is an entitlement item [#a-plan-tier-is-an-entitlement-item]

Plan tier is expressed inside the entitlement snapshot as a `plan_tier` item
(`ItemPlanTier`). Billing and subscription state maps into entitlement
**sources** of kind `external_billing` — a source grants a plan-tier item plus
the feature and limit items that tier unlocks.

```mermaid
flowchart LR
  Provider[External billing] --> Source[external_billing source]
  Source --> PlanItem[plan_tier item]
  Source --> Features[feature and limit items]
  PlanItem --> Snapshot[Entitlement snapshot]
  Features --> Snapshot
  Snapshot --> Decision[Evaluation decision]
```

### Source and item shapes [#source-and-item-shapes]

Sources and items are defined in the entitlement domain. Each source carries a
`priority` and a `status`; items carry a `kind` and an `effect`.

| Source kind          | Meaning                                                |
| -------------------- | ------------------------------------------------------ |
| `external_billing`   | Subscription/billing state (usually from the provider) |
| `manual_contract`    | A negotiated contract grant                            |
| `operator_override`  | An operator-applied override                           |
| `deployment_default` | Deployment-wide baseline                               |
| `system`             | System-level grant                                     |

* **Source status**: `active` | `stale` | `revoked`.
* **Item kinds**: `feature`, `limit`, `capability`, `plan_tier`, `contract`.
* **Item effects**: `allow` | `deny`. An explicit `deny` always wins.

## Plan-tier gating [#plan-tier-gating]

Plan-tier gating uses the requirement operator `plan_tier_at_least`
(`OpPlanTierAtLeast`). It compares the snapshot's `plan_tier` item against a
required tier using a fixed rank ladder — a request passes when the tenant's
rank is **greater than or equal to** the required rank.

| Tier value                   | Rank |
| ---------------------------- | ---- |
| `enterprise`                 | 4    |
| `growth`, `growth-v2`, `pro` | 3    |
| `starter`, `starter-v2`      | 2    |
| `free`                       | 1    |
| anything else / unknown      | 0    |

An unrecognized or missing tier ranks `0`, so it satisfies no `plan_tier_at_least`
requirement above the floor.

## Enforcement postures [#enforcement-postures]

Enforcement is the billing-adjacent posture knob: it decides what happens when a
tenant's required entitlement snapshot is **missing, stale, or the evaluator is
unavailable**. These postures are covered in depth on
[Auth → Entitlements](/docs/auth/entitlements) — summarized here.

| Mode          | Behavior                                                                                              |
| ------------- | ----------------------------------------------------------------------------------------------------- |
| `passthrough` | Zero value; honors the per-request fail-closed flag exactly as the caller set it                      |
| `off`         | Legacy fail-open for missing/stale/unavailable facts; explicit denies still deny                      |
| `report`      | **Default** — admit, but emit a loud grace-admit event for every request that would deny under strict |
| `strict`      | Fail closed on missing/stale/unavailable facts                                                        |

The mode is set via the `entitlement.enforcement` config key
(`DUCTOR_ENTITLEMENT_ENFORCEMENT`), parsed by `ParseEnforcementMode`. In
`strict` mode the `entitlement.grace_unprovisioned` flag
(`EntitlementConfig.EntitlementGraceUnprovisioned`, default `true`) admits a
tenant that has **never** had any source or snapshot under a bounded, observable
bootstrap grace, so flipping strict on does not instantly deny un-onboarded
tenants. The policy is wired at startup in `cmd/ductor/fx_entitlement.go`.

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

## Tenant plan lifecycle [#tenant-plan-lifecycle]

There is **no** Ductor-native plan-assignment API. Plan and subscription state
flows one direction: provider → `external_billing` source → snapshot.

```mermaid
sequenceDiagram
  participant Provider as External billing
  participant Source as external_billing source
  participant Snapshot as Entitlement snapshot
  participant Gate as plan_tier_at_least
  Provider->>Source: subscription state
  Source->>Snapshot: plan_tier + feature/limit items
  Gate->>Snapshot: read plan_tier
  Snapshot-->>Gate: tier rank
```

Cancellation is observed via the provider webhook `customer.subscription.deleted`
(detail on [Provider integration](/docs/billing/stripe)). Separately, the reason
code `tenant_unprovisioned` distinguishes a tenant that was never onboarded from
one that was actively denied — the former is a grace admit, not a denial.

## API surface [#api-surface]

The billing summary is `GET /api/v2/billing/summary`
(`BillingService.GetBillingSummary`), which requires `tenant:read`
authorization. The `EntitlementService` endpoints that read and mutate sources
and snapshots are owned by the entitlements docs — see
[Auth → Entitlements](/docs/auth/entitlements).

## Related [#related]

<Cards>
  <Card title="Provider integration" href="/docs/billing/stripe">
    How subscription state populates the `external_billing` source, and the
    cancellation webhook.
  </Card>

  <Card title="Usage metering" href="/docs/billing/usage-metering">
    Limit enforcement at connector admission.
  </Card>

  <Card title="Concepts → Entitlements" href="/docs/concepts/entitlements">
    The plan/quota model these tiers plug into.
  </Card>

  <Card title="Auth → Entitlements" href="/docs/auth/entitlements">
    Source/snapshot/decision mechanics and enforcement postures in depth.
  </Card>
</Cards>
