Ranke-Graph home

The RankeDB Operator's Manual

Configuring and running an instance: the launch artifact field by field, and the access policy it carries

v1.19.031 August 2026

Imported from ranke-db v1.19.0 on 9 September 2026 — see the repository for anything newer.

RankeDB serves one Ranke-Archive from one configuration file. The file is the whole of the deployment: it names where claims are kept, what advances the head, the identity merges are signed under, where secrets come from, which addresses are served, and who may act on what. Nothing else configures an instance, and nothing reconfigures one while it runs.

This manual is that file, field by field. It says what each section selects, which backends a section may name and what fields each of those takes, and how the account roster decides what a request is allowed to do. What a claim is, how a closure is verified, and the query language a read is written in belong to the Ranke-Graph specification and the ranke-go library that implements it; the REST surface itself is fixed by the OpenAPI contract, openapi/openapi.gen.yaml, which ships in this manual’s own download.

Part I — Launching an instance

1 Running an instance

A ranke-db instance is one binary and one configuration file. Put the binary somewhere, write the file, and launch: the configuration says where the data lives, which addresses are served, and who may act on the archive.

ranke-db run config.json

That serves until you stop it (Ctrl-C, or a SIGTERM). A configuration is read once, at launch: an instance is never reconfigured while it runs, so the administrative cycle is edit, run, observe, stop, edit again. Two instances that differ in any way are two processes with two files.

1.1 Checking a configuration before launching

ranke-db verify config.json answers config ok or the first problem it finds. It checks to one of three depths, and each depth includes the ones before it.

--level syntax
The default. Parses the document and shape-checks it: unknown top-level sections, malformed grants, an admit list naming an account that does not exist, and a malformed endpoint address all fail here. It touches neither the environment nor the network, so it is the check to run in a pipeline that holds no secrets.
--level resolve
Also expands every env(...) and vault(...) reference, so a missing variable or an unreadable secret fails before launch. This dials the vault if — and only if — some value references it.
--level connect
Also assembles every adapter the file names and discards them, so an unreachable database or a bad credential surfaces here rather than mid-launch.

1.2 The shape of the document

The configuration is one JSON object with six top-level sections, and no others — an unrecognised section is refused rather than ignored, so a misspelling never passes silently.

storage
Where claims are kept — the Universe. See Section 2.1.
sequencer
What advances the branch table head as contributions merge, and keeps the history of previous heads. See Section 2.2.
signer
The signing identity this server attests merges with. See Section 2.3.
vault
The secret store vault(...) references read from. Optional; needed only if some value uses one. See Section 2.4.
accounts
Who may do what, as policy of this server. See Section 4.
endpoints
The addresses served, each with its own authentication and its own admitted accounts. See Section 3.

The first four are the ports the server drives; the last two drive it. Each section names a backend under "type" and carries that backend’s own fields beside it.

A section left out yields no adapter, which is why verify --level syntax passes on a partial document: a file worth launching names at least storage, sequencer, signer and one endpoint.

1.3 Values, secrets, and where they come from

Any string value in any section may be written as a delegation instead of a literal.

env(NAME)
Reads the process environment.
vault(REF)
Reads the secret store the vault section configures.
Example
"key":   "env(RANKE_SIGNER_KEY)",
"token": "vault(kv/rankedb/openbao-token)"

The match is whole-value: a delegation is the entire string or it is not one at all, so no secret hides inside a longer value, and "addr": "env(PORT):8080" is a literal address rather than a substitution. A reference that resolves to nothing is an error — resolution fails loudly rather than yield an empty secret.

Resolution happens at use. The environment is always available; the vault is built from its own section the first time some other value asks for a secret, which is why that section may use env(...) and never vault(...). Nothing resolves during a syntax check.

1.4 Encrypting the whole file

A configuration may be stored encrypted with age, passphrase-based, and both run and verify accept it as it stands: the binary detects the encryption and decrypts before parsing.

--age-key prompt|stdin|env:VAR|file:path
Where the passphrase comes from. prompt asks on the terminal.
Example
ranke-db run --age-key prompt        config.json.age
ranke-db run --age-key env:AGE_KEY   config.json.age
ranke-db run --age-key file:/run/key config.json.age

Reading both the configuration and the passphrase from standard input is refused, since the two cannot be told apart.

Encryption covers the file as a whole, so it protects the values written literally in it. A deployment that keeps its secrets in the environment or a vault gets the same protection without the passphrase, and the two combine.

1.5 The development clock

--dev
Mounts POST /dev/clock, which steers the time the Sequencer stamps merges with instead of following real time. It requires sequencer.type to be "dev", and exists to make a test suite’s timeline reproducible.
Part II — The launch artifact

2 The ports the server drives

Four sections name backends the server reaches out to: storage, sequencer, signer and vault. Each is an object naming its backend under "type" and carrying that backend’s fields beside it. Everything below is written from the configuration’s side — the field to write, what it selects, and what happens when it is absent.

2.1 storage — where claims live

The storage section describes the Universe as a tree: composite types hold other descriptors, leaf types name a technology. The whole section is one descriptor, so the simplest storage is a single leaf, and the usual shape is a stack of them.

2.1.1 Composites

stack
layers, an array of descriptors read in order, first entry first. A read tries each layer in turn and a miss is repaired from the layer below, which is what makes a cache in front of a slow backing store useful. An empty array is refused.
partition
shards, an array of descriptors. Content is routed to one shard by its id, so the shards divide the data rather than duplicate it. An empty array is refused.

Either composite may hold the other, and either may hold a leaf, to whatever depth the deployment wants.

2.1.2 Leaves

mem
Keeps everything in memory. No fields. The process exit is the end of the data — good for trying things out and for tests.
minimal
The reference in-memory store: the smallest thing that satisfies the port, kept as an illustration and deliberately not optimised. No fields. Reach for mem instead.
fs
dir, a directory path. The store is files under it, created as needed.
sqlite
dsn, a SQLite data-source name — a file path, or :memory:.
s3
bucket and region, plus accessKeyId and secretAccessKey, all four required. Credentials come from this configuration alone (as literals or through env(...)/vault(...)) and never from an ambient credential chain, so one file accounts for a deployment’s secrets. endpoint and usePathStyle ("true") point the same backend at an S3-compatible service such as MinIO.
redis
addr (host:port), with optional password and db, the numeric database index.
neo4j
uri, required. username and password go together — give neither and the driver connects without authentication, give one and the other becomes required. database selects a database other than the default.

2.1.3 Fields every descriptor accepts

name
A label for this layer, used wherever the server reports layers and by a query that pins itself to one. Absent, the layer is named after its type; two layers of one name are distinguished by position.
mode
The storage tier this layer is expected to serve — authoritative, eager, background or lazy. An assertion rather than a setting: a backend serves the tier it serves, and a mode that disagrees fails the launch.
maxContentSize
The content cap this layer is expected to hold to, in bytes. An assertion in the same sense, and against the cap the backend reports.

Write the two assertions where a deployment wants a mismatch caught, and leave them out otherwise.

Example
"storage": {
  "type": "stack",
  "layers": [
    {"type": "mem", "name": "cache"},
    {"type": "fs", "name": "archive", "dir": "/var/lib/rankedb/blobs"}
  ]
}

2.2 sequencer — how the head advances

The sequencer moves the branch table head as contributions merge, and keeps the previous heads so a deployment can look back at where the archive stood.

type
"concurrent" or "dev". concurrent is the one to deploy: it prepares contributions in parallel and folds a whole batch into one advance of the head. dev is the blocking reference sequencer — one contribution at a time, callers queueing behind it — for tests and development, and it is what --dev requires.
history
An object naming where the head timeline is kept. {"type": "mem"} keeps it in memory, and is the default when the field is absent; {"type": "file",
"path": "..."}
keeps it in a file.

The history choice decides whether a restart reopens the existing archive or bootstraps an empty one, so a deployment that keeps its storage on disk wants its history there too.

Example
"sequencer": {
  "type": "concurrent",
  "history": {"type": "file", "path": "/var/lib/rankedb/head.log"}
}

2.3 signer — the identity merges are attested with

This is the server’s own key, and it signs merges. It never signs a contributor’s claims: those arrive signed already, by the key of whoever made them.

inmemory
key, an Ed25519 private key as PKCS#8 PEM, required — this backend mints nothing and needs to be given one. Writing it as env(...)/vault(...) keeps the key out of the file.
openbao
Signing through an OpenBao Transit engine, so the key stays in the vault. address (server URL) and token are required; mount is the Transit mount path, "transit" by default; key names the Transit key.

2.4 vault — where vault(...) reads from

Configure this section only if some value in the document is written as vault(REF). It is built the first time such a value is read, and its own fields may use env(...) — a vault cannot bootstrap itself out of the store it is the way to.

openbao
address and token, both required; mount, the KV v2 mount path, "secret" by default. A vault(REF) reference names the path within that mount.
azure
url, an Azure Key Vault URI. The backend accepts its configuration and refuses every read — secret resolution is not wired yet, so a deployment cannot rely on it.
Example
"vault":  {"type": "openbao", "address": "https://bao.internal:8200",
           "token": "env(BAO_TOKEN)", "mount": "secret"},
"signer": {"type": "inmemory", "key": "vault(rankedb/signing-key)"}

3 Endpoints and authentication

endpoints is an array, and each entry is one address the instance serves.

Example
"endpoints": [{
  "transport": {"type": "rest", "addr": ":8080"},
  "auth":      [{"type": "apikey", "keys": [{"account": "ops", "sha256": "…"}]}],
  "admit":     ["ops"]
}]
transport
Where and how it listens.
auth
How a request’s credential resolves to an account. An array, because one endpoint may accept more than one kind of credential.
admit
The accounts reachable here, by name.

An instance may serve several endpoints at once — a public port and a socket for administration, say — and each carries its own authentication and its own admitted accounts. Nothing is shared between them but the archive underneath.

3.1 transport

type
"rest" (or "rest_http") serves the REST API over HTTP. "mcp" (or "mcp_http") is reserved for the MCP transport, which the server refuses at launch until it is implemented.
addr
What to listen on. ":8080" and "127.0.0.1:8080" are TCP; "unix:///run/rankedb/admin.sock" is a Unix domain socket. The scheme is what decides — a bare path is refused with the correction, and an overlong socket path is refused too, both by verify --level syntax rather than at launch.
mode
For a socket, its permission bits as an octal string. Default "0600" — the user the server runs as, and nobody else.
group
For a socket, a group name or numeric gid that also reaches it. This is how one host group is let in without opening the socket to everyone; a name that does not exist fails the launch rather than leaving a socket nobody can connect to.
allowedOrigins
A comma-separated list of browser origins allowed to call this endpoint. Absent means none, so a server nobody browses stays closed. Written as one string, so env(...) can supply it per deployment.

A socket suits an endpoint that should not be reachable over the network at all. Forwarding one to a workstation is enough to use it:

ssh -L 8080:/run/rankedb/admin.sock host

after which http://127.0.0.1:8080 reaches the endpoint exactly as a network address would.

3.2 auth

Each entry configures one authenticator, and each authenticator consumes one credential scheme. A request is routed by the scheme it presents — never guessed from the token’s bytes — so two entries claiming one scheme are refused at launch. A request presenting two credentials at once is answered 400, and one presenting none is answered 401 unless the endpoint configures noauth.

3.2.1 noauth

Authenticates every request as one fixed account. It consumes no credential and so acts as the endpoint’s fallback.

subject
The account every request acts as. Default "anonymous".

3.2.2 apikey

Presented as the X-API-Key header. Keys shorter than 16 characters are rejected.

keys
An array of {"account": …, "sha256": …}. The configuration holds each key’s SHA-256 digest as lowercase hex, never the key itself, so a leaked file yields no usable credential — and the server cannot tell you a key it has forgotten.

3.2.3 jwt

Presented as Authorization: Bearer ….

algorithm
One of HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384, PS512, EdDSA. Taken from the configuration and never from the token.
key
A PEM public key, or the shared secret for an HS* algorithm. Exclusive with jwks_url, and one of the two is required.
jwks_url
A JWKS endpoint, for an issuer that rotates its keys.
jwks_refresh
How often that endpoint is refetched, as a duration. Default 5m.
account_claim
The claim carrying the account name. Default sub.
audience
Held to, when set. Absent, the audience goes unchecked.
issuer
Held to, when set. Absent, the issuer goes unchecked.

3.2.4 macaroon

Presented as Authorization: Macaroon …. The token’s identifier is the account and its first-party caveats are read as grants, which narrow what that account may do for this request alone (Section 4.4).

root_key
The symmetric secret shared with whatever mints macaroons for this server.

3.3 admit

admit lists the accounts this endpoint can act as. An account absent from every admit list is defined and unreachable, and an admit entry naming an account the roster does not define fails verify --level syntax.

Admission is per endpoint, and it is what separates one endpoint from another: an authenticator may resolve a request to any account it likes, but a request lands on the endpoint’s own roster, and an account it does not admit gets nothing.

4 Accounts and access

Who may read and who may contribute is a property of this server, not of the archive it serves. It belongs in the configuration, and it is never written into the graph as a claim: copy an archive to a second deployment and none of the policy below travels with it, which is the point. The graph holds what happened; the file holds who may act here.

4.1 The roster

accounts maps an account name to the grants it holds.

Example
"accounts": {
  "reader":      {"grants": ["R *", "R $branches", "R $archive"]},
  "contributor": {"grants": ["CR proj-*", "R $branches"]},
  "ops":         {"grants": ["CR *", "CR $branches", "R $archive", "R $universe"]}
}

An account is a name and a list of rights, and it carries no credential of its own. What proves a request is that account belongs to an endpoint’s auth section (Section 3.2), and which endpoints can reach it at all is admit (Section 3.3).

4.2 Grants

A grant is one string, "RIGHTS TARGET": the rights as letters run together, then what they apply to. The rights are CRUD.

C
Contribute claims to a branch, creating the branch if it is new.
R
Read it.
U
Overlay an existing claim with a newer version.
D
Delete claims, which is physical removal rather than a mutation.

"CR proj-alpha" is one grant conferring two rights. Rights are additive across an account’s grants: a request is allowed when any one grant carries the right and matches the target.

The target is either a branch pattern — lowercase letters, digits and -, with * and ? as wildcards — or one of four reserved names.

$branches
The branch table itself. R lists the branches; C is what creates one, since the table is a claim and adding a branch contributes to it.
$archive
The Ranke-Archive as a whole — its head and shape.
$universe
The unconfined read across everything stored, whatever branch it belongs to. Only R applies here; any other letter is refused.
$sequencer
Reserved for the Sequencer. A grant may name it and no operation asks for it yet.

Grants are checked when the file is parsed, so an unknown right letter, a branch pattern with an illegal character, or "C $universe" fails verify --level syntax rather than at the first request.

4.3 What each operation requires

The caller asks forThe grant it needs
Query claimsR on the queried scope
One claim, or its content, by idR on the scope it is read through
A branch’s head, or its head and heightR on that branch
The branch listR $branches
The archive’s head and shapeR $archive
Contribute claims to a branchC on that branch
Create a branchC $branches
Purge claimsD on every branch holding them
Health, storage layers, verification runsnone

U and D are accepted in a grant, and no route exercises them yet: nothing served overlays or purges a claim, so an account holding them can do what a CR account can and nothing more.

4.4 Attenuation

A macaroon carries first-party caveats, and each is read as a grant of the opposite polarity: the account’s own grants say what it may ever do, the caveats narrow what this particular token may do, and a request must satisfy both. Caveats accumulate — every one of them must allow the request, so passing a token on after adding a caveat can only ever hand over less.

Nothing else moves rights at run time. There is no call that edits the roster, and no claim in the graph that confers one: to change who may do what, stop the instance, edit the file, and start it again.

API The REST contract

Every endpoint the server serves, and what each one is for. The contract is generated in the part's own repository and ships with its manual.

Download the contract — OpenAPI 3.0.3, ranke-db API 0.2.0, and the file to point a client generator at.

read

MethodPathWhat it does
GET/archive/claims/{id}Fetch a claim within the archive head's closure
GET/archive/claims/{id}/contentFetch the content of a claim within the archive head's closure
GET/archive/infoWhat is known about the archive
GET/branchesList the branch table's branches
GET/branches/{branch}/claims/{id}Fetch a claim within a branch's closure
GET/branches/{branch}/claims/{id}/contentFetch the content of a claim within a branch's closure
GET/branches/{branch}/headCurrent head id of a branch
GET/branches/{branch}/infoWhat is known about a branch
POST/queryRead the graph with a declarative query
GET/universe/claims/{id}Fetch a claim by id from the Universe (privileged)
GET/universe/claims/{id}/contentFetch the content of a claim by id from the Universe (privileged)

write

MethodPathWhat it does
POST/contributeContribute signed claims (atomic)

dev

MethodPathWhat it does
POST/dev/clockAdvance the dev sequencer's clock

system

MethodPathWhat it does
GET/healthLiveness and signer identity
GET/system/layersList storage layers
GET/system/verificationsList verification runs
POST/system/verificationsStart a verification run
DELETE/system/verifications/{reportId}Delete a verification run
GET/system/verifications/{reportId}Show a verification run
POST/system/verifications/{reportId}/cancelCancel a running verification run