@caelo-cms/provisioning 0.11.5 → 0.11.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-db.js +523 -105
- package/dist/compose.d.ts +7 -0
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +20 -0
- package/dist/compose.js.map +1 -1
- package/dist/pool-budget-stack.d.ts +78 -0
- package/dist/pool-budget-stack.d.ts.map +1 -0
- package/dist/pool-budget-stack.js +78 -0
- package/dist/pool-budget-stack.js.map +1 -0
- package/package.json +2 -2
- package/src/compose.ts +22 -0
- package/src/gateway-env-isolation.test.ts +2 -0
- package/src/pool-budget-stack.ts +116 -0
- package/src/pool-budget-stacks.test.ts +100 -0
- package/src/self-hosted-compose.test.ts +23 -0
- package/src/stack-contract-parity.test.ts +6 -2
- package/stacks/aws/Pulumi.yaml +12 -0
- package/stacks/aws/README.md +1 -0
- package/stacks/aws/index.ts +36 -0
- package/stacks/azure/Pulumi.yaml +4 -0
- package/stacks/azure/README.md +1 -0
- package/stacks/azure/index.ts +35 -2
- package/stacks/gcp/Pulumi.yaml +16 -8
- package/stacks/gcp/README.md +7 -3
- package/stacks/gcp/index.ts +45 -20
- package/stacks/gcp-firebase/Pulumi.yaml +5 -1
- package/stacks/gcp-firebase/index.ts +27 -3
- package/stacks/self-hosted/README.md +4 -0
package/dist/compose.d.ts
CHANGED
|
@@ -16,6 +16,13 @@ export interface DevImages {
|
|
|
16
16
|
export type ComposeImages = ReleaseImages | DevImages;
|
|
17
17
|
/** The admin's HTTP port inside the Compose network (the Caddyfile proxies to it). */
|
|
18
18
|
export declare const COMPOSE_ADMIN_PORT = 5173;
|
|
19
|
+
/**
|
|
20
|
+
* #1254 — the Compose install's connection budget: one admin and one
|
|
21
|
+
* gateway container on a Postgres started with exactly
|
|
22
|
+
* `DEFAULT_MAX_CONNECTIONS["self-hosted"]` slots (the postgres service's
|
|
23
|
+
* `command`), so the pool sizes below always match the server.
|
|
24
|
+
*/
|
|
25
|
+
export declare const COMPOSE_POOL_BUDGET: import("./pool-budget-stack.js").StackPoolBudget;
|
|
19
26
|
/** `.caelo/<this>/` holds the pending Owner-bootstrap token the admin consumes. */
|
|
20
27
|
export declare const BOOTSTRAP_DIR = "bootstrap";
|
|
21
28
|
/**
|
package/dist/compose.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"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"}
|
package/dist/compose.js
CHANGED
|
@@ -27,10 +27,26 @@
|
|
|
27
27
|
*/
|
|
28
28
|
import { CADDY_REDIRECTS_FILE } from "@caelo-cms/shared";
|
|
29
29
|
import { caddyRedirectReloadCommand } from "./caddy-redirects.js";
|
|
30
|
+
import { DEFAULT_MAX_CONNECTIONS, stackPoolBudget } from "./pool-budget-stack.js";
|
|
30
31
|
import { ADMIN_MEMORY_DEFAULT, GATEWAY_TRUSTED_PROXY_HOPS, memoryQuantityMiB, publicSiteUrl, TRUSTED_PROXY_HOPS_ENV, } from "./stack-contract.js";
|
|
31
32
|
import { ghcrRepository, isImageDigest } from "./upgrade/release-images.js";
|
|
32
33
|
/** The admin's HTTP port inside the Compose network (the Caddyfile proxies to it). */
|
|
33
34
|
export const COMPOSE_ADMIN_PORT = 5173;
|
|
35
|
+
/**
|
|
36
|
+
* #1254 — the Compose install's connection budget: one admin and one
|
|
37
|
+
* gateway container on a Postgres started with exactly
|
|
38
|
+
* `DEFAULT_MAX_CONNECTIONS["self-hosted"]` slots (the postgres service's
|
|
39
|
+
* `command`), so the pool sizes below always match the server.
|
|
40
|
+
*/
|
|
41
|
+
export const COMPOSE_POOL_BUDGET = stackPoolBudget("self-hosted", {
|
|
42
|
+
maxConnections: DEFAULT_MAX_CONNECTIONS["self-hosted"],
|
|
43
|
+
adminMaxInstances: 1,
|
|
44
|
+
gatewayMaxInstances: 1,
|
|
45
|
+
});
|
|
46
|
+
/** The budget's env as Compose `environment:` lines for `service`. */
|
|
47
|
+
function poolEnvLines(service) {
|
|
48
|
+
return COMPOSE_POOL_BUDGET.env[service].map((e) => ` ${e.name}: "${e.value}"`).join("\n");
|
|
49
|
+
}
|
|
34
50
|
/** `.caelo/<this>/` holds the pending Owner-bootstrap token the admin consumes. */
|
|
35
51
|
export const BOOTSTRAP_DIR = "bootstrap";
|
|
36
52
|
/**
|
|
@@ -154,6 +170,8 @@ name: ${spec.project}
|
|
|
154
170
|
services:
|
|
155
171
|
postgres:
|
|
156
172
|
image: postgres:16-alpine
|
|
173
|
+
# The pool budget (COMPOSE_POOL_BUDGET) is computed from this number.
|
|
174
|
+
command: ["postgres", "-c", "max_connections=${DEFAULT_MAX_CONNECTIONS["self-hosted"]}"]
|
|
157
175
|
restart: unless-stopped
|
|
158
176
|
environment:
|
|
159
177
|
POSTGRES_PASSWORD: "${escapeYaml(spec.postgresPassword)}"
|
|
@@ -266,6 +284,7 @@ ${serviceRuntime(spec.images, "admin", `sh -c "cd apps/admin && bunx svelte-kit
|
|
|
266
284
|
CAELO_OUTPUT_ROOT: "${SITE_OUTPUT_ROOT}"
|
|
267
285
|
MEDIA_ROOT_DIR: "${MEDIA_ROOT}"
|
|
268
286
|
CAELO_PROVIDER: "self-hosted"
|
|
287
|
+
${poolEnvLines("admin")}
|
|
269
288
|
NODE_ENV: production
|
|
270
289
|
${env("ANTHROPIC_API_KEY", spec.anthropicApiKey)}
|
|
271
290
|
${env("RESEND_API_KEY", spec.resendApiKey)}`;
|
|
@@ -283,6 +302,7 @@ ${serviceRuntime(spec.images, "gateway", "bun run apps/api-gateway/src/server.ts
|
|
|
283
302
|
GATEWAY_PORT: "${COMPOSE_GATEWAY_PORT}"
|
|
284
303
|
# Caddy is the one proxy in front (client-ip.ts in the gateway).
|
|
285
304
|
${TRUSTED_PROXY_HOPS_ENV}: "${GATEWAY_TRUSTED_PROXY_HOPS["self-hosted"]}"
|
|
305
|
+
${poolEnvLines("gateway")}
|
|
286
306
|
# #806 — no remote MCP here: it rides Google IAP, which this install
|
|
287
307
|
# does not have. CAELO_ADMIN_URL / CAELO_MCP_PUBLIC_URL /
|
|
288
308
|
# CAELO_OPERATOR_OAUTH_CLIENT_FILE stay unset, so the gateway's /api/mcp/*
|
package/dist/compose.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAAA,mCAAmC;AAEnC;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EACL,oBAAoB,EACpB,0BAA0B,EAC1B,iBAAiB,EACjB,aAAa,EACb,sBAAsB,GACvB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,cAAc,EAAE,aAAa,EAAuB,MAAM,6BAA6B,CAAC;AAoBjG,sFAAsF;AACtF,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #1254 — each adapter's connection budget (CLAUDE.md §11.B / §11.C): what
|
|
3
|
+
* its Postgres reserves for itself, which jobs it runs, and the default
|
|
4
|
+
* `max_connections` it provisions. The formula itself is shared
|
|
5
|
+
* (`@caelo-cms/shared` pool-budget.ts); this file only states each
|
|
6
|
+
* provider's inputs, so every stack sizes its pools the same way and a
|
|
7
|
+
* stack that sets a number the formula refuses fails at `pulumi preview`,
|
|
8
|
+
* not under load.
|
|
9
|
+
*
|
|
10
|
+
* docs/dev/connection-budget.md explains the reserves.
|
|
11
|
+
*/
|
|
12
|
+
import { type PoolBudget } from "@caelo-cms/shared";
|
|
13
|
+
/** The adapters that provision a Postgres server. */
|
|
14
|
+
export type BudgetProvider = "gcp" | "gcp-firebase" | "aws" | "azure" | "self-hosted";
|
|
15
|
+
/** The fixed reserves of one provider (the part of {@link PoolBudgetInput} it decides). */
|
|
16
|
+
export interface ProviderReserves {
|
|
17
|
+
readonly maintenanceReserve: number;
|
|
18
|
+
readonly migrationsReserve: number;
|
|
19
|
+
readonly jobsReserve: number;
|
|
20
|
+
readonly concurrentBuilds: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Reserves per provider.
|
|
24
|
+
*
|
|
25
|
+
* - maintenance: Postgres keeps `superuser_reserved_connections` (3) for
|
|
26
|
+
* itself; Cloud SQL's `cloudsqladmin` and RDS's `rdsadmin` agents take
|
|
27
|
+
* about two more; Azure Flexible Server keeps 15 for replication and
|
|
28
|
+
* monitoring.
|
|
29
|
+
* - migrations: one connection per database of a migration run.
|
|
30
|
+
* - jobs: the GCP installs run the operator-access sync, upgrader and
|
|
31
|
+
* converge jobs (one serial query stream each, plus one spare); the other
|
|
32
|
+
* adapters run lifecycle commands from the operator's machine (backup).
|
|
33
|
+
* - builds: a Stage, a Publish live and a plugin-data build can overlap.
|
|
34
|
+
*/
|
|
35
|
+
export declare const PROVIDER_RESERVES: Readonly<Record<BudgetProvider, ProviderReserves>>;
|
|
36
|
+
/**
|
|
37
|
+
* `max_connections` each adapter provisions unless the operator sets the
|
|
38
|
+
* `maxConnections` knob. Every stack sets the server parameter to this value
|
|
39
|
+
* explicitly, so the budget never depends on a tier's implicit default
|
|
40
|
+
* (db-f1-micro's is 25, RDS derives it from instance memory).
|
|
41
|
+
*/
|
|
42
|
+
export declare const DEFAULT_MAX_CONNECTIONS: Readonly<Record<BudgetProvider, number>>;
|
|
43
|
+
/**
|
|
44
|
+
* Gateway instances a GCP stack lets Cloud Run start (`gatewayMaxInstances`
|
|
45
|
+
* knob). Before #1254 it was a hard-coded 100, which at even the minimum
|
|
46
|
+
* pool of 2 asks for twice the server's 100 slots; ten instances serve
|
|
47
|
+
* 800 concurrent visitor requests at Cloud Run's default concurrency.
|
|
48
|
+
*/
|
|
49
|
+
export declare const DEFAULT_GATEWAY_MAX_INSTANCES = 10;
|
|
50
|
+
/** The server and scale-out a stack sizes its pools for. */
|
|
51
|
+
export interface StackScale {
|
|
52
|
+
readonly maxConnections: number;
|
|
53
|
+
/** Every instance that opens the admin's pools (admin-side services included). */
|
|
54
|
+
readonly adminMaxInstances: number;
|
|
55
|
+
readonly gatewayMaxInstances: number;
|
|
56
|
+
}
|
|
57
|
+
/** A stack's budget plus the env each service sets from it. */
|
|
58
|
+
export interface StackPoolBudget {
|
|
59
|
+
readonly budget: PoolBudget;
|
|
60
|
+
readonly env: {
|
|
61
|
+
readonly admin: {
|
|
62
|
+
readonly name: string;
|
|
63
|
+
readonly value: string;
|
|
64
|
+
}[];
|
|
65
|
+
readonly gateway: {
|
|
66
|
+
readonly name: string;
|
|
67
|
+
readonly value: string;
|
|
68
|
+
}[];
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The budget of a `provider` install at `scale`.
|
|
73
|
+
*
|
|
74
|
+
* @throws Error (from the shared formula) naming the connections needed when
|
|
75
|
+
* the server is too small for the configured scale-out.
|
|
76
|
+
*/
|
|
77
|
+
export declare function stackPoolBudget(provider: BudgetProvider, scale: StackScale): StackPoolBudget;
|
|
78
|
+
//# sourceMappingURL=pool-budget-stack.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool-budget-stack.d.ts","sourceRoot":"","sources":["../src/pool-budget-stack.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AAEH,OAAO,EAEL,KAAK,UAAU,EAGhB,MAAM,mBAAmB,CAAC;AAE3B,qDAAqD;AACrD,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,cAAc,GAAG,KAAK,GAAG,OAAO,GAAG,aAAa,CAAC;AAEtF,2FAA2F;AAC3F,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;CACnC;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAgBhF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,QAAQ,CAAC,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAO5E,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,KAAK,CAAC;AAEhD,4DAA4D;AAC5D,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,kFAAkF;IAClF,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;CACtC;AAED,+DAA+D;AAC/D,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,GAAG,EAAE;QACZ,QAAQ,CAAC,KAAK,EAAE;YAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QACpE,QAAQ,CAAC,OAAO,EAAE;YAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;KACvE,CAAC;CACH;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,cAAc,EAAE,KAAK,EAAE,UAAU,GAAG,eAAe,CAM5F"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
/**
|
|
3
|
+
* #1254 — each adapter's connection budget (CLAUDE.md §11.B / §11.C): what
|
|
4
|
+
* its Postgres reserves for itself, which jobs it runs, and the default
|
|
5
|
+
* `max_connections` it provisions. The formula itself is shared
|
|
6
|
+
* (`@caelo-cms/shared` pool-budget.ts); this file only states each
|
|
7
|
+
* provider's inputs, so every stack sizes its pools the same way and a
|
|
8
|
+
* stack that sets a number the formula refuses fails at `pulumi preview`,
|
|
9
|
+
* not under load.
|
|
10
|
+
*
|
|
11
|
+
* docs/dev/connection-budget.md explains the reserves.
|
|
12
|
+
*/
|
|
13
|
+
import { computePoolBudget, poolBudgetEnv, } from "@caelo-cms/shared";
|
|
14
|
+
/**
|
|
15
|
+
* Reserves per provider.
|
|
16
|
+
*
|
|
17
|
+
* - maintenance: Postgres keeps `superuser_reserved_connections` (3) for
|
|
18
|
+
* itself; Cloud SQL's `cloudsqladmin` and RDS's `rdsadmin` agents take
|
|
19
|
+
* about two more; Azure Flexible Server keeps 15 for replication and
|
|
20
|
+
* monitoring.
|
|
21
|
+
* - migrations: one connection per database of a migration run.
|
|
22
|
+
* - jobs: the GCP installs run the operator-access sync, upgrader and
|
|
23
|
+
* converge jobs (one serial query stream each, plus one spare); the other
|
|
24
|
+
* adapters run lifecycle commands from the operator's machine (backup).
|
|
25
|
+
* - builds: a Stage, a Publish live and a plugin-data build can overlap.
|
|
26
|
+
*/
|
|
27
|
+
export const PROVIDER_RESERVES = {
|
|
28
|
+
gcp: { maintenanceReserve: 5, migrationsReserve: 2, jobsReserve: 4, concurrentBuilds: 2 },
|
|
29
|
+
"gcp-firebase": {
|
|
30
|
+
maintenanceReserve: 5,
|
|
31
|
+
migrationsReserve: 2,
|
|
32
|
+
jobsReserve: 4,
|
|
33
|
+
concurrentBuilds: 2,
|
|
34
|
+
},
|
|
35
|
+
aws: { maintenanceReserve: 5, migrationsReserve: 2, jobsReserve: 2, concurrentBuilds: 2 },
|
|
36
|
+
azure: { maintenanceReserve: 15, migrationsReserve: 2, jobsReserve: 2, concurrentBuilds: 2 },
|
|
37
|
+
"self-hosted": {
|
|
38
|
+
maintenanceReserve: 3,
|
|
39
|
+
migrationsReserve: 2,
|
|
40
|
+
jobsReserve: 2,
|
|
41
|
+
concurrentBuilds: 2,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* `max_connections` each adapter provisions unless the operator sets the
|
|
46
|
+
* `maxConnections` knob. Every stack sets the server parameter to this value
|
|
47
|
+
* explicitly, so the budget never depends on a tier's implicit default
|
|
48
|
+
* (db-f1-micro's is 25, RDS derives it from instance memory).
|
|
49
|
+
*/
|
|
50
|
+
export const DEFAULT_MAX_CONNECTIONS = {
|
|
51
|
+
gcp: 100,
|
|
52
|
+
"gcp-firebase": 100,
|
|
53
|
+
aws: 100,
|
|
54
|
+
// The default Standard_B2s server allows up to 429.
|
|
55
|
+
azure: 429,
|
|
56
|
+
"self-hosted": 100,
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Gateway instances a GCP stack lets Cloud Run start (`gatewayMaxInstances`
|
|
60
|
+
* knob). Before #1254 it was a hard-coded 100, which at even the minimum
|
|
61
|
+
* pool of 2 asks for twice the server's 100 slots; ten instances serve
|
|
62
|
+
* 800 concurrent visitor requests at Cloud Run's default concurrency.
|
|
63
|
+
*/
|
|
64
|
+
export const DEFAULT_GATEWAY_MAX_INSTANCES = 10;
|
|
65
|
+
/**
|
|
66
|
+
* The budget of a `provider` install at `scale`.
|
|
67
|
+
*
|
|
68
|
+
* @throws Error (from the shared formula) naming the connections needed when
|
|
69
|
+
* the server is too small for the configured scale-out.
|
|
70
|
+
*/
|
|
71
|
+
export function stackPoolBudget(provider, scale) {
|
|
72
|
+
const budget = computePoolBudget({ ...PROVIDER_RESERVES[provider], ...scale });
|
|
73
|
+
return {
|
|
74
|
+
budget,
|
|
75
|
+
env: { admin: poolBudgetEnv(budget, "admin"), gateway: poolBudgetEnv(budget, "gateway") },
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=pool-budget-stack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool-budget-stack.js","sourceRoot":"","sources":["../src/pool-budget-stack.ts"],"names":[],"mappings":"AAAA,mCAAmC;AAEnC;;;;;;;;;;GAUG;AAEH,OAAO,EACL,iBAAiB,EAGjB,aAAa,GACd,MAAM,mBAAmB,CAAC;AAa3B;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAuD;IACnF,GAAG,EAAE,EAAE,kBAAkB,EAAE,CAAC,EAAE,iBAAiB,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,gBAAgB,EAAE,CAAC,EAAE;IACzF,cAAc,EAAE;QACd,kBAAkB,EAAE,CAAC;QACrB,iBAAiB,EAAE,CAAC;QACpB,WAAW,EAAE,CAAC;QACd,gBAAgB,EAAE,CAAC;KACpB;IACD,GAAG,EAAE,EAAE,kBAAkB,EAAE,CAAC,EAAE,iBAAiB,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,gBAAgB,EAAE,CAAC,EAAE;IACzF,KAAK,EAAE,EAAE,kBAAkB,EAAE,EAAE,EAAE,iBAAiB,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,gBAAgB,EAAE,CAAC,EAAE;IAC5F,aAAa,EAAE;QACb,kBAAkB,EAAE,CAAC;QACrB,iBAAiB,EAAE,CAAC;QACpB,WAAW,EAAE,CAAC;QACd,gBAAgB,EAAE,CAAC;KACpB;CACF,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAA6C;IAC/E,GAAG,EAAE,GAAG;IACR,cAAc,EAAE,GAAG;IACnB,GAAG,EAAE,GAAG;IACR,oDAAoD;IACpD,KAAK,EAAE,GAAG;IACV,aAAa,EAAE,GAAG;CACnB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,EAAE,CAAC;AAmBhD;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,QAAwB,EAAE,KAAiB;IACzE,MAAM,MAAM,GAAG,iBAAiB,CAAC,EAAE,GAAG,iBAAiB,CAAC,QAAQ,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;IAC/E,OAAO;QACL,MAAM;QACN,GAAG,EAAE,EAAE,KAAK,EAAE,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,aAAa,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE;KAC1F,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@caelo-cms/provisioning",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"description": "Pulumi-driven provisioning for Caelo CMS — `bunx @caelo-cms/provisioning --provider <self-hosted|gcp|aws|azure>` brings up a TLS-served install with Postgres + storage + secrets manager + the admin + the API gateway.",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"prepublishOnly": "tsc -b --force && bun build ../platform-updater/src/agent-db.ts --target=bun --outfile=dist/agent-db.js && tsc --project stacks/gcp/tsconfig.json && tsc --project stacks/gcp-firebase/tsconfig.json"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@caelo-cms/shared": "^0.11.
|
|
43
|
+
"@caelo-cms/shared": "^0.11.6",
|
|
44
44
|
"@clack/prompts": "1.8.1",
|
|
45
45
|
"@pulumi/command": "^1",
|
|
46
46
|
"@pulumi/gcp": "^10",
|
package/src/compose.ts
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
|
|
30
30
|
import { CADDY_REDIRECTS_FILE } from "@caelo-cms/shared";
|
|
31
31
|
import { caddyRedirectReloadCommand } from "./caddy-redirects.js";
|
|
32
|
+
import { DEFAULT_MAX_CONNECTIONS, stackPoolBudget } from "./pool-budget-stack.js";
|
|
32
33
|
import {
|
|
33
34
|
ADMIN_MEMORY_DEFAULT,
|
|
34
35
|
GATEWAY_TRUSTED_PROXY_HOPS,
|
|
@@ -58,6 +59,23 @@ export type ComposeImages = ReleaseImages | DevImages;
|
|
|
58
59
|
|
|
59
60
|
/** The admin's HTTP port inside the Compose network (the Caddyfile proxies to it). */
|
|
60
61
|
export const COMPOSE_ADMIN_PORT = 5173;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* #1254 — the Compose install's connection budget: one admin and one
|
|
65
|
+
* gateway container on a Postgres started with exactly
|
|
66
|
+
* `DEFAULT_MAX_CONNECTIONS["self-hosted"]` slots (the postgres service's
|
|
67
|
+
* `command`), so the pool sizes below always match the server.
|
|
68
|
+
*/
|
|
69
|
+
export const COMPOSE_POOL_BUDGET = stackPoolBudget("self-hosted", {
|
|
70
|
+
maxConnections: DEFAULT_MAX_CONNECTIONS["self-hosted"],
|
|
71
|
+
adminMaxInstances: 1,
|
|
72
|
+
gatewayMaxInstances: 1,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
/** The budget's env as Compose `environment:` lines for `service`. */
|
|
76
|
+
function poolEnvLines(service: "admin" | "gateway"): string {
|
|
77
|
+
return COMPOSE_POOL_BUDGET.env[service].map((e) => ` ${e.name}: "${e.value}"`).join("\n");
|
|
78
|
+
}
|
|
61
79
|
/** `.caelo/<this>/` holds the pending Owner-bootstrap token the admin consumes. */
|
|
62
80
|
export const BOOTSTRAP_DIR = "bootstrap";
|
|
63
81
|
|
|
@@ -265,6 +283,8 @@ name: ${spec.project}
|
|
|
265
283
|
services:
|
|
266
284
|
postgres:
|
|
267
285
|
image: postgres:16-alpine
|
|
286
|
+
# The pool budget (COMPOSE_POOL_BUDGET) is computed from this number.
|
|
287
|
+
command: ["postgres", "-c", "max_connections=${DEFAULT_MAX_CONNECTIONS["self-hosted"]}"]
|
|
268
288
|
restart: unless-stopped
|
|
269
289
|
environment:
|
|
270
290
|
POSTGRES_PASSWORD: "${escapeYaml(spec.postgresPassword)}"
|
|
@@ -394,6 +414,7 @@ ${serviceRuntime(
|
|
|
394
414
|
CAELO_OUTPUT_ROOT: "${SITE_OUTPUT_ROOT}"
|
|
395
415
|
MEDIA_ROOT_DIR: "${MEDIA_ROOT}"
|
|
396
416
|
CAELO_PROVIDER: "self-hosted"
|
|
417
|
+
${poolEnvLines("admin")}
|
|
397
418
|
NODE_ENV: production
|
|
398
419
|
${env("ANTHROPIC_API_KEY", spec.anthropicApiKey)}
|
|
399
420
|
${env("RESEND_API_KEY", spec.resendApiKey)}`;
|
|
@@ -412,6 +433,7 @@ ${serviceRuntime(spec.images, "gateway", "bun run apps/api-gateway/src/server.ts
|
|
|
412
433
|
GATEWAY_PORT: "${COMPOSE_GATEWAY_PORT}"
|
|
413
434
|
# Caddy is the one proxy in front (client-ip.ts in the gateway).
|
|
414
435
|
${TRUSTED_PROXY_HOPS_ENV}: "${GATEWAY_TRUSTED_PROXY_HOPS["self-hosted"]}"
|
|
436
|
+
${poolEnvLines("gateway")}
|
|
415
437
|
# #806 — no remote MCP here: it rides Google IAP, which this install
|
|
416
438
|
# does not have. CAELO_ADMIN_URL / CAELO_MCP_PUBLIC_URL /
|
|
417
439
|
# CAELO_OPERATOR_OAUTH_CLIENT_FILE stay unset, so the gateway's /api/mcp/*
|
|
@@ -157,6 +157,8 @@ describe("self-hosted: the generated compose gateway service (#635)", () => {
|
|
|
157
157
|
const env = gateway.slice(gateway.indexOf(" environment:"));
|
|
158
158
|
const keys = [...env.matchAll(/^ {6}([A-Z_][A-Z0-9_]*):/gm)].map((m) => m[1]);
|
|
159
159
|
expect(keys.sort()).toEqual([
|
|
160
|
+
// #1254 — its cms_public pool size (pool-budget-stack.ts).
|
|
161
|
+
"CAELO_DB_PUBLIC_POOL_MAX",
|
|
160
162
|
"CAELO_TRUSTED_PROXY_HOPS",
|
|
161
163
|
"GATEWAY_PORT",
|
|
162
164
|
"NODE_ENV",
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* #1254 — each adapter's connection budget (CLAUDE.md §11.B / §11.C): what
|
|
5
|
+
* its Postgres reserves for itself, which jobs it runs, and the default
|
|
6
|
+
* `max_connections` it provisions. The formula itself is shared
|
|
7
|
+
* (`@caelo-cms/shared` pool-budget.ts); this file only states each
|
|
8
|
+
* provider's inputs, so every stack sizes its pools the same way and a
|
|
9
|
+
* stack that sets a number the formula refuses fails at `pulumi preview`,
|
|
10
|
+
* not under load.
|
|
11
|
+
*
|
|
12
|
+
* docs/dev/connection-budget.md explains the reserves.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
computePoolBudget,
|
|
17
|
+
type PoolBudget,
|
|
18
|
+
type PoolBudgetInput,
|
|
19
|
+
poolBudgetEnv,
|
|
20
|
+
} from "@caelo-cms/shared";
|
|
21
|
+
|
|
22
|
+
/** The adapters that provision a Postgres server. */
|
|
23
|
+
export type BudgetProvider = "gcp" | "gcp-firebase" | "aws" | "azure" | "self-hosted";
|
|
24
|
+
|
|
25
|
+
/** The fixed reserves of one provider (the part of {@link PoolBudgetInput} it decides). */
|
|
26
|
+
export interface ProviderReserves {
|
|
27
|
+
readonly maintenanceReserve: number;
|
|
28
|
+
readonly migrationsReserve: number;
|
|
29
|
+
readonly jobsReserve: number;
|
|
30
|
+
readonly concurrentBuilds: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Reserves per provider.
|
|
35
|
+
*
|
|
36
|
+
* - maintenance: Postgres keeps `superuser_reserved_connections` (3) for
|
|
37
|
+
* itself; Cloud SQL's `cloudsqladmin` and RDS's `rdsadmin` agents take
|
|
38
|
+
* about two more; Azure Flexible Server keeps 15 for replication and
|
|
39
|
+
* monitoring.
|
|
40
|
+
* - migrations: one connection per database of a migration run.
|
|
41
|
+
* - jobs: the GCP installs run the operator-access sync, upgrader and
|
|
42
|
+
* converge jobs (one serial query stream each, plus one spare); the other
|
|
43
|
+
* adapters run lifecycle commands from the operator's machine (backup).
|
|
44
|
+
* - builds: a Stage, a Publish live and a plugin-data build can overlap.
|
|
45
|
+
*/
|
|
46
|
+
export const PROVIDER_RESERVES: Readonly<Record<BudgetProvider, ProviderReserves>> = {
|
|
47
|
+
gcp: { maintenanceReserve: 5, migrationsReserve: 2, jobsReserve: 4, concurrentBuilds: 2 },
|
|
48
|
+
"gcp-firebase": {
|
|
49
|
+
maintenanceReserve: 5,
|
|
50
|
+
migrationsReserve: 2,
|
|
51
|
+
jobsReserve: 4,
|
|
52
|
+
concurrentBuilds: 2,
|
|
53
|
+
},
|
|
54
|
+
aws: { maintenanceReserve: 5, migrationsReserve: 2, jobsReserve: 2, concurrentBuilds: 2 },
|
|
55
|
+
azure: { maintenanceReserve: 15, migrationsReserve: 2, jobsReserve: 2, concurrentBuilds: 2 },
|
|
56
|
+
"self-hosted": {
|
|
57
|
+
maintenanceReserve: 3,
|
|
58
|
+
migrationsReserve: 2,
|
|
59
|
+
jobsReserve: 2,
|
|
60
|
+
concurrentBuilds: 2,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* `max_connections` each adapter provisions unless the operator sets the
|
|
66
|
+
* `maxConnections` knob. Every stack sets the server parameter to this value
|
|
67
|
+
* explicitly, so the budget never depends on a tier's implicit default
|
|
68
|
+
* (db-f1-micro's is 25, RDS derives it from instance memory).
|
|
69
|
+
*/
|
|
70
|
+
export const DEFAULT_MAX_CONNECTIONS: Readonly<Record<BudgetProvider, number>> = {
|
|
71
|
+
gcp: 100,
|
|
72
|
+
"gcp-firebase": 100,
|
|
73
|
+
aws: 100,
|
|
74
|
+
// The default Standard_B2s server allows up to 429.
|
|
75
|
+
azure: 429,
|
|
76
|
+
"self-hosted": 100,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Gateway instances a GCP stack lets Cloud Run start (`gatewayMaxInstances`
|
|
81
|
+
* knob). Before #1254 it was a hard-coded 100, which at even the minimum
|
|
82
|
+
* pool of 2 asks for twice the server's 100 slots; ten instances serve
|
|
83
|
+
* 800 concurrent visitor requests at Cloud Run's default concurrency.
|
|
84
|
+
*/
|
|
85
|
+
export const DEFAULT_GATEWAY_MAX_INSTANCES = 10;
|
|
86
|
+
|
|
87
|
+
/** The server and scale-out a stack sizes its pools for. */
|
|
88
|
+
export interface StackScale {
|
|
89
|
+
readonly maxConnections: number;
|
|
90
|
+
/** Every instance that opens the admin's pools (admin-side services included). */
|
|
91
|
+
readonly adminMaxInstances: number;
|
|
92
|
+
readonly gatewayMaxInstances: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** A stack's budget plus the env each service sets from it. */
|
|
96
|
+
export interface StackPoolBudget {
|
|
97
|
+
readonly budget: PoolBudget;
|
|
98
|
+
readonly env: {
|
|
99
|
+
readonly admin: { readonly name: string; readonly value: string }[];
|
|
100
|
+
readonly gateway: { readonly name: string; readonly value: string }[];
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The budget of a `provider` install at `scale`.
|
|
106
|
+
*
|
|
107
|
+
* @throws Error (from the shared formula) naming the connections needed when
|
|
108
|
+
* the server is too small for the configured scale-out.
|
|
109
|
+
*/
|
|
110
|
+
export function stackPoolBudget(provider: BudgetProvider, scale: StackScale): StackPoolBudget {
|
|
111
|
+
const budget = computePoolBudget({ ...PROVIDER_RESERVES[provider], ...scale });
|
|
112
|
+
return {
|
|
113
|
+
budget,
|
|
114
|
+
env: { admin: poolBudgetEnv(budget, "admin"), gateway: poolBudgetEnv(budget, "gateway") },
|
|
115
|
+
};
|
|
116
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* #1254 — every adapter sizes its pools from the connection budget
|
|
5
|
+
* (CLAUDE.md §11.C: all four adapters). Pins the numbers each stack's
|
|
6
|
+
* defaults produce and that each stack actually sets them on its services.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, expect, it } from "bun:test";
|
|
10
|
+
import { readFileSync } from "node:fs";
|
|
11
|
+
import { resolve } from "node:path";
|
|
12
|
+
import { COMPOSE_POOL_BUDGET } from "./compose.js";
|
|
13
|
+
import {
|
|
14
|
+
type BudgetProvider,
|
|
15
|
+
DEFAULT_GATEWAY_MAX_INSTANCES,
|
|
16
|
+
DEFAULT_MAX_CONNECTIONS,
|
|
17
|
+
stackPoolBudget,
|
|
18
|
+
} from "./pool-budget-stack.js";
|
|
19
|
+
|
|
20
|
+
const stackSource = (provider: string): string =>
|
|
21
|
+
readFileSync(resolve(import.meta.dir, "..", "stacks", provider, "index.ts"), "utf8");
|
|
22
|
+
|
|
23
|
+
/** Each stack's default scale-out, as its index.ts reads it. */
|
|
24
|
+
const DEFAULT_SCALE: Record<BudgetProvider, { admin: number; gateway: number }> = {
|
|
25
|
+
gcp: { admin: 5, gateway: DEFAULT_GATEWAY_MAX_INSTANCES },
|
|
26
|
+
"gcp-firebase": { admin: 5, gateway: DEFAULT_GATEWAY_MAX_INSTANCES },
|
|
27
|
+
aws: { admin: 5, gateway: DEFAULT_GATEWAY_MAX_INSTANCES },
|
|
28
|
+
// admin + orchestrator + runner apps, 10 replicas each.
|
|
29
|
+
azure: { admin: 30, gateway: 10 },
|
|
30
|
+
"self-hosted": { admin: 1, gateway: 1 },
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** [cms_admin pool per admin instance, connections left unassigned]. */
|
|
34
|
+
const EXPECTED: Record<BudgetProvider, [number, number]> = {
|
|
35
|
+
gcp: [5, 1],
|
|
36
|
+
"gcp-firebase": [5, 1],
|
|
37
|
+
aws: [5, 3],
|
|
38
|
+
azure: [7, 12],
|
|
39
|
+
"self-hosted": [77, 0],
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
describe("stack pool budgets at the default config", () => {
|
|
43
|
+
for (const provider of Object.keys(EXPECTED) as BudgetProvider[]) {
|
|
44
|
+
it(`${provider}: pools fit max_connections ${DEFAULT_MAX_CONNECTIONS[provider]}`, () => {
|
|
45
|
+
const { budget, env } = stackPoolBudget(provider, {
|
|
46
|
+
maxConnections: DEFAULT_MAX_CONNECTIONS[provider],
|
|
47
|
+
adminMaxInstances: DEFAULT_SCALE[provider].admin,
|
|
48
|
+
gatewayMaxInstances: DEFAULT_SCALE[provider].gateway,
|
|
49
|
+
});
|
|
50
|
+
expect([budget.adminPoolMax, budget.unassigned]).toEqual(EXPECTED[provider]);
|
|
51
|
+
expect(env.admin).toEqual([
|
|
52
|
+
{ name: "CAELO_DB_POOL_MAX", value: String(EXPECTED[provider][0]) },
|
|
53
|
+
{ name: "CAELO_DB_PUBLIC_POOL_MAX", value: "4" },
|
|
54
|
+
]);
|
|
55
|
+
expect(env.gateway).toEqual([{ name: "CAELO_DB_PUBLIC_POOL_MAX", value: "2" }]);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
it("refuses the pre-#1254 GCP gateway scale-out of 100 on 100 slots", () => {
|
|
60
|
+
expect(() =>
|
|
61
|
+
stackPoolBudget("gcp", {
|
|
62
|
+
maxConnections: 100,
|
|
63
|
+
adminMaxInstances: 5,
|
|
64
|
+
gatewayMaxInstances: 100,
|
|
65
|
+
}),
|
|
66
|
+
).toThrow(/Raise maxConnections/);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("stacks set the budget on their services", () => {
|
|
71
|
+
for (const provider of ["gcp", "gcp-firebase"]) {
|
|
72
|
+
it(`${provider}: admin + gateway env, gateway scale-out from the knob`, () => {
|
|
73
|
+
const src = stackSource(provider);
|
|
74
|
+
expect(src).toContain(`stackPoolBudget("${provider}"`);
|
|
75
|
+
expect(src).toContain("...poolBudget.env.admin");
|
|
76
|
+
expect(src).toContain("...poolBudget.env.gateway");
|
|
77
|
+
expect(src).toContain("maxInstances: gatewayMaxInstances");
|
|
78
|
+
expect(src).not.toContain("maxInstances: 100");
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
it("aws: pins max_connections and exports the task env", () => {
|
|
83
|
+
const src = stackSource("aws");
|
|
84
|
+
expect(src).toContain('stackPoolBudget("aws"');
|
|
85
|
+
expect(src).toContain('name: "max_connections"');
|
|
86
|
+
expect(src).toContain("export const poolBudgetEnvOut = poolBudget.env");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("azure: pins max_connections and sets the env on every app", () => {
|
|
90
|
+
const src = stackSource("azure");
|
|
91
|
+
expect(src).toContain('stackPoolBudget("azure"');
|
|
92
|
+
expect(src).toContain('configurationName: "max_connections"');
|
|
93
|
+
expect(src).toContain("...poolBudget.env.admin");
|
|
94
|
+
expect(src).toContain("...poolBudget.env.gateway");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("self-hosted: the Compose budget is the default one (yaml: self-hosted-compose.test.ts)", () => {
|
|
98
|
+
expect(COMPOSE_POOL_BUDGET.budget.adminPoolMax).toBe(EXPECTED["self-hosted"][0]);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
COMPOSE_ADMIN_UPSTREAM,
|
|
7
7
|
COMPOSE_APP_SERVICES,
|
|
8
8
|
COMPOSE_GATEWAY_UPSTREAM,
|
|
9
|
+
COMPOSE_POOL_BUDGET,
|
|
9
10
|
type ComposeSpec,
|
|
10
11
|
composeProjectName,
|
|
11
12
|
generateDockerCompose,
|
|
@@ -59,6 +60,28 @@ describe("self-hosted Compose — internal secret (#770)", () => {
|
|
|
59
60
|
});
|
|
60
61
|
});
|
|
61
62
|
|
|
63
|
+
describe("self-hosted Compose — connection budget (#1254)", () => {
|
|
64
|
+
const yaml = generateDockerCompose({
|
|
65
|
+
...base,
|
|
66
|
+
images: { mode: "release", admin: ADMIN, gateway: GATEWAY },
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("starts Postgres with the max_connections the budget is computed from", () => {
|
|
70
|
+
expect(service(yaml, "postgres")).toContain(
|
|
71
|
+
'command: ["postgres", "-c", "max_connections=100"]',
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("sizes the admin's two pools and the gateway's one", () => {
|
|
76
|
+
expect(service(yaml, "caelo-admin")).toContain(
|
|
77
|
+
` CAELO_DB_POOL_MAX: "${COMPOSE_POOL_BUDGET.budget.adminPoolMax}"`,
|
|
78
|
+
);
|
|
79
|
+
expect(service(yaml, "caelo-admin")).toContain(' CAELO_DB_PUBLIC_POOL_MAX: "4"');
|
|
80
|
+
expect(service(yaml, "caelo-gateway")).toContain(' CAELO_DB_PUBLIC_POOL_MAX: "2"');
|
|
81
|
+
expect(service(yaml, "caelo-gateway")).not.toContain("CAELO_DB_POOL_MAX:");
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
62
85
|
describe("self-hosted Compose — release images (#696)", () => {
|
|
63
86
|
const yaml = generateDockerCompose({
|
|
64
87
|
...base,
|
|
@@ -132,11 +132,15 @@ describe.each(["gcp", "gcp-firebase"] as const)("%s stack ↔ upgrade parity", (
|
|
|
132
132
|
});
|
|
133
133
|
|
|
134
134
|
it("builds the admin + gateway env from the shared contracts", () => {
|
|
135
|
+
// #1254 — plus the pool env of the stack's connection budget: derived
|
|
136
|
+
// from stack config (max_connections, instance counts) an upgrade roll
|
|
137
|
+
// cannot see, so it is stack-owned like the remote-MCP env; a roll
|
|
138
|
+
// never removes a var the contract does not own (planEnvUpdate).
|
|
135
139
|
expect(src).toMatch(
|
|
136
|
-
/serviceName: "admin",[\s\S]*?contractEnv: \[\s*\.\.\.adminEnvContract\([\s\S]*?\.\.\.remoteMcpEnv\.admin,?\s*\]/,
|
|
140
|
+
/serviceName: "admin",[\s\S]*?contractEnv: \[\s*\.\.\.adminEnvContract\([\s\S]*?\.\.\.remoteMcpEnv\.admin,\s*(\/\/[^\n]*\n\s*)?\.\.\.poolBudget\.env\.admin,?\s*\]/,
|
|
137
141
|
);
|
|
138
142
|
expect(src).toMatch(
|
|
139
|
-
/serviceName: "gateway",[\s\S]*?contractEnv: \[\s*\.\.\.gatewayEnvContract\([\s\S]*?\.\.\.remoteMcpEnv\.gateway,?\s*\]/,
|
|
143
|
+
/serviceName: "gateway",[\s\S]*?contractEnv: \[\s*\.\.\.gatewayEnvContract\([\s\S]*?\.\.\.remoteMcpEnv\.gateway,\s*\.\.\.poolBudget\.env\.gateway,?\s*\]/,
|
|
140
144
|
);
|
|
141
145
|
expect(src).toContain("envs: [...args.contractEnv],");
|
|
142
146
|
});
|