Modules

Self-contained units of functionality with explicit contracts.

A Forsetti module pairs a Swift type with a bundled JSON manifest. Together they declare its identity, version, capabilities, runtime requirements, and any UI contributions — and the runtime manages the full lifecycle from discovery through activation to shutdown.

Structure

Anatomy of a Module

Every module conforms to one of three protocols and ships a bundled JSON manifest. The Swift type and the manifest must describe the same module before it can activate.

protocol ForsettiModule {

    /// Runtime identity snapshot
    var descriptor: ModuleDescriptor { get }

    /// JSON-backed activation contract
    var manifest: ModuleManifest { get }

    /// Lifecycle, with a capability-scoped context
    func start(context: ForsettiContext) async throws
    func stop(context: ForsettiContext) async
}

/// UI and app modules add structured contributions
protocol ForsettiUIModule: ForsettiModule {
    var uiContributions: UIContributions { get }
}
Contracts

Module Contracts

ID

Identity & Type

Every manifest declares a stable moduleID, a moduleVersion with major, minor, and patch numbers, and a moduleType of service, ui, or app. The moduleID is unique — duplicate IDs fail discovery — and is typically written in reverse-domain form.

CC

Compatibility

Each manifest declares its supported platforms — iOS, macOS, or both — and a Forsetti version range via minForsettiVersion and an optional maxForsettiVersion. The CompatibilityChecker evaluates these, with the schema and template version, before activation.

CAP

Capabilities

Modules request permission-like capabilities — networking, storage, secure storage, telemetry, toolbar_items, view_injection, routing_overlay, and more. The host capability policy must grant each one, and the module declares matching runtime requirements for I/O, UI IDs, and data isolation.

EN

Entitlements

Paid or gated modules carry an iapProductID. Before a module starts, the entitlement provider decides whether it is unlocked — backed by StoreKit on iOS, static unlocks on macOS, or a custom provider. A module with no product ID is treated as unlocked.

Lifecycle

Module Lifecycle

A module transitions through defined states as the runtime processes it. Each state has clear entry and exit conditions.

Discovered

The manifest is loaded and indexed by moduleID. The runtime knows the module exists but has not evaluated it yet.

Registered

The manifest identity and a content hash are stored in the registration store, so later drift between the accepted contract and the current manifest is caught.

Loaded

After compatibility, registration, and entitlement checks pass, the registry factory builds the module instance from its entryPoint.

Active

start(context:) has run with a capability-scoped context. A service module runs in the background; a UI or app module also publishes its UI contributions.

Stopped

stop(context:) has run — during shutdown or when an entitlement is revoked. UI contributions are cleared, and persisted desired-activation state is left intact unless explicitly changed.

UI

UI Contribution Model

UI and app modules publish a structured UIContributions value. The runtime validates it against the manifest and the host renders the approved data — modules never touch host UI directly.

Contribution Types

A UI module publishes a UIContributions value: toolbar items, slot-based view injections, an overlay schema, and an optional theme mask. Each contribution type requires its own capability.

Declared IDs

Template 1.1 modules declare every UI ID they will contribute — viewIDs, slotIDs, toolbarItemIDs, routeIDs, and pointerIDs — under runtimeRequirements.ui. Undeclared contributions fail activation before the surface updates.

Host Rendering

The runtime publishes only the active UI or app module contributions through the UISurfaceManager. The host resolves each viewID through its ForsettiViewInjectionRegistry and renders slots by priority.

// A UI module publishes structured contributions
struct AnalyticsModule: ForsettiUIModule {

    var uiContributions: UIContributions {
        UIContributions(
            viewInjections: [
                ViewInjectionDescriptor(
                    slot: "main",
                    viewID: "analytics.summary",
                    priority: 100
                )
            ]
        )
    }

    func start(context: ForsettiContext) async throws {}
    func stop(context: ForsettiContext) async {}
}
Reference

Structured Contribution Types

Contribution Required Capability Descriptor Type
Toolbar item toolbar_items ToolbarItemDescriptor
View injection view_injection ViewInjectionDescriptor
Overlay schema routing_overlay OverlaySchema
Theme mask ui_theme_mask ThemeMask

Start building modules.

Read the documentation for step-by-step guides on creating your first module, defining contracts, and contributing UI.