-
Notifications
You must be signed in to change notification settings - Fork 0
Entitlements and Capabilities
James Daley edited this page Apr 25, 2026
·
3 revisions
Forsetti evaluates two policy gates before module activation: capability permission and entitlement unlock state.
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)"]
| 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.
| 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. |
flowchart LR
Factory["ForsettiEntitlementProviderFactory.makeDefault"] --> Platform{"Runtime platform"}
Platform -- iOS --> StoreKit["StoreKit2EntitlementProvider"]
Platform -- macOS --> Static["StaticEntitlementProvider"]
Platform -- other --> Allow["AllowAllEntitlementProvider"]
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
StoreKit2EntitlementProvider ignores revoked and expired transactions. Restore purchases calls AppStore.sync() and then refreshes entitlement state.
| 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. |
- 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.