Unreleased documentation. These pages follow the main branch and can change before the next release. For supported guidance, use v0.44.0.
Use this page to decide which Registry Stack state needs backup, off-host shipping, expiry, or deletion policy outside the products.
Find the owning retention control
Section titled “Find the owning retention control”- Base Registry Engine (BReg) retention: Action
execution.proposal.retention.mode, runtimeretention.receiptRetentionDays, and the owned history, Proposal, attachment, and Evidence erasure commands. Retained Evidence assertions have the documented fixed 24-hour window. - Casework retention: authored
reviewPolicies.<id>.retention.terminalRetentionDays,reviewPolicies.<id>.retention.accountabilityRetentionDays, andtrustedProducers.<id>.retention.receiptRetentionDays. - Messaging retention: runtime
retention.payloadRetentionDays,retention.recordRetentionDays, andretention.receiptRetentionDays. - Scheduling retention: runtime
retention.receiptRetentionDays,retention.hookPayloadRetentionDays, andretention.claimOwnerRetentionDays. - Coordinator erasure: explicit operator-selected terminal payload cutoffs, with the source’s unresolved effects and retention boundaries preserved.
- Evidence audit and Render audit:
runtime
audit.retentionDays; these products do not retain source registry Records or issued PDF storage for the application. - Audit retention: runtime
audit.rotateBytesandaudit.retentionDaysapply to each file writer. Astdoutdestination delegates collection and expiry to your platform. Retain database backups and external attachments under their own owning policy.
This map identifies the controls; each linked product guide defines their meaning and recovery.
Operator boundary
Section titled “Operator boundary”Registry Stack gives operators rotation knobs, TTL-bound protocol stores, and pseudonymized audit handles. It does not decide the legal retention period for a deployment, delete source-registry records, or prove that a local rotating file kept every historical audit event.
Two consequences follow from that boundary:
- Treat local audit files as neither tamper-evident nor complete: they are not chained, and retention deletes sealed files by age. Ship them to append-only storage when integrity or completeness matters.
- Treat keyed pseudonyms as a linkability control, not as erasure. Destroying or rotating the HMAC secret prevents recomputing old pseudonyms from raw values, but it does not delete audit records, cache entries, or source data.
Durable state and externally retained records
Section titled “Durable state and externally retained records”| Store | What it contains | Operator control |
|---|---|---|
| Registry Evidence audit file | Value-free access, release, denial, and failure events | Configure rotation and retention, and ship sealed files before local expiry. |
| Registry Render audit file | Value-free render events and artifact identities | Apply the same shared-writer handling as Registry Evidence. |
| Base Registry Engine PostgreSQL database | Current records, retained revisions, requests, access logs, and webhook delivery state | Back up the database and use the owned erasure and retention commands. |
| Operator-owned files | Runtime configuration, packages, sources, checkpoints, exports, and secret references | Protect, back up, and expire these files through platform controls. |
Process-local caches and client-held state
Section titled “Process-local caches and client-held state”Registry Evidence can keep OIDC signing keys and negative lookup entries in process. Restarting the service clears that cache. It stores no source records or assertions in the cache. Client-held continuity receipts and verification material remain the relying application’s responsibility.
Audit retention
Section titled “Audit retention”Registry Evidence, Registry Render, Base Registry Engine, Casework, and Scheduling write through the
shared per-process audit writer. The active file rotates at the configured size and sealed files
expire by age. A stdout destination delegates both controls to the log collector.
The age is set in whole days, from 1 to 36,500, and is 90 when omitted. Each of these runtimes reads
it from audit.retentionDays in its runtime.yaml. The rest of this page calls the value the audit
retention period.
Never truncate, rewrite, or reserialize the active audit file as part of cleanup: the writer treats any external change to it as a fault and stops, and audited requests fail closed until a restart. Copy sealed files to append-only storage before the audit retention period deletes them.
A stdout destination can also stall, and a stall ends without a restart.
When the log collector stops reading the stream and the writer has held one entry for 5 seconds,
the writer refuses the request waiting on that entry, refuses later audited requests at once, and
reports not ready.
When the collector takes the entry, the writer accepts entries again.
The 5 seconds are fixed, with no configuration key.
The writer also bounds the entries waiting behind the one it is writing: a request that finds that
queue full is refused at once, with no stall logged and with the writer still able to report
ready, so readiness alone does not show it.
A collector that reads slowly without stopping has the same effect: a request whose entry waits 5
seconds behind earlier entries is refused and its entry is not written, although the writer held
no single entry for 5 seconds, logs no stall, and still reports ready.
A write the stream refuses is a different fault: it stops the writer until a restart, as a failed
file write does.
A crash, a killed process, or a full disk can leave the active file’s final line incomplete (any
byte other than a newline at end of file). The next start recovers it without operator action: the
writer copies the bytes after the last complete line to the owner-only (0600) side file
<audit.path>.torn, syncs it, truncates the active file to its last complete line, and logs the
side file’s path and byte count, never its bytes, at error level. An entry is acknowledged only
after the write holding it is synced, so the torn bytes belong to no acknowledged entry and no
acknowledged entry is lost. doctor and companion checks accept a destination the writer would
recover this way and change nothing.
Archive the side file with the sealed files, then remove it. The writer never overwrites it: if a
later torn line finds <audit.path>.torn holding other bytes, the start is refused until that file
is moved. A tail longer than any entry, or a file whose first entry is in another format, is still
refused unchanged; archive that file and restart with a fresh audit.path. Do not edit the active
file to repair it; editing it while a writer holds it is the same external-change fault described
above.
Registry Evidence retention
Section titled “Registry Evidence retention”Registry Evidence has no application database and persists no selector, source, evidence, or response data. Its durable state is its governed signer configuration and public keys plus the audit file described in Durable state and externally retained records. The external Transit service retains the production private key. An external durable audit service may own its own storage, but Registry Evidence itself does not maintain one.
The runtime recognizes a sealed file by its <audit.path>.<sequence> name, with an eight-digit
sequence, and never deletes the active file. Retention deletes sealed files in sequence order,
oldest first, and stops at the first one whose last modification is inside the audit retention period, so
the local directory holds a rolling window, not the whole history. A deletion that fails is logged
and retried at the next rotation or start; it never stops the writer. Each stream reserves every <audit.path>.<sequence> name, plus
<audit.path>.lock, <audit.path>.seq, <audit.path>.seq.tmp, and <audit.path>.torn, and a
path whose file name ends in one of those suffixes is refused at startup, so no stream can be
pointed at another’s file. The .seq file records the next sequence at every start and
before every rotation, so a restart after retention or a shipper removed every sealed file
continues the numbering instead of reusing a name. Operate the Registry Evidence audit log describes
the entry format and the stdout destination.
Ship Registry Evidence audit records off host
Section titled “Ship Registry Evidence audit records off host”The local audit file is not tamper-evident, and retention deletes it by age, so the append-only store you ship to is the audit record of the deployment. A shipper reads the same directory the runtime writes, so three rules keep it safe:
- Copy sealed files, and ship each one before the audit retention period deletes it. The runtime never depends on a sealed file after sealing it, so removing one after it is archived is safe.
- Key archived copies on more than the file name, for example the host and the first entry’s
time. Sequence numbers continue across restarts only while<audit.path>.seqsurvives, so a fresh or restored directory can seal a file under a name already shipped. - Never point a shipper at the active file in a way that renames, truncates, or rewrites it, and
never touch
<audit.path>.lockor<audit.path>.seq. Ship<audit.path>.torn, when one appears, like a sealed file, then remove it. Any external replacement or modification of the active file makes readiness and later appends fail closed. Re-applying its owner-only mode, ACLs, or extended attributes, as configuration management does, is not a modification.
Archive the oldest sealed files first, for example:
cp /var/lib/registry-evidence/audit/evidence.jsonl.00000001 "$ARCHIVE/<host>-<first-entry-time>.jsonl"Alert on the shipper, not on the local directory: its size stays bounded by retention even when a shipper has stopped, and local files that retention deletes before shipping are lost.
Render retention
Section titled “Render retention”Render keeps no application database, no source data, and no rendered document beyond the response it returns; a render is a pure function over a sealed template bundle and the request body. Its only durable, product-owned state is the sealed bundle on the operator host and the audit file described in Durable state and externally retained records.
The runtime recognizes a sealed file by its <audit.path>.<sequence> name and never deletes the
active file. Retention deletes only sealed files whose last modification is older than
the audit retention period, so the local directory holds a rolling window, not the whole history. Ship
sealed files the way
Ship Registry Evidence audit records off host
describes; the mechanics are identical.
A release before 2026-09-25 kept its own keyed hash chain and exposed registry-render audit-verify
to check it, and its runtime file accepted directory, integrityKeyRef, and maxSegmentBytes.
The shared-writer release refuses all three as unknown fields and has no audit-verify, since the
new log carries no chain to verify. Before the first start on the shared-writer release, archive
the old chained log to append-only storage and point audit.path at a fresh file in a directory
that holds no old segments; do not point the new runtime at the old chained file or directory.
Upgrade Casework and Scheduling audit
Section titled “Upgrade Casework and Scheduling audit”v0.40.0 does not upgrade v0.39.0 state in place; apply to a new database. Fresh installation creates the current schema without converting or discarding earlier audit rows. A later successor waits for its publication locks before publishing a new active package, and a blocked apply leaves the prior package active.
If you retain an earlier PostgreSQL audit-outbox deployment, stop new traffic and keep its runtime running until its publisher has delivered every pending audit row. For an earlier database that has one of these outbox tables, check the pending count with your database tooling:
-- Casework databaseSELECT count(*) FROM casework_audit_outbox WHERE published_at IS NULL;-- Scheduling databaseSELECT count(*) FROM scheduling_audit_outbox WHERE published_at IS NULL;When the applicable count reaches zero, stop the old runtime and archive its audit file, sealed segments, and audit key outside the new writer’s retention directory. Keep the earlier database with the release that wrote it until its work and accountability records are exported. Do not delete pending rows to bypass a refusal.
Configure a fresh audit file path and a new database for the new runtime. Run caseworkctl apply
or schedulingctl apply with this release’s runtime configuration pointing at that new database.
The new release writes directly to the shared per-process writer. Its audit entries are not chained. Collect the service files and operator companion files, and use append-only storage for tamper evidence. See keep the audit file for the Casework state and audit boundary.
Registry Casework review retention
Section titled “Registry Casework review retention”Casework retains review requests, submitted snapshots, task state, decisions, notes, history,
clock state, and replay responses in PostgreSQL.
Current-source display fields are read for the caller rather than stored as review snapshots.
The pinned policy defines two periods measured from terminal settlement:
terminalRetentionDays for review payloads and accountabilityRetentionDays for protected
decision accountability and remaining recovery metadata.
An open review has no terminal expiry.
The serving maintenance worker erases eligible payloads in bounded passes. At terminal expiry, result reads expire and cleanup removes task, context, history, and clock data; at accountability expiry, cleanup removes the remaining protected records and request state. Staffing cursors and previews have their own short expiry and cannot extend payload retention. Keep the process and audit destination healthy, then inspect readiness and maintenance warnings when cleanup falls behind.
Payload erasure cannot be undone through Casework. Fetch results before their availability deadline, and set backup and external-result retention separately: automatic cleanup does not erase copies held by producers, backups, or audit archives.
The current CLI has no caseworkctl retention erase command.
Use the configured automatic policy, rather than deleting rows directly.
A producer-authorized cancellation starts terminal retention; it does not erase immediately.
Review retention and audit describes expiry, recovery, and the
independently retained external journal.
Base Registry Engine retention
Section titled “Base Registry Engine retention”A Base Registry Engine registry keeps record history and the webhook outbox in PostgreSQL. Its audit stream is separate: back up the database and ship the per-process audit files, including operator companion files, through your log pipeline. A database restore does not restore or erase those files.
registry_internal.registry_revisions holds the retained revisions of a record and registry_data
holds its current row. bregctl history erase is the only command that removes retained revisions.
It erases them for one record up to a revision number, scrubs the affected correction context and
retained outbox payloads, and replaces the affected cached idempotency responses with refusal
tombstones, so a retried mutation key cannot replay an erased body. It leaves the current record,
every later revision, and the audit journal in place. Snapshot references at or after the earliest
erased commit become unavailable until bregctl history rebaseline writes a new baseline commit.
Proposal detail has separate retention from record history. An Action’s
execution.proposal.retention.mode: operator-erase permits eligible detail erasure;
the default, retain, refuses it. Use bregctl proposal-retention dry-run to inspect one
exact Action, Proposal, and submission version before running bregctl proposal-retention erase.
Erasure removes its eligible frozen snapshots, draft input, application reasons, Review payloads,
held idempotency responses, and attachment references while preserving value-free lifecycle and
spent-key evidence. Active detail remains pinned. Unresolved remote Review recovery retains the
request and acceptance it still needs; external attachment deletion can remain pending separately.
Erase Proposal detail gives the commands and scope.
Spent idempotency keys live in registry_internal.registry_idempotency, one row per key, found by
a digest of the verified issuer, principal value, and key. Until its receipt is dropped, the row
also holds those raw values. Neither erasure removes that row or its raw values: both drop or
tombstone only the held response, so a retry answers 409 idempotency.key-reused. A held response
stays replayable for retention.receiptRetentionDays, 7 days by default, and
bregctl idempotency-retention erase-expired drops the ones past that horizon, together with the
raw issuer, principal value, and key, while the digest keeps the key spent. The digest row is kept
indefinitely, because the results and application of an immediate action hang off it and revision
history reads its provenance from them.
Drop expired idempotency responses gives
the command and its limits.
Audit retention uses the shared file writer described in Audit retention. History erasure leaves those files untouched. The database records committed erasure coverage in a dedicated table, so deleting an old audit segment does not affect successor-package admission.
Each audited request writes a request entry before it touches protected data and a response
entry under the same correlation before it discloses anything or once its mutation commits. A
failed entry blocks the step that follows it. A request that ends before its outcome is known is
answered with an unfinished entry; an operator command also answers unfinished when it is
refused or finds nothing to do. A crash or a killed process can leave a request entry with no
response, so read every entry under a correlation. With a stdout destination the runtime log
shares the stream, so while the collector is not reading, a probe can get no answer at all: set a
timeout on every probe. bregctl writes to a sibling file, <stem>.bregctl.<extension>, and
bregctl dev keeps its files under .breg/dev/audit, including after bregctl dev stop --remove.
The webhook outbox also has scheduled payload expiry. The runtime clears a retained payload on the first successful delivery, and clears an undelivered payload once its expiry has passed, which is also the point after which that delivery can no longer be replayed. The delivery rows themselves stay; only the payload goes.
A record type that declares accessLog writes one row per logged record read to
registry_internal.registry_subject_access_log. Each row takes its expiry from the record type’s
retentionDays (default 90) when the read happens, and the subject’s access-log route hides it once
that expiry passes. A background job erases expired rows every minute in batches of 1000 until none
remain, up to 100,000 rows a minute; a run that stops at that bound logs a warning with the
remaining backlog. The job keeps running after a later package turns accessLog off, and a package
change never extends an existing row’s expiry. Database backups keep their own copies of these rows.
Ingestion runs live in registry_internal.registry_ingestion_runs, with their chunks and chunk
receipts beside them. A run row holds the verified issuer and principal value of the caller that
created it, which decide who may list, read, continue, or cancel the run, and runs have no
retention horizon: no command removes a run or its creator values. bregctl history erase erases
the chunk receipts that describe the erased revisions and leaves the run and its counts.
Import and export checkpoints live on the operator host, not in the database. bregctl data import
writes a checkpoint file beside a .state sidecar it owns, and bregctl data export writes its
output file beside a checkpoint that binds the package revision, schema fingerprint, record type,
profile, and operation. Both refuse a run whose files no longer match rather than resuming it, so
keep each pair together and unedited or start both afresh. An export output holds record values and
needs the handling any other extract gets.
Package files are operator-owned and nothing in Base Registry Engine expires them. Keep the project sources, module locks, generated files, test receipt, signature documents, and runtime file for every activation, and store secrets separately.
Spent idempotency keys
Section titled “Spent idempotency keys”Base Registry Engine, Registry Messaging, and Registry Scheduling each keep a row for every
idempotency key a caller spent, so a retry is answered rather than run again. Until the product’s
receipt horizon, that row holds the raw issuer, subject, and key beside the held response. After
each product’s receipt horizon the raw caller and key are cleared and a digest keeps the key spent:
a retry under it is still refused, and the same key sent by another caller is that caller’s own
fresh key. Each product refuses it by the same rule: the same request is 410 idempotency.expired,
and a different request is a 409 idempotency conflict, as it is inside the horizon. Messaging
keeps the request hash with the message record, so once it deletes the record after
retention.recordRetentionDays any request under the key is 410. Base Registry Engine clears the raw
caller and key when the operator runs bregctl idempotency-retention erase-expired; Messaging and
Scheduling clear them in their retention sweeps.
What remains outside the product
Section titled “What remains outside the product”Registry Stack does not implement a general erasure workflow for source records, audit records, or cache entries, beyond the bounded Base Registry Engine commands described in Base Registry Engine retention. It also does not prune operator-owned config files, source files, or old release artifacts after the product-level expiry checks have run. Those controls belong in the operator’s storage, backup, incident-response, and data-protection processes.
Cookies on Registry Stack
We use optional cookies to understand how you use the main Registry Stack website and this documentation. If you contact us, we may link this activity to your enquiry. You can change your choice at any time. Read the privacy policy.
Your browser asks sites not to track you, so optional tracking stays off.
We could not use or save that choice. Optional tracking remains off.