Skip to content

6. Providers

A provider answers resolution requests from one source of truth: the OpenEntitlement server over OEREP, embedded memory, or a Postgres schema. The SDK owns the API surface, the checks, hooks, errors, and locking. The provider owns facts and resolution.

There are two ways to write one:

  • Facts-based: implement a small fact source (assignment, plan, plan value, overrides, grants, plans) and reuse the SDK’s resolveFromFacts. The in-memory and Postgres reference providers work this way, and conformance mostly comes for free.
  • Resolving: implement resolve directly, for remote resolution (the generic OEREP RemoteProvider, for example).
interface Provider {
metadata: { name: string };
capabilities: Capabilities;
initialize?(): Promise<void>;
shutdown?(): Promise<void>;
resolve(privilege: PrivilegeDefinition, ctx: ResolvedContext): Promise<ResolutionDetails>;
suggestPlan?(privilege: PrivilegeDefinition, need: Need, ctx: ResolvedContext): Promise<string | undefined>;
mutations?: Mutations; // present when capabilities.mutations
}

A provider MUST expose metadata with a name.

A provider MUST implement resolve(privilegeDefinition, context), where the context’s now is already set, and MUST follow §3 for every fact it supports.

A provider MUST declare its capabilities, each of them true or false:

capability meaning
overrides the provider stores overrides and applies them
grants the provider stores grants and applies them
mutations the provider implements the mutation interface (§6.3)
planCatalog the provider can enumerate plans and implements suggestPlan
fixed the provider is a fixed provider (§6.4)
inspection OPTIONAL: the provider implements the read-only inspection interface (§6.6)

A provider without the overrides capability MUST NOT return reason OVERRIDE. A provider without the grants capability MUST NOT return reason GRANT.

A provider SHOULD NOT throw from resolve. The SDK MUST catch anything a provider throws and convert it into reason ERROR with code GENERAL (or PROVIDER_NOT_READY if the provider signalled it).

If the provider has an initialize function, the SDK MUST call it before the first evaluation, and evaluations MUST NOT reach resolve before it completes. If initialize fails, evaluations MUST return reason ERROR with code PROVIDER_NOT_READY.

On a plan change, a provider MUST NOT delete or modify overrides or grants, and MUST NOT delete, hide, or lock application data.

Overrides go stale (Requirement 1.5.4). Grants keep applying (Requirement 1.6.3).

Mutations are the administrative surface: the back office, support tooling, and billing webhooks.

interface Mutations {
upsertPlan(plan, meta): Promise<MutationResult>;
setPlanValue(planId, privilegeKey, value, meta): Promise<MutationResult>;
assignPlan(organizationId, planId | null, channel | undefined, meta): Promise<MutationResult>;
createOverride(input, meta): Promise<MutationResult>;
deleteOverride(overrideId, meta): Promise<MutationResult>;
createGrant(input, meta): Promise<MutationResult>;
revokeGrant(grantId, meta): Promise<MutationResult>;
}
type MutationMeta = { actor: string; reason: string };

A provider with the mutations capability MUST implement every operation above. Clients MUST NOT call them directly on the provider: they MUST go through the SDK’s admin interface, so that mutation hooks run (§7.2).

Every mutation MUST require a non-empty actor and a non-empty reason, and MUST fail with INVALID_ARGUMENT when either is missing.

Every successful mutation MUST return an audit event describing it.

A provider MUST fail with NOT_FOUND when a mutation references a plan, override, or grant that doesn’t exist, and with INVALID_VALUE when a value violates Requirement 1.5.6 or Requirement 1.6.2. A failed mutation MUST NOT change any state.

A provider SHOULD persist the audit event atomically with the mutation, so that no committed change can exist without its audit record.

Calling a mutation on the admin interface of a provider without the mutations capability MUST throw NotSupported.

Self-hosted and on-premise installs often have no billing system to ask. In that case everything is on and nothing can be overridden.

SDKs MUST ship a fixed provider. It resolves boolean privileges to true. It resolves integer privileges to UNLIMITED when unlimitedAllowed is set, and otherwise to an explicitly configured value. It resolves enum privileges to their last (most generous) option, unless configured otherwise. The reason MUST be FIXED.

Creating a fixed provider for a catalog that contains an integer privilege with unlimitedAllowed: false and no configured value MUST fail.

The fixed provider MUST declare fixed: true and every other capability false.

Adapters for third-party vendors MUST be distributed separately from the core SDK. The core SDK MUST NOT depend on any vendor SDK (packaging requirement).

An adapter MUST document how it maps the vendor’s concepts onto this model, including which capabilities it lacks.

Admin consoles, support tooling, and migrations need to list what a provider holds. Resolution never needs it. Inspection is an optional, read-only capability, added in v0.1 for the OpenEntitlement Console.

interface Inspection {
listPlans(): Promise<Array<Plan & { values: Record<string, Value> }>>;
listAssignments(): Promise<Assignment[]>;
listOverrides(organizationId: string): Promise<Override[]>; // including stale and expired ones
listGrants(organizationId: string): Promise<Grant[]>; // including revoked and expired ones
listAudit(filter?: { entity?: string; entityId?: string; limit?: number }): Promise<AuditEvent[]>; // most recent first
}

A provider that declares inspection MUST implement every operation above. Inspection MUST NOT change state.

listOverrides MUST return stale and expired overrides. listGrants MUST return revoked and expired grants. Filtering them is the caller’s job, so that history stays visible.

A provider that declares both inspection and mutations MUST return from listAudit every audit event its mutations produced, most recent first.

Consumers MUST NOT require inspection to evaluate. Every evaluation feature of a console or tool MUST keep working, through getDetails, on providers without it.