Expand description
Where the control plane’s own state lives — the catalog, the identity
table, and every document crate::doc puts behind a name.
§Why this is not the object store any more (the certificate-identity plan)
Until 2026-08-09 a control document was a blob in the repository object
store with a reference pointing at it — gunnar’s own ObjectStore and
RefStore traits, both since deleted in favour of GitOps: git’s own
trick, and defensible while the only durable backend gunnar had was a git
directory. It stopped being defensible when the storage engine landed
(the storage restart). That engine takes objects as packs — GitOps::put_pack is
where the closure check and the durability ordering live — and it refuses a
single-object write by design. So principal add and namespace create
both died before they reached any of their own logic, with
the znippy-git store takes objects as packs, not one at a timeThe fix is not a pack wrapper around a 200-byte JSON blob. User records were never git objects for a reason; they were git objects because the control plane predates anywhere better. This is that better place: an embedded key/value store with real transactions, which is what a compare-and-swap on a document wanted all along.
redb adds nothing to the appliance that was not already there — it is the znippy engine’s own tail — and it has no build script beyond a version probe and no dependencies of its own.
§What is stored, and in which table
documents name → 32-byte blob id the current version
blobs 32-byte id → bytes content-addressed
document_history (name, seq) → millis ‖ blob id append-only
secrets key → tag ‖ sealed bytes crate::secretsThe fourth table arrived on 2026-08-10 and finished the migration this one
started: the SSH host key and the issuer private key were still in an Arrow
log inside control.git, which is what kept gunnar-server opening a
repository — and therefore needing a storage engine — in order to boot. It
is a table here rather than a document because a secret is not a document:
there is no compare-and-swap on it, no history worth keeping (a superseded
host key is the defect, not the audit trail) and no content addressing to
want. See crate::secrets, which owns everything about its shape.
Content addressing is kept, and not out of nostalgia for git. Three
properties of crate::doc rest on it and would each have to be rebuilt
without it: a shard that did not change keeps its id, so a document that did
not change hashes to the same bytes and a no-op update is visible as the
pointer did not move rather than as a new version; a losing writer’s shards
are simply unreferenced rather than needing a rollback; and a reader that
follows a document to its shards can never find a leaf that was rewritten
underneath it.
§The two things moving off git objects cost (the certificate-identity plan)
- Replication. Metadata used to replicate as a repository, by the same
mechanism as everything else:
control.gitmirrored, and the catalog and identity table came with it. It does not any more.<data-root>/control.redbis a file a standby has to be given some other way, and there is no mechanism here that gives it one. Named, not rebuilt. - History. A ref update per change was a free audit trail: every previous
version of the identity table stayed in the object store with the reflog
ordering them. That is not free on a key/value store, so it is explicit:
[
DOC_HISTORY] is appended in the same write transaction as the pointer move, andControlStore::historyreads it back. Rebuilt, deliberately — an ACL whose changes cannot be listed is an ACL nobody can audit, and the whole table is one B-tree append per change.
Nothing prunes either table. Old document versions and superseded shards
accumulate exactly as loose objects did, which is the same bargain git makes
and the same one a gunnar control gc would have to unmake.
Structs§
- BlobId
- A content address: sha-256 over the stored bytes.
- Control
Db - The one
redb::Databaseunder the control plane — and the door the runtime data-set growth takes it off the stripe through. - Control
Export - The control database’s current state, read without taking it off the stripe: every
document pointer, every blob, every secret row (
None= a tombstone; values are the SEALED bytes). What a twin snapshot carries and what a restored box re-seals. - Control
View - See
ControlDb::view. - DocVersion
- One version a document has had, out of
ControlStore::history. - RamCopy
- The RAM copy of the control database, taken by
ControlDb::suspendthe instant beforecontrol.redb’s file is closed and held untilControlDb::resumereopens it. Every current document pointer, every blob (current versions, their shards, and the history — the blobs table whole, because a shard is reached from a document by id and nothing short of the table says which ids a document reaches), and every sealed secret row. Reads are answered from it verbatim; writes are refused by name (Error::OffStripe). - Redb
Control Store - The redb-backed
ControlStore: one file, three tables. - Resume
Report - What
ControlDb::resumefound when it compared the disk to the copy.
Enums§
- Export
Row - One row of
ControlDb::export_each, borrowed from the read transaction (or the RAM copy): nothing of the database is gathered to hand it over.
Constants§
- BLOB_
ID_ LEN - How many bytes a
BlobIdis. - RAM_
COPY_ CEILING_ BYTES - The most a RAM copy may weigh before
ControlDb::suspendrefuses: the control database is small by design (documents and shards, no git objects), and a copy past this is a sign something else is in it. - TWIN_
APPLIED - The last transaction-log entry whose control-plane writes are in this database — a
twin’s applied watermark. Committed in the SAME redb transaction as the writes it vouches
for (
ControlDb::commit_group), so after a crash the database is exactly the state through the marker, and the entries after it are re-applied in order from there. A twin used to re-apply only its log’s head, which is exact only when every entry is made durable on its own; a group commit needs the marker.
Traits§
- Control
Store - Where a control document is kept.
Functions§
- memory_
control open_controlwith no file under it, for tests and for a--dry-runthat must not touch the data path.- memory_
control_ store - An in-memory control store, as a
SharedControlStore. - open_
control - The whole control plane at
path: the documents and the secrets, over one database handle. - open_
control_ store - The redb store at
path, as aSharedControlStore. - open_
control_ with_ db open_control, and theControlDbhandle both halves share — what the runtime data-set growth suspends and resumes (gunnar-server’sgrow). The handle is the same object either store reaches; it is returned separately only because the store is type-erased on the way out.
Type Aliases§
- Shared
Control Store - A control store, ready to be handed to
crate::AuthTableandcrate::RepoCatalog.