7.4 KiB
Cloud configuration migration and package qualification
This procedure covers saved adapter preferences, the allowlisted nonsecret startup configuration, and selected bundled plugin packages. The result is a point-in-time report. It does not authorize a deployment, guarantee zero downtime, establish general Railway admission, or prove that a volume can be removed. Secret and key bytes, database credentials, artifacts, logs, local storage, workspaces, and plugin state outside PostgreSQL need their own storage and replacement checks. This includes the decision signing key and local encryption keys. Their byte authority is unchanged by this procedure.
Migration prerequisite
Stop every legacy file writer before the first import. This includes old application replicas and operator commands that can write configuration or adapter settings. One maintenance window is sufficient. The importer cannot make an old file writer participate in its database transaction. Keep those writers stopped after import; later file edits do not update database authority.
Keep the normal database backup and recovery procedure for the instance. Use the new release with the same managed environment and database connection. The server runs migration before constructing configuration-dependent services. For an explicit source-checkout invocation, use:
pnpm exec tsx server/src/qualify-cloud-configuration.ts --migrate
The command uses DATABASE_URL and the normal Cloud environment. It does not
accept credentials as arguments. It writes the receipt to PostgreSQL and prints
a JSON report; no report file or local completion marker is required. Exit code
0 means the requested configuration/package check passed, 2 means the report has
blockers, and 1 means the operation failed. A fresh installation can report a
missing plugin registration before normal boot installation completes. Run the
check again after that installation settles.
Database authority and retry behavior
The private instance_settings singleton cloud-configuration-migration:v1
contains the versioned receipt. The transaction locks that row and composes the
adapter and startup-config owners' atomic seed methods. Completion and both
seeds commit together. A failed write or crash before commit leaves no completed
receipt. A retry uses committed database state and never replaces a later
settings revision. Earlier independently seeded owner records remain authoritative.
The receipt records source kind, a digest of the nonsecret semantic candidate,
the imported revision, and bounded legacy external-registration evidence.
An absent startup configuration remains an authoritative config: null; it is
distinct from an uninitialized database record. No raw secret values are copied.
Once the receipt exists, ordinary boots do not reread legacy source files.
A replacement home can be empty or absent without changing saved authority.
All legacy readers have a 64 KiB limit. They require regular files, reject symlinks and FIFOs, and use bounded reads. External registration inspection reads metadata only. It never loads a registered module or follows a registered package path. A nonempty, invalid, or unreadable registration file creates a durable blocker. Removing the old home does not clear that blocker.
To resolve an external-registration blocker, stop its writer, deliberately remove the unsupported registration metadata at the original source location, and leave a present valid empty JSON array file. Read the current receipt revision from the report, then run:
pnpm exec tsx server/src/qualify-cloud-configuration.ts --recheck-external-revision 3
Use the actual current revision in place of 3. A changed revision, a different
source location, or a missing/invalid source file rejects the recheck. Reusing
the same container path with an empty home is insufficient. This is an explicit
operator resolution; qualification never clears the blocker automatically.
Current packages and overlap comparison
Every qualification reads current settings revisions and plugin registrations. It resolves selected packages from this image with the same bundled package identity rules as normal loading. It verifies package bytes before importing a trusted image manifest, then compares that manifest with the approved database registration. Missing packages, missing evidence, incompatible versions, unselected registrations, and integrity failures prevent qualification. Legacy package paths and npm discovery cannot supply missing image bytes.
Image inspection happens outside database write locks. A short transaction then
locks and rereads the current configuration and registration records. A change
during inspection produces a blocker and requires a new check. The report gives
adapterRevision, configRevision, configurationDigest, and
packageEvidenceDigest. A historical completed receipt is evidence, not current
authorization. Call qualification again for a current result.
The server records this report before ordinary bundled-plugin reconciliation.
It retains lastPackageMismatch after later successful checks, so reconciliation
cannot erase the evidence seen before an upgrade. The report does not block
normal maintenance upgrades or replace capability approval. After an upgrade
settles, its current packages can qualify normally.
For a same-package comparison, capture the current release's
packageEvidenceDigest and pass it explicitly on the replacement:
pnpm exec tsx server/src/qualify-cloud-configuration.ts --expected-package-evidence sha256:REPLACE_WITH_THE_64_HEX_DIGEST
configurationPackagesEligible describes current database/configuration/package
compatibility. samePackageOverlapEligible is true only when an explicit expected
digest also matches. There is no permanent first-release baseline that would
prevent future maintenance releases from establishing their own evidence.
The package evidence digest covers selected packages, not container image bytes.
Distribution packages reuse and reverify their catalog digest. Builtin packages
have a bounded inventory of physical file bytes, modes, and contained symlink
text, including standalone pnpm dependency trees. Dangling or escaping links,
special files, more than 10,000 entries, depth above 32, or more than 256 MiB per
package fail verification. Inventory work is synchronous and linear in selected
package bytes. It runs only during startup before the application is constructed
or in the explicit operator process, with no idle polling. The report includes
packageInspectionDurationMs. Packages must remain immutable throughout a check.
Verification scope
The focused PostgreSQL tests use two independent clients and separate home/image roots. They import legacy settings, change saved revisions, remove the old roots, reconstruct from the database, and start a real plugin worker from the replacement image fixture. They also cover concurrent migration, commit failure and retry, database failure, stale sources, durable external blockers, package mismatch, maintenance reconciliation, and changes during manifest inspection.
Set PAPERCLIP_CLOUD_CONFIGURATION_TEST_DATABASE_URL only to an owned disposable
PostgreSQL database when embedded PostgreSQL is unavailable. The suite applies
migrations and writes/deletes test records. A complete release image, hosted
replacement, overlapping production processes, secret-byte migration, and volume
removal are not established by these local fixtures.