Skip to main content

Architecture

The default installable solution is a metadata Marketplace App (hosting: runtime). The platform validates, stores, installs, renders UI, persists entities, and runs Business Flows on FlowRunner.

Developers do not ship a /qefro server for Restaurant, Clinic, Real Estate, Booking, or CRM apps. That SDK path is external integration.

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 and configure settings. Runtime apps have no /qefro binding.
  • Channels: WhatsApp (and similar) bind to the workspace, not to package settings.

Scaffolding a new app: App scaffold. Publishing into the catalog: Publishing.

Runtime vs SDK​

Marketplace AppExternal Integration
DefinitionMetadataSDK
RuntimeQefro RuntimeExternal server
Business logicQefro RuntimeCustomer system
StorageQefro managed storageExternal system
ToolsRuntime capabilitiesSDK capabilities
EventsRuntime eventsSDK events
FlowQefro FlowRunnerQefro FlowRunner + SDKAdapter
Marketplace App:
metadata → installer → Qefro Runtime → entity tools → managed storage

External ERP / POS / CRM:
customer system → SDK → /qefro → FlowRunner + SDKAdapter

SDK Connections (hosting: external) are how Focus ERP / Yaaz attach — not Marketplace Apps. See Managed apps only as a pointer to the removed /qefro Marketplace model.

The pipeline​

StageComponentTenant-scopedPage
App packageMetadata directory (entities/ required for hosting: runtime)n/aManifest
RegistryGlobal signed catalogNoPublishing
InstallerTenant activationYesInstallation
Qefro RuntimeFlowRunner + entity tools + event busYesWorkflows
Managed storageDocuments for declared entitiesYes (per op)Managed storage
Connector bridgeShared pool for external SoRPool shared; calls carry tenantConnectors
SDKAdapterExternal /qefro toolsYes (per connection)Runtime vs SDK
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 Marketplace Apps it provides:

  • Workflow execution — installed workflow definitions run on FlowRunner (ask, tool, condition, delay, approval, challenge, complete). Metadata flows compile into the same BusinessFlow model. See Runtime concept.
  • Entity tools — entity.<id>.create (and siblings) persist declared entities through managed storage. execution: runtime — no SDKAdapter.
  • Runtime data sources — metrics, executions and workflows targets (capability runtime.query).
  • Event bus — ui.* lifecycle events and business events. See Events.
info

A solution never executes workflows itself. Installation registers definitions; the runtime owns execution.

Domain boundary (ADR-006)​

Runtime is domain-agnostic. It executes, routes, persists conversation drafts, authorizes, and orchestrates. It does not know what a reservation, appointment, guest, table, or visit type is.

LayerOwns
App (entities/ + workflows/ + manifest)Business nouns, field names, choice values, when to confirm
ManifestGeneric conversation_slots, trigger reply_signals / required_slots / identity
RuntimeExtract declared slots, map {chip_prefix}:{value} chips, identity OTP, fire the pending declared capability on generic “yes”

Runtime-owned protocol vocabulary (Runtime may understand these): conversation_slots, required_slots, forbidden_slots, reply_signals, confirmation, identity_challenge, chip_prefix, chip_value.

App-owned opaque vocabulary (Runtime must never special-case, even if common): guest_name, pickup_date, room_type, visit_type, table_id, order_id, …. If it is app-defined, it is opaque.

A new app (Hotel, Salon, Real Estate, …) plugs in by declaring entities, slots, and triggers — no Runtime change. The contract is ADR-006 (qefro-plugin-platform/docs/adr-006-domain-agnostic-runtime.md).

Managed storage​

Marketplace App entities persist through Qefro-managed storage. Packages never receive a Mongo connection string and never invent storage-service URLs.

entity.reservation.create (Runtime capability)
→ 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.

SDK Connections persist only via ctx.storage inside /qefro — never from workflow/UI YAML targeting 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 an SDK-hosted install skip the pool bridge and call that installation /qefro (gated on runtime.query). Metadata apps use type: entity sources instead — see Sources.

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.