Skip to content
Registry StackDocsv0.38.0

Change an active registry

For the operator

View as Markdown

You operate an active registry, deployed as deploy a registry describes, and hold an edited project or a reviewed candidate. At the end of this page the successor package is active, the runtime file names it, and the process serves it; or a failed activation has been assessed and resolved without editing migration state by hand.

A successor goes through the same test, package, plan, and apply sequence as the first package, with the active package as its baseline. Two records keep it in order. The package chain is in the packages themselves: each successor names the digest of the package it follows. The activation history is in each database: its activation ledger records every activation that database has run, and bregctl status reads it. One package promoted through several environments is one link of the chain and one ledger row in each environment’s database. The extra work is the comparison before you start, the migration evidence a reviewed change needs, and the recovery path when an activation fails. Every command reads --help, and paths given to --runtime-config, --package, --backup, and --output must be absolute.

Compare the candidate with the active package

Section titled “Compare the candidate with the active package”

Start by comparing the edited project with the package the runtime file names, so the review covers exactly the changes the successor would carry:

Terminal window
bregctl --format json diff ./my-registry \
--runtime-config /etc/breg/runtime.yaml

--package <directory> in place of --runtime-config takes a closed package directory as the baseline and checks only its integrity, so a reviewer can inspect a candidate without runtime credentials or activation authority; the report labels that baseline integrity_only rather than runtime_bound.

The diff lists model, access, and route changes with their change codes and, for access changes, their direction: a scope, purpose, row-boundary, field, count, history, or export change is marked as widening or narrowing, and mixed changes are marked for review. A change to consent, recipients, or a consent-issuing action carries a reason when it alters who holds an existing consent, such as a client added to a recipient organization or a gated profile widened in place. The reason is a review aid in the diff output, not a runtime guarantee: the successor enforces exactly what its package declares. Replacing a batch grant with import, which a successor that puts an entity under change control needs, removes the entity’s :batch route, and the diff reports it as a route removal; an entity whose loader already held import keeps the same route when change control is added.

Removing a field or an entity is not erasure. The successor drops the live column or table, but every revision snapshot recorded before it still holds the removed values in registry_internal.registry_revisions, and so does every database backup. The diff reports each removal as diff.history.removed_values_retained so the reviewer sees this before packaging. When the removal is for data minimization or a legal reason, erase the retained revisions of each affected record with history erase as well; there is no command that removes one field from history and keeps the rest of each snapshot.

registry.version is bound to the database for its lifetime: a package that changes it can only initialize a new database.

Pass the active package directory as --baseline-package to both test and package, so the receipt and the package bind to the baseline they will succeed. A change the migration planner classifies as compatible additive, such as a new optional field or a code added to a vocabulary a field uses (field_vocabulary_codes_added), a higher maxLength on a text field, or a lower minLength on a string field under the same maxLength (both field_length_widened), needs no further evidence. The diff classifies these as lock_or_rewrite_risk, because the migration replaces the column check under an exclusive table lock and validates every stored row; a minLength lowered to 0 drops the check instead. A string field’s maxLength is its column type, so raising it is field_type_changed and needs a reviewed migration, as does lowering any maxLength, raising a minLength, or widening a decimal. An action that sets or requires the entity is reported as action_target_fields_widened when a relaxed length bound is its only change, and stays additive. A widened grant is an access change: the planner accepts it as metadata-only, but it still needs a reviewed migration descriptor and rehearsal evidence, as the metadata-only case below describes.

An action input that starts accepting more codes is additive (action_vocabulary_codes_added) only when every code it gained was added to its vocabulary in the same successor. An input that starts accepting a code its vocabulary already had, such as a withdraw action that also accepts given, is action_changed and needs a reviewed migration. A predecessor package built before the engine recorded each input’s vocabulary cannot show that a code is new, so every input widening against it is reviewed. An additive input widening is still marked for review in the diff, because the action accepts the new codes with no further check. When test or package reports migration.review.required, prepare a reviewed migration directory and pass it to both commands as --reviewed-migrations:

reviewed-migrations/
modules/<module-id>/migrations/<migration-id>/
descriptor.json
rehearsal.json
steps/<step-id>.sql when the descriptor declares SQL steps
assertions/<assertion-id>.sql when the descriptor declares assertions
fixtures/<fixture-id>.jsonl when the rehearsal references fixtures

The descriptor names the change class, the exact change codes and targets from diff --format json, timeouts, recovery mode, and its evidence paths. The rehearsal receipt binds the prior package digest and schema, the descriptor and SQL hashes, the measured target schema, and the timeout and recovery proofs. Your rehearsal process produces that evidence against the exact candidate; the command line validates it and does not generate it. The only worked example of the exact descriptor and rehearsal receipt fields is the review construction in products/breg/scripts/test-adopter-workflow.sh at the release tag you operate, which builds both documents for a metadata-only access change and passes them to test. JSON artifacts use canonical JSON: sorted keys, no extra whitespace. Keep only referenced files in the directory; symbolic links and unrelated files are refused. A metadata-only change can declare empty steps and assertions and still needs rehearsal evidence. A field added as required is created without its constraint, your reviewed steps backfill it, and the plan constrains the column afterwards.

Terminal window
bregctl --format json test ./my-registry \
--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/schema-test-receipt-2.json

Every file is canonical JSON or SQL, and every path inside a descriptor is relative to the reviewed directory root, starting with modules/<module-id>/migrations/<migration-id>/.

  • descriptor.json holds id, changeClass, covers (each change code and target from diff --format json that is not compatible additive), recovery (exact_target_resume), lockTimeoutMs, statementTimeoutMs, steps, preAssertions, postAssertions, rehearsalReceiptPath, and, for a destructive change, backupBindingPath.
  • A step is one of three kinds. transactional_sql runs one statement from sqlPath in its own transaction, which also records the step’s ledger progress, so a later step or post-assertion that fails does not roll it back and apply resumes after it; DML must declare affectedRows bounds, and every row it changes gets a history revision. chunked_backfill runs one UPDATE of its declared entity once per chunk of record identifiers, bound as a UUID array, within chunkSize (at most 1,000) and maxTotalRows; each chunk commits on its own and appends one history revision for every row it changed, in one history commit, so snapshot and as-of reads agree with the backfilled rows. field_encryption_backfill carries no SQL; the engine seals the covered fields chunk by chunk and journals each chunk the same way. A journaled step is one UPDATE, after any leading comments, and may not name a record metadata column (record_revision, record_lifecycle, active_package_revision, created_at, updated_at) anywhere in the statement, even to read it. Outside comments and plain string literals, its text may not contain a second statement or another statement word such as drop or delete, and a transactional_sql step may not name record_id. A dollar-quoted body or a literal holding a backslash is read as written, so a statement word inside it refuses the step. A Unicode-escape identifier or string (U&"...", U&'...', with or without UESCAPE) refuses the step wherever it appears. The journal also refuses a step that changed any record metadata column. Each step lists the objects (schema, table, entity, kind, member, physical name) its SQL may touch.
  • An assertion is one SELECT returning exactly one boolean column. Pre-assertions run against the predecessor tables before any step, post-assertions against the successor tables after every step, and activation stops on a false result.
  • rehearsal.json binds the prior package digest and schema fingerprint, the descriptor and SQL digests, the fixture inventory, the PostgreSQL major version, the affected row counts your rehearsal observed, the final schema fingerprint, and the lock-timeout and resume proofs. A destructive descriptor’s backupBindingPath is modules/<module-id>/migrations/<migration-id>/backup.json, but that file is not part of the reviewed directory or the package. It names the backup binding each environment supplies at activation, because every environment takes its own backup of its own database. The binding is a JSON document you write beside the backup, outside the package, and pass to plan and apply as --backup <backupBindingPath>=<absolute binding file>. It holds exactly these keys: databaseId, priorPackageDigest, priorSchemaFingerprint, backupFile (the absolute path of the backup), sha256 (sha256: and the hex digest of the backup), byteLength, createdAt, and maxAgeSeconds (at most 31 days).

apply accepts a backup only when all of these hold: the binding names the database, package digest, and schema fingerprint this database records as active; createdAt is no older than maxAgeSeconds and not in the future; backupFile is absolute and is a regular file rather than a symbolic link, owned by the user running apply, with mode 0600 and exactly one hard link; its length and SHA-256 digest equal the binding. Take the backup with your PostgreSQL tooling immediately before apply, chmod 600 it, and record its digest and length in the binding. A backup that fails any check refuses the apply before maintenance begins, with apply.backup_evidence.refused, and plan with the same --backup runs the same checks without changing anything. The ledger row of the activation records each binding path with the backup’s path, digest, length, and creation time.

With --baseline-package, test rebuilds the predecessor schema from the baseline package’s sources on the disposable database, checks that it reproduces the predecessor’s schema fingerprint, then runs the successor migration over it in the order apply would: pre-assertions, the compiler’s statements, your reviewed steps, the deferred constraints and views, and post-assertions. It then requires the result to match the candidate’s schema fingerprint, and rolls everything back before the ordinary journey test runs. A statement PostgreSQL refuses, an assertion that is not a single boolean column, or a plan that does not reach the candidate schema fails test, and so does a journaled step whose SQL the history journal refuses. package, which requires that receipt, therefore cannot publish a plan apply would reject. The report names the migration, step or assertion, the SQLSTATE and its class, and the table, column, or constraint PostgreSQL named. It never includes a PostgreSQL message, because those can quote row values.

The rehearsal runs over empty tables, so it proves that the SQL is valid, ordered, and reaches the target schema. It does not prove that your data satisfies a step or an assertion: a duplicate key, a row count outside affectedRows, or an assertion that is false over real rows is found only by your own rehearsal on a restored copy of production and by apply itself. Field-encryption backfill steps are skipped, because they need key material and rows. Reviewed fixture files are bound by digest only; the rehearsal does not load them.

Package as deploy a registry describes, with the same --baseline-package and --reviewed-migrations arguments. If any source, reviewed artifact, or baseline changes afterwards, repeat the test and the package. A refusal names its cause. A code ending in _refused names the part of the reviewed directory that failed (the troubleshooting table below lists each); migration.review.refused means a precondition such as the prior package digest or the baseline binding failed first; for migration.review.fingerprint_mismatch, rehearse the exact candidate again. Reviewed artifacts cannot authorize a change the planner classifies as unsupported.

For a successor, the runtime file given to plan and apply still names the active package; the target comes from --package. apply activates a package only when it follows the active one: the package belongs to this registry and its migrationPlan.fromPackageDigest, which --baseline-package set, is the active package digest. It refuses a package built against another baseline, an older package, and a package of another registry with apply.package.refused, and a runtime file whose identity.databaseId is not the one the database records with apply.database.identity_mismatch. Run plan first: under the apply lock it makes these checks and the ones apply makes before maintenance begins, in the same order, then rolls everything back and records nothing. It does not run the migration statements; test rehearsed those. It lists the backup bindings the migration requires in requiredBackups. A destructive change needs one binding per environment, passed to both commands by its descriptor path:

Terminal window
bregctl --format json plan \
--runtime-config /etc/breg/runtime.yaml \
--package /srv/registry/build-2/package \
--backup modules/core/migrations/retire-legacy-field/backup.json=/srv/registry/backups/retire-legacy-field-binding.json
bregctl --format json apply \
--runtime-config /etc/breg/runtime.yaml \
--package /srv/registry/build-2/package \
--backup modules/core/migrations/retire-legacy-field/backup.json=/srv/registry/backups/retire-legacy-field-binding.json

apply reports activation: successor and the activationId of the ledger row it recorded; bregctl status --runtime-config /etc/breg/runtime.yaml shows that row with outcome applied beside every earlier one.

Update the runtime file’s package.root to the successor’s package directory, run bregctl verify --runtime-config /etc/breg/runtime.yaml, and restart the server promptly. Editing the runtime file does not change the active process, but the still-running old process does not keep serving the package it started with either: its readiness check re-verifies the database’s active identity on every poll, and once the successor’s apply moves that identity forward, the check fails. GET /ready and record reads return 503 and the process logs repeated refusals until you restart it onto the successor package. A read checks maintenance status and the active package when it runs its query. A result it already read under the old package can still be returned after the activation or after maintenance begins; its audit response entry does not check the package again.

apply refuses the package that is already active under the same roles with apply.package.already_active and exits 1, changing nothing. The exception is a split role database whose runtime role lost a grant the package gives it: apply then reissues the grants. A deploy job that may meet an environment already on the package runs plan first, which exits 0 with pending: false and activation: none in that case, and calls apply only when pending is true.

An activation supersedes every open import authority, because each is bound to the package it opened under: from the moment the successor is active, no chunk or new run is admitted under it, and an import run in progress stops as blocked. The activation closes each one inside its own transaction and appends one audit record per supersession once it commits. Open a new authority under the successor to continue a load, as move data in bulk describes.

migration reconcile assesses a pinned registry and changes nothing without --execute:

Terminal window
bregctl --format json migration reconcile \
--runtime-config /etc/breg/runtime.yaml \
--package /srv/registry/build-2/package \
--operator-reference "<change reference>"

It takes the same exclusive migration lock an activation takes, so it never reads a half-applied package, and it compares the live managed catalog against both the pinned target’s expected catalog and the active package’s using the verification an activation performs. It reports exactly one outcome:

OutcomeWhat the database holdsNext move
readyNothing is pinned.Nothing to reconcile.
in_progressAnother session holds the migration lock.Wait for that activation to finish, then assess again.
completableEvery durable step of the pinned target succeeded and the catalog is exactly the target’s. Only the activation transition is missing.Rerun with --execute to finish the activation.
revertibleThe pinned target reached no durable step and the catalog is still exactly the active package’s.Rerun with --execute to abandon the target.
unresolvableThe catalog matches neither package, or a reviewed migration committed steps only the same target can finish.Restore the pre-activation backup. The assessment exits as a refusal, migration.reconcile.outcome.unresolvable, whose message names the reason and the catalog finding for each package.

--execute performs only the transition the assessment named, refuses every other outcome, and writes the transition’s response audit entry after the database transaction commits. Completing an activation writes the same ledger, history baseline, and postconditions a successful apply writes, so update the runtime file and restart as you would after any activation. Abandoning a target clears the pin, leaves the active package untouched, and closes the target’s ledger row as reverted. The abandoned package can be applied again once its cause is fixed; that attempt is a separate activation with an activationId of its own, and bregctl status lists both rows.

Packages apply forward only: apply activates a package only when its migrationPlan.fromPackageDigest is the active package digest, so the package that was active before, or any older one, is refused with apply.package.refused. There are two ways back, and which one fits depends on whether the data must return too.

Roll forward to undo a model change. When the activated model is wrong but the data it holds is sound, build a successor that reverts the change:

  1. Restore the earlier project sources, for example from version control. registry.version stays as it is.
  2. Run diff against the active runtime file. The planner classifies the revert like any other change: undoing an added field is a field removal, which is destructive and needs a reviewed migration and its backup artifact; undoing a widened grant is an access change that needs reviewed evidence.
  3. Test, package, and apply the successor with the active package as --baseline-package, as the sections above describe, then update the runtime file and restart.

The revert is a new package with its own activation, ledger row, audit record, and history baseline. Records written under the undone model stay, reshaped by the revert’s own migration.

Restore the pre-activation backup when the data itself must go back: migration reconcile reported unresolvable, or a reviewed migration changed rows in a way no successor can undo.

  1. Stop the server and preserve its audit files, including operator companion files, in your append-only archive.
  2. Restore the database from the backup you took before the apply, with your PostgreSQL tooling.
  3. Point the runtime file’s package.root at the package that was active when the backup was taken, and run bregctl verify. The restored activation ledger ends at that package, so bregctl status no longer lists the activations committed after the backup.
  4. A logical restore is a new database, so the server refuses to serve it until you adopt it: run bregctl instance-claim adopt as back up and restore the database describes, then start the server. The adopt supersedes every import authority the backup held open; open again only the ones you still need.

A restore discards every database write and activation committed after the backup, and it brings back history that a later history erase removed: repeat those erasures before the registry serves traffic again. Webhook deliveries and exports already sent are not recalled. Build the next successor with the restored active package as --baseline-package, not the discarded one.

Take a logical backup of the serving database with the PostgreSQL tools, as an administrator, before every activation:

Terminal window
pg_dump --format=custom --file=/srv/backup/registry-before-build-2.dump registry

To restore it, stop every breg process that serves the registry, then recreate the database with the database-level statements from provision PostgreSQL, which a dump does not carry, and restore the dump into it as an administrator:

CREATE DATABASE registry_restored;
REVOKE ALL ON DATABASE registry_restored FROM PUBLIC;
GRANT CONNECT ON DATABASE registry_restored TO registry_migration, registry_runtime;
Terminal window
pg_restore --exit-on-error --dbname=registry_restored /srv/backup/registry-before-build-2.dump

For a spatial project, also repeat the REVOKE CREATE ON DATABASE statement. Point the runtime file’s database references at the restored database, then check it before anything serves from it:

Terminal window
bregctl verify --runtime-config /etc/breg/runtime.yaml
bregctl --format json instance-claim status --runtime-config /etc/breg/runtime.yaml

If the restored database is pinned in maintenance, run migration reconcile before going further.

Every registry database holds an instance claim: the PostgreSQL system identifier and database object identifier of the database the registry serves from, recorded at the first apply. A restored copy carries the claim of the database it was dumped from, so instance-claim status reports "matches": false, bregctl doctor refuses with startup.instance_claim.mismatch, breg exits logging that the database is not the instance its claim names, and GET /ready answers 503. The refusal exists because two databases serving one registry become divergent writers: each accepts writes, admits imports under the authorities the backup held open, and delivers the same outbox work from the backup onward, and nothing can merge the two histories afterwards.

Every activation, including the first apply and the apply that adopts a database into the activation ledger, records the claim of the database it runs in when the database records none, so a registry upgraded from a release without the claim is claimed by its next apply and needs no adopt. An activation never replaces a claim that is already recorded: a restored copy keeps the claim of its original until you adopt it.

The claim is checked when breg starts and on every GET /ready, not on each request. A process that keeps running while its database host name is repointed at a restored copy keeps serving requests on the connections it opens until something acts on its readiness 503. Gate traffic on GET /ready, or restart every breg process, whenever the database behind the registry changes.

The system identifier comes from pg_control_system(). PostgreSQL grants it to every role, but a managed service may withhold it. When the migration role cannot call it, the claim is recorded without a system identifier; when either side lacks one, the claim compares the database object identifier alone, and instance-claim status shows the system identifier as not readable. The object identifier alone still tells another database in the same cluster apart, but a copy restored into a new cluster can receive the same object identifier and is not refused. On such a service the rule that the original is stopped before a copy serves is the only control, so keep it, and grant EXECUTE on pg_control_system() to both roles where the service allows it.

Once the database the claim names is stopped for good, adopt the copy:

Terminal window
bregctl --format json instance-claim adopt \
--runtime-config /etc/breg/runtime.yaml \
--acknowledge-original-retired

adopt runs under the migration credential. Under the same lock an activation takes, it moves the claim to this database with its epoch raised by one, supersedes every open import authority, all in one transaction. Once that commits, it appends each superseded authority’s transition record and an audit entry naming the previous and the adopted claim and the authorities it superseded to the bregctl companion of the configured audit destination. A backup holds the authorities that were open when it was taken, including any you closed afterwards, so no authority survives an adopt: open again the ones you still need. On a database the claim already names, as after a physical restore, it claims the database again the same way and records the event reclaimed. Without --acknowledge-original-retired it refuses before it opens a connection. Then run bregctl doctor, start the server, and check GET /ready.

An upgrade with pg_upgrade moves the registry into a new cluster with a new system identifier, so it needs the same adopt once the old cluster is stopped. A failover to a streaming replica keeps the system identifier and needs none.

migration explain --runtime-config <file> describes the configured package’s migration plan without executing it; it does not report live database status. New schema fingerprints identify columns by name, so a fresh installation and an in-place upgrade match even when PostgreSQL stores columns in a different order. A package measured with the updated algorithm needs the updated server binary, and earlier packages keep verifying under their recorded rules.

SymptomNext move
diff reports the baseline as integrity_onlyYou passed --package. That is the reviewer’s view; pass --runtime-config for the comparison against the activated package.
test or package reports migration.review.requiredThe planner classified a change as needing review. Rehearse it, write the reviewed migration directory, and pass --reviewed-migrations to both commands.
test or package reports migration.review.descriptor_refusedA descriptor is malformed, for example an empty or duplicate covers entry, a missing or unknown field-encryption history choice, a chunk size beyond the commit budget, or a plaintext drop ordered before its sealing backfill. Fix the descriptor, then repeat test and package.
test or package reports migration.review.coverage_refusedThe directory does not cover exactly the reviewed changes: a change is uncovered, a cover names a change the candidate does not make or the wrong change class, a fixture is not bound to any step, or the change is one reviewed artifacts cannot authorize.
test or package reports migration.review.sql_refusedA step or assertion uses SQL outside the reviewed subset, for example more than one statement, transaction or role control, an unqualified or undeclared object, or DML without affected-row bounds.
test or package reports migration.review.evidence_refusedThe rehearsal receipt, external backup binding, or fixture bytes do not match this candidate, baseline, and database. Rehearse again and copy the new receipt.
test or package reports migration.review.closure_refusedThe set of files does not close: a descriptor the directory names is missing, a path appears twice, or there are too many artifacts.
test or package reports migration.review.refusedA precondition failed before the reviewed directory was read, such as the prior package digest or the baseline binding. Check the baseline arguments and the fingerprints in the rehearsal receipt.
test or package reports migration.review.fingerprint_mismatchThe rehearsal receipt was taken for a different candidate or baseline. Rehearse the exact candidate again.
test reports migration.rehearsal.step_failed, migration.rehearsal.assertion_failed, or migration.rehearsal.compiler_statement_failedPostgreSQL refused the named step, assertion, or generated statement when test rehearsed the successor over the predecessor schema, so apply would refuse it too. The message names the SQLSTATE, its class, and the object. Correct the reviewed SQL or the project, then repeat test and package.
test reports migration.rehearsal.history_step_refusedThe named step changes rows, and apply journals every row it changes, but the step’s SQL is not an update the journal can record: it does not start with UPDATE once leading comments are set aside, it holds a second statement or a semicolon before its end, it names a record metadata column (record_revision, record_lifecycle, active_package_revision, created_at, updated_at) anywhere, even in a read, it uses a Unicode-escape identifier or string (U&"..." or U&'...'), a transactional step names record_id, it uses a refused statement word (insert, delete, truncate, alter, drop, create, merge) outside a comment or plain string literal, or inside a dollar-quoted body or a literal holding a backslash, or its objects span more than the step’s entity. Correct the reviewed SQL, then repeat test and package.
test reports migration.rehearsal.schema_mismatchThe rehearsed migration does not reach the candidate schema, for example a required field the reviewed steps never constrain. Activation would refuse it.
test reports migration.rehearsal.baseline_unavailable or migration.rehearsal.baseline_not_reproducibleThis bregctl cannot rebuild the active package’s schema from its sources. Run test with a bregctl release that compiles the predecessor, or report the mismatch.
apply reports apply.migration.statement_failedPostgreSQL refused a statement after maintenance began. The message names the SQLSTATE, its class, and the table, column, or constraint PostgreSQL reported, for example 23502 for stored rows a new NOT NULL refuses or 22P02 for a value a reviewed step cannot convert. Fix that cause and retry the same target, or run migration reconcile as for apply.migration.failed.
apply reports apply.migration.failedThe apply failed after maintenance began. Read the report, fix the cause, and retry the same target. If that is not available, run migration reconcile to assess the pinned target, execute the transition it names, and restore the pre-activation backup only when it reports unresolvable.
apply reports apply.database.unavailableThe migration database could not be reached, or another apply held the migration lock past the lock timeout, before maintenance began. Check that the database is reachable and accepts the migration role, then retry the same apply. Nothing was changed, so there is nothing to reconcile.
apply reports apply.package.empty_planThe successor has nothing to apply: its plan has no schema statement and no reviewed migration, and it is not an access or disclosure change alone, for example a successor built from unchanged sources. Keep the active package until the model changes, then build the successor from that change. Nothing was changed.
apply reports apply.package.already_activeThe database already runs this package under the configured roles, so there is nothing to activate and nothing was changed. bregctl status shows the active package. A deploy job runs plan first and applies only when it reports pending: true.
apply or plan reports apply.package.refused with does not follow the active packageThe package’s migrationPlan.fromPackageDigest is not the active package digest, or the package belongs to another registry: it was built against another baseline, it is older than the active package, or it skips a successor this database has not activated yet. Rebuild the successor with --baseline-package naming the active package directory. To go back, roll forward or restore, as roll back by rolling forward describes.
apply or plan reports apply.database.identity_mismatchThe database records another database id than the runtime file’s identity.databaseId. Point database.migrationUrlRef at the database the file names, or correct identity.databaseId. Nothing was changed.
apply or plan reports apply.backup_evidence.refusedA backup binding is missing, names another database, package, or schema fingerprint than the active one, is older than maxAgeSeconds, or its backupFile fails the file, owner, mode, length, or digest check. Take a fresh backup of this environment’s database, write its binding, and pass it with --backup. Nothing was changed.
apply reports apply.package.active_mismatchThe runtime file’s package.root names a package the database does not record as active and ready. Run bregctl status to read the active digest, set package.root to that package directory, and apply again. If the database has never been activated, apply with --initial. If the database is pinned in maintenance, run migration reconcile. Nothing was changed.
apply reports apply.history.coverage_incompleteRetained history coverage does not admit a successor: a field-encryption erase-history run has not finished, or an erasure reached the coverage baseline or left a gap the dedicated history-erasure coverage table does not record. Finish the erase-history run, or run history rebaseline, then apply the same package again. Maintenance state was not changed, so there is nothing to reconcile.
breg refuses to start, or doctor reports startup.instance_claim.mismatchThe database is not the one the registry’s instance claim names, as after a logical restore or a pg_upgrade. Stop the database the claim names for good, then run instance-claim adopt as back up and restore the database describes.
migration reconcile reports in_progressAnother session holds the migration lock. Wait for it to finish, then assess again.
The process still serves the old package after applyActivation changes the database, not the process. Update the runtime file and restart the server.