Metadata DB
The metadata DB is the relational store that records everything about a space except the blob bytes themselves: every delegation, invocation and revocation event, the epoch/event ordering that sequences them, and the kv_write/kv_delete rows that map a key to the content hash of its current value. It is a single SeaORM database — SQLite by default, Postgres or MySQL by feature — shared across all spaces hosted by a node, with the SpaceId as part of the primary key on every per-space table.
Role
This is the Layer 1 system of record. The blob-store holds opaque content; the metadata DB holds the authorization and ordering facts a node needs to answer "may this capability act, and what is the current value of this key?" without consulting any external service. Capability verification (models/delegation.rs, models/invocation.rs) reads parent delegations, abilities, and time-bounds from here; KV reads resolve the latest write from here (see conflict-resolution). Because these records are small, ordered, and content-hashed, they are also the unit that replication is designed to sync — distinct from the bulk-content blob-store.
Mechanics
SeaORM over three backends
Models live in tinycloud-core/src/models/* (one DeriveEntityModel per table) and are driven by SeaORM, which abstracts the SQL dialect. The backend is chosen by tinycloud-core Cargo features sqlite | postgres | mysql. The server resolves the connection string from config and tunes it (tinycloud-node-server/src/lib.rs:192-210):
- SQLite (default) —
max_connections(1)(a single connection serializing writes, because two concurrent DEFERRED transactions deadlock on writer upgrade) plus WAL journal mode and a 5 s busy-timeout so reads stay concurrent. - Postgres / MySQL —
max_connections(100).
The SpaceDatabase (db.rs) wraps the DatabaseConnection together with the blob-store handle and the per-space key source, and optionally a column-encryption key (with_encryption) used to encrypt sensitive serialized columns at rest.
Tables
The schema is built by an ordered migration sequence (migrations/mod.rs):
init_tables—space,delegation,invocation,revocation,actor,abilities,kv_write,kv_delete,epoch,event_order,epoch_order,invoked_abilities,parent_delegations.sql_database,hook_tables,signed_kv_tickets,database_artifacts,encryption_networks,rename_encryption_owner_did— adding the SQL, hooks, signed-URL, artifact, and encryption-network tables.
Three groups matter for the protocol:
- Authorization —
space(PKSpaceIdWrap, the space's root identity),delegation/invocation/revocation(the signed capability events, hashed by content),abilities+invoked_abilities+parent_delegations(the relationship rows that let verification walk a delegation chain by parent CID). - Ordering —
epoch,event_order,epoch_order: the epoch DAG that totally-orders events within a space (seq, epoch, epoch_seq). - KV index —
kv_writeandkv_delete: eachkv_writecarries(space, key, invocation, seq, epoch, epoch_seq, value: Hash, metadata);valueis the blob-store content hash, and theseq/epoch/epoch_seqcolumns are what make reads last-writer-wins.
Shape
Every per-space table embeds the space in its primary key via the SpaceIdWrap newtype (tinycloud-core/src/types/space_id_wrap.rs), e.g. the kv_write model:
#[sea_orm(table_name = "kv_write")]
pub struct Model {
#[sea_orm(primary_key)] pub space: SpaceIdWrap,
#[sea_orm(primary_key)] pub key: Path,
#[sea_orm(primary_key)] pub invocation: Hash,
pub seq: i64, pub epoch: Hash, pub epoch_seq: i64,
pub value: Hash, // → blob-store content hash
pub metadata: Metadata,
}
Events (delegations/invocations/revocations) are keyed by their content Hash, so re-inserting an identical signed event is idempotent (on-conflict-do-nothing on the hash PK).
Relationships
Records the capability events (delegation/invocation/revocation) that authorization verification reads; holds the epoch/event ordering tables; indexes KV keys to blob-store content hashes; its rows are resolved last-writer-wins on read; is the replicable record set targeted by replication; distinct from the per-space SQL databases, which are separate on-disk files (the metadata DB only stores their artifacts and pointers). Sensitive columns may be encrypted via the column-encryption key.
Example
A /delegate request inserts one delegation row (keyed by the event hash) plus abilities and parent_delegations rows; a later tinycloud.kv/put /invoke inserts an invocation row, advances the space's epoch (epoch + event_order + epoch_order rows), and writes a kv_write row whose value is the new blob-store hash. A subsequent tinycloud.kv/get reads only the metadata DB to find the latest kv_write, then streams the blob.
Status & drift
Shipped. SQLite is the default and the common single-node deployment; Postgres/MySQL are feature-gated for larger deployments. One subtlety: in app() the with_encryption column key is currently the webhook-secrets-derived key reused for delegation/invocation column encryption (lib.rs:235) — whether that context reuse is intentional vs. a dedicated context is an open question, not user-facing. See contradictions.
Sources
tinycloud-node:tinycloud-core/src/db.rs(SpaceDatabase,transact, KV read/write),tinycloud-core/src/models/*(entity models),tinycloud-core/src/migrations/mod.rs(migration sequence + table set),tinycloud-node-server/src/lib.rs:192-210(connection wiring, SQLite single-conn/WAL, Postgres/MySQL pool)