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 orprice_idstring)?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.
Summary
Functions
Returns true iff billable's resolved active feature set contains
feature. Fail-closed false otherwise.
Returns the seat/quota count for quota_key (min(cap, quantity) where a
cap exists, else the raw quantity). Fail-closed 0.
Returns the sorted, deduped list of features granted by billable's
active plans. Always a plain [atom], never a MapSet. Fail-closed [].
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.
Functions
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.
@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.
Returns the sorted, deduped list of features granted by billable's
active plans. Always a plain [atom], never a MapSet. Fail-closed [].
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.