-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture Overview
James Daley edited this page Jun 18, 2026
·
3 revisions
Forsetti is organized around strict package boundaries. The runtime core has no dependency on UI frameworks, StoreKit, or host app code. Platform and host layers adapt the core to Apple services and presentation.
flowchart TB
subgraph Consumer["Consumer Application"]
AppTarget["App target"]
AppModules["App-owned modules"]
AppServices["Host-owned services"]
end
subgraph Forsetti["Forsetti Package"]
HostTemplate["ForsettiHostTemplate"]
Platform["ForsettiPlatform"]
Examples["ForsettiModulesExample"]
Core["ForsettiCore"]
end
AppTarget --> HostTemplate
AppTarget --> Platform
AppTarget --> Core
AppModules --> Core
AppServices --> Core
HostTemplate --> Platform
HostTemplate --> Core
Platform --> Core
Examples --> Core
| Target | Responsibility | Must Not Do |
|---|---|---|
ForsettiCore |
Define contracts, module models, lifecycle orchestration, events, logging, compatibility, activation, and UI contribution state. | Import SwiftUI, StoreKit, or host app code. |
ForsettiPlatform |
Provide Apple platform adapters such as StoreKit entitlements and default service implementations. | Own app-specific product rules or host-specific UI. |
ForsettiHostTemplate |
Provide reusable SwiftUI host control surfaces, controller state, overlay routing, and view injection registry. | Become the application domain layer. |
ForsettiModulesExample |
Demonstrate modules and manifests for tests and reference. | Become a production dependency by accident. |
| Consumer app | Own concrete app modules, product configuration, services, and end-user presentation choices. | Patch Forsetti internals to solve app-specific behavior. |
flowchart LR
Core["ForsettiCore<br/>lowest layer"]
Platform["ForsettiPlatform"]
Host["ForsettiHostTemplate"]
Examples["ForsettiModulesExample"]
App["Consumer App"]
Platform --> Core
Host --> Core
Host --> Platform
Examples --> Core
App --> Core
App --> Platform
App --> Host
The rule is directional: higher layers may depend on lower layers, but lower layers cannot know about higher layers. Required behavior crosses boundaries through protocols, manifests, service injection, or events.
| Component | Layer | Role |
|---|---|---|
ForsettiRuntime |
Core | Composes services, policy, entitlement provider, registry, loader, module manager, event bus, logger, router, registration store, and UI surface manager. |
ManifestLoader |
Core | Loads bundled JSON manifests from ForsettiManifests and validates required fields, schema/template pairing, runtime requirement shape, and duplicate IDs. |
CompatibilityChecker |
Core | Validates schema version, manifest template version, platform, Forsetti version range, and requested capabilities. |
ModuleRegistry |
Core | Maps manifest entryPoint strings to module factories. |
ModuleRegistrationStore |
Core | Persists manifest identity records and hashes for drift detection. |
ModuleManager |
Core | Registers discovered manifests, activates, deactivates, restores, persists, validates module identity and runtime requirements, and manages the active UI module. |
UISurfaceManager |
Core | Publishes active UI module contributions into host-readable state. |
ForsettiContext |
Core | Gives modules scoped access to services, eventing, logging, and routing. |
ForsettiHostController |
Host template | Converts runtime state into UI-ready module lists and user operations. |
ForsettiEntitlementProviderFactory |
Platform | Creates StoreKit or debug/static entitlement providers. |
flowchart TD
Control["Control plane<br/>manifests, registration, compatibility, capability policy, entitlements"] --> Activate["Activation decision"]
Data["Data plane<br/>services, events, logging, routing"] --> Running["Running module"]
Activate --> Running
Running --> UI["UI surface contributions"]
UI --> Host["Host presentation"]
The control plane decides whether a module may run. The data plane is what an already-running module can use safely.
| Constraint | Reason |
|---|---|
| Modules declare capabilities before activation. | Permission-like behavior must be reviewable. |
| Modules declare runtime requirements before activation. | Required services, UI IDs, data isolation, and provider roles must be inspectable. |
| Discovered manifests are registered before activation. | Manifest drift is detected before lifecycle code runs. |
| Service and UI contribution use is enforced at runtime. | Capabilities are runtime permissions, not advisory metadata. |
Paid modules use entitlement checks before start(context:). |
Lock state is runtime policy, not just UI decoration. |
| UI modules expose structured contributions. | Host composition remains deterministic and inspectable. |
| Exactly one UI/app module is active by default. | Application UI ownership stays clear while service modules can run concurrently. |
| Service dependencies are resolved through protocols. | Modules stay portable and testable. |
| Architecture tests enforce import direction. | Boundary drift is caught in CI instead of by convention. |