# `Accrue.Entitlements`
[🔗](https://github.com/szTheory/accrue/blob/accrue-v1.5.1/lib/accrue/entitlements.ex#L1)

Public, fail-closed entitlement gate API.

Four boolean/scalar functions answer "what has this billable paid for?"
from **local subscription state only** (via the configured
`Accrue.Entitlements.Resolver`, default
`Accrue.Entitlements.Resolver.LocalMap`):

  * `entitled?/2` — does the billable have a given feature?
  * `has_active_plan?/2` — does the billable hold a given plan (by atom or
    `price_id` string)?
  * `features_for/1` — the sorted, deduped list of granted features.
  * `entitlement_quantity/2` — the seat/quota count for a quota key.

## Fail-closed contract

Every function fails closed: `nil`/non-billable/no-customer/no-active-sub/
unmapped/raising-resolver all collapse to `false` / `[]` / `0`. `{:ok,
true}` (a present affirmative match) is the SOLE path to `true`. Errors,
exceptions, throws, and exits are caught and collapse to the fail-closed
value — a billing/availability hiccup never grants a paid feature for free.

## Multi-active-plan

`has_active_plan?/2` tests membership in the resolved `active_plans` SET
(ALL active plan atoms), never the representative `:plan` — so a billable
holding two active subscriptions on two different mapped plans answers
`true` for BOTH, consistent with the UNION semantics of `entitled?/2` and
`features_for/1`.

## Telemetry (per-check, NOT the audit ledger)

Each check emits `[:accrue, :entitlements, :check, :start | :stop |
:exception]` via `Accrue.Telemetry.span/3` with metadata
`%{feature, result, resolver, reason, surface, subject_type, subject_id}`.
`subject_id` is the internal customer/billable id only — never email/name
or any PII. Per-check decisions are **telemetry only**; this module NEVER
writes to the `accrue_events` audit ledger.

`surface: :plug | :live` is an additive, guard-supplied metadata dimension
(D-18): `entitled?/3` and `has_active_plan?/3` accept an optional
`opts` keyword list whose `:surface` is merged onto the same `:check`
span, so a deny/allow from the Plug guard is distinguishable from one from
the LiveView `on_mount` guard. It defaults to `nil` for direct (non-guard)
callers and is internal telemetry only — the public `Accrue.entitled?/2` /
`Accrue.has_active_plan?/2` facade delegates stay arity 2.

# `apple_family_sharing`

```elixir
@spec apple_family_sharing() :: {:ok, Accrue.Entitlements.Apple.Intake.Outcome.t()}
```

Returns an explicit policy deferral for Apple Family Sharing.

# `apple_management`

```elixir
@spec apple_management() :: {:ok, Accrue.Entitlements.Source.Outcome.t()}
```

Returns the exact, externally-managed Apple subscription guidance.

# `apple_offer_authoring`

```elixir
@spec apple_offer_authoring() :: {:ok, Accrue.Entitlements.Apple.Intake.Outcome.t()}
```

Returns an explicit policy deferral for Apple offer authoring.

# `apple_purchase_context`

Returns the bounded Apple purchase context for an authenticated entitlement account.

# `entitled?`

```elixir
@spec entitled?(term(), atom(), keyword()) :: boolean()
```

Returns `true` iff `billable`'s resolved active feature set contains
`feature`. Fail-closed `false` otherwise.

`opts` is an additive, internal keyword list; `surface: :plug | :live`
(guard-supplied, D-18) is merged onto the `:check` span metadata. All
existing 2-arity callers (incl. the `Accrue.entitled?/2` facade delegate)
are unaffected.

# `entitlement_quantity`

```elixir
@spec entitlement_quantity(term(), atom()) :: non_neg_integer()
```

Returns the seat/quota count for `quota_key` (`min(cap, quantity)` where a
cap exists, else the raw quantity). Fail-closed `0`.

# `features_for`

```elixir
@spec features_for(term()) :: [atom()]
```

Returns the sorted, deduped list of features granted by `billable`'s
active plans. Always a plain `[atom]`, never a `MapSet`. Fail-closed `[]`.

# `has_active_plan?`

```elixir
@spec has_active_plan?(term(), atom() | String.t(), keyword()) :: boolean()
```

Returns `true` iff `billable` holds `plan` among its active plans. `plan`
is a plan atom or a `price_id` string (reverse-indexed to its plan atom).
Tests membership in the SET of ALL active plans — multi-active-plan
correct. Fail-closed `false` otherwise.

`opts` is an additive, internal keyword list; `surface: :plug | :live`
(guard-supplied, D-18) is merged onto the `:check` span metadata. All
existing 2-arity callers (incl. the `Accrue.has_active_plan?/2` facade
delegate) are unaffected.

# `observe_apple_evidence`

Observes opaque signed Apple evidence through host-configured verification.

# `override_purchase_decision`

```elixir
@spec override_purchase_decision(
  Accrue.Entitlements.PurchaseDecision.t(),
  String.t(),
  term(),
  keyword()
) :: Accrue.Entitlements.PurchaseDecision.t()
```

Explicitly converts a still-current block to a bounded warning decision.

# `provision_account`

```elixir
@spec provision_account(String.t(), String.t(), keyword()) ::
  {:ok, Accrue.Entitlements.Account.t()} | {:error, term()}
```

Explicitly provisions the stable account for an authenticated host owner.

# `purchase_decision`

```elixir
@spec purchase_decision(
  Accrue.Entitlements.Account.t() | String.t(),
  atom(),
  String.t(),
  keyword()
) ::
  Accrue.Entitlements.PurchaseDecision.t()
  | {:error, :unauthorized_billable_reference | :account_fetch_failed}
```

Returns a typed, revision-bound purchase preflight decision.

# `reconcile_apple_lineage`

```elixir
@spec reconcile_apple_lineage(Accrue.Entitlements.Account.t(), binary(), keyword()) ::
  {:ok, Accrue.Entitlements.Apple.Intake.Outcome.t()}
  | {:error, :unauthorized | :reconciliation_unavailable}
```

Queues bounded Apple lineage reconciliation without exposing provider history state.

# `repair_apple_lineage`

```elixir
@spec repair_apple_lineage(Accrue.Entitlements.Account.t(), binary(), keyword()) ::
  {:ok, Accrue.Entitlements.Apple.Intake.Outcome.t()}
  | {:error, :unauthorized | :verification_failed}
```

Repairs an authorized, currently unbound Apple lineage without exposing ownership details.

# `snapshot`

```elixir
@spec snapshot(
  Accrue.Entitlements.Account.t() | term(),
  keyword()
) :: {:ok, Accrue.Entitlements.Snapshot.t()} | {:error, :not_found}
```

Reads a canonical entitlement snapshot without provisioning or provider I/O.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
