-
Notifications
You must be signed in to change notification settings - Fork 0
Entitlements and Capabilities
Forsetti evaluates two policy gates before module activation: capability permission and entitlement unlock state. Capability checks also apply after activation eligibility: module contexts expose services only when the active module has the matching granted capability, and UI contributions require matching UI capabilities before they are applied.
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 --> Scoped["Build module-scoped context"]
Scoped --> Contributions["Validate UI contribution capabilities"]
Contributions --> 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.
| Runtime Surface | Required Capability |
|---|---|
NetworkingService |
networking |
StorageService |
storage |
SecureStorageService |
secure_storage |
FileExportService |
file_export |
TelemetryService |
telemetry |
| Toolbar item contributions | toolbar_items |
| View injection contributions | view_injection |
| Overlay schema contributions | routing_overlay |
| Theme mask contributions |
ui_theme_mask and framework policy approval |
Unknown service types are denied by default by the capability-scoped service provider. Denied service resolution is logged as a warning and returns nil.
Module identity is supplied by the scoped context. Scoped modules can publish events and send module messages only as their own module ID.
| 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.
- Service resolution and UI contributions are covered by positive and negative capability tests.