Unreleased documentation. These pages follow the main branch and can change before the next release. For supported guidance, use v0.43.0.
You run a registry that is already active, deployed as deploy a registry describes, and you have edited its project. This page takes the edit to a successor package that the database runs, and covers what to do when an activation fails or a change has to be undone.
A successor goes through the same test, package, plan, and apply steps as the first package, with
the active package as its baseline. Each successor names the digest of the package it follows, and
each database keeps an activation ledger that bregctl status reads. Paths given to
--runtime-config, --package, --backup, and --output must be absolute.
What changes safely and what needs review
Section titled “What changes safely and what needs review”bregctl sorts every change between two packages into a class. A compatible additive change, such
as a new optional field, a code added to a vocabulary a field uses, or a widened length bound on a
text field, applies with no further evidence. A widened or narrowed grant on a Record type’s
access profile is an access change and also applies without review: the review is the holder of the
migration credential reading the diff before apply. Every change to an Action needs a reviewed
migration, even a changed description, and so does a query inventory change that is more than a
route gaining or losing a profile. Removing a field, changing its type, or anything else that can
lose or rewrite stored Records needs a reviewed migration too, and a few changes, such as a new
project.version, are refused outright: the version is bound to the database for its lifetime.
Compare the candidate with the active package
Section titled “Compare the candidate with the active package”Compare the edited project with the active package before anything else, so the review covers exactly what the successor would carry. Here the minimal starter’s project changes one Action’s description, and the comparison reads the active package directory:
bregctl diff ./directory --package /srv/registry/build-1/packageClassified the candidate against the baseline. 1 change. profile authoring baseline assurance integrity-only baseline package revision sha256:f55a33146a03b318240ea9329f9707434cf24098910501b083a7ac30bdd5480f baseline registry revision sha256:1bff3a06d9fbe1a666a3961a48fc5eec8efd8ea4bd0b613ad0fe6d4495d90dfe candidate registry revision sha256:860bbd3a1f5734e46a7a363dd12b6d426e26b7bedf5da44d0cc7b108b44d2cfe changes 1
{"classification":"access-change","change":{"class":"access-or-disclosure-change","code":"action-changed","target":{"kind":"action","memberId":"update-contact-email"}}}--package checks only the package’s integrity, so a reviewer can run it without runtime
credentials; the report says integrity-only. Pass --runtime-config instead to compare against
the package the database has activated. For an access change the diff also marks each detail as
widening, narrowing, or review-required.
Removing a field or a Record type is not erasure. The successor drops the live column or table, but
every earlier revision snapshot and every backup still holds the removed values, and the diff warns
with diff.history.removed-values-retained. When the removal is for data minimization, also
erase the retained history of each affected Record.
Write a reviewed migration descriptor
Section titled “Write a reviewed migration descriptor”When a change needs review, test refuses the candidate and names what to cover:
bregctl test refused.
error migration.review.required reviewedMigrations the successor cannot be applied automatically; the changes to review are: action-changed at registry. Run diff, review the migration and its rehearsal evidence, then provide --reviewed-migrations to both test and package
1 error, 0 findings.A reviewed migration is a directory you write and keep beside the project. Each migration lives at
modules/<module>/migrations/<id>/ and holds a descriptor.json, a rehearsal.json, and any SQL
steps, assertions, or fixtures the descriptor names. Every path inside it is relative to the
directory root. The descriptor covers exactly the changes diff reported that are not compatible
additive, each with its code and target. A change to an Action alone needs no SQL:
{ "apiVersion": "id.registrystack.org/formats/breg/migration-descriptor/v1alpha1", "kind": "BRegMigrationDescriptor", "id": "describe-email-update", "changeClass": "access-or-disclosure-change", "covers": [ { "code": "action-changed", "target": {"kind": "action", "memberId": "update-contact-email"} } ], "recovery": "exact-target-resume", "lockTimeoutMilliseconds": 1000, "statementTimeoutMilliseconds": 60000, "steps": [], "preAssertions": [], "postAssertions": [], "rehearsalReceiptPath": "modules/directory/migrations/describe-email-update/rehearsal.json"}A migration that rewrites stored Records adds SQL steps scoped to one recordType, pre-assertions
and post-assertions that each return one boolean, and, when the change is destructive, a
backupBindingPath. Migration descriptor
lists every key with its values and bounds.
The rehearsal receipt, rehearsal.json, binds the migration to the exact baseline and candidate:
the prior package digest and schema fingerprint, the descriptor’s planDigest, the digest of every
SQL file and fixture, the PostgreSQL major version, the row counts your rehearsal observed, and the
candidate’s final schema fingerprint. You write it from your own rehearsal; bregctl checks it and
does not generate it. Two commands give you the values it needs:
jq -jcS . descriptor.json | shasum -a 256bregctl test ./directory --fingerprint-only --runtime-config /srv/registry/runtime-test.yamlThe first prints the planDigest (prefix it with sha256:) for a descriptor of ASCII text and
integers. The second installs the candidate on the disposable database, reports its schema
fingerprint, and rolls back without writing a receipt.
Test and package the successor
Section titled “Test and package the successor”Pass the active package directory as --baseline-package to both test and package, and the
reviewed migration directory as --reviewed-migrations when there is one:
bregctl test ./directory \ --baseline-package /srv/registry/build-1/package \ --reviewed-migrations ./reviewed-migrations \ --runtime-config /srv/registry/runtime-test.yaml \ --credentials /srv/registry/schema-test-credentials.yaml \ --output /srv/registry/build-2/schema-test-receipt.jsonFixture run passed. 1 journey. profile production registry revision sha256:860bbd3a1f5734e46a7a363dd12b6d426e26b7bedf5da44d0cc7b108b44d2cfe schema fingerprint sha256:9c2373a0550d2c11dd8514931e2f789367a18666b658370b21bd3875cfcddc62 successful journeys directory-contactWith a baseline, test first rebuilds the predecessor schema on the disposable database, then runs
the successor migration over it in the order apply will: pre-assertions, the generated
statements, your steps, then post-assertions. It requires the result to reach the candidate’s
schema fingerprint, and rolls it all back before the journeys run. A statement PostgreSQL refuses
fails test, so package cannot publish a migration apply would reject. The rehearsal runs over
empty tables: it does not prove that your data satisfies a step or an assertion. Rehearse that on a
restored copy of production. The run uses up its disposable database; recreate it before the next
run.
Package the successor with the same two arguments, as deploy and serve describes:
Sealed and published a deployment package. profile production package digest sha256:6efe2189c718b7383607b78fd2e3714fc54aaf630b7260e4083ac52dc2146111 registry revision sha256:860bbd3a1f5734e46a7a363dd12b6d426e26b7bedf5da44d0cc7b108b44d2cfe package files 24If a source file, a reviewed file, or the baseline changes afterwards, test and package again. A
refusal of the reviewed directory ends in -refused and names the part that failed, such as
migration.review.coverage-refused for a cover that does not match the diff.
Activate the successor
Section titled “Activate the successor”For a successor, the runtime file still names the active package, and the target comes from
--package. Run plan first. It takes the apply lock, makes every check apply makes before
maintenance begins, then rolls back and records nothing:
bregctl plan --runtime-config /etc/breg/runtime.yaml --package /srv/registry/build-2/packagePlanned a successor activation over the active package; nothing was changed. Run `bregctl apply --package DIR` to activate it. activation successor package digest sha256:6efe2189c718b7383607b78fd2e3714fc54aaf630b7260e4083ac52dc2146111 registry revision sha256:860bbd3a1f5734e46a7a363dd12b6d426e26b7bedf5da44d0cc7b108b44d2cfe active package digest sha256:f55a33146a03b318240ea9329f9707434cf24098910501b083a7ac30bdd5480f role mode split plan kind reviewed change count 1 reviewed migration count 1 checks passed successor-binding, migration-role, runtime-write-authority, prerequisites, proposals, history-coverage, webhook-bindings, active-stateapply activates a package only when it was built over the active one. It refuses a package built
against another baseline, an older package, or another registry’s package with
apply.package.refused, and a runtime file that names another database with
apply.database.identity-mismatch. A destructive migration needs a fresh backup of this
environment’s database: plan lists it under requiredBackups, and both commands take it as
--backup <backupBindingPath>=<binding file>. The binding names the backup file, its digest and
size, and when it was taken, at most 31 days ago; the file must be owner-only (0600) with one
hard link. Backup binding lists its keys, and
a binding that fails a check refuses the apply with apply.backup-evidence.refused before anything
changes.
If plan or apply reports apply.hooks.local-retained, the active package stays ready and no
target is pinned. Settle local hooks before go-live,
then run plan again. This refusal needs no migration reconciliation.
Pass the digest you reviewed as --expected-digest, so a package directory replaced since the
review is refused with apply.package.digest-mismatch before any database contact:
bregctl apply --runtime-config /etc/breg/runtime.yaml --package /srv/registry/build-2/package \ --expected-digest sha256:6efe2189c718b7383607b78fd2e3714fc54aaf630b7260e4083ac52dc2146111 \ --operator-reference change-2026-014Activated the package over its predecessor. activation successor package digest sha256:6efe2189c718b7383607b78fd2e3714fc54aaf630b7260e4083ac52dc2146111 schema fingerprint sha256:9c2373a0550d2c11dd8514931e2f789367a18666b658370b21bd3875cfcddc62 activation id b4db0b17-ae38-440b-a908-10096475a58bThen point the runtime file’s package.root at the successor’s package directory, run
bregctl verify --runtime-config /etc/breg/runtime.yaml, and restart the server. The old process
does not keep serving: its readiness check sees the database move forward, and GET /ready and
Record reads return 503 until it restarts on the successor. bregctl status lists the activation
with outcome applied beside every earlier one.
Applying the package that is already active is refused with apply.package.already-active and
changes nothing; plan reports pending: false in that case, so a deploy job applies only when
plan reports pending: true. An activation also closes every open import authority, because each
is bound to the package it opened under; open a new one to continue a load, as
move data in bulk describes.
Recover a failed activation
Section titled “Recover a failed activation”An apply that fails after maintenance begins leaves the database in maintenance with its target
pinned. Readiness stays unavailable, a restart does not repair it, and no other successor can
activate:
bregctl apply refused.
error apply.migration.failed database the Registry package apply failed and requires exact-target reconciliation
1 error, 0 findings.The ordinary answer is to fix the cause and retry the same apply. When that is not possible, ask
migration reconcile what the database actually holds. Without --execute it changes nothing:
bregctl migration reconcile --runtime-config /etc/breg/runtime.yaml \ --package /srv/registry/build-3/package --operator-reference change-2026-015Reconciled the migration. Outcome revertible, 0 steps. outcome revertible executed false maintenance status failed pinned target digest sha256:71e1800791a91c501d567762119ab15dc16139c19bc9bb09b16f40263989fb0c active package digest sha256:6efe2189c718b7383607b78fd2e3714fc54aaf630b7260e4083ac52dc2146111 presented target digest sha256:71e1800791a91c501d567762119ab15dc16139c19bc9bb09b16f40263989fb0c target catalog finding none active catalog finding none unresolvable reason none plan kind reviewed migration steps 0 reviewed plan closed false durable step progress falseThe outcome is one of five. ready means nothing is pinned. in_progress means another apply or
reconcile holds the lock; wait and assess again. completable means every step of the target
committed and only the activation is missing. revertible means the target committed no step and
the database still matches the active package. unresolvable means the database matches neither
package; restore the backup, as the next section describes.
--execute commits the transition the assessment named, finishing or abandoning the activation, and you cannot take it back.
bregctl migration reconcile --runtime-config /etc/breg/runtime.yaml \ --package /srv/registry/build-3/package --operator-reference change-2026-015 --executeThe report repeats the assessment with executed true. Its maintenance status is the state read
before the transition, so confirm the result with bregctl status, whose first line reads:
The database runs its active package and is ready.Abandoning a target closes its ledger row as reverted and leaves the active package in place; you
can apply the same package again once its cause is fixed. Completing one writes what a successful
apply writes, so update the runtime file and restart as after any activation.
Roll back by rolling forward
Section titled “Roll back by rolling forward”Packages only move forward: the package that was active before is refused with
apply.package.refused. When the activated model is wrong but the data is sound, undo the change
with a new successor. Restore the earlier project sources, keep project.version, run diff
against the active package, and test, package, and apply the result with the active package as
--baseline-package. The diff classifies the revert like any other change: undoing an added field
is a field removal and needs a reviewed migration and a backup. Records written under the undone
model stay, reshaped by the revert’s migration.
An incompatible activation withdraws in-flight Reviews and closes their application jobs. A successor that restores the earlier Action does not revive them. Check the affected submitted Proposals before activation and arrange requester recovery.
When the data itself must go back, because migration reconcile reported unresolvable or a
migration changed rows no successor can restore, restore the backup you took before the apply.
Restoring the pre-activation backup discards every write and activation committed after it, for good.
Follow back up, restore, and drill recovery for the procedure,
including the instance-claim adoption a restored database needs before it serves. Point
package.root at the package that was active when the backup was taken, and build the next
successor over that package. A restore also brings back history that a later history erase
removed, so repeat those erasures before the registry serves.
Restoring Base Registry Engine alone also restores its earlier view of Review work, but cannot undo cancellations already settled by Casework. Compare affected retained rounds with Casework before resuming that work; recover ended rounds through requester Cancel or Rebase and Submit for fresh Review. A database restore does not restore cross-service Review authority.
Recover a blocked Proposal
Section titled “Recover a blocked Proposal”When activation reports package-changed, its withdrawn Review and closed application stay
closed after rollback. The requester must Cancel, or Rebase and Submit for fresh Review.
Operator Resubmit and Retry cannot reopen that round, and a late approval cannot authorize Apply.
You can still read and list these submitted Proposals. An automatic application reports
blocked, reason contract-changed, recovery correct-input, and automaticRetry.scheduled: false,
including when a withdrawn Review received approval before any application job existed.
The historical decision remains evidence; the current turn belongs to the requester.
A Proposal reports its applicationStatus with a safe reason and a recovery hint. A missing review
authority binding stops only the Proposals that depend on it; restore the exact endpoint, producer,
policy, and credential, and never read an authority outage as an approval. When automatic
application is blocked with a last error of dependency-outage, source-unavailable, or
lease-expired, fix the cause and requeue it:
bregctl review-recovery retry-application --runtime-config /etc/breg/runtime.yaml \ --action-id <action> --proposal-id <proposal-id> --submission-version <version>The command needs attempts remaining, puts the application back in the queue, and cannot replace a review decision or reset a refusal on the merits.
Resubmit or close a review the authority lost
Section titled “Resubmit or close a review the authority lost”When the review authority lost or never answered an accepted review, two commands take the same
arguments as retry-application. bregctl review-recovery resubmit sends the exact retained review
request again under its original idempotency key. It applies when the authority reports the result
unknown, when polling ran out of attempts, when submission recovery expired, or after an operator
close. bregctl review-recovery close ends the wait for an accepted review without a result, and
marks it failed as operator-closed. Neither runs on a withdrawn Proposal, one with a recorded
result, or one whose retained request was erased. Fetch the Proposal again afterwards and keep its
submissionVersion.
Turn encryption on over existing plaintext
Section titled “Turn encryption on over existing plaintext”Adding encrypted: true to a field that already holds values is a reviewed migration with a
backfill step and a history choice, as
encrypt a field that already holds data
explains. Around its apply you run two commands.
Before the apply, while the runtime file still names the active package, run the preflight:
bregctl field-encryption preflight --runtime-config /etc/breg/runtime.yaml \ --package /srv/registry/build-4/packageIt changes nothing and reports no values. It binds the same predecessor the apply binds, and reports per covered field the rows still holding plaintext and the retained revisions, Proposal snapshots, idempotency responses, and outbox payloads that mention it. When the field has a unique lookup, it names up to 64 Records whose values would collide; correct one of each pair first, since the apply refuses the same collision before it seals anything.
When the history choice is retain-plaintext-history, the apply also refuses while a retained
Proposal snapshot still holds the field’s plaintext, with
field-encryption.history.retained-proposal-snapshot. Choose erase-and-rebaseline instead and
build the package again.
With erase-and-rebaseline, the apply seals the live values but erases nothing. Once the package is
active, write an owner-only request file with an operator reference and a reason:
{ "operatorReference": "change-2026-016", "reason": "erase plaintext history after the field encryption flip"}chmod 600 erase-history.jsonerase-history erases the whole retained history of every Record still holding pre-flip plaintext, for good.
bregctl field-encryption erase-history --runtime-config /etc/breg/runtime.yaml \ --request-file /srv/registry/private/erase-history.jsonThe request names no Record or field: its scope is the recorded erase-and-rebaseline flips, and a
file with any other key, or one other users can read, is refused before a connection opens. For
each affected Record it uses the same erasure as history erase,
then restores snapshot coverage with one rebaseline, which blocks reads and writes until it commits;
run it in a maintenance window as
restore snapshot coverage
describes. Backups taken before the flip still hold plaintext until you retire them.
Codes this page does not cover, and what each asks of you, are on monitor and recover.
- Change a registry with an Action to practise an Action and a Proposal on a local registry.
- Retain, erase, and audit for erasure and the audit journal every activation writes to.
- Base Registry Engine configuration reference for the descriptor and backup binding keys.