Skip to content

Entitlements and Capabilities

James Daley edited this page Apr 25, 2026 · 3 revisions

Entitlements and Capabilities

Forsetti evaluates two policy gates before module activation: capability permission and entitlement unlock state.

Activation Gate Stack

flowchart TD
    Manifest["ModuleManifest"] --> Compatibility["CompatibilityChecker"]
    Compatibility --> Schema["schemaVersion"]
    Compatibility --> Platform["supportedPlatforms"]
    Compatibility --> Version["Forsetti version range"]
    Compatibility --> Capabilities["CapabilityPolicy"]
    Capabilities --> Report{"Compatible?"}
    Report -- no --> Block["Block activation"]
    Report -- yes --> Entitlement["EntitlementProvider.isUnlocked"]
    Entitlement --> Unlocked{"Unlocked?"}
    Unlocked -- no --> Locked["moduleLocked"]
    Unlocked -- yes --> Start["start(context)"]
Loading

Capability Policy

Type Behavior
AllowAllCapabilityPolicy Allows every capability except framework-reserved checks enforced by compatibility logic.
FixedCapabilityPolicy Allows only the configured set of capabilities.
Custom policy Implement CapabilityPolicy.evaluate(moduleID:capability:) for product-specific governance.

The current compatibility checker always denies ui_theme_mask because framework shell theming is reserved.

Entitlement Providers

Provider Typical Use
AllowAllEntitlementProvider Free modules, tests, local prototypes, or debug bypass.
StaticEntitlementProvider macOS defaults, test fixtures, local unlock simulation, or non-StoreKit products.
StoreKit2EntitlementProvider iOS production StoreKit entitlement checks.
Custom provider Enterprise licensing, server-backed entitlement state, or alternate commerce flows.

Default Provider Factory

flowchart LR
    Factory["ForsettiEntitlementProviderFactory.makeDefault"] --> Platform{"Runtime platform"}
    Platform -- iOS --> StoreKit["StoreKit2EntitlementProvider"]
    Platform -- macOS --> Static["StaticEntitlementProvider"]
    Platform -- other --> Allow["AllowAllEntitlementProvider"]
Loading

StoreKit Flow

sequenceDiagram
    participant Runtime as ForsettiRuntime
    participant Provider as StoreKit2EntitlementProvider
    participant StoreKit as StoreKit
    participant Manager as ModuleManager

    Runtime->>Provider: refreshEntitlements()
    Provider->>StoreKit: Transaction.currentEntitlements
    StoreKit-->>Provider: verified active products
    Manager->>Provider: isUnlocked(moduleID, productID)
    Provider-->>Manager: true or false
    StoreKit-->>Provider: Transaction.updates
    Provider-->>Runtime: entitlementsDidChangeStream event
Loading

StoreKit2EntitlementProvider ignores revoked and expired transactions. Restore purchases calls AppStore.sync() and then refreshes entitlement state.

Product ID Rules

Manifest Field Result
iapProductID omitted or null Module is treated as unlocked.
iapProductID set and provider reports product unlocked Module can pass entitlement gate.
iapProductID set and provider reports product locked Activation fails with moduleLocked.

Capability Review Checklist

  • Requested capabilities match actual module behavior.
  • Paid modules have stable product IDs before release.
  • Debug entitlement bypass cannot leak into production configuration.
  • Capability denials are covered by tests when custom policies are used.
  • Entitlement changes deactivate modules that are no longer unlocked.

Clone this wiki locally