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 automationSignup, 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.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.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.| Group | Verbs | Reads data? |
|---|---|---|
| Resource | apply-cap(threads, ram, storage) → executor re-reads budget live | no |
| Replica | add-follower(peer, token) · ship-snapshot · stream-wal | no (ciphertext) |
| Cutover | drain(grace) · fifo-hold · promote(epoch) · step-down · repoint | no |
| Lifecycle | start · stop · checkpoint · health · version · metrics | no |
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.
$ 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
memoryfor 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.
| Screen | Path | Min cloud role |
|---|---|---|
| Overview | /cloud | any authed operator |
| Tenants | /cloud/tenants | admin |
| Plans & Subscriptions | /cloud/plans | admin |
| Nodes & Capacity | /cloud/nodes | admin |
| Billing | /cloud/billing | admin |
| Governance | /cloud/governance | custodian |
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=20Response
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.