Skip to content

Runtime Lifecycle

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

Runtime Lifecycle

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.

Boot Pipeline

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["Reconcile persisted desired activation"]
    I -- no --> K["Return discovered manifests"]
    J --> K
Loading

Activation Logic

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 --> V["Validate descriptor and manifest identity"]
    I --> V
    V --> J["start(scoped context)"]
    I --> K{"Conforms to UI protocol?"}
    K -- no --> Q["notUIModule"]
    K -- yes --> U["Validate UI contribution capabilities"]
    U --> 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
Loading

Module State Model

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 Compatibility state for the active UI/app module. The default model keeps this empty or one ID. enabledUIModuleIDs
Active UI UI/app module currently active for presentation. Activating another UI/app module deactivates the previous one. activeUIModuleID / selectedUIModuleID

Persisted activation state is desired state. Restore activates persisted IDs through the same compatibility, entitlement, identity, capability, lifecycle, and UI contribution path used for explicit activation. Failed restores do not leave false active state behind.

Entitlement Reconciliation

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
Loading

Shutdown

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"]
Loading

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 Handling

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 mismatched descriptor or manifest identity Activation throws moduleIdentityMismatch.
Factory returns a non-UI module for ui or app manifest after identity validation Activation throws notUIModule.
UI contribution lacks the required capability Activation throws missingCapability.
start(context:) throws Module instance is removed from loadedModules, the error is logged, and activation fails.

Clone this wiki locally