Skip to content

Runtime Lifecycle

James Daley edited this page Apr 25, 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["Restore persisted services and UI modules"]
    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 --> 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
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 UI/app module is active and started. enabledUIModuleIDs
Selected UI Foreground UI module chosen for presentation. activeUIModuleID / selectedUIModuleID

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 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.

Clone this wiki locally