Configuration Reference
Application gRPC has a closed listener profile: development-only cleartext on a literal loopback address, verified direct TLS on TCP, or a protected Unix socket. Hosted MCP supports literal-loopback cleartext or verified direct TLS on TCP. There is no insecure remote mode. See Remote and Local Application Ingress.
Project riffdb.toml
The database-shaped init, push, generate, status, diff, and migrate
verbs use a closed project document. They select riffdb.toml in the current
directory by default; explicit --config and then RIFFDB_CONFIG take
precedence. Other CLI commands retain the existing no-discovery behavior.
[client]
endpoint = "http://127.0.0.1:7443"
database = "default"
[project]
schema = "riffdb.application.json"
generators = ["rust"]
The schema path must be bounded, workspace-relative, and free of . or ..
components. generators contains one through four unique values from rust,
go, typescript, and python. Unknown keys, duplicate targets, symlinked
configuration files, absolute or escaping schema paths, and empty target lists
reject without fallback. See the Database-Shaped Project
Workflow for command semantics.
riffdbd
Every server field resolves independently in this order:
- an explicit CLI value;
- its
RIFFDB_*environment value; - an explicitly selected TOML document; then
- the built-in safe local default.
A present invalid higher-precedence value rejects instead of falling through.
The TOML path is selected by --config, then RIFFDB_CONFIG, then absence.
There is no implicit configuration-file discovery.
The legacy database, environment, and backup settings below define one database
with alias default. A process may instead define 1 through 512
[databases.<alias>] tables. Mixing the legacy and named forms rejects.
| Setting | CLI | Environment | TOML | Default | Restart | Impact |
|---|---|---|---|---|---|---|
| Database | --database | RIFFDB_DATABASE | server.database | data/riffdb.redb | Yes | Correctness, storage compatibility |
| Application listener | --listen (legacy loopback only) | RIFFDB_LISTEN (legacy loopback only) | server.grpc_listen or server.application_listener | 127.0.0.1:7443 loopback cleartext | Yes | Availability, security |
| Environment | --environment | RIFFDB_ENVIRONMENT | server.environment | local | Yes | Capability authentication compatibility |
| gRPC audience | --audience | RIFFDB_AUDIENCE | server.audience | riffdb-grpc-loopback | Yes | Capability authentication compatibility |
| Hosted MCP listen | --mcp-listen | RIFFDB_MCP_LISTEN | server.mcp_listen | absent | Yes | Availability, security |
| Hosted MCP origins | repeatable --mcp-origin | RIFFDB_MCP_ORIGINS | server.mcp_origins | empty | Yes | Browser-origin security |
| Metrics listen | --metrics-listen | RIFFDB_METRICS_LISTEN | server.metrics_listen | absent | Yes | Observability, loopback security |
| Telemetry flight recorder | --telemetry-flight-recorder | RIFFDB_TELEMETRY_FLIGHT_RECORDER | server.telemetry_flight_recorder | absent | Yes | Observability, disk usage |
| Telemetry flight interval | --telemetry-flight-interval-ms | RIFFDB_TELEMETRY_FLIGHT_INTERVAL_MS | server.telemetry_flight_interval_ms | 10000 | Yes | Observability sampling cadence |
| Backup root | --backup-root | RIFFDB_BACKUP_ROOT | maintenance.backup_root | $CWD/backups | Yes | Correctness, recovery, security |
| Capability key path | --capability-keys | RIFFDB_CAPABILITY_KEYS | server.capability_keys | config/capability.keys | Yes | Authentication and key compatibility |
| Idempotency key path | --idempotency-keys | RIFFDB_IDEMPOTENCY_KEYS | server.idempotency_keys | config/idempotency.keys | Yes | Same-key recovery compatibility |
The only TOML tables and keys are:
[server]
database = "/var/lib/riffdb/data/riffdb.redb"
grpc_listen = "127.0.0.1:7443"
environment = "local"
audience = "riffdb-grpc-loopback"
capability_keys = "/etc/riffdb/capability.keys"
idempotency_keys = "/etc/riffdb/idempotency.keys"
# mcp_listen = "127.0.0.1:7444"
# mcp_origins = ["http://127.0.0.1:3000"]
# metrics_listen = "127.0.0.1:9464"
# telemetry_flight_recorder = "/var/lib/riffdb/telemetry/flight.ndjson"
# telemetry_flight_interval_ms = 10000
[maintenance]
backup_root = "/var/lib/riffdb/backups"
Loopback metrics and telemetry flight recorder
When --metrics-listen (RIFFDB_METRICS_LISTEN or server.metrics_listen) is
configured, riffdbd binds a literal IPv4 or IPv6 loopback address with a
non-zero port and serves read-only HTTP/1 GET /metrics in Prometheus text
format (text/plain; version=0.0.4; charset=utf-8) and GET /v1/metrics in
OpenTelemetry JSON ExportMetricsServiceRequest format (application/json).
Non-loopback addresses, zero ports, non-GET methods, and other request paths
fail closed. All exported counters, gauges, and stage histograms use closed
compile-time label vocabularies and numeric identifiers; raw tenant, entity,
request, and secret values are never emitted.
When --telemetry-flight-recorder <PATH> (RIFFDB_TELEMETRY_FLIGHT_RECORDER or
server.telemetry_flight_recorder) is configured, riffdbd appends one bounded
riffdb.telemetry.flight-record.v1 NDJSON line at startup, every
--telemetry-flight-interval-ms (100..=3600000, default 10000), and after
graceful drain on shutdown. Operators can aggregate a recorded flight file
offline with riffdb telemetry summarize <PATH> (in human or --output json
mode) to inspect throughput deltas, stage latency p50/p95/p99 high-water
marks, writer busy saturation, health transitions, online retention state/fence
and pruned-sequence progress, and storage/journal byte growth across 24-hour and
72-hour soak runs.
Named archive directories
An operator may bind up to 16 archive names per database in TOML. The legacy
single-database form uses [[maintenance.archives]]; the named form uses
[[databases.<alias>.archives]]. Each entry requires name, path, and encryption:
[[maintenance.archives]]
name = "daily"
path = "/var/lib/riffdb/archives/daily"
encryption = "operator_managed"
Names are checked identifiers and must be unique within their database. Paths
must be bounded absolute directories and disjoint from every database, backup,
projection, archive and other reserved process path. Invalid or overlapping
entries reject configuration. encryption is either unencrypted or
operator_managed; the latter declares external operator-provided encryption,
and does not ask RiffDB to encrypt files. Archive paths never come from restore
request input.
Add backup = "baseline" to the entry to collect published changelog frames
beside the verified full backup named baseline under this database’s
backup_root. Without backup, the entry is available for restore only.
The backup must already exist when the database starts. Each collector verifies
its backup and existing archive before appending; it never rebinds an archive
to a different backup or history incarnation. Use a new directory for a new base.
Collection runs independently of command acknowledgement and the write gate.
Each collector buffers at most one uncertain frame and retries those exact bytes
up to three attempts. A missing or invalid backup, unavailable startup sink,
pruned history gap, changed lineage, or exhausted append retries stops that
collector with a redacted diagnostic. It does not stop primary writes. Correct
the cause and restart the database to revalidate and resume. Shutdown joins all
collectors before closing storage. The POC supports local filesystem sinks;
operator_managed encryption must be supplied outside RiffDB.
See Backup and Restore.
Durable local outbox destination
An optional [maintenance.outbox] section, or [databases.<alias>.outbox]
for named databases, configures one durable_local destination with an absolute
protected directory and positive quota_encoded_bytes. Configuration is
restart-applied and refused on followers. See Durable Local Outbox
Archive for custody, delivery, accounting,
and offline inspection.
Online retention
riffdbd can compact commit history while it serves. The legacy
single-database form uses [maintenance.retention]; the named form uses
[databases.<alias>.retention]:
[maintenance.retention]
retained_sequences = 1000000
sequences_per_epoch = 256
Set exactly one of retained_sequences, a positive count of commit sequences
to keep below the durable application head, or hold = true.
sequences_per_epoch bounds each step, from 1 through 256 (default 256). An
absent section is a hold, so a configuration that never names retention prunes
nothing online. Targets are sequence counts, never ages. Any other key, both
forms together, or an out-of-range value rejects configuration.
With a target, one background lane per database advances the retention watermark in steps. Each step is one administration transition through the database’s single ordered writer. It removes whole command segments, appends the chained tombstone, and advances the watermark in one atomic commit. A journal-backed command waits until its extent is sealed. A sealed extent may span several logical prune steps; each step still covers at most 256 sequences. Logical pruning keeps the extent’s bytes intact. The lane reclaims a whole file only after every command in it has been pruned and the existing source-history protocol has released every receipt reference. The retained source resume anchor, older pinned snapshots, and an active background scrub keep the file alive. The lane never creates a source acknowledgement or discards a receipt to free space. One retired file can supply the next preallocated journal spare; excess eligible files are deleted. File work runs outside the ordered writer gate. Repeated requests for a missing spare share the unfinished preparation job and its completion signal. They do not queue additional waiting sealer threads or create new extent-publication obligations. Each actual extent publication keeps its own completion tracking. A completed failed preparation permits a fresh attempt; it does not certify a failed extent publication. The lane holds no exclusive lease and opens no second writer. A writer waits behind at most the one step in progress. After each attempt that took the writer gate, the lane rests nineteen times the time it held the gate, so it holds the writer at most about five percent of the time and a large backlog compacts gradually rather than at full speed. It plans each step without the writer gate and takes the gate only when it has history to remove. With the Standard durability profile, it prepares retained views and the tombstone digest from a pinned composite snapshot outside the gate. The writer revalidates every precondition and current fence before submitting one V4 prune frame through the existing group-sync journal lane. Durability completes after releasing the gate; watermark publication precedes the corresponding deletion view. It does not drain unrelated command frames just to prune. Checkpoint materialization remains background work. Hardened profile steps retain their direct engine transaction. Once the lane reaches its target or a fence it rechecks about once a second. A single command segment larger than the budget is removed alone, never more than 256 sequences. A crash at any point in a step reopens with the watermark either before or after that step, and the restarted lane resumes.
The lane never passes a fence: projection frontiers, durable event-consumer
checkpoints, undelivered outbox events, a staged contract migration, operator
holds, and registered followers’ acknowledged frontiers. An unreadable fencing
source stops the lane with an internal incident until the next restart; it never
prunes past it. No gRPC health member, control RPC, or MCP tool drives the lane,
and application principals have no retention operation. While riffdbd serves,
/metrics, /v1/metrics, and the periodic NDJSON flight recorder export the
closed online retention state (riffdb_online_retention_state /
online_retention_state), active binding fence
(riffdb_online_retention_fence / online_retention_fence), completed step
count (riffdb_online_retention_steps_total), pruned sequence count
(riffdb_online_retention_sequences_total), and latest watermark sequence
(riffdb_online_retention_watermark_sequence). The shutdown release census line
also records its state after graph teardown has joined the lane, the name of the
fence that last held it, and its final step and sequence counts. An enabled lane
normally ends as cancelled; a refused lane remains refused, and a configured
hold that starts no worker remains holding. The graph and process release
receipts share that frozen observation. The separate
riffdb-retention-extents-v1 line records files deleted or recycled, passes held
by references or readers, and off-writer reclamation microseconds. Logical
sequence progress does not imply physical space reclamation.
The retention lane also owns source-receipt reclamation across observations.
It validates immutable history in pages of at most 256 rows, then yields if a
writer is queued, a checkpoint is in flight, or the journal is not drained.
It also yields until off-gate extent sealing finishes, before capturing a
validation pin. A changed publication discards the candidate. At a later independently durable
checkpoint it rechecks consumer and derived-reader holds and removes at most
256 receipts. Source holds retain their separate 4,096-registration bound.
Its own reclamation and prefix-refresh receipts cannot create an
idle reclamation loop. The separate riffdb-retention-receipts-v1 shutdown line
records removed receipts and gate_us; that time participates in the lane’s
existing writer-occupancy pacing. Receipt removal does not prove that an extent
was deleted or reused.
Eligibility planning and checkpoint licensing resolve receipt points on the same immutable source view that passed native validation, including its acknowledged journal suffix. A newer installed checkpoint does not extend an older captured source’s readable range. Missing coverage beyond that range yields; a missing receipt inside claimed coverage still refuses the lane.
Follower acknowledgement preparation shares this source-proof owner and its
maintenance rest deadline. Initial validation runs in bounded pages outside the
writer; the closed acknowledgement enters the existing commit coordinator only
after preparation. A completed candidate retains custody until admission,
yield or cancellation. Ordinary queued writers keep their FIFO order. The
32-frame report cadence and existing request deadlines are unchanged.
The internal riffdb-follower-ack-work-v1 diagnostic reports numeric phase
(1 preparation, 2 admission), result (0 ready/completed, 1 yielded, 2 refused),
off_gate_us, writer_wait_us, gate_held_us, initial_rows and
continued_rows. Writer wait and gate residence come from the native FIFO
census; off-gate elapsed work excludes both. Durations are
elapsed observations, not CPU measurements or a latency promise; the maximum
unsigned value means unknown. These observations contain no hold identity or
payload and grant no authority to advance a hold.
Held bootstrap manifest/page reads share that same source certificate through
a closed native read protocol. The first complete proof advances in bounded
pages outside the writer; subsequent checks use the qualified source pin or
checked native successors. Each release rechecks current exact custody after
bounded artifact I/O. Incomplete validation releases no bytes, and a reader
cannot displace a prepared follower acknowledgement. These remain foreground
reads under existing source-job capacity and deadlines; they create no control
write, reclamation license or extra maintenance budget. Receiver startup and
publication continue to use their complete validators.
The internal riffdb-bootstrap-custody-work-v1 diagnostic reports numeric
result (0 ready, 1 yielded, 2 refused), elapsed_us, initial_rows and
continued_rows for actual native proof work. It contains no hold identity,
payload or endpoint. Elapsed observations are not CPU measurements; the maximum
unsigned value means unknown.
The runtime binds the shared source scheduler even when retention is held. Acknowledgement custody uses the same rest policy. Rebinding cannot reset already charged rest or clock-overflow refusal.
After retention invalidates optional validated-prefix startup evidence, the maintenance lane can rebuild it from a complete structural, catalog-semantic, and retained-history scrub on one pinned snapshot. Each validation page contains at most 64 rows and observes shutdown cancellation. Population validation and checkpoint planning run outside the writer gate. Publication requires the exact unchanged history binding and a drained, sealed checkpoint; otherwise the candidate is discarded. The lane never reuses deleted evidence or forces a journal drain to publish it. Pin capture and publication participate in the existing maintenance pacing. Sustained-load recovery remains subject to the milestone’s first-successful-write rehearsal gate.
Pruned history stays auditable. A pruned command keeps its outcome and
provenance, so its idempotent retry answers the original result. Reads of its
events or commit, and commit scans that start at or below the watermark,
return the typed HistoryPruned error.
Read the retention limits in Known limitations
before enabling it. In particular, unless [maintenance.outbox] (or
[databases.<alias>.outbox]) is configured so
the local outbox worker archives and acknowledges emitted events, a contract
that emits events compacts only the history before its first undelivered event.
The offline riffdb retention operations remain available on a stopped
database; see Backup and Restore.
The legacy server.grpc_listen form always means literal-loopback cleartext.
For an explicit closed listener profile, omit grpc_listen and select exactly
one tagged table:
[server.application_listener]
mode = "loopback_cleartext"
listen = "127.0.0.1:7443"
[server.application_listener]
mode = "direct_tls"
listen = "0.0.0.0:7443"
public_endpoint = "https://riffdb.internal.example:7443"
certificate_chain = "/etc/riffdb/tls/server-chain.pem"
private_key = "/etc/riffdb/tls/server-key.pem"
[server.application_listener.bounds]
max_connections = 1024
max_streams_per_connection = 128
handshake_timeout_seconds = 10
idle_timeout_seconds = 300
keepalive_interval_seconds = 30
drain_timeout_seconds = 30
[server.application_listener]
mode = "local_socket"
path = "/run/riffdb/application.sock"
access = "owner_only" # or "owner_and_group"
The explicit listener table cannot be mixed with grpc_listen, --listen, or
RIFFDB_LISTEN. TLS has no cleartext fallback, trust-all
mode, mutual-TLS selector, cipher-suite selector, or provider selector. The
certificate must contain the DNS/IP identity in public_endpoint; validation
happens before bind. Certificate/key replacements are adopted atomically for
new handshakes only after the complete pair validates.
The equivalent multi-database form is:
[server]
grpc_listen = "127.0.0.1:7443"
audience = "riffdb-grpc-loopback"
capability_keys = "/etc/riffdb/capability.keys"
idempotency_keys = "/etc/riffdb/idempotency.keys"
[databases.ea]
path = "/var/lib/riffdb/data/ea.redb"
backup_root = "/var/lib/riffdb/backups/ea"
environment = "local"
[databases.budget_demo]
path = "/var/lib/riffdb/data/budget-demo.redb"
backup_root = "/var/lib/riffdb/backups/budget-demo"
environment = "local"
Aliases match [a-z][a-z0-9_-]{0,63}, sort by canonical bytes, and are unique.
All database, backup, projection, source/receiver artifact, and key paths across
the process must be pairwise lexically disjoint. The process opens every database and builds every graph
before publishing readiness. One structural startup failure stops the process.
When riffdbd runs in primary mode with [databases.<alias>] loaded from a
configuration file (--config or RIFFDB_CONFIG), updating that file on disk
reconciles added and removed database aliases online without interrupting peer
databases:
- Newly added
[databases.<alias>]sections are validated, opened, and published onto the gRPC and Hosted MCP routing tables only after startup and disjoint-path checks succeed. - Removed
[databases.<alias>]sections (when at least one database remains configured) are atomically unpublished from the routing tables, drained, closed with a clean-close checkpoint, and released from theirredbfile locks. - Any reload that fails TOML validation, introduces overlapping paths, mutates
process-global listener/audience/key settings, or changes a retained
database’s immutable storage paths or environment fails closed, logs a
riffdbd config reload rejecteddiagnostic, and retains the active inventory unchanged.
Backup roots must be siblings. This is valid:
[databases.default]
path = "/var/lib/riffdb/data/riffdb.redb"
backup_root = "/var/lib/riffdb/backups/default"
environment = "local"
[databases.ea]
path = "/var/lib/riffdb/data/ea.redb"
backup_root = "/var/lib/riffdb/backups/ea"
environment = "local"
This is invalid because one configured root contains another:
[databases.default]
path = "/var/lib/riffdb/data/riffdb.redb"
backup_root = "/var/lib/riffdb/backups"
environment = "local"
[databases.ea]
path = "/var/lib/riffdb/data/ea.redb"
backup_root = "/var/lib/riffdb/backups/ea"
environment = "local"
Startup reports the two conflicting logical roles and aliases, but never echoes configured path values.
The complete document is UTF-8 and at most 524,288 bytes. Unknown tables or keys, duplicates, wrong types, duplicate scalar flags, missing flag values, and empty selected values reject. Paths are nonempty, at most 4,096 platform bytes, and lexically normalized. The database and key paths may be relative to the server working directory; the backup root must be absolute.
The database path, backup root, projection root, source and receiver artifact roots, capability key path, and idempotency key path must be lexically disjoint. A file is not accepted when another configured path is equal to it, contains it, or is contained by it.
Each database reserves a private replication artifact root by appending
.riffreplication to its path (for example, data/main.redb.riffreplication).
The derived path also has the 4,096-byte ceiling. This directory holds at most
four source bootstrap artifacts and an exclusion lock; it cannot overlap any
other configured database or root. Source composition creates it with private
permissions and refuses symlink or identity substitution.
Each database also reserves .riffreceiver beside its database path, with the
same path-length and disjointness checks. Its fixed receiver inventory is
separate from source artifacts so promotion cannot mix the two layouts. The
managed receiver holds an exclusive inventory lock and creates private transfer
and candidate directories. Initial creation uses temporary names until the first
durable progress checkpoint exists; crash recovery discards only those known
unpublished files and resumes published progress. After authenticated attachment
succeeds, the receiver removes candidate scratch links and then transfer evidence,
while retaining the live follower’s engine lock. Interrupted cleanup resumes
against the validated live follower. See follower mode for daemon
configuration and lifecycle behavior.
The packaged systemd unit fixes the authoritative state root at
/var/lib/riffdb/data, the backup root at /var/lib/riffdb/backups, and the
configuration root at /etc/riffdb. The latter contains the root-owned
riffdbd.toml path-only configuration and the two protected digest-key
documents. It contains no raw digest key in TOML.
Per-database resource limits
An optional [maintenance.limits] table (or [databases.<alias>.limits] in the
multi-database form) bounds per-database storage, request rates, blocking pool
concurrency, commit coordinator capacity, journal extent sizing, and idle
hibernation:
[maintenance.limits]
max_storage_bytes = 536870912
max_write_rps = 50
write_burst = 100
max_read_rps = 250
read_burst = 500
max_blocking_operations = 4
blocking_worker_threads = 2
max_coordinator_capacity = 32
extent_profile = "compact"
idle_hibernate_seconds = 300
max_storage_bytes(65_536..): refuses new state-growing commands, contract deployments, migrations, and capability creations with typedOverloadedwhen the authoritative database file plus journal footprint reaches or exceeds the ceiling. Reads,ResolveCommandOutcome,RevokeCapability, and application exports (StartApplicationExport,GetApplicationExportPage,GetApplicationExport,CancelApplicationExport) remain permitted at the storage ceiling so data and outcomes are never stranded.max_write_rps(1..=1_000_000) and optionalwrite_burst(1..=1_000_000, defaultmax_write_rps): monotonic token-bucket governor for mutating commands and administration writes.max_read_rps(1..=1_000_000) and optionalread_burst(1..=1_000_000, defaultmax_read_rps): monotonic token-bucket governor for reads, queries, and export scans (GetHealthandGetStatisticsprobes are exempt).max_blocking_operations(1..=256),blocking_worker_threads(1..=8), andmax_coordinator_capacity(1..=4_096): override the per-database blocking pool and commit coordinator queue bounds.extent_profile("standard"or"compact", default"standard"): selects the per-database journal extent profile."standard"preallocates320 MiBactive and spare extents;"compact"preallocates an8 MiBactive extent (4 MiBcheckpoint rotation threshold) and defers.riffjournal.spareallocation until2 MiB(25%) active utilization or on-demand checkpoint rotation. Switching profiles across restarts normalizes empty active extents in place and seals extents that contain materialized history stubs before opening the next generation at the newly configured capacity.idle_hibernate_seconds(1..=86_400, default absent): enables scale-to-zero idle hibernation for a database in multi-database hosting. When no gRPC or hosted MCP request has touched the database foridle_hibernate_seconds, and the database has no active service jobs, no active commit notification subscribers, and zero pending outbox deliveries, the supervisor cleanly drains the database’s runtime graph, commits a clean-close certificate, and releases its exclusiveredbfile lock and worker threads while keeping the process and alias routes alive. The next gRPC or hosted MCP request targeting the hibernated database transparently wakes it via single-flight initialization before dispatching the request.
Optional hosted MCP settings:
| Option | Value | Restart | Impact |
|---|---|---|---|
--mcp-listen | Literal loopback address with nonzero port | Yes | Enables loopback-cleartext Streamable HTTP MCP |
--mcp-origin | Repeatable checked origin | Yes | Browser-origin authorization |
In TOML, [server.mcp_listener] may also select an explicit loopback_cleartext
or direct_tls profile (mutually exclusive with mcp_listen / mcp_origins /
--mcp-listen / --mcp-origin):
[server.mcp_listener]
mode = "direct_tls"
listen = "0.0.0.0:7444"
public_endpoint = "https://riffdb.internal.example:7444"
certificate_chain = "/etc/riffdb/tls/server-chain.pem"
private_key = "/etc/riffdb/tls/server-key.pem"
origins = ["https://app.internal.example:8443"]
--mcp-origin without --mcp-listen rejects. With no allowed origins, only
requests without an Origin header are accepted. The MCP protected-resource
audience is derived as http://<listen-authority>/mcp for loopback_cleartext
and <public_endpoint>/mcp for direct_tls; it cannot be configured and must
differ from the gRPC audience. Direct-TLS origins must use https://.
RIFFDB_MCP_ORIGINS is one nonempty comma-separated list with no trimming or
normalization. TOML uses a string array and CLI uses one flag per origin.
Whichever source wins supplies the whole list. At most 16 unique origins and
8,192 aggregate bytes are accepted; each origin is at most 512 visible ASCII
bytes and must pass the hosted transport’s origin checks.
Other RIFFDB_* variables are not server settings. There is no
storage-engine selector, group-commit tuning, transaction callback,
or nondeterministic scheduler setting in the POC configuration surface.
Digest-Key Documents
Capability keys:
riffdb-capability-digest-keys-v1
1:<exactly 64 lowercase hexadecimal characters>
Idempotency keys:
riffdb-idempotency-digest-keys-v1
1:<exactly 64 lowercase hexadecimal characters>
Documents are at most 1,024 bytes, end with LF, contain one through eight entries, use unique nonzero canonical decimal IDs, and contain no duplicate material. The first listed entry is the write key; later entries remain readable for rotation. The two namespaces must never reuse material.
On Linux, each secret source must be a regular non-symlink final component owned by the process effective user with no group or other mode bits. Metadata is checked before and after a no-follow open.
riffdb Client
Configuration is resolved independently for each field, highest precedence first:
| Field | Flag | Environment | TOML | Default |
|---|---|---|---|---|
| Endpoint | --endpoint | RIFFDB_ENDPOINT | client.endpoint | http://127.0.0.1:7443 |
| Database | --database | RIFFDB_DATABASE | client.database | default |
| Output | --output | RIFFDB_OUTPUT | client.output | human |
| Attempts | --max-attempts | RIFFDB_MAX_ATTEMPTS | client.max_attempts | 3 |
| Credential file | --credential-file | RIFFDB_CREDENTIAL_FILE | client.credential_file | absent |
| TLS trust root | --tls-trust-root | RIFFDB_TLS_TRUST_ROOT | client.tls_trust_root | system CA bundle on https:// |
| TLS server name | --tls-server-name | RIFFDB_TLS_SERVER_NAME | client.tls_server_name | endpoint host on https:// |
The TOML path is selected only by --config, then RIFFDB_CONFIG, then
absence. There is no directory discovery. A present invalid higher-precedence
value rejects instead of falling through.
The endpoint is either exact lowercase
http://<literal-loopback-IP>:<port-1..65535> or canonical
https://<DNS-or-IP>:<port-1..65535>. For HTTPS, omitting tls_trust_root
selects the platform system CA bundle, and omitting tls_server_name derives
the expected peer identity from the endpoint authority host; when supplied,
tls_trust_root must be an absolute, normalized path and tls_server_name must
match the endpoint authority. Supplying either TLS field for cleartext or
requesting a trust-all/downgrade mode rejects.
Generated application client configuration retains any explicit non-secret TLS
selectors. Unknown, duplicate, or wrong-type input rejects. Output is human
or json. Attempts is canonical decimal 1..=10.
Exactly one normal credential source may resolve:
- the protected credential file selected above; or
RIFFDB_CAPABILITY_TOKEN, an exact 43-byte bearer presentation.
Supplying both rejects. Prefer the protected file. Machine output is one compact JSON object plus LF and no other stdout bytes.
riffdb-mcp Stdio Bridge
The bridge accepts only:
--config PATH
--endpoint LOOPBACK_HTTP_ENDPOINT
--database DATABASE
doctor --config PATH
Configuration path precedence is --config, RIFFDB_MCP_CONFIG, then absent.
Endpoint precedence is --endpoint, RIFFDB_MCP_ENDPOINT, [mcp].endpoint,
then http://127.0.0.1:7443. Database precedence is --database,
RIFFDB_MCP_DATABASE, [mcp].database, then default.
Its TOML contains only:
[mcp]
endpoint = "http://127.0.0.1:7443"
database = "default"
credential_file = "/var/lib/riffdb-mcp/credential"
expected_audience = "riffdb-grpc-loopback"
Credential selection is exactly one of RIFFDB_MCP_CAPABILITY_TOKEN,
RIFFDB_MCP_CREDENTIAL_FILE, or [mcp].credential_file. The environment file
path takes precedence over the TOML path, while simultaneous raw token and file
selection rejects. The credential is loaded once and moved into the ordinary
public gRPC client. expected_audience, when present, is bounded target-identity
evidence written by application provisioning; it grants no authority and has
no flag or environment override.
The bridge accepts a database alias, not a path. It does not accept a storage option, environment, audience authority override, policy, or server-side authority.
Follower mode
--mode follower (or RIFFDB_MODE=follower, or [server] mode = "follower")
selects the separate follower startup path before primary initialization or
maintenance. The default remains primary. Each configured database must have
one complete replication_source block; primary mode rejects such blocks.
The mode follows the usual CLI, environment, then file precedence. Source
identity and trust settings are file-only and cannot be overridden individually.
For the legacy single-database form, use [server.replication_source]. For a
named database, use [databases.<alias>.replication_source]. Required fields are:
| Field | Meaning |
|---|---|
endpoint | Canonical HTTPS primary endpoint |
trust_root | Absolute path to the primary’s CA certificate file |
server_name | Exact verified server identity matching the endpoint |
credential_file | Absolute path to a protected replication capability token file |
database | Database alias at the source |
database_id | Expected UUIDv7 database identity, with hyphens |
history_incarnation | Expected nonzero history incarnation |
leadership_epoch | Expected nonzero leadership epoch |
hold_id | Stable nonzero 16-byte source hold ID, as 32 lowercase hex digits |
Credential files use the existing protected-file loader. TLS always verifies the
configured CA and server name. Trust and credential paths cannot overlap database,
backup, projection, replication scratch or digest-key paths. All source peers are
checked before local construction starts. Receiver scratch is the reserved
<database-path>.riffreceiver sibling directory.
Startup bootstraps an absent follower or fully validates and reopens an existing one. A retained complete transfer permits the existing candidate-publication recovery path to check its exact fence and destination before replacement. Tail reception uses the supervised worker’s bounded retry and shutdown rules. A committed promotion is reconciled from local authoritative control and external audit before loading former-source credentials. Once completely validated, it starts as a source even with its existing follower configuration. Unresolved pre-cutover attempts enter restricted promotion-retry hosting after full local validation, without connecting to the former source at boot. Ordinary readiness stays fenced. A freshly authorized exact retry requires that source’s authenticated fence proof before cutover. See follower administration.
After validation, the daemon activates a writer-free application service for each
configured alias, binds configured hosted MCP routes, and emits the ordinary
application-ready receipt. Standard input accepts shutdown; process signals
also stop the daemon. Shutdown closes transport admission, stops replication and
drains accepted service reads before releasing the remaining snapshot pins.
Authentication and current policy observe the latest completed follower prefix. A capability or contract installed later on the source becomes visible through replication; followers expose no local capability-bootstrap convenience path. Commands, administration, migration, maintenance and export return the typed follower-mode refusal. The experimental promotion operation has its own external audit owner. Policy-required durable read audit still refuses.
The daemon process tests compare every authoritative namespace under the app-baseline workload and resume across repeated source and follower crashes. The follower replication and projection freshness checks pass their full CI gates, including follower freshness, provider equality and sequence-lag reporting.
Follower named queries can use the existing exact-text, exact-predicate,
tokenized-text and long-pattern providers. Their local checkpoints live under
the configured projections_root and remain rebuildable. Background workers
read only completed follower snapshots; the applied application frontier is
their local head for minimum-commit and AdmissionHead checks. A provider that
has not caught up returns the existing typed freshness or availability refusal.
Configured scalar and vector columnar sources stay cold until demand. One owned
worker builds independently validated disposable V2 views under
projections_root/follower-columnar; no source artifact is required and no local
authoritative control is written. Cold or activating sources return the existing
typed Building/unavailable outcome and keep columnar health degraded. Failed
materialization remains closed until owned recovery or restart; a request cannot
clear failure. Restart rebuilds these disposable views from the completed prefix.
Projected reads retain Causal, Bounded and Available semantics using the applied local head. Replication source-head observations only report lag. Foreign database/history tokens refuse before waiting, and opaque cursors are local to one live process. See sequence-lag health and statistics for both-node reporting.
Exact-provider checkpoint custody
On Linux, exact-text, predicate, nullable-predicate, tokenized-text and
long-pattern checkpoint directories are created privately (0700), with private
regular checkpoint files (0600) owned by the daemon user. Checkpoint access
rejects symlinked path components, foreign ownership, broadened permissions,
extra hard links and replaced directory or file identities. Paths are bounded
by 4096 encoded bytes and 32 components; relative configured paths remain
supported. Unsupported platforms refuse this checkpoint custody.
Publication synchronizes complete pending bytes, publishes without replacing an occupied name, and synchronizes the pinned directory. Restart completes only a verified two-name publication or rebuilds absent derived state. Partial work cannot become authoritative state. Checkpoint envelope formats remain unchanged. An installation with older permissive derived directories or files must stop the daemon and make these owned directories and files private before restarting; RiffDB refuses unsafe custody instead of changing permissions on existing objects. Ownership records retain live directory and file descriptors through cleanup, so a deleted object’s inode cannot be reused while its ownership claim remains. Retirement removes only the recorded selected and pending derived objects after all captures release. A foreign replacement is preserved; the retiring registration continues to consume capacity until its custody can be proved.
Exact-provider retention clock
Primary and follower servers use the same process-local monotonic clock for
cursor expiry and exact-provider minimum retention. A failed clock sample,
backward time, or an unrepresentable retention deadline returns the existing
provider Unavailable error. There is no application or operator override.
Each exact-provider family retains its 256-registration limit. Under registration
or aggregate admission pressure, the server retires the least-recently-used eligible
owner, with canonical family/key tie breaking. Eligibility requires the existing
300-second minimum retention to expire and all query, cursor, worker and
publication pins to release. Successful access extends retention; failed capture
does not. When every owner is protected, new demand returns typed unavailable.
Later access to an evicted registration starts ordinary Building/rebuild admission
with a fresh generation. It cannot resume a cursor using a substitute epoch.
One pre-admitted 512-KiB cleanup lane remains reserved for the runtime’s lifetime, allowing cleanup at budget saturation. Cleanup runs outside the discovery lock; contention refuses new admission without changing an owner’s phase. Bounded, admitted worker inventories hold keys only and capture one live owner at a time.
A freshly authorized higher revision of the same capability retires its older registrations across all five families. Existing captures remain charged until released; retirement cannot shorten the 300-second retention obligation. Obsolete workers stop before source observation and at bounded replay, scan, materialization and publication points. Each worker pass attempts at most four eligible retired-owner cleanups, including when no capacity pressure exists; this cleanup does not evict idle active registrations.
New exact-provider registrations reserve owned registration data and shared
compiled metadata against one compiled 1-GiB process budget. Shared metadata is
charged once and retains its charge through its final data reference, including
catalog references. Budget pressure or incomplete ownership evidence returns the
existing typed Unavailable result. Servers sharing a process share this pool;
there is no budget configuration or application cache control. Queries and
workers also reserve their explicit capture pins against this pool; capture
pressure returns the same typed unavailable result. Encoded checkpoint read
buffers reserve capacity before reading and retain their charge through the last
buffer reference. Budget pressure leaves rebuild work eligible for a later retry.
Provider source decoding and derived-row normalization reserve construction
capacity before allocation. Unchanged rows share their admitted original;
materialized fields retain their charge through the final semantic row clone.
Private provider row and delta conversion buffers also reserve native capacities
before copying, and retain their reservations through destruction.
The exact-provider worker emits bounded operator diagnostics on stderr at startup,
at most once per 60 seconds while its service clock is healthy, and at worker stop.
Lines use trigger=exact_provider_lifecycle and fixed numeric fields: the process
limit, fixed owner bytes, owned/reserved bytes, outstanding reservations
(reserved_work), live/released accounting allocations, active/retiring owners,
unique pinned owners, distinct pin guards, and finally released owners.
Cloned references to one pin guard count once. Retiring or pinned state retains
its bytes; removing discovery entries cannot report an owner as released.
Clock or accounting failure emits available=false without fabricated counters.
These lines contain no keys, tenants, capabilities, paths or payloads. Multiple
runtimes in a process report the same aggregate pool.
Provider catalog capture borrows an exactly matching catalog already published by the ordinary catalog adapter or completed follower prefix. It reserves shared ownership before capture and refuses when that publication is absent, stale or contended; providers cannot cold-decode a private catalog. Retained physical source descriptors retain their metadata charge through their final capture.
Provider receipt replay and source-pin validation reserve native construction work before decoding, including archived history stubs. Retained cursors and receipts carry their charges through destruction; pressure refuses without advancing a receipt cursor. Provider head checks read the immutable snapshot frontier without decoding a command record.
Provider policy reads, observation copies and evaluation work now reserve capacity on the same captured source; resulting proofs retain their charge through final destruction.
A strictly newer contract can retire eligible provider state for the same named operation and exact partition/authority identity after pins and minimum retention clear. Historical explicit queries can rebuild with a fresh owner.