Skip to content

Qirava Cloud docs

Cloud architecture

Qirava Cloud sells managed DMS. One rule makes it secure: the cloud controls the envelope (availability — place, scale, move, upgrade, drain), and the tenant controls the data (the seed). The cloud can move the box; it can never read inside it. Architecturally, Qirava Cloud is itself a Qirava DMS running a cloud app — "a DMS that manages other DMSes" — dogfooded exactly the way the website and Studio are Quill apps on the qdms engine. It does not fork the engine; it reuses it and adds three things: the cloud.* orchestration functions, the _cp_* control-plane catalogs, and the RBAC-gated Cloud Console. This page covers the entity model, dense per-node packing, cross-account delegation, the two access layers, the envelope-only control channel, and how the control plane is actually built today.

The entity model#

Everything the cloud does is one of a few entities and the relationships between them. Accounts buy a resource pool and spend it across many isolated DMS instances; nodes pack many DMSes; the control plane places, meters, and bills them. The control plane NEVER touches tenant data — it allocates resources and manages lifecycle through the _cp_* model only.

 ACCOUNT  (email + password — anyone signs up publicly)
   ├─ Subscription: a POOL bought by the unit → N threads · M GB RAM · S GB storage
   │                priced $/unit, metered hourly | monthly | yearly
   ├─ owns ──▶ DMS instances [1 .. many]      ← one account runs MANY isolated DMSes
   └─ can be DELEGATED other accounts' DMSes (manage them, within a granted scope)

 DMS INSTANCE  (an isolated process = the SAME self-hosted DMS, control_owner=managed)
   ├─ cap: threads · RAM · storage   (drawn from the owner's pool; cloud-enforced)
   ├─ domain: id.qirava.in (default)  [+ custom domain via cloudflared tunnel]
   ├─ mode: standalone | cluster   (tenant's choice, switchable)
   ├─ inside: the tenant's own dbs · workers · KMS · governance  (they control it)
   └─ managers: the OWNER account  +  any DELEGATED emails (each with a scope/role)

 NODE (bare metal)  →  PACKS MANY capped DMS processes (bin-packed — no wasted nodes)
 CONTROL PLANE (the cloud's own 3+ node cluster DMS): _cp_* catalogs + placer +
   meter + billing + rollout orchestrator + domain automation
The cloud entity model

Signup, subscription, billing#

Anyone can sign up publicly with an email. An account buys a resource pool — measured in threads, GB of RAM, and GB of storage — and is metered and billed per unit, on an hourly, monthly, or yearly cycle. The slider IS the plan: there are no fixed tiers, just one per-unit metered plan persisted as data and read by the console.

Public signup
Self-serve account creation by email; no sales gate. (PLANNED — see status.)
Resource pool
The units you purchase, drawn down by each DMS you create.
Metered billing
Per-unit pricing (thread · GB RAM · GB storage · bandwidth), hourly / monthly / yearly.
Self-serve
Create DMS instances, then create databases and worker apps inside them.

Many isolated DMSes, densely packed#

A single account can create MANY DMS instances, each fully isolated — the same as if they had stood up separate self-hosted DMSes. Each instance gets a resource cap drawn from the account's pool, a default subdomain (id.qirava.in), and an optional custom domain via a cloudflared tunnel. Nodes are not wasted on one tenant: a node packs many capped DMS processes, bin-packed until its pool is full. Isolation comes from the per-process cap (executor budget + cgroup) and the per-tenant seed — not from giving each tenant a whole node.

 NODE-1  (e.g. 32 thr · 128 GB · 4 TB)  ── packs MANY capped DMS processes ──
 ┌DMS acctA·p1 2thr·4GB┐ ┌DMS acctA·p2 1·2┐ ┌DMS acctB 1·2┐ ┌DMS acctC 4·8┐ …
 │ :p1  a1.qirava.in    │ │ :p2 a2.qirava.in│ │ :p3 …       │ │ :p4 …        │
 └──────────────────────┘ └─────────────────┘ └─────────────┘ └──────────────┘
   acctA runs MULTIPLE isolated DMSes; many accounts co-reside; each hard-capped.
   The cloud bin-packs until the pool is full, then uses the next node.
One node packs many capped DMS processes

Delegation, and the two access layers#

An account can grant another email a scope to manage one of its DMSes — cross-account delegation. This lives at the cloud layer and is kept distinct from the DMS's own internal RBAC; a delegation bridges the two by issuing the grantee an identity inside the DMS at the granted role. Two access layers exist, and they must not be confused.

 LAYER A — CLOUD / ACCOUNT IAM   (who may MANAGE a DMS instance)
   owner account + delegated emails.   _cp_delegations{ dms_id, grantee_email, scope }
   e.g.  acctA grants  bob@x.com : manage  on  DMS-7
         → Bob (a signed-up account) administers DMS-7 from his own login.

 LAYER B — DMS-INTERNAL RBAC    (who may touch the DATA inside a DMS)
   the tenant's own custodian > admin > user > guest (L1/L2/L3, invite-only).

 BRIDGE: a Layer-A delegation makes the cloud issue that email an invite/identity
   INTO the DMS at the granted role → cloud-sharing maps to a real DMS account.
Cloud IAM (manage the box) vs DMS RBAC (touch the data)

The envelope-only control channel#

Each node runs a node agent (cloud authority) that spawns DMS processes, sets cgroup caps, and manages routing. Each DMS exposes a localhost-only control socket that accepts a NARROW lifecycle vocabulary — never data. The control plane talks to node agents over authenticated, out-of-band control envelopes (signed + mTLS) that never carry tenant data.

 ┌──────────── CLOUD CONTROL PLANE (3+ node cluster DMS) ────────────┐
 │ _cp_* · placer · meter · billing · ROLLOUT orchestrator · domains │
 └───────────────────────────┬───────────────────────────────────────┘
        authenticated, OUT-OF-BAND control envelopes (signed + mTLS)
                              │   ← never carries tenant data
     ┌─────────────────────────┼─────────────────────────┐
     ▼                         ▼                         ▼
 ┌ NODE-1 ───────────┐   ┌ NODE-2 ───────────┐   ┌ NODE-3 ─── … ─┐
 │ NODE AGENT (cloud)│   │ NODE AGENT        │   │ NODE AGENT    │
 │  spawn/stop·cgroup│   │                   │   │               │
 │  cap·storage·route│   │                   │   │               │
 │   │ localhost     │   │                   │   │               │
 │   ▼ control socket│   │                   │   │               │
 │ ┌DMS (capped)   ┐ │   │ ┌DMS┐ ┌DMS┐       │   │ ┌DMS┐ ┌DMS┐   │
 │ │seed=RAM/TEE    │ │   │ └───┘ └───┘       │   │ └───┘ └───┘   │
 │ │data=CIPHERTEXT │ │   │                   │   │               │
 │ └────────────────┘ │   │                   │   │               │
 └────────────────────┘   └───────────────────┘   └───────────────┘
   The node agent is the cloud's hands; it holds NO seed, so it manages
   processes + ciphertext, never plaintext.
Control plane → node agent → DMS control socket
GroupVerbsReads data?
Resourceapply-cap(threads, ram, storage) → executor re-reads budget liveno
Replicaadd-follower(peer, token) · ship-snapshot · stream-walno (ciphertext)
Cutoverdrain(grace) · fifo-hold · promote(epoch) · step-down · repointno
Lifecyclestart · stop · checkpoint · health · version · metricsno

How the control plane is built today#

The control plane is a binary (qcloud) that boots a control DMS and loads the cloud app onto it — it does NOT fork the engine. Its boot sequence is fixed and idempotent, mirroring the qdms launcher: open a durable Qdb, ensure the framework _sys_* tables and replay the WAL, ensure the _cp_* catalogs and seed the default plan/nodes, register the cloud.* functions, register the Cloud Console handlers and seed its _sys_routes, bootstrap one cloud custodian on first install (password printed once), and serve the system worker with the console's session gate attached.

text
$ cd qcloud && cargo run
Qirava Cloud — managed-DMS control plane
  shape:   a Qirava DMS running the `cloud` app (dogfooded)
  engine:  one control DMS + executor (auto resource config)
  data:    durable @ ~/.qcloud/qcloud-data
  catalogs:_cp_tenants/_cp_nodes/_cp_plans/_cp_subscriptions/_cp_usage/_cp_invoices/_cp_audit
  fns:     cloud.provision/scale_vertical/scale_horizontal/switch_mode/suspend/resume/terminate/generate_invoice
  console: RBAC-gated Cloud Console at http://127.0.0.1:7180/cloud  (login at /cloud/login)
  note:    real infra (DMS spawn, cgroup caps, live scaling, payment) is SIMULATED + clearly badged PLANNED
  BOOTSTRAP CLOUD CUSTODIAN (shown once): user=… password=…
listening…
  • Default bind address 127.0.0.1:7180 (override with QCLOUD_ADDR); data dir ~/.qcloud/qcloud-data (override with QCLOUD_DATA, or memory for ephemeral).
  • Zero third-party deps; q* crates via ../-relative path deps (qdms, qquill/*, qpkgs/*, qbrand). No cargo workspace.
  • The Cloud Console is an RBAC-gated Quill app served at /cloud/*, the cloud's equivalent of Studio.

The Cloud Console (the control-plane-over-tenants UI)#

The Console is a CLIENT of the control DMS like any other app: server-rendered pages plus form-POST mutations that call the cloud.* functions WITH the cloud session's role in ctx. There is no privileged backdoor — every mutation is re-checked server-side by the function (deny-by-default), and every read goes through the planner. The sidebar hides what a role cannot reach; each handler re-checks the SAME predicate and serves a real 403 on a forbidden direct navigation. This nav gate is a CLIENT-side convenience — the boundary is the planner plus the functions' own role checks.

ScreenPathMin cloud role
Overview/cloudany authed operator
Tenants/cloud/tenantsadmin
Plans & Subscriptions/cloud/plansadmin
Nodes & Capacity/cloud/nodesadmin
Billing/cloud/billingadmin
Governance/cloud/governancecustodian

Authenticated routes are covered by the cloud_session group, whose before-chain runs auth.session (validates the session cookie and injects {user, role} into ctx) and whose after-chain runs auth.session_extend. Login and logout are public. Each mutation form carries a session-bound CSRF token (_csrf); the action handler resolves and CSRF-verifies the caller, does a cheap client-side rank check, then calls the cloud.* function with the role in ctx and finishes with a Post/Redirect/Get that surfaces the function's simulated PLANNED note in the flash.

Request

POST /cloud/login        username=<custodian> password=<printed-once>
  → 302 /cloud  (sets HttpOnly qdms_session cookie)

POST /cloud/actions/provision
  _csrf=<token>  id=acme  owner=acme-corp  mode=standalone
  storage=dynamic  threads=2  mem_gb=4  storage_gb=20

Response

302 /cloud/tenants?flash=Provisioned+tenant+'acme'+—+PLANNED:+boot+tenant+DMS+on+node-eu-1+(own+WAL/_sys_*/seed),+mint+tenant+custodian+(returned+once)

Output

A 'user'-role operator hitting GET /cloud/tenants directly gets a real 403 "Access denied" page, not a blank screen.

Status: what is built vs planned#