Skip to content

Entitlements and Capabilities

Jim Daley edited this page May 1, 2026 · 3 revisions

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.

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 --> Scoped["Build module-scoped context"]
    Scoped --> Contributions["Validate UI contribution capabilities"]
    Contributions --> 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.

Runtime Capability Enforcement

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.

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.
  • Service resolution and UI contributions are covered by positive and negative capability tests.

Clone this wiki locally