Skip to content

Services Events and Diagnostics

James Daley edited this page Jun 18, 2026 · 3 revisions

Services, Events, and Diagnostics

ForsettiContext is the safe runtime bridge given to modules during start(context:) and stop(context:). It exposes service resolution, eventing, module messaging, logging, and routing without exposing host internals. Runtime-created module contexts are scoped to the active module ID and granted capabilities.

Context Map

flowchart LR
    Module["Module"] --> Context["ForsettiContext"]
    Context --> Services["ForsettiServiceProviding"]
    Context --> Events["ForsettiEventBus"]
    Context --> Logger["ForsettiLogger"]
    Context --> Router["OverlayRouting"]
    Context --> Guard["ModuleCommunicationGuard"]
Loading

Service Injection

Services are registered by protocol type in ForsettiServiceContainer.

let services = ForsettiServiceContainer()
services.register(NetworkingService.self, service: URLSessionNetworkingService())
services.register(StorageService.self, service: UserDefaultsStorageService())
Service Protocol Default Platform Adapter or Role
NetworkingService URLSessionNetworkingService
StorageService UserDefaultsStorageService
SecureStorageService KeychainSecureStorageService
FileExportService LocalFileExportService
TelemetryService NoopTelemetryService
SharedDatabaseService Host adapter or service module with defaultModuleRole: "shared_database".
AuthenticationService Host adapter or service module with defaultModuleRole: "authentication".
DiagnosticsService Host adapter or service module with defaultModuleRole: "diagnostics".
APIService Host adapter or service module with defaultModuleRole: "api".
SecurityService Host adapter or service module with defaultModuleRole: "security".

Event Bus

sequenceDiagram
    participant ModuleA as Module A
    participant Context as ForsettiContext
    participant Bus as InMemoryEventBus
    participant ModuleB as Module B

    ModuleB->>Context: subscribeToFrameworkEvents(type)
    Context->>Bus: subscribe(eventType)
    ModuleA->>Context: publishEvent(type, payload)
    Context->>Bus: publish(event)
    Bus-->>ModuleB: handler(event)
Loading

InMemoryEventBus dispatches events to handlers registered for the exact event type. Scoped contexts always publish with the bound module ID, even if module code attempts to pass a different source ID.

Module-to-Module Messaging

Module messaging is intentionally guarded. The default guard rejects empty IDs, self-relay, empty event types, and the reserved forsetti.internal. event namespace.

flowchart TD
    A["sendModuleMessage"] --> B["DefaultModuleCommunicationGuard"]
    B --> C{"Allowed?"}
    C -- no --> D["Log blocked communication and throw"]
    C -- yes --> E["Add _forsetti.targetModuleID to payload"]
    E --> F["Publish event"]
    F --> G["Subscriber receives only matching targetModuleID"]
Loading

Logging

ForsettiModuleLogger enriches module logs with moduleID metadata.

API Use
debug Diagnostic details useful during development.
info Lifecycle and normal operation events.
warning Recoverable problems or skipped work.
error Failed operations with optional Error details.

Diagnostics Workflow

flowchart LR
    Symptom["Runtime symptom"] --> Logs["Check ForsettiLogger output"]
    Logs --> Manifest["Check manifest fields"]
    Manifest --> Compatibility["Check compatibility report"]
    Compatibility --> Entitlement["Check entitlement state"]
    Entitlement --> Registry["Check ModuleRegistry entry point"]
    Registry --> Tests["Add regression test"]
Loading

Service Boundary Rules

  • Modules resolve protocols, not concrete host implementations.
  • Host apps decide which services are registered.
  • Modules can resolve only services covered by their granted capabilities.
  • Sensitive storage should use KeychainSecureStorageService in production; in-memory secure storage is an explicit test/debug adapter.
  • Telemetry should remain host-governed so privacy and analytics policy stay centralized. Service resolution is capability-scoped for module contexts:
Service Protocol Required Capability
NetworkingService networking
StorageService storage
SecureStorageService secure_storage
FileExportService file_export
TelemetryService telemetry
SharedDatabaseService shared_database
AuthenticationService authentication
DiagnosticsService diagnostics
APIService api
SecurityService security

Unknown service protocols are denied by default.

Provider Role Flow

flowchart TD
    Provider["Service module manifest"] --> Role["defaultModuleRole"]
    Role --> Discovery["Discovery records provider role"]
    Consumer["Consumer module manifest"] --> Requirement["runtimeRequirements.dataIsolation.requiredDefaultRoles"]
    Requirement --> Activation["Activation checks provider availability"]
    Discovery --> Activation
    Activation -- available --> Start["Consumer starts"]
    Activation -- missing --> Block["unsatisfiedRuntimeRequirement"]
Loading

Provider roles make shared services explicit without forcing consumers to know concrete module implementations.

Clone this wiki locally