@caelo-cms/provisioning 0.11.13 → 0.11.14

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.
Files changed (159) hide show
  1. package/dist/admin-domain.d.ts +4 -46
  2. package/dist/admin-domain.d.ts.map +1 -1
  3. package/dist/admin-domain.js +4 -101
  4. package/dist/admin-domain.js.map +1 -1
  5. package/dist/agent-db.js +29 -5
  6. package/dist/audit-job-setup.d.ts.map +1 -1
  7. package/dist/audit-job-setup.js +14 -19
  8. package/dist/audit-job-setup.js.map +1 -1
  9. package/dist/caddy.d.ts +2 -1
  10. package/dist/caddy.d.ts.map +1 -1
  11. package/dist/caddy.js +21 -3
  12. package/dist/caddy.js.map +1 -1
  13. package/dist/cli.js +111 -62
  14. package/dist/cli.js.map +1 -1
  15. package/dist/compose.d.ts +22 -2
  16. package/dist/compose.d.ts.map +1 -1
  17. package/dist/compose.js +53 -3
  18. package/dist/compose.js.map +1 -1
  19. package/dist/converge-checkpoint.d.ts +1 -0
  20. package/dist/converge-checkpoint.d.ts.map +1 -1
  21. package/dist/converge-checkpoint.js +1 -0
  22. package/dist/converge-checkpoint.js.map +1 -1
  23. package/dist/deployed-release.d.ts.map +1 -1
  24. package/dist/deployed-release.js +40 -8
  25. package/dist/deployed-release.js.map +1 -1
  26. package/dist/gcp-names.d.ts +33 -14
  27. package/dist/gcp-names.d.ts.map +1 -1
  28. package/dist/gcp-names.js +43 -0
  29. package/dist/gcp-names.js.map +1 -1
  30. package/dist/gcp-url-map.d.ts +19 -4
  31. package/dist/gcp-url-map.d.ts.map +1 -1
  32. package/dist/gcp-url-map.js +33 -5
  33. package/dist/gcp-url-map.js.map +1 -1
  34. package/dist/infra-contract.d.ts +1 -1
  35. package/dist/infra-contract.d.ts.map +1 -1
  36. package/dist/infra-contract.js +18 -1
  37. package/dist/infra-contract.js.map +1 -1
  38. package/dist/lifecycle.d.ts.map +1 -1
  39. package/dist/lifecycle.js +5 -39
  40. package/dist/lifecycle.js.map +1 -1
  41. package/dist/mcp-auth-proxy-setup.d.ts +8 -2
  42. package/dist/mcp-auth-proxy-setup.d.ts.map +1 -1
  43. package/dist/mcp-auth-proxy-setup.js +4 -0
  44. package/dist/mcp-auth-proxy-setup.js.map +1 -1
  45. package/dist/release-manifest.d.ts +1 -1
  46. package/dist/release-manifest.d.ts.map +1 -1
  47. package/dist/release-manifest.js +2 -0
  48. package/dist/release-manifest.js.map +1 -1
  49. package/dist/remote-mcp.d.ts.map +1 -1
  50. package/dist/remote-mcp.js +11 -1
  51. package/dist/remote-mcp.js.map +1 -1
  52. package/dist/runtime-secrets.d.ts +10 -0
  53. package/dist/runtime-secrets.d.ts.map +1 -1
  54. package/dist/runtime-secrets.js +10 -1
  55. package/dist/runtime-secrets.js.map +1 -1
  56. package/dist/stack-contract.d.ts +61 -20
  57. package/dist/stack-contract.d.ts.map +1 -1
  58. package/dist/stack-contract.js +173 -35
  59. package/dist/stack-contract.js.map +1 -1
  60. package/dist/stack-converge.d.ts.map +1 -1
  61. package/dist/stack-converge.js +4 -2
  62. package/dist/stack-converge.js.map +1 -1
  63. package/dist/staging-admin.d.ts +2 -2
  64. package/dist/staging-admin.d.ts.map +1 -1
  65. package/dist/staging-gateway-setup.d.ts +140 -0
  66. package/dist/staging-gateway-setup.d.ts.map +1 -0
  67. package/dist/staging-gateway-setup.js +848 -0
  68. package/dist/staging-gateway-setup.js.map +1 -0
  69. package/dist/upgrade/app-roll.d.ts +4 -4
  70. package/dist/upgrade/app-roll.d.ts.map +1 -1
  71. package/dist/upgrade/app-roll.js +7 -5
  72. package/dist/upgrade/app-roll.js.map +1 -1
  73. package/dist/upgrade/converge.d.ts.map +1 -1
  74. package/dist/upgrade/converge.js +36 -27
  75. package/dist/upgrade/converge.js.map +1 -1
  76. package/dist/upgrade/image-forms.d.ts +3 -3
  77. package/dist/upgrade/image-forms.d.ts.map +1 -1
  78. package/dist/upgrade/image-forms.js +2 -2
  79. package/dist/upgrade/image-forms.js.map +1 -1
  80. package/dist/upgrade/release-images.d.ts +20 -0
  81. package/dist/upgrade/release-images.d.ts.map +1 -1
  82. package/dist/upgrade/release-images.js +28 -0
  83. package/dist/upgrade/release-images.js.map +1 -1
  84. package/dist/upgrade/upgrade.d.ts +3 -1
  85. package/dist/upgrade/upgrade.d.ts.map +1 -1
  86. package/dist/upgrade/upgrade.js +22 -1
  87. package/dist/upgrade/upgrade.js.map +1 -1
  88. package/dist/upgrader-job.d.ts.map +1 -1
  89. package/dist/upgrader-job.js +30 -5
  90. package/dist/upgrader-job.js.map +1 -1
  91. package/dist/upgrader-records.d.ts +2 -2
  92. package/dist/upgrader-records.d.ts.map +1 -1
  93. package/dist/upgrader-records.js +2 -2
  94. package/dist/upgrader-records.js.map +1 -1
  95. package/dist/wizard.d.ts.map +1 -1
  96. package/dist/wizard.js +3 -3
  97. package/dist/wizard.js.map +1 -1
  98. package/dist/wizards/gcp.d.ts.map +1 -1
  99. package/dist/wizards/gcp.js +48 -9
  100. package/dist/wizards/gcp.js.map +1 -1
  101. package/package.json +2 -2
  102. package/src/admin-domain.test.ts +0 -156
  103. package/src/admin-domain.ts +4 -117
  104. package/src/admin-memory.test.ts +2 -0
  105. package/src/audit-job-setup.test.ts +11 -11
  106. package/src/audit-job-setup.ts +14 -19
  107. package/src/caddy-redirects.docker.test.ts +10 -5
  108. package/src/caddy-redirects.test.ts +7 -5
  109. package/src/caddy-validate.test.ts +1 -1
  110. package/src/caddy.test.ts +12 -6
  111. package/src/caddy.ts +23 -4
  112. package/src/cli.ts +138 -77
  113. package/src/compose.ts +67 -2
  114. package/src/converge-checkpoint.ts +1 -0
  115. package/src/database-tls-builders.test.ts +3 -1
  116. package/src/deployed-release.test.ts +21 -0
  117. package/src/deployed-release.ts +42 -7
  118. package/src/gateway-env-isolation.test.ts +2 -0
  119. package/src/gcp-names.ts +53 -0
  120. package/src/gcp-url-map.test.ts +26 -2
  121. package/src/gcp-url-map.ts +36 -5
  122. package/src/index.test.ts +9 -3
  123. package/src/infra-contract.test.ts +4 -3
  124. package/src/infra-contract.ts +18 -1
  125. package/src/lifecycle.ts +8 -52
  126. package/src/mcp-auth-proxy-setup.test.ts +15 -6
  127. package/src/mcp-auth-proxy-setup.ts +14 -2
  128. package/src/release-manifest.ts +3 -1
  129. package/src/remote-mcp-stack-config.test.ts +1 -0
  130. package/src/remote-mcp.test.ts +5 -1
  131. package/src/remote-mcp.ts +10 -1
  132. package/src/runtime-secrets.test.ts +10 -1
  133. package/src/runtime-secrets.ts +21 -0
  134. package/src/self-hosted-backup.test.ts +6 -4
  135. package/src/self-hosted-compose.test.ts +2 -0
  136. package/src/stack-contract-parity.test.ts +7 -3
  137. package/src/stack-contract.test.ts +73 -13
  138. package/src/stack-contract.ts +195 -42
  139. package/src/stack-converge-secrets.test.ts +7 -2
  140. package/src/stack-converge.test.ts +37 -9
  141. package/src/stack-converge.ts +4 -1
  142. package/src/staging-admin.ts +2 -2
  143. package/src/staging-gateway-setup.test.ts +361 -0
  144. package/src/staging-gateway-setup.ts +1083 -0
  145. package/src/upgrade/app-roll.ts +7 -4
  146. package/src/upgrade/converge.ts +40 -26
  147. package/src/upgrade/image-forms.ts +7 -7
  148. package/src/upgrade/release-images.test.ts +28 -0
  149. package/src/upgrade/release-images.ts +44 -0
  150. package/src/upgrade/upgrade.test.ts +63 -11
  151. package/src/upgrade/upgrade.ts +29 -2
  152. package/src/upgrader-job.test.ts +23 -0
  153. package/src/upgrader-job.ts +45 -11
  154. package/src/upgrader-records.ts +7 -7
  155. package/src/wizard.ts +2 -3
  156. package/src/wizards/gcp.ts +73 -9
  157. package/stacks/gcp/index.ts +47 -6
  158. package/stacks/gcp-firebase/index.ts +44 -12
  159. package/stacks/self-hosted/index.ts +9 -2
@@ -28,6 +28,8 @@ const base: Omit<ComposeSpec, "images"> = {
28
28
  caeloSecretKek: "0".repeat(64),
29
29
  dbTlsServerCertSha256: "abababababababababababababababababababababababababababababababab",
30
30
  internalSecret: "i".repeat(48),
31
+ stagingHost: "staging.example.com",
32
+ stagingGateSecret: "g".repeat(64),
31
33
  anthropicApiKey: "sk-ant-x",
32
34
  diskSize: "20Gi",
33
35
  ports: { http: 80, https: 443 },
@@ -63,6 +63,7 @@ const MEMBER_EXPR: Record<IamInvariant["member"], string> = {
63
63
  // The secret loop resolves the SA per service through `serviceSa`.
64
64
  "gateway-sa": "gatewaySa.email",
65
65
  "mcp-auth-proxy-sa": "mcpAuthProxySa.email",
66
+ "staging-gateway-sa": "stagingGatewaySa.email",
66
67
  "static-publisher-sa": "staticPublisherSa.email",
67
68
  "iap-service-agent": "iapServiceIdentity.email",
68
69
  allUsers: 'member: "allUsers"',
@@ -166,15 +167,18 @@ describe.each(["gcp", "gcp-firebase"] as const)("%s stack ↔ upgrade parity", (
166
167
  /serviceName: "gateway",[\s\S]*?contractEnv: \[\s*\.\.\.gatewayEnvContract\(envContractInputs\),\s*\.\.\.poolBudget\.env\.gateway,?\s*\]/,
167
168
  );
168
169
  if (provider === "gcp-firebase") {
169
- // The proxy: its contract, its identity env, and (until #1331) the
170
- // data gateway it forwards to — a stack output no contract can name.
170
+ // The proxy: its contract and its identity env, nothing else (#1331:
171
+ // no forward origin any more).
171
172
  expect(src).toMatch(
172
- /serviceName: "mcp-auth-proxy",[\s\S]*?contractEnv: \[\s*\.\.\.mcpAuthProxyEnvContract\(\{ provider: "gcp-firebase", env \}\),\s*\.\.\.remoteMcpEnv\["mcp-auth-proxy"\],[\s\S]*?\{ name: INTERIM_FORWARD_ORIGIN_ENV, value: gatewaySvc\.uri \},\s*\]/,
173
+ /serviceName: "mcp-auth-proxy",[\s\S]*?contractEnv: \[\s*\.\.\.mcpAuthProxyEnvContract\(\{ provider: "gcp-firebase", env \}\),\s*\.\.\.remoteMcpEnv\["mcp-auth-proxy"\],?\s*\]/,
173
174
  );
175
+ expect(src).not.toContain("INTERIM_FORWARD_ORIGIN");
174
176
  } else {
175
177
  // gcp: the proxy is CLI-owned (mcp-auth-proxy-setup.ts), not a stack service.
176
178
  expect(src).not.toContain('serviceName: "mcp-auth-proxy"');
177
179
  }
180
+ // #1330 — the staging gateway is CLI-owned on both (staging-gateway-setup.ts).
181
+ expect(src).not.toContain('serviceName: "staging-gateway"');
178
182
  expect(src).toContain("envs: [...args.contractEnv],");
179
183
  });
180
184
 
@@ -14,7 +14,6 @@ import {
14
14
  defaultStagingHost,
15
15
  fixedCloudRunServiceName,
16
16
  gatewayEnvContract,
17
- INTERIM_FORWARD_ORIGIN_ENV,
18
17
  iamMember,
19
18
  MCP_AUTH_PROXY_TRUSTED_PROXY_HOPS,
20
19
  mcpAuthProxyEnvContract,
@@ -24,12 +23,15 @@ import {
24
23
  RETIRED_SERVICE_ENV,
25
24
  runtimeSecretBindings,
26
25
  SECRETS_SET_UP_AHEAD_OF_THEIR_READER,
26
+ SERVICE_IMAGE,
27
27
  SERVICE_SECRET_ENV,
28
28
  SERVICE_SECRET_FILES,
29
29
  SERVICE_VPC_EGRESS,
30
30
  STACK_IAM_NOT_ENSURED,
31
+ STAGING_GATEWAY_TRUSTED_PROXY_HOPS,
31
32
  serviceSecrets,
32
33
  stackIamInvariants,
34
+ stagingGatewayEnvContract,
33
35
  } from "./stack-contract.js";
34
36
 
35
37
  const base = {
@@ -108,6 +110,8 @@ describe("adminEnvContract", () => {
108
110
  CAELO_CAPTURE_JOB: "projects/acme/locations/europe-west1/jobs/caelo-production-capture",
109
111
  CAELO_STATIC_BUCKET: "acme-caelo-production-static",
110
112
  CAELO_STAGING_BUCKET: "acme-caelo-production-staging",
113
+ // #1330 — every provider serves staging at its own host now.
114
+ CAELO_STAGING_HOST: "staging.acme.com",
111
115
  ...ADMIN_SECRETS,
112
116
  });
113
117
  });
@@ -119,10 +123,13 @@ describe("adminEnvContract", () => {
119
123
  ...base,
120
124
  provider: "gcp-firebase",
121
125
  firebaseSiteId: "caelo-production-site-abc123",
126
+ firebaseStagingSiteId: "caelo-production-stg-6eace1",
122
127
  gatewayService: "caelo-production-gateway-1a2b3c4",
123
128
  }),
124
129
  ),
125
130
  ).toEqual({
131
+ CAELO_FIREBASE_STAGING_SITE: "caelo-production-stg-6eace1",
132
+ CAELO_STAGING_BUCKET: "acme-caelo-production-staging",
126
133
  CAELO_PROVIDER: "gcp-firebase",
127
134
  CAELO_ENV: "production",
128
135
  MEDIA_STORAGE_URL: "gs://acme-caelo-production-media",
@@ -210,8 +217,8 @@ describe("gatewayEnvContract", () => {
210
217
  PUBLIC_DATABASE_URL:
211
218
  "postgresql://public_role@db1.p-label.europe-west1.sql-psa.goog:5432/cms_public?sslmode=verify-full&sslrootcert=/etc/caelo/db-ca/cloudsql-global.pem",
212
219
  PUBLIC_DATABASE_PASSWORD: "secret:caelo-production-public-role-password:latest",
213
- CAELO_STAGING_GATE_SECRET: "secret:caelo-production-staging-gate-secret:latest",
214
- // No remote-MCP env: the broker is the MCP-Auth-Proxy's (#1328).
220
+ // No remote-MCP env: the broker is the MCP-Auth-Proxy's (#1328); no
221
+ // staging gate key: the staging gateway's, through its projection (#1330).
215
222
  });
216
223
  });
217
224
 
@@ -222,10 +229,53 @@ describe("gatewayEnvContract", () => {
222
229
  expect(asRecord(gateway).CAELO_TRUSTED_PROXY_HOPS).toBe("2");
223
230
  });
224
231
 
225
- it("#650 — reads the staging host from the database, not its env", () => {
232
+ it("#1330 — the live gateway knows no staging host and holds no staging gate key", () => {
226
233
  const fb = asRecord(gatewayEnvContract({ ...base, provider: "gcp-firebase" }));
227
234
  expect(fb.CAELO_STAGING_HOST).toBeUndefined();
228
- expect(fb.CAELO_STAGING_GATE_SECRET).toBe("secret:caelo-production-staging-gate-secret:latest");
235
+ expect(fb.CAELO_STAGING_GATE_SECRET).toBeUndefined();
236
+ expect(RETIRED_SERVICE_ENV.gateway).toContain("CAELO_STAGING_GATE_SECRET");
237
+ });
238
+
239
+ it("#1330 — the staging gateway is the gateway image set to staging: its one login, its storage, nothing live", () => {
240
+ for (const provider of ["gcp", "gcp-firebase"] as const) {
241
+ expect(asRecord(stagingGatewayEnvContract({ ...base, provider }))).toEqual({
242
+ CAELO_PROVIDER: provider,
243
+ CAELO_ENV: "production",
244
+ CAELO_GATEWAY_ENVIRONMENT: "staging",
245
+ CAELO_TRUSTED_PROXY_HOPS: String(STAGING_GATEWAY_TRUSTED_PROXY_HOPS[provider]),
246
+ PUBLIC_DATABASE_URL:
247
+ "postgresql://public_staging_role@db1.p-label.europe-west1.sql-psa.goog:5432/cms_public_staging?sslmode=verify-full&sslrootcert=/etc/caelo/db-ca/cloudsql-global.pem",
248
+ CAELO_STAGING_STORAGE: "gs://acme-caelo-production-staging",
249
+ PUBLIC_DATABASE_PASSWORD: "secret:caelo-production-public-staging-role-password:latest",
250
+ });
251
+ }
252
+ });
253
+
254
+ it("pins the trusted hop count per provider, so a forged X-Forwarded-For entry is never trusted", () => {
255
+ expect(STAGING_GATEWAY_TRUSTED_PROXY_HOPS).toEqual({
256
+ gcp: 2,
257
+ "gcp-firebase": 1,
258
+ "self-hosted": 1,
259
+ aws: 2,
260
+ azure: 1,
261
+ });
262
+ });
263
+
264
+ it("shares no secret with the live gateway and reads exactly its own login", () => {
265
+ expect(serviceSecrets("staging-gateway")).toEqual(["public-staging-role-password"]);
266
+ const live = new Set(serviceSecrets("gateway"));
267
+ expect(serviceSecrets("staging-gateway").some((x) => live.has(x))).toBe(false);
268
+ expect(serviceSecrets("gateway")).not.toContain("staging-gate-secret");
269
+ expect(SECRETS_SET_UP_AHEAD_OF_THEIR_READER["public-staging-role-password"]).toBeUndefined();
270
+ });
271
+
272
+ it("runs the live gateway's image under its own fixed name", () => {
273
+ expect(SERVICE_IMAGE["staging-gateway"]).toBe("gateway");
274
+ for (const provider of ["gcp", "gcp-firebase"] as const) {
275
+ expect(fixedCloudRunServiceName(provider, "production", "staging-gateway")).toBe(
276
+ "caelo-production-staging-gateway",
277
+ );
278
+ }
229
279
  });
230
280
 
231
281
  it("#650 — the admin's staging host seed is the install setting, any host", () => {
@@ -235,6 +285,7 @@ describe("gatewayEnvContract", () => {
235
285
  stagingHost: "preview.other.de",
236
286
  provider: "gcp-firebase",
237
287
  firebaseSiteId: "s",
288
+ firebaseStagingSiteId: "s-stg",
238
289
  gatewayService: "g",
239
290
  }),
240
291
  );
@@ -243,7 +294,7 @@ describe("gatewayEnvContract", () => {
243
294
  });
244
295
 
245
296
  it("the gateway's run SA reads no admin-only secret (admin password, KEK, internal, tool approval)", () => {
246
- expect(serviceSecrets("gateway")).toEqual(["public-role-password", "staging-gate-secret"]);
297
+ expect(serviceSecrets("gateway")).toEqual(["public-role-password"]);
247
298
  for (const v of gateway) {
248
299
  expect(v.name).not.toMatch(/KEK|INTERNAL|TOOL_APPROVAL|PUBLIC_ADMIN/);
249
300
  }
@@ -336,10 +387,11 @@ describe("runtime secrets", () => {
336
387
  secret: "public-role-password",
337
388
  stackResource: "gateway-public-role-password-binding",
338
389
  },
390
+ // #1330 — the staging gateway: its own login only.
339
391
  {
340
- service: "gateway",
341
- secret: "staging-gate-secret",
342
- stackResource: "gateway-staging-gate-secret-binding",
392
+ service: "staging-gateway",
393
+ secret: "public-staging-role-password",
394
+ stackResource: "staging-gateway-public-staging-role-password-binding",
343
395
  },
344
396
  // #1328 — the broker's seal key (env) and Google sign-in client (a
345
397
  // file, which counts like an env secret: the SA reads it).
@@ -385,6 +437,8 @@ describe("self-hosted compose", () => {
385
437
  caeloSecretKek: "k",
386
438
  dbTlsServerCertSha256: "abababababababababababababababababababababababababababababababab",
387
439
  internalSecret: "i".repeat(48),
440
+ stagingHost: "staging.example.com",
441
+ stagingGateSecret: "g".repeat(64),
388
442
  diskSize: "10G",
389
443
  ports: { http: 80, https: 443 },
390
444
  project: "caelo-example-com",
@@ -424,14 +478,19 @@ describe("stackIamInvariants", () => {
424
478
  }
425
479
  });
426
480
 
427
- it("grants the gateway SA only its own role password, the staging gate key + telemetry roles", () => {
481
+ it("grants the gateway SA only its own role password + telemetry roles (#1330: no staging gate key)", () => {
428
482
  for (const provider of ["gcp", "gcp-firebase"] as const) {
429
483
  const gateway = stackIamInvariants(provider).filter((i) => i.member === "gateway-sa");
430
484
  expect(gateway.map((i) => `${i.role} ${JSON.stringify(i.target)}`).sort()).toEqual([
431
485
  'roles/logging.logWriter {"kind":"project"}',
432
486
  'roles/monitoring.metricWriter {"kind":"project"}',
433
487
  'roles/secretmanager.secretAccessor {"kind":"secret","name":"public-role-password"}',
434
- 'roles/secretmanager.secretAccessor {"kind":"secret","name":"staging-gate-secret"}',
488
+ ]);
489
+ const staging = stackIamInvariants(provider).filter((i) => i.member === "staging-gateway-sa");
490
+ expect(staging.map((i) => `${i.role} ${JSON.stringify(i.target)}`).sort()).toEqual([
491
+ 'roles/logging.logWriter {"kind":"project"}',
492
+ 'roles/monitoring.metricWriter {"kind":"project"}',
493
+ 'roles/secretmanager.secretAccessor {"kind":"secret","name":"public-staging-role-password"}',
435
494
  ]);
436
495
  }
437
496
  });
@@ -508,10 +567,11 @@ describe("MCP-Auth-Proxy contract (#1328)", () => {
508
567
  CAELO_REMOTE_MCP: "unavailable",
509
568
  CAELO_TRUSTED_PROXY_HOPS: String(MCP_AUTH_PROXY_TRUSTED_PROXY_HOPS.azure),
510
569
  });
511
- // The interim forward origin is the stack's / converge's, never the contract's.
570
+ // #1331 — the interim forwarder is gone; the roll removes its env.
512
571
  expect(
513
572
  mcpAuthProxyEnvContract({ provider: "gcp-firebase", env: "production" }).map((v) => v.name),
514
- ).not.toContain(INTERIM_FORWARD_ORIGIN_ENV);
573
+ ).not.toContain("CAELO_INTERIM_FORWARD_ORIGIN");
574
+ expect(RETIRED_SERVICE_ENV["mcp-auth-proxy"]).toContain("CAELO_INTERIM_FORWARD_ORIGIN");
515
575
  });
516
576
 
517
577
  it("the proxy's SA reads exactly its two secrets and holds no other invariant", () => {