Skip to content
Registry StackDocsDevelopment (unreleased)

Evaluate Base Registry Engine

View as Markdown

This page is for someone sizing up Base Registry Engine before committing a database, a token issuer, a signing process, and operational capacity to it. It assumes the fit question in Which product fits your problem is settled and asks the next one: what does running it cost. The Base Registry Engine overview defines the terms this page uses.

Base Registry Engine is one crate, registry-breg, and one binary, breg. One process verifies one package and serves its compiled REST API against one PostgreSQL database, with no control plane or worker process beside it. The crate is unpublished, so a source build means building the Registry Stack workspace.

At startup the process reads one runtime configuration file, kind: BRegRuntimeConfig under registry.registrystack.org/breg-runtime/v1alpha1. It binds a listener, a deployment identity, a package directory with its trust anchor (the public keys a package signature may come from, and how many must sign), two database connection references, an OpenID Connect verifier, an audit key reference, a cursor secret reference, and one binding per declared webhook destination. The file holds references, never secret values: a database block carrying a url, password, or plaintext member is refused. Secrets resolve through a file provider (owner-only files under one root) or, when declared, an environment provider.

The process keeps no local state. The audit journal, revision history, outbox (the table holding one row per configured event until it is delivered), and change-request rows live in PostgreSQL, and the release image carries no writable directory. Metrics are opt-in: a second listener on a loopback or private address serves GET /metrics in Prometheus text format; a public, unspecified, or shared address is refused.

bregctl is the second binary. Its authoring commands (init, check, explain, generate, project) open no database, so the modeling loop runs on a laptop. test needs an empty schema-test database and real tokens; apply, doctor, migration, history, audit, webhook list, webhook replay, and request-retention read the runtime configuration and open the database; verify reads the same file but opens no runtime dependency; data import and data export talk to a running server over HTTP.

PostgreSQL is the only database; SQLite is not a compatibility target. The runtime refuses a server older than 15, and a project with spatial fields needs 16 or newer for its bounding-box role plus PostGIS 3.5 or later. PostGIS is required only for spatial predicates: ordinary Point storage and GeoJSON reads work without it. Geometry support is CRS84 points with bounding-box queries; polygons and other geometry types are not a field type the schema offers, so store them elsewhere and reference them by identifier. The quickstart pins PostgreSQL 17.11. The administrator installs btree_gist, and PostGIS where needed, in a schema neither registry role owns; neither role creates a schema, an extension, or a role.

The connection path is fixed: tokio-postgres 0.7.18, deadpool-postgres 0.14.2, and tokio-postgres-rustls 0.14.0, with TLS verified against the host’s native roots or an operator-supplied certificate authority; plaintext is confined to the test harness. Two login roles are required, migration (owns the schema, applies packages) and runtime (serves), and the configuration refuses one role or one connection reference for both. Startup verifies ownership, privilege, and extension boundaries for both roles and refuses to serve when they are wrong; generated row-level security policies run under the runtime role. Those policies defend against application mistakes, not against a party holding the runtime credential, so credential custody stays with you.

A token issuer. The server verifies bearer access tokens and issues none. The verifier is OpenID Connect: one issuer, one audience, one algorithm, a maximum token lifetime, and the claim names that carry the principal and purpose. Keys come from issuer discovery or from a static JWKS document held behind a secret reference; static keys load once at startup and rotate through a restart. Deployments without an identity provider can run Registry Mint, which the first tutorial uses; a registry depends on Mint only when Mint is its issuer.

Signing keys for production packages. bregctl package compiles a production package and stops at awaiting_signatures; it never accepts a private key. An external signer produces a detached Ed25519 signature over signing-input.json, and a second package run with the signature document publishes the package. The trust anchor the runtime file names lists the accepted public keys and a threshold, bound to the deployment identity. A threshold of zero is valid only for the local environment; production needs at least one. Who holds those keys, and how they sign, is a decision the product leaves to you.

Nothing else at runtime. Webhook delivery runs inside the breg process from an outbox table written in the same transaction as the change, so there is no message broker. No cache, object store, or second service takes part in a request. Registry Relay and Evidence Gateway are separate products, not dependencies.

Base Registry Engine ships first in Registry Stack v0.26.0, published 2026-09-04, and the Python and Node client packages follow in v0.26.1, published the same day, so an application that will call the registry through a client library needs a v0.26.1 or later deployment. A release carries three runtime artifacts for the product:

  • breg and bregctl binaries for each platform in platform support, with a SHA256SUMS file.
  • An installer, breg-install.sh, that verifies both binaries against the checksums before anything reaches the install directory and installs both or neither. ~/.local/bin is the default directory, BREG_INSTALL_DIR changes it, and BREG_ASSET_DIR reads already-verified assets. It does not verify release authenticity; OpenSSF and release trust covers the chain that does.
  • A container image, ghcr.io/registrystack/breg, built on distroless nonroot (uid 65532). Its entrypoint runs breg --config /etc/breg/runtime.yaml, it exposes port 8080, and it contains the runtime binary and the license only. Configuration, package, trust anchor, and secrets mount read-only under /etc/breg. There is no shell and no healthcheck subcommand; probe GET /health or GET /healthz for liveness and GET /ready for readiness.

The release ships no Helm chart, Compose file, or hosted offering; scheduling, TLS termination, and the reverse proxy in front of the listener are yours. The listener derives no authority from the request Host or forwarded headers; generated links use the configured publicOrigin. The quickstart’s Docker use is a loopback development path, not a deployment pattern. Keep bregctl and breg on one release: the installer installs the pair from one tag, and both binaries must compute the schema fingerprint a test receipt binds the same way.

The Rust client crate, registry-breg-client, is unpublished (publish = false), so a Rust consumer pins the workspace by git tag. Query a registry from Python and Node covers the client packages an application installs.

You hold six kinds of secret material: two database connection strings (runtime and migration), the audit hash key, the cursor secret, one HMAC-SHA-256 key per webhook destination, and the OpenID Connect key material when you pin a static JWKS. Package signing keys stay with the signer and never reach bregctl or the server. The audit key and the cursor secret must outlive the journal and any cursor still in a client’s hands. A package may not embed a trust anchor, a runtime secret, a migration credential, or a signing key; the layout contract forbids those roles.

Every production change, including a one-line access widening, goes through check --production, test against a schema-test database with real tokens, package, external signing, and apply. The test receipt binds the candidate source, database identity, schema fingerprint, and signature policy, and package refuses a receipt taken for anything else. Budget for a signer who is not the author: the split between authoring, signing, and migration authority is deliberate.

Activation runs under the migration credential, which the serving host does not need between activations. A successor package starts from diff against the active runtime configuration. A change that diff classifies as additive needs no further evidence; a change it marks for review needs a reviewed migration directory with a descriptor, a rehearsal receipt, SQL steps, and, for a destructive change, a backup artifact, all validated by test and package and never generated by them. A failed activation leaves the database in maintenance with readiness unavailable and the target pinned; migration reconcile reports the one safe transition, and the remaining case is restoring your pre-activation backup. No command clears a failed maintenance state on its own.

Every admitted request and every refusal that names a principal appends one hash-chained record to the journal in PostgreSQL. Refusals of unauthenticated requests are counted on the metrics listener instead, so an anonymous caller cannot grow the chain. bregctl audit verify walks the chain, audit export writes it as JSON Lines, and audit prune removes the prefix older than a boundary after a dry run. A prune leaves no copy behind, so export first; after a prune, verification proves the retained links, not completeness.

Retention obligations are met by bounded operator commands, not API permissions. history erase removes the retained revisions of one record up to a revision number, at most 10,000 revisions per transaction, with no dry run and no undo; it also scrubs affected correction context, retained webhook payloads, and cached idempotency responses. Snapshot references at or after the erased commit become unavailable until a rebaseline re-establishes coverage, and history rebaseline refuses a registry with more than 1,000 live rows, so a larger registry cannot restore snapshot coverage after an erasure. Change-request detail has its own retention mode per request entity, and request-retention erase works only where the mode is operator_erase. Retained webhook payloads expire after payloadRetentionDays, 7 by default and at most 30. Retain, erase, and audit walks through each retention command end to end.

The product has no backup command and restores nothing itself. Your PostgreSQL backup, taken before every apply, is the recovery path for a failed activation that reconcile reports as unresolvable, and a destructive migration must name its backup artifact on the apply command line. Erased history is gone from the database; account for backups, exports, and copies delivered to consumers under your own retention policy.

No throughput or latency figure is committed for Base Registry Engine, and this page states none. What exists is a k6 load-test harness that runs against a disposable PostgreSQL 17, Registry Mint, the business-establishments acceptance fixture, and the private metrics listener. The steady profile asks whether 50 mixed operations per second hold for 10 minutes with p99 under 250 ms; sweep, burst, herd, and token-soak step, spike, stampede, and soak the same registry, and cursor-smoke is the end-to-end check. Runs write a k6 summary, one-second server telemetry, and a report with p50, p95, and p99 by operation. The README’s own caveat applies: figures from Docker on macOS are directional, and capacity claims are to be re-run on a representative Linux host before anyone cites them.

What is bounded by construction is easier to state. Every request runs under operator-set timeouts: HTTP requests at most 60 seconds, record locks at most 30, migration locks at most 300, and migration statements at most one hour. The connection pool has a configured maximum size, and its occupancy gauges are published on the metrics listener. GeoJSON and bounding-box responses are capped at 2 MiB of canonical output and refuse rather than truncate. Metrics label sets are closed, so a scrape cannot grow with traffic. The runtime applies no request-rate limit of its own; a registry that admits anonymous reads belongs behind an upstream limiter.

Registry Stack is pre-1.0. The compatibility promise in API stability and versioning takes effect at v1.0.0; until then a minor release may contain breaking changes, each announced with a BREAKING: entry in the release note, and a patch release stays compatible with the latest minor. Security fixes follow the roll-forward model in Support window: pre-1.0 releases receive fixes only in the latest v0.x release, so a deployment pinned to an earlier minor has to upgrade to be covered.

The product’s own contracts carry that status: the runtime configuration is v1alpha1, and v0.26.0 is its first release. The security posture is already fixed: the invariant matrix names 31 invariants, each enforced with a named negative test, covering package verification, role and row-security boundaries, claim handling, connection-context cleanup, and value-free refusals.