Skip to content

Runtime Lifecycle

James Daley edited this page Jun 20, 2026 · 2 revisions

Runtime Lifecycle

The runtime lifecycle is owned by ForsettiRuntime and ModuleManager. ForsettiRuntime is the host-facing orchestrator. ModuleManager is the activation authority for discovery, compatibility, entitlement, module identity, UI surface state, and activation persistence.

Boot Flow

sequenceDiagram
    participant Host as Host application
    participant Runtime as ForsettiRuntime
    participant Manager as ModuleManager
    participant Loader as ManifestLoader
    participant Registration as ModuleRegistrationService
    participant Requirements as ModuleRequirementValidator
    participant Store as ActivationStore

    Host->>Runtime: boot()
    Runtime->>Manager: discoverManifests(manifestDirectory)
    Manager->>Loader: loadManifests(directory)
    Loader-->>Manager: validated manifests by moduleID
    Manager->>Registration: confirm discovered manifests
    Runtime->>Manager: restorePersistedActivation()
    Manager->>Store: loadState()
    Store-->>Manager: ActivationState
    Manager->>Manager: restore service modules
    Manager->>Manager: restore selected UI/app module last
    Manager->>Store: save reconciled state
    Runtime-->>Host: booted
Loading

Boot does not invent modules. It loads manifests, restores persisted activation state when possible, and records restore failures for diagnostics.

Activation State Machine

stateDiagram-v2
    [*] --> Discovered
    Discovered --> Registered: canonical manifest confirmed
    Registered --> Compatible: schema/platform/version/policy pass
    Compatible --> Entitled: moduleID or product ID unlocked
    Entitled --> RequirementsSatisfied: runtime requirements pass
    RequirementsSatisfied --> Resolved: registry factory returns module
    Resolved --> Validated: descriptor and bundled manifest match
    Validated --> ActiveService: service start succeeds
    Validated --> ActiveUI: UI/app surface setup and start succeed
    ActiveService --> Inactive: deactivate
    ActiveUI --> Inactive: deactivate or replacement
    Discovered --> Rejected: missing or stale registration
    Compatible --> Rejected: compatibility error
    Entitled --> Rejected: entitlement missing
    RequirementsSatisfied --> Rejected: required service/default role/UI declaration failure
    Resolved --> Rejected: missing factory
    Validated --> Rejected: identity/type/version/manifest mismatch
    ActiveUI --> Rejected: start or surface failure rollback
Loading

Activation Checks

Activation proceeds in a fixed order:

Step Check Failure Result
Manifest lookup moduleID exists in discovered manifests ModuleNotFound
Registration confirmation Canonical manifest hash and registration record match registration mismatch error
Compatibility schema version, platform, framework version, capability policy IncompatibleModule
Entitlement unlocked by moduleID or iapProductID EntitlementRequired
Runtime requirements Required I/O, default roles, UI declarations, and data-isolation requirements are satisfiable requirement validation error
Active state module is not already loaded AlreadyActive
Factory lookup entryPoint resolves in ModuleRegistry ModuleNotFound
Descriptor validation descriptor module ID, type, and version match activation manifest identity/type/version errors
Bundled manifest validation module-provided manifest matches activation manifest field-by-field ModuleManifestMismatch
Runtime contract UI/app manifests produce UI-capable modules; app manifests also satisfy the app marker ModuleTypeMismatch
UI contribution validation contributions match granted capabilities and declared schema 1.1 UI IDs CapabilityDenied or UI declaration error

UI/App Activation

UI and app modules share one active surface slot. Activating a new UI/app module replaces the previous one only after the incoming surface and module start path succeeds.

sequenceDiagram
    participant Manager as ModuleManager
    participant Incoming as Incoming UI/app module
    participant Previous as Previous UI/app module
    participant Surface as UISurfaceManager
    participant Store as ActivationStore

    Manager->>Incoming: uiContributions()
    Manager->>Manager: validate capabilities
    Manager->>Manager: validate declared UI IDs and policy-approved theme masks
    opt previous active module
        Manager->>Surface: remove previous contributions
    end
    Manager->>Surface: add incoming contributions
    Manager->>Surface: rebuildSurfaceState()
    Manager->>Incoming: start(scoped context)
    opt previous active module
        Manager->>Previous: stop(previous context)
        Manager->>Manager: unload previous module
    end
    Manager->>Manager: activeUIModuleID = incoming
    Manager->>Store: saveState()
Loading

If surface setup, incoming start, or previous stop fails, the manager attempts to stop the incoming module and restore the previous surface state. The previously active UI/app module remains the selected active module when rollback succeeds.

Service Activation

Service modules are simpler:

  1. Validate manifest and entitlement.
  2. Resolve and validate the module instance.
  3. Validate required runtime requirements.
  4. Create a capability-scoped module context.
  5. Call start(context).
  6. Add the module ID to enabledServiceModuleIDs.
  7. Persist state.

Service modules can run concurrently without a fixed upper limit.

Deactivation

flowchart TD
    Start["deactivateModule(moduleID)"]
    Loaded{"loaded?"}
    Stop["module.stop(context)"]
    RemoveSets["remove from enabled sets"]
    UI{"active UI/app?"}
    Surface["remove UI contributions\nrebuild surface"]
    Unload["erase module and context"]
    Persist["save activation state"]
    Error["throw NotActive"]

    Start --> Loaded
    Loaded -- no --> Error
    Loaded -- yes --> Stop
    Stop --> RemoveSets
    RemoveSets --> UI
    UI -- yes --> Surface
    UI -- no --> Unload
    Surface --> Unload
    Unload --> Persist
Loading

Restore Diagnostics

restorePersistedActivation() returns an ActivationRestoreResult:

Field Meaning
restoredModuleIDs Modules successfully restored.
failures Module IDs and diagnostic messages for restore failures.
succeeded() true when no failures were recorded.

Restore behavior is intentionally reconciliatory: modules that cannot be restored are omitted from the newly persisted activation state.

Entitlement Reconciliation

When entitlements change, ForsettiRuntime::reconcileActiveModulesWithEntitlements() deactivates active modules that no longer satisfy entitlement requirements. This prevents a previously unlocked module from remaining active after access is revoked.

Clone this wiki locally