Files
PaperClipAI/server/src/__tests__/cloud-runtime-config.test.ts
T
DottaandPaperclip 198218bf55 fix(config): require shared storage for managed cloud (#18350)
## 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>
2026-10-30 16:21:46 -05:00

330 lines
19 KiB
TypeScript

import fs from "node:fs";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { fileURLToPath, pathToFileURL } from "node:url";
import os from "node:os";
import path from "node:path";
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { applyPendingMigrations, closeRegisteredClients, createDb, instanceSettings, type Db } from "@paperclipai/db";
import { eq } from "drizzle-orm";
import { paperclipConfigSchema } from "@paperclipai/shared";
import { loadCloudBootstrapConfig, loadConfig } from "../config.js";
import { readConfigFile } from "../config-file.js";
import {
CLOUD_RUNTIME_CONFIG_KEY,
cloudRuntimeConfigService,
initializeCloudRuntimeConfig,
readLegacyCloudRuntimeConfig,
} from "../services/cloud-runtime-config.js";
import {
getCloudRuntimeConfigSnapshot,
projectCloudRuntimeConfig,
setCloudRuntimeConfigSnapshot,
} from "../services/cloud-runtime-config-snapshot.js";
import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase } from "./helpers/embedded-postgres.js";
const externalDatabaseUrl = process.env.PAPERCLIP_RUNTIME_CONFIG_TEST_DATABASE_URL;
const support = externalDatabaseUrl ? { supported: true } : await getEmbeddedPostgresTestSupport();
const describePostgres = support.supported ? describe : describe.skip;
let home: string;
let configPath: string;
function legacyConfig() {
return {
$meta: { version: 1, updatedAt: "2026-10-30T00:00:00.000Z", source: "configure" },
llm: { provider: "openai", apiKey: "legacy-openai-secret" },
database: {
mode: "postgres",
connectionString: "postgres://legacy:database-secret@example.test/paperclip",
backup: { enabled: false, intervalMinutes: 73, retentionDays: 11, dir: "/tmp/legacy-backups" },
},
logging: { mode: "cloud", logDir: "/tmp/legacy-logs" },
server: { deploymentMode: "authenticated", exposure: "private", port: 3210, serveUi: false, allowedHostnames: ["saved.example.test"] },
auth: { baseUrlMode: "explicit", publicBaseUrl: "https://saved.example.test", disableSignUp: true },
storage: { provider: "s3", s3: { bucket: "saved-bucket", region: "eu-west-1", prefix: "saved/", forcePathStyle: true } },
secrets: { provider: "local_encrypted", strictMode: true, localEncrypted: { keyFilePath: "/tmp/legacy-master.key" } },
telemetry: { enabled: false },
updates: { checkEnabled: false },
unsupportedSecret: "unknown-secret-value",
};
}
beforeEach(() => {
home = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-runtime-config-"));
configPath = path.join(home, "config.json");
vi.stubEnv("PAPERCLIP_HOME", home);
vi.stubEnv("PAPERCLIP_CONFIG", configPath);
vi.stubEnv("PAPERCLIP_CLOUD_TENANT_SERVER_TOKEN", "test-token");
vi.stubEnv("PAPERCLIP_MANAGED_CONFIG", undefined);
vi.stubEnv("DATABASE_MIGRATION_URL", undefined);
vi.stubEnv("PAPERCLIP_DEPLOYMENT_MODE", undefined);
vi.stubEnv("PAPERCLIP_AUTH_PUBLIC_BASE_URL", undefined);
vi.stubEnv("PAPERCLIP_AUTH_BASE_URL_MODE", undefined);
vi.stubEnv("PAPERCLIP_PUBLIC_URL", undefined);
vi.stubEnv("BETTER_AUTH_URL", undefined);
vi.stubEnv("BETTER_AUTH_BASE_URL", undefined);
vi.stubEnv("PAPERCLIP_ALLOWED_HOSTNAMES", undefined);
vi.stubEnv("PAPERCLIP_TAILNET_BIND_HOST", "100.100.0.1");
vi.stubEnv("OPENAI_API_KEY", "environment-api-secret");
vi.stubEnv("PAPERCLIP_SECRETS_MASTER_KEY", "12345678901234567890123456789012");
vi.stubEnv("PAPERCLIP_STORAGE_PROVIDER", undefined);
vi.stubEnv("PAPERCLIP_STORAGE_S3_BUCKET", undefined);
setCloudRuntimeConfigSnapshot(null);
});
afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllEnvs();
setCloudRuntimeConfigSnapshot(null);
fs.rmSync(home, { recursive: true, force: true });
});
describe("Cloud configuration file boundary", () => {
it.each(["null", "{", JSON.stringify({ unexpected: true })])("rejects malformed present legacy data %s", async (contents) => {
fs.writeFileSync(configPath, contents);
await expect(readLegacyCloudRuntimeConfig()).rejects.toThrow(/Legacy Cloud runtime configuration/);
});
it("rejects oversized and linked legacy sources", async () => {
fs.writeFileSync(configPath, " ".repeat(65_537));
await expect(readLegacyCloudRuntimeConfig()).rejects.toThrow(/safely/);
fs.rmSync(configPath);
const target = path.join(home, "target.json");
fs.writeFileSync(target, JSON.stringify(legacyConfig()));
fs.symlinkSync(target, configPath);
await expect(readLegacyCloudRuntimeConfig()).rejects.toThrow(/safely/);
});
it("requires an environment credential for the legacy OpenAI consumer", async () => {
fs.writeFileSync(configPath, JSON.stringify(legacyConfig()));
vi.stubEnv("OPENAI_API_KEY", undefined);
await expect(readLegacyCloudRuntimeConfig()).rejects.toThrow(/OPENAI_API_KEY/);
expect(() => readConfigFile()).toThrow(/not been initialized/);
});
it.each(["https://user:secret@saved.example.test", "https://saved.example.test?key=secret", "https://saved.example.test#secret"])("rejects secret-bearing URL %s", async (url) => {
const config = legacyConfig();
config.auth.publicBaseUrl = url;
fs.writeFileSync(configPath, JSON.stringify(config));
await expect(readLegacyCloudRuntimeConfig()).rejects.toThrow(/configuration is invalid/);
});
it("loads the minimal Cloud bootstrap from environment only", () => {
fs.writeFileSync(configPath, "malformed");
vi.stubEnv("DATABASE_URL", "postgres://bootstrap:bootstrap-secret@127.0.0.1:5432/runtime");
expect(loadCloudBootstrapConfig()).toMatchObject({ databaseMode: "postgres", databaseUrl: process.env.DATABASE_URL });
vi.stubEnv("DATABASE_URL", undefined);
expect(() => loadCloudBootstrapConfig()).toThrow(/requires DATABASE_URL/);
vi.stubEnv("DATABASE_URL", "sqlite:///tmp/db");
expect(() => loadCloudBootstrapConfig()).toThrow(/must use PostgreSQL/);
});
it.each(["", " "])("treats an empty migration URL as absent (%j)", (migrationUrl) => {
vi.stubEnv("DATABASE_URL", "postgres://bootstrap:password@127.0.0.1/runtime");
vi.stubEnv("DATABASE_MIGRATION_URL", migrationUrl);
expect(loadCloudBootstrapConfig().databaseMigrationUrl).toBeUndefined();
});
it("normalizes and validates an explicit migration URL", () => {
vi.stubEnv("DATABASE_URL", "postgres://bootstrap:password@127.0.0.1/runtime");
vi.stubEnv("DATABASE_MIGRATION_URL", " postgres://migration:password@127.0.0.1/runtime ");
expect(loadCloudBootstrapConfig().databaseMigrationUrl).toBe("postgres://migration:password@127.0.0.1/runtime");
vi.stubEnv("DATABASE_MIGRATION_URL", "https://wrong.example.test");
expect(() => loadCloudBootstrapConfig()).toThrow("DATABASE_MIGRATION_URL must use PostgreSQL");
});
it("preserves local config file behavior without managed signals", () => {
vi.stubEnv("PAPERCLIP_CLOUD_TENANT_SERVER_TOKEN", undefined);
fs.writeFileSync(configPath, JSON.stringify(legacyConfig()));
expect(readConfigFile()?.llm?.apiKey).toBe("legacy-openai-secret");
expect(loadConfig()).toMatchObject({ port: 3210, telemetryEnabled: false, storageS3Bucket: "saved-bucket" });
});
});
describePostgres("Cloud runtime configuration in PostgreSQL", () => {
let database: Awaited<ReturnType<typeof startEmbeddedPostgresTestDatabase>>;
let first: Db;
let second: Db;
beforeAll(async () => {
if (externalDatabaseUrl) {
await applyPendingMigrations(externalDatabaseUrl);
database = { connectionString: externalDatabaseUrl, cleanup: () => closeRegisteredClients(externalDatabaseUrl, { scope: "database" }) };
} else {
database = await startEmbeddedPostgresTestDatabase("paperclip-runtime-config-db-");
}
first = createDb(database.connectionString);
second = createDb(database.connectionString);
}, 90_000);
beforeEach(async () => {
await first.delete(instanceSettings).where(eq(instanceSettings.singletonKey, CLOUD_RUNTIME_CONFIG_KEY));
vi.stubEnv("DATABASE_URL", database.connectionString);
});
afterAll(async () => { await database?.cleanup(); });
it.each(["PAPERCLIP_CLOUD_TENANT_SERVER_TOKEN", "PAPERCLIP_MANAGED_CONFIG"])("seeds a fresh database with %s", async (signal) => {
vi.stubEnv("PAPERCLIP_CLOUD_TENANT_SERVER_TOKEN", undefined);
vi.stubEnv(signal, signal === "PAPERCLIP_MANAGED_CONFIG" ? "" : "test-token");
const result = await initializeCloudRuntimeConfig(first);
expect(result).toMatchObject({ seeded: true, current: { version: 1, revision: 0, config: null } });
expect(await cloudRuntimeConfigService(second).inspectStored()).toEqual(result?.current);
expect(fs.existsSync(configPath)).toBe(false);
vi.stubEnv("PAPERCLIP_DEPLOYMENT_MODE", "authenticated");
vi.stubEnv("PAPERCLIP_PUBLIC_URL", "https://environment.example.test");
vi.stubEnv("PAPERCLIP_MANAGED_CONFIG", undefined);
vi.stubEnv("PAPERCLIP_CLOUD_TENANT_SERVER_TOKEN", "test-token");
vi.stubEnv("PAPERCLIP_STORAGE_PROVIDER", "s3");
vi.stubEnv("PAPERCLIP_STORAGE_S3_BUCKET", "environment-bucket");
expect(loadConfig()).toMatchObject({ databaseMode: "postgres", authBaseUrlMode: "explicit", secretsStrictMode: true, telemetryEnabled: true });
});
it("loads the saved authenticated mode before validating an environment host bind", async () => {
fs.writeFileSync(configPath, JSON.stringify(legacyConfig()));
vi.stubEnv("HOST", "0.0.0.0");
vi.stubEnv("PAPERCLIP_DEPLOYMENT_MODE", undefined);
expect(loadCloudBootstrapConfig()).toEqual({
databaseMode: "postgres",
databaseUrl: database.connectionString,
databaseMigrationUrl: process.env.DATABASE_MIGRATION_URL,
});
await initializeCloudRuntimeConfig(first);
expect(loadConfig()).toMatchObject({ deploymentMode: "authenticated", host: "0.0.0.0", port: 3210 });
});
it("imports existing choices without credentials and restores them from a second client and new home", async () => {
fs.writeFileSync(configPath, JSON.stringify(legacyConfig()));
const initial = await initializeCloudRuntimeConfig(first);
expect(loadConfig()).toMatchObject({ port: 3210, telemetryEnabled: false, databaseBackupEnabled: false, storageS3Bucket: "saved-bucket", authDisableSignUp: true });
const [row] = await second.select().from(instanceSettings).where(eq(instanceSettings.singletonKey, CLOUD_RUNTIME_CONFIG_KEY));
const persisted = JSON.stringify(row.general);
for (const secret of ["legacy-openai-secret", "database-secret", "unknown-secret-value", "environment-api-secret", database.connectionString]) {
expect(persisted).not.toContain(secret);
}
const replacementHome = fs.mkdtempSync(path.join(home, "replacement-"));
const replacementConfigDir = path.join(replacementHome, ".paperclip");
fs.mkdirSync(replacementConfigDir);
const envPath = path.join(replacementConfigDir, ".env");
const envContents = "PORT=9999\nPAPERCLIP_RUNTIME_CONFIG_ENV_SENTINEL=should-not-load\n";
fs.writeFileSync(envPath, envContents);
vi.stubEnv("PAPERCLIP_HOME", replacementHome);
vi.stubEnv("PAPERCLIP_CONFIG", path.join(replacementConfigDir, "config.json"));
setCloudRuntimeConfigSnapshot(null);
const files = vi.spyOn(fs, "readFileSync");
expect(await initializeCloudRuntimeConfig(second)).toEqual({ seeded: false, current: initial?.current });
expect(loadConfig()).toMatchObject({ port: 3210, telemetryEnabled: false, storageS3Bucket: "saved-bucket" });
expect(files.mock.calls.map(([file]) => String(file)).filter(file => file.startsWith(replacementHome))).toEqual([]);
files.mockRestore();
const root = fileURLToPath(new URL("../../../", import.meta.url));
const childScript = path.join(home, "restore.mts");
fs.writeFileSync(childScript, `
import { createDb } from ${JSON.stringify(pathToFileURL(path.join(root, "packages/db/src/index.ts")).href)};
import { initializeCloudRuntimeConfig } from ${JSON.stringify(pathToFileURL(path.join(root, "server/src/services/cloud-runtime-config.ts")).href)};
import { loadConfig } from ${JSON.stringify(pathToFileURL(path.join(root, "server/src/config.ts")).href)};
const db = createDb(process.env.DATABASE_URL);
await initializeCloudRuntimeConfig(db);
const config = loadConfig();
console.log(JSON.stringify({ port: config.port, telemetryEnabled: config.telemetryEnabled, storageS3Bucket: config.storageS3Bucket,
envSentinel: process.env.PAPERCLIP_RUNTIME_CONFIG_ENV_SENTINEL ?? null }));
await db.$client.end({ timeout: 1 });
`);
const child = await promisify(execFile)(process.execPath, [path.join(root, "server/node_modules/tsx/dist/cli.mjs"), childScript], {
cwd: replacementHome,
env: { ...process.env },
timeout: 30_000,
});
expect(JSON.parse(child.stdout.trim())).toEqual({ port: 3210, telemetryEnabled: false, storageS3Bucket: "saved-bucket", envSentinel: null });
expect(fs.readFileSync(envPath, "utf8")).toBe(envContents);
}, 45_000);
it("keeps environment overlays out of saved settings and ignores corrupt old files after seed", async () => {
fs.writeFileSync(configPath, JSON.stringify(legacyConfig()));
await initializeCloudRuntimeConfig(first);
vi.stubEnv("PORT", "4567");
vi.stubEnv("PAPERCLIP_STORAGE_S3_BUCKET", "operator-bucket");
vi.stubEnv("PAPERCLIP_AUTH_DISABLE_SIGN_UP", "false");
fs.writeFileSync(configPath, "malformed legacy contents");
await initializeCloudRuntimeConfig(second);
expect(loadConfig()).toMatchObject({ port: 4567, storageS3Bucket: "operator-bucket", authDisableSignUp: false, telemetryEnabled: false });
expect((await cloudRuntimeConfigService(first).inspectStored())?.config).toMatchObject({ server: { port: 3210 }, storage: { s3: { bucket: "saved-bucket" } }, auth: { disableSignUp: true } });
});
it("keeps one committed seed when two clients propose different initial values", async () => {
const config = projectCloudRuntimeConfig(paperclipConfigSchema.parse(legacyConfig()));
const results = await Promise.all([
cloudRuntimeConfigService(first).seedIfAbsent({ ...config, telemetry: { enabled: true } }),
cloudRuntimeConfigService(second).seedIfAbsent({ ...config, telemetry: { enabled: false } }),
]);
expect(results.filter(result => result.seeded)).toHaveLength(1);
expect(results[0].current).toEqual(results[1].current);
expect(await cloudRuntimeConfigService(first).inspectStored()).toEqual(results[0].current);
});
it("rejects unknown or credential fields on the direct mutation path", async () => {
const config = projectCloudRuntimeConfig(paperclipConfigSchema.parse(legacyConfig()));
await cloudRuntimeConfigService(first).seedIfAbsent(config);
await expect(cloudRuntimeConfigService(first).replace({ ...config, llm: { provider: "openai", apiKey: "must-not-store" } } as never, 0)).rejects.toThrow(/configuration is invalid/);
expect((await cloudRuntimeConfigService(second).inspectStored())?.revision).toBe(0);
});
it("serializes revisions and rejects competing stale writes from two database clients", async () => {
const config = projectCloudRuntimeConfig(paperclipConfigSchema.parse(legacyConfig()));
await cloudRuntimeConfigService(first).seedIfAbsent(config);
const results = await Promise.allSettled([
cloudRuntimeConfigService(first).replace({ ...config, telemetry: { enabled: true } }, 0),
cloudRuntimeConfigService(second).replace({ ...config, telemetry: { enabled: false } }, 0),
]);
expect(results.filter(result => result.status === "fulfilled")).toHaveLength(1);
expect(results.filter(result => result.status === "rejected")).toHaveLength(1);
expect((await cloudRuntimeConfigService(second).inspectStored())?.revision).toBe(1);
});
it("composes a seed with the caller transaction and rolls back both on failure", async () => {
const config = projectCloudRuntimeConfig(paperclipConfigSchema.parse(legacyConfig()));
await expect(first.transaction(async tx => {
await cloudRuntimeConfigService(first).seedIfAbsent(config, tx);
await tx.insert(instanceSettings).values({ singletonKey: "runtime-config-test-receipt", general: {} });
throw new Error("injected migration failure");
})).rejects.toThrow(/injected migration failure/);
expect(await cloudRuntimeConfigService(second).inspectStored()).toBeNull();
expect(await second.select().from(instanceSettings).where(eq(instanceSettings.singletonKey, "runtime-config-test-receipt"))).toEqual([]);
});
it.each([undefined, "", " "])("rejects raw canonical S3 rows with missing or blank bucket %j", async bucket => {
const config = structuredClone(projectCloudRuntimeConfig(paperclipConfigSchema.parse(legacyConfig())));
const raw = config;
if (bucket === undefined) Reflect.deleteProperty(raw.storage.s3, "bucket");
else raw.storage.s3.bucket = bucket;
await first.insert(instanceSettings).values({ singletonKey: CLOUD_RUNTIME_CONFIG_KEY, general: { version: 1, revision: 0, config: raw } });
await expect(initializeCloudRuntimeConfig(second)).rejects.toThrow(/Persisted Cloud runtime configuration is invalid/);
expect(() => getCloudRuntimeConfigSnapshot()).toThrow(/not been initialized/);
});
it.each(["database-bucket", "paperclip"])("accepts canonical DB bucket %s without local configuration provenance", async bucket => {
const config = projectCloudRuntimeConfig(paperclipConfigSchema.parse(legacyConfig()));
config.storage.s3.bucket = bucket;
await cloudRuntimeConfigService(first).seedIfAbsent(config);
fs.writeFileSync(configPath, "invalid local file must not be consulted");
await initializeCloudRuntimeConfig(second);
expect(loadConfig()).toMatchObject({ storageProvider: "s3", storageS3Bucket: bucket });
expect(readConfigFile({ includeProvenance: true })).toMatchObject({ explicitStorageS3Bucket: true });
});
it("fails closed on invalid database settings without consulting a valid legacy file", async () => {
fs.writeFileSync(configPath, JSON.stringify(legacyConfig()));
await first.insert(instanceSettings).values({ singletonKey: CLOUD_RUNTIME_CONFIG_KEY, general: { version: 99, config: { secret: "hidden" } } });
await expect(initializeCloudRuntimeConfig(second)).rejects.toThrow(/Persisted Cloud runtime configuration is invalid/);
expect(() => getCloudRuntimeConfigSnapshot()).toThrow(/not been initialized/);
});
it("does not fall back to a file or old snapshot when the database fails", async () => {
fs.writeFileSync(configPath, JSON.stringify(legacyConfig()));
await initializeCloudRuntimeConfig(first);
const unavailable = createDb(database.connectionString);
await unavailable.$client.end({ timeout: 1 });
await expect(initializeCloudRuntimeConfig(unavailable)).rejects.toThrow();
expect(() => readConfigFile()).toThrow(/not been initialized/);
});
});