Skip to content

The config store

The config store is what turns Hivemind from a single-operator tool into a product: per-user configuration, stored in the Hub, owner-scoped, consumed per need. It holds the saved records that the runtimes read — how to deploy a Commander, how to run a workflow, and how each target should be gated — each owner-scoped and validated against its schema.

Status — dark by default, and single-tenant

The config-store surface is dark behind a default-OFF flag (HIVEMIND_CONFIG_STORE_ENABLED) and is not mounted unless it is set, so it does not exist on an instance that has not enabled it.

Where it is enabled, owner scoping is real: every read and write is scoped by (tenant, owner_id) resolved from the verified principal — never from a request body or query parameter — and the filter is fail-closed.

Multi-tenant isolation is designed, not shipped. The tenant half of that key is currently the instance's own value, so it separates owners within an instance rather than customers across one. Deployments are single-tenant today and we would not put two customers on one instance. Read the per-tenant isolation described below as the design it is aiming at, not as a property to rely on — the difference between a truthful gap and a security misrepresentation is exactly this paragraph.

The store invents no new runtime. It is CRUD plus schema validation plus provenance over a small set of record types; each record is consumed by the runtime that already knows how to act on it.

The record types

Record What it configures Consumed by
commander_deployments a saved "deploy a Commander like this" — box + project + model + budget + cred refs the remote-Commander control surface
workflows a saved LLM-workflow — model, prompt, allowed actions, trigger, output, gates, budget, cred refs the workflow runtime — not wired in this release, so these rows are stored and never executed (see that page's Status)
gate_rules per-(target, action) operator rules — auto / ask-me / never the unified gate evaluator

A Commander-deployment record references, rather than copies, the composed pieces: the target box, the project the Commander coordinates, the model, and a budget ceiling. Its per-target gate rules are not embedded — they live in gate_rules keyed by that box. The deployment points at the box; the gate model composes the rules for it. This compose-not-duplicate discipline runs through the whole design: one fact lives in one place and is referenced, never mirrored.

With a saved deployment, the control surface's deploy path can consume a stored record by id — "config stored in the Hub, used per need" — instead of a raw request body.

Isolation is the point

Every record carries two keys:

  • owner_id — the per-user key within a tenant.
  • tenant — the outer boundary.

Every read and every write filters by both, resolved from the request's verified principal. A second user cannot read, list, or consume another user's saved deployment — the (tenant, owner_id) filter denies. A tenant mismatch denies exactly as a capability check denies. That cross-user isolation is the thesis proof of the config store: the smallest meaningful test is "user B cannot see user A's config."

Status — gate_rules does not implement the read half yet

Measured on main, for the gate_rules record type specifically:

  • Writes record both. upsert takes tenant and binds it into the INSERT.
  • The read path filters by owner alone. rules_for(owner_id) selects WHERE owner_id = $1; the tenant column is written and never read back.
  • There is no production writer. upsert's only callers are inside a test, so on a customer install the table holds no rows.

So for this record type the paragraph above describes the intended design rather than what the code implements, and what is missing is the read-side filter — unbuilt work, not a control that has been defeated. Nothing is mis-scoped on a customer install, because there is nothing in the table to mis-scope.

Corrected 2026-09-17 (HIVE-1279, finding C07): the "both" sentence was written for every record type and is not yet true of gate_rules.

The schema is designed to be defense-in-depth ready. In the shared-instance model, a per-connection tenant setting plus row-level security in the database is the backstop so a missing WHERE clause cannot leak across tenants; in the container-per-tenant model the container itself is the boundary and row-level security is optional. Building the schema isolation-ready from the start costs little and keeps the shared-SaaS move a later flip rather than a rewrite.

Tenancy models

Two deployment shapes reach the same product surface:

  • Container-per-tenant — each customer gets their own Hub container, database, and encryption key. Isolation is the container/DB boundary: the strongest isolation, and the shape the platform runs today. Productization here is per-user config and isolation within a tenant's instance.
  • Shared multi-tenant — one Hub serves many tenants; the tenant is resolved per-request from auth, with row-level isolation and per-tenant credential-key derivation. Maximum density, at the cost of moving the isolation boundary from the container to every query — the classic multi-tenant-SaaS hard problem.

The config store and credential scoping work identically in both models, so they are built first and the model choice is deferred. The near-term default is container-per-tenant with the shared-tenant seam kept live, so the shared move is a configuration flip, not a re-architecture.

Dark by default

The productization records land behind a flag that defaults OFF, following the same dark-launch pattern as the rest of the Hub. The record and its isolation are proven before any live deployment consumes them.