6. Providers
Overview
Section titled “Overview”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
resolvedirectly, for remote resolution (the generic OEREPRemoteProvider, 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}6.1. Interface
Section titled “6.1. Interface”Requirement 6.1.1
Section titled “Requirement 6.1.1”A provider MUST expose
metadatawith aname.
Requirement 6.1.2
Section titled “Requirement 6.1.2”A provider MUST implement
resolve(privilegeDefinition, context), where the context’snowis already set, and MUST follow §3 for every fact it supports.
Requirement 6.1.3
Section titled “Requirement 6.1.3”A provider MUST declare its capabilities, each of them
trueorfalse:
capability meaning overridesthe provider stores overrides and applies them grantsthe provider stores grants and applies them mutationsthe provider implements the mutation interface (§6.3) planCatalogthe provider can enumerate plans and implements suggestPlanfixedthe provider is a fixed provider (§6.4) inspectionOPTIONAL: the provider implements the read-only inspection interface (§6.6)
Requirement 6.1.4
Section titled “Requirement 6.1.4”A provider without the
overridescapability MUST NOT return reasonOVERRIDE. A provider without thegrantscapability MUST NOT return reasonGRANT.
Requirement 6.1.5
Section titled “Requirement 6.1.5”A provider SHOULD NOT throw from
resolve. The SDK MUST catch anything a provider throws and convert it into reasonERRORwith codeGENERAL(orPROVIDER_NOT_READYif the provider signalled it).
Requirement 6.1.6
Section titled “Requirement 6.1.6”If the provider has an
initializefunction, the SDK MUST call it before the first evaluation, and evaluations MUST NOT reachresolvebefore it completes. Ifinitializefails, evaluations MUST return reasonERRORwith codePROVIDER_NOT_READY.
6.2. Plan changes
Section titled “6.2. Plan changes”Requirement 6.2.1
Section titled “Requirement 6.2.1”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).
6.3. Mutations
Section titled “6.3. Mutations”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 };Requirement 6.3.1
Section titled “Requirement 6.3.1”A provider with the
mutationscapability 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).
Requirement 6.3.2
Section titled “Requirement 6.3.2”Every mutation MUST require a non-empty
actorand a non-emptyreason, and MUST fail withINVALID_ARGUMENTwhen either is missing.
Requirement 6.3.3
Section titled “Requirement 6.3.3”Every successful mutation MUST return an audit event describing it.
Requirement 6.3.4
Section titled “Requirement 6.3.4”A provider MUST fail with
NOT_FOUNDwhen a mutation references a plan, override, or grant that doesn’t exist, and withINVALID_VALUEwhen a value violates Requirement 1.5.6 or Requirement 1.6.2. A failed mutation MUST NOT change any state.
Requirement 6.3.5
Section titled “Requirement 6.3.5”A provider SHOULD persist the audit event atomically with the mutation, so that no committed change can exist without its audit record.
Requirement 6.3.6
Section titled “Requirement 6.3.6”Calling a mutation on the admin interface of a provider without the
mutationscapability MUST throwNotSupported.
6.4. The fixed provider
Section titled “6.4. The fixed provider”Self-hosted and on-premise installs often have no billing system to ask. In that case everything is on and nothing can be overridden.
Requirement 6.4.1
Section titled “Requirement 6.4.1”SDKs MUST ship a fixed provider. It resolves
booleanprivileges totrue. It resolvesintegerprivileges toUNLIMITEDwhenunlimitedAllowedis set, and otherwise to an explicitly configured value. It resolvesenumprivileges to their last (most generous) option, unless configured otherwise. The reason MUST beFIXED.
Requirement 6.4.2
Section titled “Requirement 6.4.2”Creating a fixed provider for a catalog that contains an
integerprivilege withunlimitedAllowed: falseand no configured value MUST fail.
Requirement 6.4.3
Section titled “Requirement 6.4.3”The fixed provider MUST declare
fixed: trueand every other capabilityfalse.
6.5. Adapters
Section titled “6.5. Adapters”Requirement 6.5.1
Section titled “Requirement 6.5.1”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).
Requirement 6.5.2
Section titled “Requirement 6.5.2”An adapter MUST document how it maps the vendor’s concepts onto this model, including which capabilities it lacks.
6.6. Inspection
Section titled “6.6. Inspection”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}Requirement 6.6.1
Section titled “Requirement 6.6.1”A provider that declares
inspectionMUST implement every operation above. Inspection MUST NOT change state.
Requirement 6.6.2
Section titled “Requirement 6.6.2”
listOverridesMUST return stale and expired overrides.listGrantsMUST return revoked and expired grants. Filtering them is the caller’s job, so that history stays visible.
Requirement 6.6.3
Section titled “Requirement 6.6.3”A provider that declares both
inspectionandmutationsMUST return fromlistAuditevery audit event its mutations produced, most recent first.
Requirement 6.6.4
Section titled “Requirement 6.6.4”Consumers MUST NOT require
inspectionto evaluate. Every evaluation feature of a console or tool MUST keep working, throughgetDetails, on providers without it.