Skip to main content

Module control

Module control 

Source
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 time

The 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::secrets

The 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.git mirrored, and the catalog and identity table came with it. It does not any more. <data-root>/control.redb is 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, and ControlStore::history reads 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.
ControlDb
The one redb::Database under the control plane — and the door the runtime data-set growth takes it off the stripe through.
ControlExport
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.
ControlView
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::suspend the instant before control.redb’s file is closed and held until ControlDb::resume reopens 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).
RedbControlStore
The redb-backed ControlStore: one file, three tables.
ResumeReport
What ControlDb::resume found when it compared the disk to the copy.

Enums§

ExportRow
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 BlobId is.
RAM_COPY_CEILING_BYTES
The most a RAM copy may weigh before ControlDb::suspend refuses: 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§

ControlStore
Where a control document is kept.

Functions§

memory_control
open_control with no file under it, for tests and for a --dry-run that 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 a SharedControlStore.
open_control_with_db
open_control, and the ControlDb handle both halves share — what the runtime data-set growth suspends and resumes (gunnar-server’s grow). 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§

SharedControlStore
A control store, ready to be handed to crate::AuthTable and crate::RepoCatalog.