-
Notifications
You must be signed in to change notification settings - Fork 0
Runtime Lifecycle
James Daley edited this page Apr 25, 2026
·
3 revisions
The runtime lifecycle has five phases: construct, boot, discover, activate, and reconcile. Shutdown reverses activation without erasing persisted activation state unless the caller explicitly changes state before shutdown.
flowchart TD
A["Create ForsettiRuntime"] --> B["Build CompatibilityChecker"]
B --> C["Build ForsettiContext"]
C --> D["Build ModuleManager"]
D --> E["boot(bundle, subdirectory)"]
E --> F["ManifestLoader.loadManifests"]
F --> G["Refresh entitlements"]
G --> H["Start entitlement observation"]
H --> I{"restoreActivationState?"}
I -- yes --> J["Restore persisted services and UI modules"]
I -- no --> K["Return discovered manifests"]
J --> K
flowchart TD
A["activateModule(moduleID)"] --> B{"Manifest discovered?"}
B -- no --> X["moduleNotDiscovered"]
B -- yes --> C["Evaluate compatibility"]
C --> D{"Any error severity issue?"}
D -- yes --> Y["incompatible(report)"]
D -- no --> E["Check entitlement provider"]
E --> F{"Unlocked?"}
F -- no --> Z["moduleLocked"]
F -- yes --> G{"moduleType"}
G -- service --> H["Resolve module factory"]
G -- ui/app --> I["Resolve module factory and require ForsettiUIModule"]
H --> J["start(context)"]
I --> K{"Conforms to UI protocol?"}
K -- no --> Q["notUIModule"]
K -- yes --> J
J --> L{"start throws?"}
L -- yes --> R["Report module error and rollback loaded module"]
L -- no --> M["Record active state"]
M --> N{"UI or app module?"}
N -- yes --> O["Apply sanitized UI contributions"]
N -- no --> P["Persist activation state"]
O --> P
| State | Meaning | Stored In |
|---|---|---|
| Discovered | Manifest was loaded and indexed by moduleID. |
manifestsByID |
| Loaded | Module instance was created from the registry. | loadedModules |
| Enabled service | Service module is active and started. | enabledServiceModuleIDs |
| Enabled UI | UI/app module is active and started. | enabledUIModuleIDs |
| Selected UI | Foreground UI module chosen for presentation. |
activeUIModuleID / selectedUIModuleID
|
sequenceDiagram
participant Provider as EntitlementProvider
participant Runtime as ForsettiRuntime
participant Manager as ModuleManager
participant Module as Active Module
participant Store as ActivationStore
Provider-->>Runtime: entitlementsDidChangeStream event
Runtime->>Manager: inspect enabled service and UI modules
loop each active module
Runtime->>Provider: isUnlocked(moduleID, productID)
alt no longer unlocked
Runtime->>Manager: deactivateModule(moduleID)
Manager->>Module: stop(context)
Manager->>Store: save updated activation state
else still unlocked
Runtime-->>Runtime: leave active
end
end
flowchart LR
A["runtime.shutdown()"] --> B["Cancel entitlement observation"]
B --> C["deactivateAllModules(persistState: false)"]
C --> D["Call stop(context) on loaded modules"]
D --> E["Clear loaded and enabled runtime state"]
E --> F["Leave persisted activation state unchanged"]
Shutdown is suitable for app lifecycle teardown. If a product decision should disable a module permanently, call deactivateModule(moduleID:) before shutdown so the activation store is updated.
| Failure | Result |
|---|---|
| Missing manifest directory | Boot throws manifestsDirectoryNotFound. |
| Invalid manifest JSON | Discovery throws validationFailed. |
Duplicate moduleID
|
Discovery throws duplicateModuleID. |
| Denied capability | Activation throws incompatible(report:). |
| Locked module | Activation throws moduleLocked. |
Factory returns a non-UI module for ui or app manifest |
Activation throws notUIModule. |
start(context:) throws |
Module instance is removed from loadedModules, the error is logged, and activation fails. |