Skip to main content
Wildo.ai Coming soon

Plans, entitlements and usage

Make access decisions inspectable

Keep grants, allowances and manual exceptions in explicit records for each owner.

Separate application, workspace and personal records each hold grants, allowances and exceptions.

Make access decisions inspectable

Entitlement state is stored as a resource, not hidden only in a billing provider or a browser flag. The application, an organisation and an individual each have a defined place for their granted features, allowances and manual exceptions.

That gives administration, billing synchronisation and feature resolution a common record to work from, while preserving who is allowed to change it.

Example — Explain why a feature is available

An operator sees reporting in the plan-derived set and a higher project allowance in the manual override block. The two sources explain the customer’s package without reverse-engineering a collection of plan-name conditions.

For engineers

The resource contains a grant source plus a separate override object. The resolver combines those with defaults, applicable profiles and other scopes. A stored row therefore does not by itself describe every feature available in a request.

Application example: an authorized administrative view shows the stored organisation agreement beside its effective scope result. The context and organisation identifier must refer to the same authorized customer. Keep the external context when using the resource service so its read policy remains in force:

import {
  CoreResourceType, ResourcePrimaryScope,
  type ScopeFeaturesWithOverrides,
} from '@wildo-ai/saas-models';
import type {
  ExecutionContext, FeatureResolutionBackendService,
  ServicesRegistryHandlerBackendService,
} from '@wildo-ai/saas-backend-lib';

export async function inspectOrganizationAccess(
  services: ServicesRegistryHandlerBackendService,
  featureResolution: FeatureResolutionBackendService,
  context: ExecutionContext<any>,
  organizationId: string,
) {
  const stored = await services.read<ScopeFeaturesWithOverrides>(
    CoreResourceType.ORGANIZATION_FEATURES, context, { organizationId },
  );
  const effective = await featureResolution.resolveForScope(
    ResourcePrimaryScope.ORGANIZATIONS, organizationId,
  );

  return {
    stored,
    effective,
    sourceRecordExists: stored !== null,
  };
}

The explicit filter selects the owner’s record; it is not a replacement for authorization. This helper neither grants access nor refreshes caches. The resolver may return its cached scope result. After an administrative change, distinguish a fresh resolution from a previously cached result.

Illustrative before/after, assuming reporting has no default, profile or other grant:

Point in the agreementStored base featuresManual addedFeaturesFresh effective reporting
Before the exceptionDoes not include reportingDoes not include reportingDisabled
Operator grants reportingUnchangedIncludes reportingEnabled
Billing refreshes the planRecomputed from the subscriptionReporting exception preservedEnabled while the exception applies
Operator removes the exceptionWhatever the current plan grantsReporting exception removedDetermined by the remaining sources

removedFeatures and limitOverrides live beside addedFeatures in manualOverrides. An absent row is a legitimate result and can still resolve defaults or an applicable profile; a failed read is an error, not an empty agreement. Neither the ledger nor this inspection helper contains an automatic expiration schedule for exceptions.

The effective result above belongs to one organisation. resolve(context) answers the different question of effective access across the request’s applicable application, organisation and user layers. Use the appropriate result when explaining a protected action.

Use the resource that owns the agreement

ResourceOwnership and read policyWrite policy
APPLICATION_FEATURESApplication-wide, super-administrator readsSuper-administrator
ORGANIZATION_FEATURESOrganisation-context member readsSuper-administrator
USER_FEATURESUser-context resource with its configured member read roleSuper-administrator

Resource relationships determine the contextual address. Use generated resource operations and their actual context rather than inventing an unscoped /features mutation. Generic updates are powerful administration operations because the override fields must remain writable to the operator; tenant users cannot use that authority.

Preserve both producers

Billing sync owns the subscription-derived features and limits. Named enable/disable/reset operations own their specific override mutations and invalidate the affected scope cache. A bespoke administrator that edits records directly must also handle cache freshness through an authorised backend flow.

For verification, read the record after a subscription change, check that its manual exceptions remain, then inspect the resolver’s effective result and exercise a protected action. A successful database write alone proves neither fresh resolution nor backend enforcement. Keep direct storage repair separate from normal administrative operations so it cannot silently bypass these effects.

Building a B2B product or an internal tool?

Wildo is not self-service yet. Tell us what you have in mind and we will say plainly whether it fits, and what happens next.