Skip to main content

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 /qefro endpoint.
  • 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

StageComponentTenant-scopedPage
Solution packageYour source directory (src/ required)n/aManifest
RegistryGlobal signed catalogNoPublishing
InstallerTenant activation + bindingYesInstallation
RuntimeFlow engine + event busYesWorkflows
SDK appDomain tools on /qefroYes (per install)Managed apps
Managed storageDocuments via ctx.storageYes (per op)Managed storage
Connector bridgeShared pool for external SoRPool shared; calls carry tenantConnectors
Portal rendererNative widget registryYesPages

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|checksum before 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, complete steps). See Runtime concept.
  • Runtime data sourcesmetrics, executions and workflows targets serve the tenant's own runtime data to solution UIs (capability runtime.query).
  • Event busui.* lifecycle events and business events ride the existing bus unchanged. See Events.
info

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/route calls to a shared, stateless connector pool (containers named qefro-connector-{name}-{version}-{id} — never per-tenant),
  • attaches the tenant context to every call,
  • enforces that the calling solution holds connector.invoke and 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:

EngineRole
Solution UI hostLoads the tenant bundle, scoped theme container, page tabs, lifecycle events
Theme enginetheme.yaml → CSS custom properties on the solution container only
Navigation engineBundle navigation under Managed solution in the portal sidebar
Widget registryClosed widget-kind list rendered with platform UI primitives
Layout engineResponsive grid (1 column on mobile, columns at ≥ 1024 px)
Data sourcesCapability-gated fetches from runtime, own-app /qefro, or pool bridge
CapabilitiesThe ui.* host API, implemented in-process
UI boundaryError 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

DataOwnerTenant-scoped
Published packages (manifest, UI, workflows)Registry / solution-serviceNo — global catalog
Installations, settings, granted capabilitiesInstaller / solution-serviceYes
Workflow executionsRuntimeYes
Solution application documentsstorage-service → Mongo managed_appsYes (per op)
Running connector containersConnector managerNo — shared pool
Connector credentialsSecret managerYes
UI event log (ui_events)RuntimeYes

Trust boundaries

  1. Validation rejects unknown widget kinds, unknown capabilities, icons outside the closed set, non-grid layouts and executable assets — see Validation.
  2. Signing binds every package to id|version|checksum with Ed25519.
  3. Negotiation caps capabilities at install time.
  4. Re-check gates every host call and every data-source fetch at runtime.
  5. Boundaries ensure a malformed definition degrades to a scoped error card — the portal itself never crashes.