Unreleased documentation. These pages follow the main branch and can change before the next release. For supported guidance, use v0.43.0.
You run an active registry under an institutional retention policy and hold its migration credential. This page shows you how to erase retained record history and Proposal detail, restore snapshot coverage afterwards, drop expired Evidence and idempotency material, and keep the audit journal.
Every command here reads the runtime file and connects to PostgreSQL directly with the migration
credential, so run it from the operator host. The erasures and the rebaseline take the registry’s
migration lock: run them while no activation is in progress. A database whose certificate a
private authority issued is trusted through database.trustedRootCertificateRef in the same runtime
file. Every path you pass must be absolute. Reports carry counts, never record values.
The examples use the record types and Actions of the facility template.
What cannot be undone
Section titled “What cannot be undone”| Command | Removes for good | Leaves in place |
|---|---|---|
history erase | The retained revisions of one record up to a revision number, the correction context and outbox payloads that referenced them, and the held responses that could replay them. | The current record, every later revision, the audit journal, and each spent idempotency key. |
proposal-retention erase | The payload detail of one Proposal submission version: frozen snapshots, draft input, application reasons, Review payloads, held responses, and attachment references. | The Proposal’s value-free state history, record history, and each spent idempotency key. |
proposal-retention cleanup-attachments | Unreferenced attachment bytes in external storage that are waiting for deletion. | Live attachment references, Proposal detail, and deletion tombstones. |
evidence-retention erase-expired | The signed Evidence responses and verification context an Action kept, once their 24-hour window has closed. | Assertions still inside their window, and the receipts of the Actions that used them. |
idempotency-retention erase-expired | Held responses past their horizon, with the raw issuer, principal value, and key each was sent under. | Every spent key, as a digest, so a retry is still refused rather than run. |
A saved export or a snapshot reference is not a way back: an erased revision is gone from the
registry, and a reference that named it can no longer be served. A package change removes nothing
from history either. A successor that drops a field leaves the old values in every earlier
revision, and bregctl diff reports each one as diff.history.removed-values-retained. Only
history erase removes them, and it removes whole revisions of one record, not one field.
Backups, replicas, saved exports, and copies already delivered to consumers or to an external verifier keep their own copies. Registry erasure cannot reach them.
Erase retained history
Section titled “Erase retained history”history erase removes the retained revisions of one record up to a revision number. It is
maintenance under the migration credential, not an API permission, and it does not remove the
current record: apply your live-data removal policy separately.
Write the request to an owner-only file (mode 0600), so the record identifier and the reason never
appear in process arguments. recordType names the record type, and eraseThroughRevision is the
last revision to erase:
{ "recordType": "operator", "recordId": "92f1b1e6-348c-4045-9acc-a0dc85877136", "eraseThroughRevision": 1, "operatorReference": "ticket-4821", "reason": "Data subject request: remove the superseded contact details"}No command restores erased revisions and there is no dry run, so confirm the record and the revision boundary against the approved request before you run it.
The command refuses to run without --acknowledge-irreversible:
chmod 600 /srv/registry/private/erasure.jsonbregctl history erase \ --runtime-config /etc/breg/runtime.yaml \ --request-file /srv/registry/private/erasure.json \ --acknowledge-irreversibleErased the requested history. 1 commit affected. package revision sha256:1d38a738597da4a8254a53f3cb5ad6889243374134eb9c0c4dda1768fd78750b coverage ready true unavailable after position 0 affected commits 1 erased revisions 1 erased commit members 1 scrubbed change contexts 0 scrubbed outbox payloads 0 scrubbed cached responses 1 scrubbed Proposal targets 0 scrubbed Proposal submissions 0 removed descriptors 0One run erases at most 10,000 revisions in one transaction. It replaces each held response that
could replay an erased revision with a refusal, so a retry of that key answers
409 idempotency.conflict instead of returning the erased body. A request file that is not
owner-only is refused before any connection opens. Live reads and writes continue.
Snapshot references at or after the earliest erased commit become unavailable, and erasing a
record’s baseline data makes all snapshot coverage unavailable. The next package still activates
when the only gap is an erasure the database recorded as committed. Any other gap refuses a
successor with apply.history.coverage-incomplete until you restore coverage; apply the same
package again afterwards.
Restore snapshot coverage after an erasure
Section titled “Restore snapshot coverage after an erasure”After an erasure, a snapshot reference for the current state is refused until history rebaseline
writes a new baseline. Its request file is owner-only too and carries the operator reference
alone:
{ "operatorReference": "ticket-4821"}Every API read and write waits until the run commits, and a request that waits past operationalTimeouts.httpRequestMilliseconds fails, so plan it as a maintenance window.
bregctl history rebaseline \ --runtime-config /etc/breg/runtime.yaml \ --request-file /srv/registry/private/rebaseline.jsonRebaselined the history. 4 records verified.The run proves that the retained revision at the head of every live record still reproduces that
record, then installs one baseline commit at the head. It restores nothing that was erased:
references taken before the new baseline stay unavailable, so take a fresh one afterwards. A
second run refuses with history.rebaseline.coverage.complete, because there is nothing left to
rebaseline.
The outage comes from row security. The migration role has no authority to bypass it, so the run
lifts forced row security on every record table and holds each table exclusively until the
transaction ends. The run grows with the number of live records and retained revisions. To size the
window, restore a recent backup to a scratch database, erase there, and time the rebaseline; allow
at least twice that. Set operationalTimeouts.migrationLockMilliseconds long enough to wait out
in-flight requests, and operationalTimeouts.migrationStatementMilliseconds long enough for one
scan of the whole revision journal.
When a live record changed outside the server’s write path, the journal never saw the change, and
the run refuses with history.rebaseline.live-rows.unverified without naming the record. Read the
revisions of the records you suspect with GET /v1/records/{route}/{recordId}/revisions.
Erase Proposal detail
Section titled “Erase Proposal detail”Proposal detail has its own retention, separate from record history. An Action whose
execution.proposal.retention.mode is operator-erase lets you erase the detail of a finished
Proposal; retain, the mode the facility template uses, refuses it. A Proposal is eligible once it
is cancelled, applied, or rejected. A current draft or submitted version stays pinned. The outputs
below come from a copy of the facility template with deactivate-facility switched to
operator-erase.
List the retained Proposals of an Action:
bregctl proposal-retention list \ --runtime-config /etc/breg/runtime.yaml \ --action-id deactivate-facilityListed the retained Proposals. 1 request.
{"actionId":"deactivate-facility","proposalId":"a491e8e0-27a6-4945-b78b-30e4dfc20cf3","submissionVersion":1,"proposalState":"cancelled","current":true,"retentionMode":"operator-erase","pinned":false,"eligibleForErasure":true,"detailErased":false,"contractFingerprint":null,"effectDigest":null}Preview the erasure of one exact submission version. The dry run counts what would go and erases nothing:
bregctl proposal-retention dry-run \ --runtime-config /etc/breg/runtime.yaml \ --action-id deactivate-facility \ --proposal-id a491e8e0-27a6-4945-b78b-30e4dfc20cf3 \ --submission-version 1Previewed the erasure. The request is eligible for erasure. Nothing was erased. action deactivate-facility proposal id a491e8e0-27a6-4945-b78b-30e4dfc20cf3 submission version 1 retention mode operator-erase pinned false eligible for erasure true erasure {"proposalSnapshots":0,"targetSnapshots":0,"reviewSubmissionRequests":0,"reviewAcceptedBindings":0,"reviewResultPayloads":0,"applicationReasons":0,"idempotencyResults":2,"draftInputs":1,"attachmentReferences":0}The erasure removes the submission’s payload detail and attachment access for good, so check the dry run against your retention decision first.
Run the same selection with erase:
bregctl proposal-retention erase \ --runtime-config /etc/breg/runtime.yaml \ --action-id deactivate-facility \ --proposal-id a491e8e0-27a6-4945-b78b-30e4dfc20cf3 \ --submission-version 1Erased the retained Proposal. action deactivate-facility proposal id a491e8e0-27a6-4945-b78b-30e4dfc20cf3 submission version 1 retention mode operator-erase erased {"proposalSnapshots":0,"targetSnapshots":0,"reviewSubmissionRequests":0,"reviewAcceptedBindings":0,"reviewResultPayloads":0,"applicationReasons":0,"idempotencyResults":2,"draftInputs":1,"attachmentReferences":0}Listing again shows "detailErased":true. A pinned version, or an Action in retain mode, is
refused and left intact: finish the Proposal under its ordinary authority first. Identical
attachment bytes stay while another retained reference needs them. A Review still waiting on
remote recovery keeps the exact request and acceptance that recovery needs.
Retry external attachment cleanup
Section titled “Retry external attachment cleanup”When external storage fails to delete an attachment, the registry keeps the deletion as durable work. A download can already be revoked while the bytes still wait to be deleted.
The command deletes unreferenced objects from external storage with no copy kept, so confirm that your storage retention policy allows it first.
bregctl proposal-retention cleanup-attachments \ --runtime-config /etc/breg/runtime.yamlRetried unreferenced attachment cleanup. pending external deletions 0 external deletion tombstones 0Run it again while deletions remain pending. If the storage backend or its identity binding is refused, fix that first. Cleanup does not make any Proposal eligible for detail erasure, and your bucket’s own retention policy still applies.
Erase expired Evidence uses
Section titled “Erase expired Evidence uses”An Action that declares evidence keeps each assertion it accepted: the signed response bytes,
the verification context, and the times that bound it. Subject and selector values are never kept.
Each kept assertion expires 24 hours after the registry accepted it. Erasing record history does
not cover it. The runtime removes expired Action and Proposal Evidence uses every minute in
bounded, audited batches. Use the manual command when you need cleanup before the next sweep.
The runtime and manual command delete the signed responses and verification context without taking a copy, so export what your retention policy requires before each window closes.
bregctl evidence-retention erase-expired \ --runtime-config /etc/breg/runtime.yaml \ --before 2026-10-16T00:00:00ZErased expired action Evidence. erased 0The command removes every assertion whose window closed at or before --before, and none still
inside its window. A cutoff in the future is refused. The receipts of the Actions that used the
assertions stay replayable, so the registry can still account for what it decided. The runtime
credential can execute only the fixed expiry function. It cannot read retained assertions or run
the manual command, which requires the migration credential.
Drop expired idempotency responses
Section titled “Drop expired idempotency responses”A successful write holds its response, so a retry with the same Idempotency-Key returns it
instead of writing again. Only successes are held. A key is spent per caller across every write,
and stays spent for good.
The held response stays available for retention.receiptRetentionDays after the write commits:
7 days unless the runtime file sets a value from 1 to 365. The horizon is fixed at commit, so a
changed setting applies to later writes only. Past it, an exact retry answers
410 idempotency.expired and never runs. A key whose response an erasure removed answers
409 idempotency.conflict on either side of the horizon.
Until the drop runs, a spent key also keeps the raw issuer, principal value, and key it was sent under, beside the history it produced. Schedule the drop to match the horizon:
bregctl idempotency-retention erase-expired \ --runtime-config /etc/breg/runtime.yaml \ --before 2026-10-16T00:00:00ZDropped expired held idempotency responses; the keys stay spent. erased 0The drop removes held responses whose horizon passed at or before --before, together with the
raw caller and key, and keeps a digest with the request binding and times. Every key then answers
as before: an exact retry 410, a changed retry or an erased key 409. A cutoff in the future is
refused, and a second run with the same cutoff drops nothing.
Keep the audit journal
Section titled “Keep the audit journal”The runtime file’s audit block chooses where the journal goes. The starter project’s example
writes a file:
audit: hashKeyRef: secret:file/audit-key path: /var/log/breg/audit.jsonlThe destination is file unless you set destination: stdout. The file rotates at rotateBytes,
100 MiB by default, and sealed segments older than retentionDays, 90 by default, are removed when
the writer opens or rotates. Give each runtime instance its own absolute path. Operator commands
write to a sibling file, audit.bregctl.jsonl here, on the operator host; collect it with the
service’s stream.
Entries carry keyed references and closed vocabulary, never raw record values, and they have no hash chain or signature. Ship sealed segments to append-only storage before they expire, and never edit the active file. Database backups do not contain the journal. How the writer stalls and recovers a torn line is in Audit retention, and how entries pair up is in Base Registry Engine retention.
When a command on this page is refused or waits on the migration lock, see Monitor and recover.
- Retention and persistent state for the retention of the other Registry Stack runtimes and the audit writer’s behavior.
- Harden a production deployment for the controls around the migration credential and the secret root these commands rely on.
- Security overview for how audit integrity and data minimization fit the stack’s security model.
- Change an active registry for the backup every activation needs.