## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Managed Cloud instances must retain shared objects and secrets across server replacement. > - Implicit storage defaults can select local disk or an unintended bucket. > - Cloud runtime settings now come from a canonical database snapshot. > - This pull request validates bootstrap prerequisites before database setup and effective S3 settings after snapshot resolution. > - The benefit is an actionable refusal before local signing state or HTTP setup, while valid canonical Cloud settings work. ## Linked Issues or Issue Description **What happened?** A managed Cloud instance could start with local disk storage, an implicit bucket, or a local key file. After canonical Cloud configuration landed, checking only environment or file values would also reject a valid database bucket. Shared schema defaults could make an omitted raw bucket appear explicit. **Expected behavior** Managed Cloud requires external PostgreSQL, a stable environment master key, and effective S3 settings with a nonblank bucket. The canonical snapshot remains the configuration authority. Invalid configuration refuses startup before signing or HTTP effects. Self-hosted defaults remain compatible. **Steps to reproduce** 1. Select managed Cloud with its tenant token or managed configuration document. 2. Leave storage as local disk, or omit the S3 bucket from a raw canonical snapshot. 3. Start the server. Startup must refuse. An explicit valid bucket in the canonical snapshot must work without a file or environment bucket. **Paperclip version or commit** Current base: `4edd10804e2b42941aa2038e90c362c13a0b98dc`. **Deployment mode** Managed Cloud. Ordinary self-hosted defaults remain unchanged. Related PRs: #18335 owns canonical Cloud runtime configuration. #18327 owns S3 run logs. #18352 owns credential persistence. #18347 owns cutover checks. This PR adds configuration prerequisites to those existing boundaries. ## What Changed - Validate external PostgreSQL and a stable environment master key at the Cloud bootstrap boundary, before database setup. - Validate effective S3 settings after canonical snapshot publication and before Cloud signing, runtime consumers, or HTTP setup. - Require a nonblank bucket in the Cloud snapshot schema. Reject omitted or blank buckets in every legacy Cloud file before one-time import normalization, even when an environment bucket is supplied. Keep shared self-hosted schema defaults. - Preserve typed bucket provenance at the existing file-read boundary. Accept explicit canonical bucket names, including `paperclip`, and environment settings when the snapshot is null. - Validate effective legacy storage with the existing resolver before seeding. A rejected candidate leaves no canonical rows, so a corrected source file can be imported on retry. Environment overlays are checked but never persisted. - Add real PostgreSQL snapshot and migration-retry tests, startup refusal tests, and environment/file compatibility controls. Document that Cloud always requires its stable environment key, even with an external default secrets provider. ## Verification - Latest focused acceptance passed 199/199 in six files (54.29 seconds), using the command below plus `server/src/__tests__/cloud-configuration-migration-postgres.test.ts`. Real PostgreSQL proves rejected storage leaves all three migration authority rows absent, then a corrected legacy file imports on retry. An external-default-secrets control proves the deliberate unconditional Cloud master-key contract. - Before the peer correction, the frozen product diff passed 178 tests in five files: managed-cloud-prerequisites, cloud-runtime-config, server-startup-feedback-export, config-file, and cloud-configuration-readers. Command: `pnpm exec vitest run server/src/__tests__/{managed-cloud-prerequisites,cloud-runtime-config,server-startup-feedback-export,config-file,cloud-configuration-readers}.test.ts --maxWorkers=1 --no-file-parallelism`. Real PostgreSQL used a qualified POSIX test-only PG18.6 override because the host cannot supply the stock runtime's SysV semaphore allocation. - Coverage includes raw database and legacy omitted/blank bucket rejection, valid database and environment buckets, null snapshots, explicit default-named buckets, self-hosted compatibility, and refusal before local signing mutation. - The real AWS SDK configured-provider control uses a simulated loopback S3 service. It writes, reads from a fresh provider, and deletes an object. It also reads an encrypted credential with the stable environment key. This does not qualify production cloud permissions. - `git diff --check` passed. Final merges add unrelated master changes from #18241, #18363, and #18370; the focused storage product diff is unchanged. - Peer review reproduced a fresh legacy local-disk import that gained the default bucket before an S3 environment overlay. The importer now requires a raw explicit bucket for every legacy Cloud file. Six controls cover omission/blank values with and without an environment bucket; the full focused rerun passed 184/184 across all five files in 194.89 seconds. - Initial revised startup testing passed 167/168: one fixture omitted the database URL and failed before its intended key assertion. The corrected final run passed 178/178. An initial direct server typecheck failed because Runner declarations were not built; this is a setup failure, not a compile pass. Integration verification rebuilds dependencies before full checks. - Prior head `fae1e4aa` passed complete CI with 53 successes, two intended skips, and Greptile 5/5. `de71345f379cecb71dbfb219f35e14f942c10a3f` then passed complete CI (55 successes, three workflow skips, and a neutral external review), with Greptile 5/5 and zero unresolved threads. The external review identified the preseed rejection problem; its correction is committed at `75e04d0d77e19dcef0a8cb070b6b518f0f173b14`. The final merge head is `19b809ea2823a6a4e5ceb48ef2c2bac4721733d6`; [Fresh complete CI](https://github.com/paperclipai/paperclip/actions/runs/41127309762) passed on that head: 55 successful checks, three workflow skips, and one neutral external review. Greptile reviewed this exact head at 5/5 with no actionable issues. Both earlier external review threads are resolved with the tested fixes. Earlier infrastructure retries remain documented in [the first diagnosis](https://github.com/paperclipai/paperclip/pull/18350#issuecomment-6529964690) and [the runner shutdown evidence](https://github.com/paperclipai/paperclip/pull/18350#issuecomment-6530364769). Earlier missing SDK build and fixture metadata failures were repaired before those passes. - Current frozen-head CI first attempt failed one unchanged Antigravity authentication success case in Runner Vitest 1/2 (`outcome=failure`, 1994 tests passed). The same file passed 11/11 locally in 2.33 seconds, and identical Runner source passed the prior complete CI run. Attempt 2 retried only failed jobs (the Runner shard and dependent aggregate). Both passed without a source change. No second retry was used. The exact timing cause is not established. [Failed job](https://github.com/paperclipai/paperclip/actions/runs/41127309762/job/124309181778). - No production data, buckets, or live credentials were used. Complete local repository suites are not duplicated in this worktree; current-head required CI supplies the full suite gate. ## Risks - Managed deployments with local disk, omitted buckets, file-only keys, or missing external database URLs now refuse startup. Operators must supply valid prerequisites before rollout. - Required deployment environment overlays must remain stable across replacements. For example, a saved local-disk provider with an explicit bucket can use an environment S3 provider; removing that required overlay correctly refuses startup until the environment or canonical configuration is corrected. Environment settings are never silently copied into the canonical row. - Effective legacy storage must also pass before first seeding. A bad candidate does not create authority rows. Existing saved rows remain authoritative and can require environment overrides or a canonical configuration change if they are invalid. - A present legacy Cloud file needs an explicit raw bucket even if its saved provider is local disk or its environment supplies a bucket. Correct the file before one-time import, or use the existing absent-file/null-snapshot environment-only setup. Environment overlays are not persisted as operator choices. - A previously stored canonical nonblank `paperclip` bucket is authoritative. Prior normalization erased whether that value was originally explicit. This change cannot recover that provenance and does not reject existing nonblank canonical values. - Validation proves configuration shape, not cloud permissions, database reachability, or key compatibility with existing encrypted data. Keep the master key stable across replacements. - Cloud snapshot JSON parsing is stricter; omitted or blank raw S3 bucket values need correction. No database migration or provider operation changes are added. These prerequisites do not prove that every server persistence path is disposable. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used OpenAI Codex, exact model ID `gpt-6.1-sol`. Context window: unknown. The model used reasoning, repository tools, code execution, and test execution. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
9.4 KiB
Cloud runtime configuration
Cloud-managed servers store nonsecret startup and application choices in the
private instance_settings singleton cloud-runtime-config:v1. The row contains
version: 1, an increasing revision, and a strict allowlist of configuration
fields. It is separate from the normal instance settings row and is not exposed
by the instance settings API.
The canonical managed-instance predicate applies: a nonblank Cloud tenant token
or the presence of PAPERCLIP_MANAGED_CONFIG, including a blank value, selects
this configuration path. Invalid managed feature documents still retain their
existing validation at application startup.
Startup and authority
- The hosting environment supplies
DATABASE_URLand, if needed,DATABASE_MIGRATION_URL. Cloud requires a PostgreSQL URL and never falls back to embedded PostgreSQL. Credentials, trust keys, provider configuration and deployment wiring needed before the database connection remain supplied by the environment or the existing secret system. This phase parses only the database connection record and requires the stable environment encryption master key. Runtime mode, host binding and authentication checks wait until the saved snapshot has loaded. - After schema migration and database connection, startup reads the private runtime configuration row. An existing invalid row or a database error stops startup. Neither condition selects a legacy file or an old process snapshot.
- If no row exists, startup imports the legacy config once. It uses
PAPERCLIP_CONFIGwhen supplied, otherwise the instance home config path. It does not search working-directory ancestors. A truly absent file saves an authoritativeconfig: nullmarker. This retains the existing dynamic environment/image defaults, including authentication and secret strictness. The marker never enables a later file fallback. A present malformed, linked, nonregular, or oversized file stops initialization. Reads and stored snapshots are limited to 64 KiB. - The seed uses an insert that does not replace an existing row. Concurrent initializers read the committed winner. Later starts do not inspect the legacy file, even when it is absent, corrupt, or changed.
- Startup publishes a detached configuration snapshot, restores the signed Cloud runtime identity, then initializes telemetry, secret-provider defaults, authentication and application services. A saved telemetry opt-out therefore takes effect before a telemetry client can be created.
Managed Cloud always requires a stable environment master key, including when an external default secrets provider is selected. Internal local-encrypted credential paths still need that key.
Managed Cloud validates effective S3 storage after publishing the canonical
snapshot, before signing-key initialization, telemetry, authentication or app
construction. Cloud requires S3 and a nonblank bucket; an authoritative absent
configuration needs explicit environment storage settings. Raw saved snapshots
cannot obtain a bucket from a schema default. Every legacy Cloud file must name its
bucket before its one-time import, even if its saved provider is local disk and
an environment bucket is present. Omission or blank values fail before seeding;
The effective storage candidate is validated before seeding, so a rejected
legacy file leaves no canonical row and can be corrected before retry.
Environment overlays are not saved as operator choices. Correct that file before
import, or use the existing absent-file/null-snapshot environment-only setup.
Ordinary self-hosted file defaults remain unchanged.
Already-canonical nonblank values, including paperclip, remain authoritative.
Prior normalization cannot reliably distinguish an operator's deliberate name
from a historical default, so this release does not invent retrospective
provenance or replace those saved values.
Existing operator environment overlays keep precedence over saved values. Reading an overlaid value does not save it back to the database. Credentials and connection URLs are never copied into this JSONB row.
Cloud does not load instance or working-directory .env files and does not run
legacy worktree configuration repair. The hosting system must supply those
bootstrap variables directly. In particular, a database connection kept only
in .env cannot bootstrap this path. Self-hosted configuration and dotenv
behavior remain unchanged.
Field ownership
The table lists every field consumed from the existing config file and the
remaining known schema fields. Environment-only Config fields, such as
heartbeat maintenance flags and feedback-export credentials, retain their
existing environment sources and are not frozen into the saved snapshot.
| Config file fields | Cloud owner |
|---|---|
server.deploymentMode, exposure, bind, customBindHost, host, port, allowedHostnames, serveUi |
Saved runtime configuration, with existing environment overlays. |
auth.baseUrlMode, publicBaseUrl, disableSignUp |
Saved runtime configuration, with environment overlays and the signed Cloud identity restoration. Saved URLs cannot contain userinfo, query parameters, or fragments. |
database.backup.enabled, intervalMinutes, retentionDays, dir |
Saved runtime configuration, with existing backup environment overlays. |
database.mode, connectionString |
Cloud bootstrap requires the environment DATABASE_URL. The legacy URL is never saved. |
database.embeddedPostgresDataDir, embeddedPostgresPort |
Local embedded-database settings. Cloud does not use embedded PostgreSQL or these paths. |
storage.provider, localDisk.baseDir, s3.bucket, s3.region, s3.endpoint, s3.prefix, s3.forcePathStyle |
Saved runtime configuration, with existing environment overlays. Saved endpoints cannot contain userinfo, query parameters, or fragments. |
secrets.provider, strictMode, localEncrypted.keyFilePath |
Saved nonsecret provider choice and locator, with existing environment overlays. No key bytes are imported or saved. |
telemetry.enabled |
Saved choice, passed to the existing telemetry initialization and opt-out policy before client creation. |
llm.provider |
Saved provider metadata. |
llm.apiKey |
Never saved. The existing OpenAI model-discovery consumer uses OPENAI_API_KEY first. A legacy OpenAI key requires that environment credential before import, or startup stops. Claude key metadata has no server consumer. |
logging.mode, logDir, updates.checkEnabled |
Nonsecret legacy/CLI metadata is preserved. Server logging continues to use its existing environment configuration. |
$meta |
CLI file-write metadata is not application authority. The compatibility view synthesizes valid metadata. |
| Unknown extension keys | No server config consumer exists. They are excluded from the strict saved allowlist, including any unknown credential fields. |
A saved path is a locator. This change does not make local storage objects, backup bytes, logging files, or secret-key bytes durable. Their storage owners and existing environment/secret mechanisms retain responsibility for those bytes. Restart tests prove configuration restoration under a different home; they do not prove durable file, secret, or backup storage.
The decision signing service still uses PAPERCLIP_DECISION_SIGNING_SECRET when
supplied and otherwise reads or creates its instance key file. Managed Cloud
performs that step after effective storage validation; self-hosted startup keeps
its pre-connection ordering. The local encrypted secret provider still uses its environment key
or existing key-file mechanism. Those byte-storage fallbacks are outside this
configuration migration. Qualification must account for them separately; this
PR does not establish file-free secret storage on Cloud.
Internal update and migration interface
cloudRuntimeConfigService(db) provides inspectStored(handle?),
seedIfAbsent(config, handle?), and replace(config, expectedRevision, handle?).
Each method accepts a database or transaction handle. A migration can compose a
config seed, related settings and a receipt in one transaction.
Replacement validates the same allowlist, locks the singleton row, checks the expected revision, and increases it. A stale revision fails. There is no new public configuration endpoint or settings UI. Startup consumers use a snapshot; a saved replacement takes effect on the next initialization/restart.
readLegacyCloudRuntimeConfig(env?) returns the validated nonsecret candidate
without writing it. initializeCloudRuntimeConfig performs the standalone
one-time seed and publishes its committed snapshot. Integrated migration
receipts and deployed Cloud qualification are separate consumers of this
interface.
Verification
server/src/__tests__/cloud-runtime-config.test.ts exercises real PostgreSQL
clients, concurrent writes, transaction rollback, changed homes, a fresh Node
process, environment precedence, secret exclusion, invalid data and database
failure. It uses embedded PostgreSQL by default. A dedicated disposable database
can be selected with PAPERCLIP_RUNTIME_CONFIG_TEST_DATABASE_URL when the host
cannot start embedded PostgreSQL. That database must be reserved for this test.
The test applies the current schema before use.
The startup orchestration controls in
server-startup-feedback-export.test.ts hold configuration initialization and
verify that telemetry, authentication and app construction wait for it. These
local controls do not claim a live Cloud rollout.