Architecture
An installable solution is an SDK application plus optional declarative UI/workflows. The platform validates, stores, installs, routes tools, persists documents, and renders the staff UI — it never owns domain rules.
Tenant → workspace → app
Tenant
└── Workspace
├── Channels (WhatsApp, …) # owned by workspace
└── One primary application # install of a published package
- Catalog (global): signed packages published by platform admins only.
- Install (per workspace): tenant admins activate a published version,
configure settings, and bind the install’s
/qefroendpoint. - Channels: WhatsApp (and similar) bind to the workspace, not to
package settings. Booking links get
?n=from that binding.
Scaffolding a new app: App scaffold. Publishing into the catalog: Publishing.
SDK application (ADR-003)
runtime → tool invoker → installation binding → /qefro → app tools → ctx.storage → Mongo
Required: src/, signed /qefro, hosting + endpoint. Optional: UI,
workflows, prompts, assets. Workflows and UI call {solution}/{tool} —
never platform storage/* directly. See Managed apps.
The pipeline
| Stage | Component | Tenant-scoped | Page |
|---|---|---|---|
| Solution package | Your source directory (src/ required) | n/a | Manifest |
| Registry | Global signed catalog | No | Publishing |
| Installer | Tenant activation + binding | Yes | Installation |
| Runtime | Flow engine + event bus | Yes | Workflows |
| SDK app | Domain tools on /qefro | Yes (per install) | Managed apps |
| Managed storage | Documents via ctx.storage | Yes (per op) | Managed storage |
| Connector bridge | Shared pool for external SoR | Pool shared; calls carry tenant | Connectors |
| Portal renderer | Native widget registry | Yes | Pages |
Registry
The registry is the global catalog of published solutions and connectors. It is not tenant-scoped: every tenant resolves against the same signed catalog.
Who can write: only platform admins (UUIDs in
QEFRO_PLATFORM_ADMIN_IDS on solution-service). Tenant / workspace admins
install from the catalog; they cannot publish or yank versions.
Responsibilities:
- Accept signed packages (
manifest,components,signature,signature_kid,publisher_id). - Verify the Ed25519 signature over
id|version|checksumbefore storing. - Track version lifecycle:
draft → published → deprecated → yanked. - Resolve dependency constraints (connector versions) at install time.
Packages are immutable once published: a new version is a new package. See Publishing.
Installer
The installer turns a published version into a tenant installation. Installing a solution registers flows and prompts with the runtime — the solution itself never executes anything.
Key properties:
- Capability negotiation: the granted set is the intersection of the capabilities requested by the package and what the installation grants. It is computed at install time, stored with the bundle, and re-checked on every host call. See Capabilities.
- Activation plan: the install pipeline executes explicit steps — enable tenant connectors, register flows, ensure connector pool, store secrets, register the UI bundle. The wizard shows requested vs granted capabilities before activation.
Runtime
The runtime is the single execution engine of the platform. For solutions it provides:
- Workflow execution — installed workflow definitions run on the
runtime's flow engine (
ask,tool,condition,delay,approval,challenge,completesteps). See Runtime concept. - Runtime data sources —
metrics,executionsandworkflowstargets serve the tenant's own runtime data to solution UIs (capabilityruntime.query). - Event bus —
ui.*lifecycle events and business events ride the existing bus unchanged. See Events.
A solution never executes workflows itself. Installation registers definitions; the runtime owns execution.
Managed storage
Solution-owned documents go through the install’s SDK (ctx.storage) —
never from workflow/UI YAML targeting storage/*, and never via a Mongo
connection string in the package.
app tool on /qefro
→ ctx.storage.*
→ storage-service /v1/internal/storage/*
→ MongoDB `managed_apps` ({solution_slug}__{logical})
Isolation, reserved metadata, soft delete, and audit are enforced by storage-service. See Managed storage.
Connector bridge
connector data sources that target declared external pool connectors
never call a connector directly. They are forwarded through the connector
bridge, which:
- routes
POST /v1/routecalls to a shared, stateless connector pool (containers namedqefro-connector-{name}-{version}-{id}— never per-tenant), - attaches the tenant context to every call,
- enforces that the calling solution holds
connector.invokeand declared the connector in its manifest.
Sources whose target is {solution}/{tool} for this install skip the
pool bridge and call the installation /qefro (gated on runtime.query).
See Connectors and the connector reference.
Portal renderer
The portal renders solution UIs natively at
/app/solutions/ui/:name/:pageId? using its own component registry:
| Engine | Role |
|---|---|
| Solution UI host | Loads the tenant bundle, scoped theme container, page tabs, lifecycle events |
| Theme engine | theme.yaml → CSS custom properties on the solution container only |
| Navigation engine | Bundle navigation under Managed solution in the portal sidebar |
| Widget registry | Closed widget-kind list rendered with platform UI primitives |
| Layout engine | Responsive grid (1 column on mobile, columns at ≥ 1024 px) |
| Data sources | Capability-gated fetches from runtime, own-app /qefro, or pool bridge |
| Capabilities | The ui.* host API, implemented in-process |
| UI boundary | Error boundary + schema coercion — broken definitions degrade to a scoped error card |
Nothing from a package ever executes: no iframes, no postMessage, no
injected scripts. See Security.
Data ownership
| Data | Owner | Tenant-scoped |
|---|---|---|
| Published packages (manifest, UI, workflows) | Registry / solution-service | No — global catalog |
| Installations, settings, granted capabilities | Installer / solution-service | Yes |
| Workflow executions | Runtime | Yes |
| Solution application documents | storage-service → Mongo managed_apps | Yes (per op) |
| Running connector containers | Connector manager | No — shared pool |
| Connector credentials | Secret manager | Yes |
UI event log (ui_events) | Runtime | Yes |
Trust boundaries
- Validation rejects unknown widget kinds, unknown capabilities, icons outside the closed set, non-grid layouts and executable assets — see Validation.
- Signing binds every package to
id|version|checksumwith Ed25519. - Negotiation caps capabilities at install time.
- Re-check gates every host call and every data-source fetch at runtime.
- Boundaries ensure a malformed definition degrades to a scoped error card — the portal itself never crashes.