@caelo-cms/provisioning 0.11.9 → 0.11.11

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 (352) hide show
  1. package/dist/adapter.d.ts +6 -0
  2. package/dist/adapter.d.ts.map +1 -1
  3. package/dist/admin-domain.d.ts +10 -1
  4. package/dist/admin-domain.d.ts.map +1 -1
  5. package/dist/admin-domain.js +13 -3
  6. package/dist/admin-domain.js.map +1 -1
  7. package/dist/adopt-existing.d.ts +12 -3
  8. package/dist/adopt-existing.d.ts.map +1 -1
  9. package/dist/adopt-existing.js +143 -0
  10. package/dist/adopt-existing.js.map +1 -1
  11. package/dist/agent/command.d.ts +23 -0
  12. package/dist/agent/command.d.ts.map +1 -1
  13. package/dist/agent/command.js +32 -9
  14. package/dist/agent/command.js.map +1 -1
  15. package/dist/agent/run.d.ts +2 -0
  16. package/dist/agent/run.d.ts.map +1 -1
  17. package/dist/agent/run.js +5 -1
  18. package/dist/agent/run.js.map +1 -1
  19. package/dist/agent-db.js +69 -10
  20. package/dist/aws-task-roles.d.ts +4 -2
  21. package/dist/aws-task-roles.d.ts.map +1 -1
  22. package/dist/aws-task-roles.js +4 -1
  23. package/dist/aws-task-roles.js.map +1 -1
  24. package/dist/caddy.d.ts +21 -4
  25. package/dist/caddy.d.ts.map +1 -1
  26. package/dist/caddy.js +27 -3
  27. package/dist/caddy.js.map +1 -1
  28. package/dist/cli-routing.js +1 -1
  29. package/dist/cli-routing.js.map +1 -1
  30. package/dist/cli.js +68 -7
  31. package/dist/cli.js.map +1 -1
  32. package/dist/cloudsql-tls.d.ts +93 -0
  33. package/dist/cloudsql-tls.d.ts.map +1 -0
  34. package/dist/cloudsql-tls.js +295 -0
  35. package/dist/cloudsql-tls.js.map +1 -0
  36. package/dist/compose.d.ts +43 -6
  37. package/dist/compose.d.ts.map +1 -1
  38. package/dist/compose.js +148 -23
  39. package/dist/compose.js.map +1 -1
  40. package/dist/converge-checkpoint.d.ts +1 -0
  41. package/dist/converge-checkpoint.d.ts.map +1 -1
  42. package/dist/converge-checkpoint.js +8 -1
  43. package/dist/converge-checkpoint.js.map +1 -1
  44. package/dist/converge-db-trust.d.ts +44 -0
  45. package/dist/converge-db-trust.d.ts.map +1 -0
  46. package/dist/converge-db-trust.js +158 -0
  47. package/dist/converge-db-trust.js.map +1 -0
  48. package/dist/converge-job-gcp.d.ts +5 -2
  49. package/dist/converge-job-gcp.d.ts.map +1 -1
  50. package/dist/converge-job-gcp.js +11 -13
  51. package/dist/converge-job-gcp.js.map +1 -1
  52. package/dist/converge-job-preflight.d.ts.map +1 -1
  53. package/dist/converge-job-preflight.js +8 -0
  54. package/dist/converge-job-preflight.js.map +1 -1
  55. package/dist/converge-job.d.ts +17 -0
  56. package/dist/converge-job.d.ts.map +1 -1
  57. package/dist/converge-job.js +12 -0
  58. package/dist/converge-job.js.map +1 -1
  59. package/dist/deployed-release.d.ts +4 -1
  60. package/dist/deployed-release.d.ts.map +1 -1
  61. package/dist/deployed-release.js +18 -8
  62. package/dist/deployed-release.js.map +1 -1
  63. package/dist/gcp-names.d.ts +34 -0
  64. package/dist/gcp-names.d.ts.map +1 -1
  65. package/dist/gcp-names.js +45 -0
  66. package/dist/gcp-names.js.map +1 -1
  67. package/dist/gcp-url-map.d.ts.map +1 -1
  68. package/dist/gcp-url-map.js +6 -2
  69. package/dist/gcp-url-map.js.map +1 -1
  70. package/dist/index.d.ts +3 -2
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +2 -1
  73. package/dist/index.js.map +1 -1
  74. package/dist/infra-contract.d.ts +1 -1
  75. package/dist/infra-contract.d.ts.map +1 -1
  76. package/dist/infra-contract.js +45 -1
  77. package/dist/infra-contract.js.map +1 -1
  78. package/dist/install-job.d.ts +4 -2
  79. package/dist/install-job.d.ts.map +1 -1
  80. package/dist/install-job.js +4 -1
  81. package/dist/install-job.js.map +1 -1
  82. package/dist/install-state.d.ts +13 -0
  83. package/dist/install-state.d.ts.map +1 -1
  84. package/dist/install-state.js +17 -4
  85. package/dist/install-state.js.map +1 -1
  86. package/dist/lifecycle.d.ts.map +1 -1
  87. package/dist/lifecycle.js +20 -8
  88. package/dist/lifecycle.js.map +1 -1
  89. package/dist/mcp-auth-proxy-setup.d.ts +48 -0
  90. package/dist/mcp-auth-proxy-setup.d.ts.map +1 -0
  91. package/dist/mcp-auth-proxy-setup.js +280 -0
  92. package/dist/mcp-auth-proxy-setup.js.map +1 -0
  93. package/dist/migration-runner.d.ts.map +1 -1
  94. package/dist/migration-runner.js +8 -3
  95. package/dist/migration-runner.js.map +1 -1
  96. package/dist/migration-targets.d.ts +11 -0
  97. package/dist/migration-targets.d.ts.map +1 -0
  98. package/dist/migration-targets.js +12 -0
  99. package/dist/migration-targets.js.map +1 -0
  100. package/dist/operator-access.d.ts +10 -0
  101. package/dist/operator-access.d.ts.map +1 -1
  102. package/dist/operator-access.js +5 -0
  103. package/dist/operator-access.js.map +1 -1
  104. package/dist/pool-budget-stack.d.ts +13 -1
  105. package/dist/pool-budget-stack.d.ts.map +1 -1
  106. package/dist/pool-budget-stack.js +11 -1
  107. package/dist/pool-budget-stack.js.map +1 -1
  108. package/dist/public-role-isolation.d.ts +7 -3
  109. package/dist/public-role-isolation.d.ts.map +1 -1
  110. package/dist/public-role-isolation.js +25 -13
  111. package/dist/public-role-isolation.js.map +1 -1
  112. package/dist/release-contract.d.ts.map +1 -1
  113. package/dist/release-contract.js +3 -0
  114. package/dist/release-contract.js.map +1 -1
  115. package/dist/release-manifest.d.ts +25 -1
  116. package/dist/release-manifest.d.ts.map +1 -1
  117. package/dist/release-manifest.js +36 -3
  118. package/dist/release-manifest.js.map +1 -1
  119. package/dist/remote-mcp.d.ts +55 -18
  120. package/dist/remote-mcp.d.ts.map +1 -1
  121. package/dist/remote-mcp.js +307 -77
  122. package/dist/remote-mcp.js.map +1 -1
  123. package/dist/runtime-secrets.d.ts +11 -0
  124. package/dist/runtime-secrets.d.ts.map +1 -1
  125. package/dist/runtime-secrets.js +40 -25
  126. package/dist/runtime-secrets.js.map +1 -1
  127. package/dist/scheduler-setup.d.ts +75 -0
  128. package/dist/scheduler-setup.d.ts.map +1 -0
  129. package/dist/scheduler-setup.js +331 -0
  130. package/dist/scheduler-setup.js.map +1 -0
  131. package/dist/seal-key-rotation.d.ts +7 -7
  132. package/dist/seal-key-rotation.d.ts.map +1 -1
  133. package/dist/seal-key-rotation.js +22 -22
  134. package/dist/seal-key-rotation.js.map +1 -1
  135. package/dist/self-hosted-db-tls.d.ts +37 -0
  136. package/dist/self-hosted-db-tls.d.ts.map +1 -0
  137. package/dist/self-hosted-db-tls.js +220 -0
  138. package/dist/self-hosted-db-tls.js.map +1 -0
  139. package/dist/self-hosted-release.d.ts +3 -1
  140. package/dist/self-hosted-release.d.ts.map +1 -1
  141. package/dist/self-hosted-release.js +33 -9
  142. package/dist/self-hosted-release.js.map +1 -1
  143. package/dist/self-hosted-roles.d.ts +43 -2
  144. package/dist/self-hosted-roles.d.ts.map +1 -1
  145. package/dist/self-hosted-roles.js +87 -1
  146. package/dist/self-hosted-roles.js.map +1 -1
  147. package/dist/self-hosted-scheduler-interval.d.ts +21 -0
  148. package/dist/self-hosted-scheduler-interval.d.ts.map +1 -0
  149. package/dist/self-hosted-scheduler-interval.js +68 -0
  150. package/dist/self-hosted-scheduler-interval.js.map +1 -0
  151. package/dist/self-hosted-start.d.ts +0 -21
  152. package/dist/self-hosted-start.d.ts.map +1 -1
  153. package/dist/self-hosted-start.js +39 -1
  154. package/dist/self-hosted-start.js.map +1 -1
  155. package/dist/self-hosted-upgrade.d.ts +19 -7
  156. package/dist/self-hosted-upgrade.d.ts.map +1 -1
  157. package/dist/self-hosted-upgrade.js +78 -25
  158. package/dist/self-hosted-upgrade.js.map +1 -1
  159. package/dist/stack-contract.d.ts +234 -28
  160. package/dist/stack-contract.d.ts.map +1 -1
  161. package/dist/stack-contract.js +397 -39
  162. package/dist/stack-contract.js.map +1 -1
  163. package/dist/stack-converge.d.ts +31 -0
  164. package/dist/stack-converge.d.ts.map +1 -1
  165. package/dist/stack-converge.js +71 -30
  166. package/dist/stack-converge.js.map +1 -1
  167. package/dist/staging-database.d.ts +22 -0
  168. package/dist/staging-database.d.ts.map +1 -0
  169. package/dist/staging-database.js +117 -0
  170. package/dist/staging-database.js.map +1 -0
  171. package/dist/upgrade/app-roll.d.ts +25 -4
  172. package/dist/upgrade/app-roll.d.ts.map +1 -1
  173. package/dist/upgrade/app-roll.js +63 -17
  174. package/dist/upgrade/app-roll.js.map +1 -1
  175. package/dist/upgrade/converge.d.ts +5 -0
  176. package/dist/upgrade/converge.d.ts.map +1 -1
  177. package/dist/upgrade/converge.js +207 -20
  178. package/dist/upgrade/converge.js.map +1 -1
  179. package/dist/upgrade/health.d.ts +31 -7
  180. package/dist/upgrade/health.d.ts.map +1 -1
  181. package/dist/upgrade/health.js +41 -17
  182. package/dist/upgrade/health.js.map +1 -1
  183. package/dist/upgrade/image-forms.d.ts +1 -1
  184. package/dist/upgrade/image-forms.d.ts.map +1 -1
  185. package/dist/upgrade/image-forms.js +24 -7
  186. package/dist/upgrade/image-forms.js.map +1 -1
  187. package/dist/upgrade/release-images.d.ts +12 -3
  188. package/dist/upgrade/release-images.d.ts.map +1 -1
  189. package/dist/upgrade/release-images.js +13 -4
  190. package/dist/upgrade/release-images.js.map +1 -1
  191. package/dist/upgrade/upgrade.d.ts +19 -3
  192. package/dist/upgrade/upgrade.d.ts.map +1 -1
  193. package/dist/upgrade/upgrade.js +37 -2
  194. package/dist/upgrade/upgrade.js.map +1 -1
  195. package/dist/upgrader-gcp.d.ts +13 -0
  196. package/dist/upgrader-gcp.d.ts.map +1 -1
  197. package/dist/upgrader-gcp.js +50 -1
  198. package/dist/upgrader-gcp.js.map +1 -1
  199. package/dist/upgrader-iam-preflight.d.ts.map +1 -1
  200. package/dist/upgrader-iam-preflight.js +6 -1
  201. package/dist/upgrader-iam-preflight.js.map +1 -1
  202. package/dist/upgrader-job.d.ts +1 -1
  203. package/dist/upgrader-job.d.ts.map +1 -1
  204. package/dist/upgrader-job.js +20 -14
  205. package/dist/upgrader-job.js.map +1 -1
  206. package/dist/upgrader-records.d.ts +1 -1
  207. package/dist/upgrader-records.d.ts.map +1 -1
  208. package/dist/upgrader-records.js +26 -7
  209. package/dist/upgrader-records.js.map +1 -1
  210. package/dist/upgrader-setup.d.ts.map +1 -1
  211. package/dist/upgrader-setup.js +10 -5
  212. package/dist/upgrader-setup.js.map +1 -1
  213. package/dist/wizard.d.ts +7 -0
  214. package/dist/wizard.d.ts.map +1 -1
  215. package/dist/wizard.js +11 -0
  216. package/dist/wizard.js.map +1 -1
  217. package/dist/wizards/gcp-cost.d.ts +31 -0
  218. package/dist/wizards/gcp-cost.d.ts.map +1 -1
  219. package/dist/wizards/gcp-cost.js +103 -18
  220. package/dist/wizards/gcp-cost.js.map +1 -1
  221. package/dist/wizards/gcp.d.ts.map +1 -1
  222. package/dist/wizards/gcp.js +129 -27
  223. package/dist/wizards/gcp.js.map +1 -1
  224. package/package.json +2 -2
  225. package/src/adapter.ts +6 -0
  226. package/src/admin-domain.test.ts +9 -8
  227. package/src/admin-domain.ts +13 -3
  228. package/src/admin-memory.test.ts +2 -1
  229. package/src/adopt-existing.test.ts +85 -0
  230. package/src/adopt-existing.ts +141 -3
  231. package/src/agent/command.test.ts +49 -1
  232. package/src/agent/command.ts +39 -9
  233. package/src/agent/run.test.ts +19 -0
  234. package/src/agent/run.ts +11 -2
  235. package/src/aws-task-roles.ts +5 -2
  236. package/src/caddy-redirects.docker.test.ts +2 -0
  237. package/src/caddy-redirects.test.ts +4 -1
  238. package/src/caddy-validate.test.ts +7 -1
  239. package/src/caddy.test.ts +39 -1
  240. package/src/caddy.ts +36 -4
  241. package/src/checkpoint-secrets.test.ts +14 -6
  242. package/src/cli-routing.test.ts +1 -1
  243. package/src/cli-routing.ts +1 -1
  244. package/src/cli.ts +71 -5
  245. package/src/cloudsql-tls.test.ts +255 -0
  246. package/src/cloudsql-tls.ts +359 -0
  247. package/src/compose.ts +193 -24
  248. package/src/converge-checkpoint.ts +8 -1
  249. package/src/converge-db-trust.test.ts +237 -0
  250. package/src/converge-db-trust.ts +212 -0
  251. package/src/converge-job-gcp.ts +11 -12
  252. package/src/converge-job-preflight.test.ts +5 -0
  253. package/src/converge-job-preflight.ts +8 -0
  254. package/src/converge-job.test.ts +70 -0
  255. package/src/converge-job.ts +32 -0
  256. package/src/database-tls-builders.test.ts +56 -0
  257. package/src/deployed-release.test.ts +107 -40
  258. package/src/deployed-release.ts +30 -9
  259. package/src/gateway-env-isolation.test.ts +50 -12
  260. package/src/gcp-names.ts +56 -0
  261. package/src/gcp-url-map.test.ts +6 -8
  262. package/src/gcp-url-map.ts +6 -2
  263. package/src/index.test.ts +10 -4
  264. package/src/index.ts +7 -0
  265. package/src/infra-contract.test.ts +7 -3
  266. package/src/infra-contract.ts +45 -1
  267. package/src/install-job.ts +4 -1
  268. package/src/install-state.ts +33 -4
  269. package/src/lifecycle.ts +23 -11
  270. package/src/mcp-auth-proxy-setup.test.ts +305 -0
  271. package/src/mcp-auth-proxy-setup.ts +374 -0
  272. package/src/mcp-iap-stacks.test.ts +1 -1
  273. package/src/media-volume.test.ts +1 -1
  274. package/src/migration-runner.ts +14 -9
  275. package/src/migration-targets.ts +12 -0
  276. package/src/operator-access-stacks.test.ts +1 -1
  277. package/src/operator-access.test.ts +20 -4
  278. package/src/operator-access.ts +14 -0
  279. package/src/pool-budget-stack.ts +15 -0
  280. package/src/pool-budget-stacks.test.ts +39 -14
  281. package/src/public-role-isolation.test.ts +18 -8
  282. package/src/public-role-isolation.ts +31 -17
  283. package/src/release-contract.ts +3 -0
  284. package/src/release-manifest.test.ts +25 -0
  285. package/src/release-manifest.ts +59 -4
  286. package/src/remote-mcp-stack-config.test.ts +19 -9
  287. package/src/remote-mcp.test.ts +252 -57
  288. package/src/remote-mcp.ts +336 -77
  289. package/src/runtime-secrets.test.ts +37 -14
  290. package/src/runtime-secrets.ts +87 -27
  291. package/src/scheduler-setup.test.ts +201 -0
  292. package/src/scheduler-setup.ts +375 -0
  293. package/src/seal-key-rotation.test.ts +2 -2
  294. package/src/seal-key-rotation.ts +25 -25
  295. package/src/self-hosted-backup.test.ts +1 -0
  296. package/src/self-hosted-compose.test.ts +180 -6
  297. package/src/self-hosted-db-tls.test.ts +111 -0
  298. package/src/self-hosted-db-tls.ts +287 -0
  299. package/src/self-hosted-release-resolve.test.ts +71 -0
  300. package/src/self-hosted-release.ts +41 -11
  301. package/src/self-hosted-roles.integration.test.ts +2 -0
  302. package/src/self-hosted-roles.test.ts +87 -3
  303. package/src/self-hosted-roles.ts +101 -3
  304. package/src/self-hosted-scheduler-interval.test.ts +66 -0
  305. package/src/self-hosted-scheduler-interval.ts +79 -0
  306. package/src/self-hosted-start.test.ts +35 -2
  307. package/src/self-hosted-start.ts +20 -2
  308. package/src/self-hosted-upgrade.test.ts +104 -14
  309. package/src/self-hosted-upgrade.ts +123 -31
  310. package/src/stack-contract-parity.test.ts +79 -12
  311. package/src/stack-contract.test.ts +199 -31
  312. package/src/stack-contract.ts +472 -45
  313. package/src/stack-converge-secrets.test.ts +28 -13
  314. package/src/stack-converge.test.ts +203 -28
  315. package/src/stack-converge.ts +92 -30
  316. package/src/staging-database.test.ts +114 -0
  317. package/src/staging-database.ts +148 -0
  318. package/src/state-store.test.ts +2 -1
  319. package/src/upgrade/app-roll.ts +97 -19
  320. package/src/upgrade/converge.ts +249 -19
  321. package/src/upgrade/health.test.ts +63 -25
  322. package/src/upgrade/health.ts +54 -22
  323. package/src/upgrade/image-forms.test.ts +34 -0
  324. package/src/upgrade/image-forms.ts +23 -8
  325. package/src/upgrade/release-images.test.ts +5 -1
  326. package/src/upgrade/release-images.ts +22 -6
  327. package/src/upgrade/upgrade.test.ts +438 -46
  328. package/src/upgrade/upgrade.ts +64 -6
  329. package/src/upgrader-gcp.ts +66 -3
  330. package/src/upgrader-iam-preflight.test.ts +19 -0
  331. package/src/upgrader-iam-preflight.ts +6 -0
  332. package/src/upgrader-job.test.ts +101 -27
  333. package/src/upgrader-job.ts +27 -14
  334. package/src/upgrader-records.test.ts +97 -12
  335. package/src/upgrader-records.ts +28 -11
  336. package/src/upgrader-setup.test.ts +2 -0
  337. package/src/upgrader-setup.ts +10 -4
  338. package/src/wizard.ts +19 -0
  339. package/src/wizards/gcp-cost.test.ts +103 -0
  340. package/src/wizards/gcp-cost.ts +125 -18
  341. package/src/wizards/gcp.ts +164 -22
  342. package/stacks/aws/Pulumi.yaml +1 -1
  343. package/stacks/aws/index.ts +72 -14
  344. package/stacks/azure/index.ts +108 -21
  345. package/stacks/gcp/Pulumi.yaml +1 -1
  346. package/stacks/gcp/README.md +1 -0
  347. package/stacks/gcp/index.ts +133 -24
  348. package/stacks/gcp-firebase/Pulumi.yaml +1 -1
  349. package/stacks/gcp-firebase/index.ts +212 -34
  350. package/stacks/self-hosted/Pulumi.yaml +3 -0
  351. package/stacks/self-hosted/README.md +6 -1
  352. package/stacks/self-hosted/index.ts +40 -5
@@ -29,6 +29,17 @@ function fakeGcloud(answers: Record<string, GcloudResult[]>) {
29
29
  return { run, calls };
30
30
  }
31
31
 
32
+ /**
33
+ * A gcp-firebase install since #1328: the admin, the data gateway and the
34
+ * MCP-Auth-Proxy — the service that was the gateway (E13 a).
35
+ */
36
+ const FIREBASE = { provider: "gcp-firebase" as const, projectId: "p", region: "europe-west1" };
37
+ const firebaseLists = () => ({
38
+ "metadata.name~^caelo-production-admin": [ok("caelo-production-admin-abc\n")],
39
+ "metadata.name~^caelo-production-data-gateway": [ok("caelo-production-data-gateway\n")],
40
+ "metadata.name~^caelo-production-gateway": [ok("caelo-production-gateway-def\n")],
41
+ });
42
+
32
43
  describe("digestFromImageRef", () => {
33
44
  it("extracts the pinned digest", () => {
34
45
  expect(digestFromImageRef(`${REG}/admin@${A}\n`)).toBe(A);
@@ -39,16 +50,43 @@ describe("digestFromImageRef", () => {
39
50
  });
40
51
 
41
52
  describe("readDeployedImageDigests", () => {
42
- it("reads both services' digests", async () => {
53
+ it("reads the three services' digests, each from the service playing its role", async () => {
43
54
  const { run, calls } = fakeGcloud({
55
+ ...firebaseLists(),
56
+ "describe caelo-production-admin-abc": [ok(`${REG}/admin@${A}`)],
57
+ "describe caelo-production-data-gateway": [ok(`${REG}/gateway@${B}`)],
58
+ "describe caelo-production-gateway-def": [ok(`${REG}/mcp-auth-proxy@${C}`)],
59
+ });
60
+ const r = await readDeployedImageDigests({ ...FIREBASE, run });
61
+ expect(r).toEqual({ ok: true, digests: { admin: A, gateway: B, "mcp-auth-proxy": C } });
62
+ expect(calls.every((c) => c.includes("--project=p"))).toBe(true);
63
+ });
64
+
65
+ it("finds the gcp proxy under its own name", async () => {
66
+ const { run } = fakeGcloud({
44
67
  "metadata.name~^caelo-production-admin": [ok("caelo-production-admin-abc\n")],
45
68
  "metadata.name~^caelo-production-gateway": [ok("caelo-production-gateway-def\n")],
69
+ "metadata.name~^caelo-production-mcp-auth-proxy": [ok("caelo-production-mcp-auth-proxy\n")],
46
70
  "describe caelo-production-admin-abc": [ok(`${REG}/admin@${A}`)],
47
71
  "describe caelo-production-gateway-def": [ok(`${REG}/gateway@${B}`)],
72
+ "describe caelo-production-mcp-auth-proxy": [ok(`${REG}/mcp-auth-proxy@${C}`)],
48
73
  });
49
- const r = await readDeployedImageDigests({ projectId: "p", region: "europe-west1", run });
50
- expect(r).toEqual({ ok: true, digests: { admin: A, gateway: B } });
51
- expect(calls.every((c) => c.includes("--project=p"))).toBe(true);
74
+ const r = await readDeployedImageDigests({ ...FIREBASE, provider: "gcp", run });
75
+ expect(r).toEqual({ ok: true, digests: { admin: A, gateway: B, "mcp-auth-proxy": C } });
76
+ });
77
+
78
+ it("sends an install without its #1328 services to upgrade", async () => {
79
+ const { run } = fakeGcloud({
80
+ "metadata.name~^caelo-production-admin": [ok("caelo-production-admin-abc\n")],
81
+ "metadata.name~^caelo-production-data-gateway": [ok("")],
82
+ "describe caelo-production-admin-abc": [ok(`${REG}/admin@${A}`)],
83
+ });
84
+ const r = await readDeployedImageDigests({ ...FIREBASE, run });
85
+ expect(r.ok).toBe(false);
86
+ if (!r.ok) {
87
+ expect(r.error).toContain("no caelo-production-data-gateway* Cloud Run service");
88
+ expect(r.error).toContain("Run `upgrade` first");
89
+ }
52
90
  });
53
91
 
54
92
  it("refuses an ambiguous service match", async () => {
@@ -57,7 +95,7 @@ describe("readDeployedImageDigests", () => {
57
95
  ok("caelo-production-admin-a\ncaelo-production-admin-b\n"),
58
96
  ],
59
97
  });
60
- const r = await readDeployedImageDigests({ projectId: "p", region: "r", run });
98
+ const r = await readDeployedImageDigests({ ...FIREBASE, run });
61
99
  expect(r.ok).toBe(false);
62
100
  if (!r.ok)
63
101
  expect(r.error).toContain("found caelo-production-admin-a, caelo-production-admin-b");
@@ -68,14 +106,14 @@ describe("readDeployedImageDigests", () => {
68
106
  "metadata.name~^caelo-production-admin": [ok("caelo-production-admin-abc\n")],
69
107
  "describe caelo-production-admin-abc": [ok(`${REG}/admin:latest`)],
70
108
  });
71
- const r = await readDeployedImageDigests({ projectId: "p", region: "r", run });
109
+ const r = await readDeployedImageDigests({ ...FIREBASE, run });
72
110
  expect(r.ok).toBe(false);
73
111
  if (!r.ok) expect(r.error).toContain("not pinned to a digest");
74
112
  });
75
113
 
76
114
  it("surfaces a gcloud failure", async () => {
77
115
  const { run } = fakeGcloud({ "services list": [fail("PERMISSION_DENIED")] });
78
- const r = await readDeployedImageDigests({ projectId: "p", region: "r", run });
116
+ const r = await readDeployedImageDigests({ ...FIREBASE, run });
79
117
  expect(r).toEqual({ ok: false, error: "list Cloud Run services: PERMISSION_DENIED" });
80
118
  });
81
119
  });
@@ -87,40 +125,53 @@ describe("checkDeployedRuntimeEnv", () => {
87
125
  name,
88
126
  valueFrom: { secretKeyRef: { name: `caelo-production-${id}`, key: "latest" } },
89
127
  });
90
- const lists = () => ({
91
- "metadata.name~^caelo-production-admin": [ok("caelo-production-admin-abc\n")],
92
- "metadata.name~^caelo-production-gateway": [ok("caelo-production-gateway-def\n")],
93
- });
128
+ const adminOk = svc([
129
+ secret("ADMIN_DATABASE_PASSWORD"),
130
+ secret("PUBLIC_ADMIN_DATABASE_PASSWORD"),
131
+ // #1329 — the staging database's pool: a release from before it is
132
+ // refused here too and goes through `upgrade` first.
133
+ secret("PUBLIC_STAGING_ADMIN_DATABASE_PASSWORD"),
134
+ ]);
135
+ const gatewayOk = svc([secret("PUBLIC_DATABASE_PASSWORD", "public-role-password")]);
136
+ const proxyOk = svc([secret("CAELO_MCP_OAUTH_SEAL_KEY", "mcp-oauth-seal-key")]);
94
137
 
95
- it("passes once both services read the password from Secret Manager", async () => {
138
+ it("passes once every service runs the current env", async () => {
96
139
  const { run } = fakeGcloud({
97
- ...lists(),
98
- "describe caelo-production-admin-abc": [
99
- svc([secret("ADMIN_DATABASE_PASSWORD"), secret("PUBLIC_ADMIN_DATABASE_PASSWORD")]),
100
- ],
101
- "describe caelo-production-gateway-def": [
102
- svc([secret("PUBLIC_DATABASE_PASSWORD", "public-role-password")]),
103
- ],
140
+ ...firebaseLists(),
141
+ "describe caelo-production-admin-abc": [adminOk],
142
+ "describe caelo-production-data-gateway": [gatewayOk],
143
+ "describe caelo-production-gateway-def": [proxyOk],
104
144
  });
105
- expect(await checkDeployedRuntimeEnv({ projectId: "p", region: "r", run })).toEqual({
106
- ok: true,
145
+ expect(await checkDeployedRuntimeEnv({ ...FIREBASE, run })).toEqual({ ok: true });
146
+ });
147
+
148
+ it("#1328 — stops a wizard re-run while the proxy is still the old gateway (database env)", async () => {
149
+ const { run } = fakeGcloud({
150
+ ...firebaseLists(),
151
+ "describe caelo-production-admin-abc": [adminOk],
152
+ "describe caelo-production-data-gateway": [gatewayOk],
153
+ "describe caelo-production-gateway-def": [gatewayOk],
107
154
  });
155
+ const r = await checkDeployedRuntimeEnv({ ...FIREBASE, run });
156
+ expect(r.ok).toBe(false);
157
+ if (!r.ok) {
158
+ expect(r.error).toContain("PUBLIC_DATABASE_PASSWORD");
159
+ expect(r.error).toContain("Run `upgrade` first");
160
+ }
108
161
  });
109
162
 
110
163
  it("stops a wizard re-run on a gateway from before #635 (it needs its gateway_role pool)", async () => {
111
164
  const { run } = fakeGcloud({
112
- ...lists(),
113
- "describe caelo-production-admin-abc": [
114
- svc([secret("ADMIN_DATABASE_PASSWORD"), secret("PUBLIC_ADMIN_DATABASE_PASSWORD")]),
115
- ],
116
- "describe caelo-production-gateway-def": [
165
+ ...firebaseLists(),
166
+ "describe caelo-production-admin-abc": [adminOk],
167
+ "describe caelo-production-data-gateway": [
117
168
  svc([
118
169
  secret("GATEWAY_DATABASE_PASSWORD", "gateway-role-password"),
119
170
  secret("PUBLIC_DATABASE_PASSWORD", "public-role-password"),
120
171
  ]),
121
172
  ],
122
173
  });
123
- const r = await checkDeployedRuntimeEnv({ projectId: "p", region: "r", run });
174
+ const r = await checkDeployedRuntimeEnv({ ...FIREBASE, run });
124
175
  expect(r.ok).toBe(false);
125
176
  if (!r.ok) {
126
177
  expect(r.error).toContain("GATEWAY_DATABASE_PASSWORD");
@@ -130,15 +181,13 @@ describe("checkDeployedRuntimeEnv", () => {
130
181
 
131
182
  it("stops a wizard re-run on a gateway from before #613 (it needs the admin_role pool)", async () => {
132
183
  const { run } = fakeGcloud({
133
- ...lists(),
134
- "describe caelo-production-admin-abc": [
135
- svc([secret("ADMIN_DATABASE_PASSWORD"), secret("PUBLIC_ADMIN_DATABASE_PASSWORD")]),
136
- ],
137
- "describe caelo-production-gateway-def": [
184
+ ...firebaseLists(),
185
+ "describe caelo-production-admin-abc": [adminOk],
186
+ "describe caelo-production-data-gateway": [
138
187
  svc([secret("ADMIN_DATABASE_PASSWORD"), secret("PUBLIC_DATABASE_PASSWORD")]),
139
188
  ],
140
189
  });
141
- const r = await checkDeployedRuntimeEnv({ projectId: "p", region: "r", run });
190
+ const r = await checkDeployedRuntimeEnv({ ...FIREBASE, run });
142
191
  expect(r.ok).toBe(false);
143
192
  if (!r.ok) {
144
193
  expect(r.error).toContain("ADMIN_DATABASE_PASSWORD");
@@ -148,14 +197,17 @@ describe("checkDeployedRuntimeEnv", () => {
148
197
 
149
198
  it("stops a wizard re-run on an install upgrade hasn't moved over (password in the URL)", async () => {
150
199
  const { run } = fakeGcloud({
151
- ...lists(),
200
+ ...firebaseLists(),
152
201
  "describe caelo-production-admin-abc": [
153
202
  svc([
154
- { name: "ADMIN_DATABASE_URL", value: "postgres://admin_role:pw@10.0.0.3:5432/cms_admin" },
203
+ {
204
+ name: "ADMIN_DATABASE_URL",
205
+ value: "postgres://admin_role:pw@db1.p-label.europe-west1.sql-psa.goog:5432/cms_admin",
206
+ },
155
207
  ]),
156
208
  ],
157
209
  });
158
- const r = await checkDeployedRuntimeEnv({ projectId: "p", region: "r", run });
210
+ const r = await checkDeployedRuntimeEnv({ ...FIREBASE, run });
159
211
  expect(r.ok).toBe(false);
160
212
  if (!r.ok) {
161
213
  expect(r.error).toContain("Run `upgrade` first");
@@ -165,29 +217,44 @@ describe("checkDeployedRuntimeEnv", () => {
165
217
  });
166
218
 
167
219
  describe("chooseImageDigests", () => {
168
- const latest = { admin: C, gateway: C };
220
+ const latest = { admin: C, gateway: C, "mcp-auth-proxy": C };
221
+ const recorded = { admin: A, gateway: B, "mcp-auth-proxy": C };
169
222
  const never = () => {
170
223
  throw new Error("must not be called");
171
224
  };
172
225
 
173
226
  it("keeps the recorded release", async () => {
227
+ const r = await chooseImageDigests({
228
+ recorded,
229
+ deployed: true,
230
+ readLive: never,
231
+ resolveLatest: never,
232
+ });
233
+ expect(r).toEqual({ ok: true, source: "recorded", digests: recorded });
234
+ });
235
+
236
+ it("#1328 — sends a record from before the MCP-Auth-Proxy to upgrade instead of mixing releases", async () => {
174
237
  const r = await chooseImageDigests({
175
238
  recorded: { admin: A, gateway: B },
176
239
  deployed: true,
177
240
  readLive: never,
178
241
  resolveLatest: never,
179
242
  });
180
- expect(r).toEqual({ ok: true, source: "recorded", digests: { admin: A, gateway: B } });
243
+ expect(r.ok).toBe(false);
244
+ if (!r.ok) {
245
+ expect(r.error).toContain("records no mcp-auth-proxy image");
246
+ expect(r.error).toContain("Run `upgrade` first");
247
+ }
181
248
  });
182
249
 
183
250
  it("keeps what a provisioned install without a record runs (no :latest roll)", async () => {
184
251
  const r = await chooseImageDigests({
185
252
  recorded: null,
186
253
  deployed: true,
187
- readLive: async () => ({ ok: true, digests: { admin: A, gateway: B } }),
254
+ readLive: async () => ({ ok: true, digests: recorded }),
188
255
  resolveLatest: never,
189
256
  });
190
- expect(r).toEqual({ ok: true, source: "live", digests: { admin: A, gateway: B } });
257
+ expect(r).toEqual({ ok: true, source: "live", digests: recorded });
191
258
  });
192
259
 
193
260
  it("stops and points at upgrade when the running release can't be read", async () => {
@@ -15,7 +15,14 @@ import { gcloud as defaultGcloud } from "./gcloud.js";
15
15
  import type { GcloudRunner } from "./gcloud-retry.js";
16
16
  import { GCP_STACK_ENV, gcpNamePrefix } from "./gcp-names.js";
17
17
  import type { ImageDigests } from "./install-state.js";
18
- import { type CloudRunSlug, RETIRED_SERVICE_ENV, SERVICE_SECRET_ENV } from "./stack-contract.js";
18
+ import {
19
+ CLOUD_RUN_SLUGS,
20
+ type CloudRunSlug,
21
+ cloudRunServicePrefix,
22
+ type GcpProvider,
23
+ RETIRED_SERVICE_ENV,
24
+ SERVICE_SECRET_ENV,
25
+ } from "./stack-contract.js";
19
26
  import { liveContainerEnv } from "./stack-converge.js";
20
27
 
21
28
  /** The `sha256:…` digest an image reference pins, or null for a tag reference. */
@@ -23,13 +30,13 @@ export function digestFromImageRef(ref: string): string | null {
23
30
  return ref.trim().match(/@(sha256:[0-9a-f]{64})$/)?.[1] ?? null;
24
31
  }
25
32
 
26
- /** The single deployed (Pulumi-suffixed) Cloud Run service for `slug`. */
33
+ /** The single deployed Cloud Run service playing `slug` (stack-contract.ts cloudRunServicePrefix). */
27
34
  async function findDeployedService(
28
35
  run: GcloudRunner,
29
- opts: { projectId: string; region: string },
36
+ opts: { provider: GcpProvider; projectId: string; region: string },
30
37
  slug: CloudRunSlug,
31
38
  ): Promise<{ ok: true; name: string } | { ok: false; error: string }> {
32
- const prefix = `${gcpNamePrefix(GCP_STACK_ENV)}-${slug}`;
39
+ const prefix = cloudRunServicePrefix(opts.provider, GCP_STACK_ENV, slug);
33
40
  const list = await run([
34
41
  "run",
35
42
  "services",
@@ -47,7 +54,10 @@ async function findDeployedService(
47
54
  if (names.length !== 1) {
48
55
  return {
49
56
  ok: false,
50
- error: `expected one ${prefix}* Cloud Run service, found ${names.length === 0 ? "none" : names.join(", ")}`,
57
+ error:
58
+ names.length === 0
59
+ ? `no ${prefix}* Cloud Run service: the install runs a release from before it (the ${slug} became its own service in #1328). Run \`upgrade\` first: it creates the service and moves the release over.`
60
+ : `expected one ${prefix}* Cloud Run service, found ${names.join(", ")}`,
51
61
  };
52
62
  }
53
63
  return { ok: true, name: names[0] as string };
@@ -68,12 +78,13 @@ async function findDeployedService(
68
78
  * and the env in one revision.
69
79
  */
70
80
  export async function checkDeployedRuntimeEnv(opts: {
81
+ provider: GcpProvider;
71
82
  projectId: string;
72
83
  region: string;
73
84
  run?: GcloudRunner;
74
85
  }): Promise<{ ok: true } | { ok: false; error: string }> {
75
86
  const run = opts.run ?? defaultGcloud;
76
- for (const slug of ["admin", "gateway"] as const) {
87
+ for (const slug of CLOUD_RUN_SLUGS) {
77
88
  const found = await findDeployedService(run, opts, slug);
78
89
  if (!found.ok) return found;
79
90
  const describe = await run([
@@ -109,18 +120,19 @@ export async function checkDeployedRuntimeEnv(opts: {
109
120
  }
110
121
 
111
122
  /**
112
- * Read the image digests the admin and gateway services run. Fails, naming
123
+ * Read the image digests the Cloud Run services run. Fails, naming
113
124
  * what is missing, when a service can't be found unambiguously or doesn't
114
125
  * run a digest-pinned image.
115
126
  */
116
127
  export async function readDeployedImageDigests(opts: {
128
+ provider: GcpProvider;
117
129
  projectId: string;
118
130
  region: string;
119
131
  run?: GcloudRunner;
120
132
  }): Promise<{ ok: true; digests: ImageDigests } | { ok: false; error: string }> {
121
133
  const run = opts.run ?? defaultGcloud;
122
- const digests = { admin: "", gateway: "" };
123
- for (const slug of ["admin", "gateway"] as const) {
134
+ const digests: Record<CloudRunSlug, string> = { admin: "", gateway: "", "mcp-auth-proxy": "" };
135
+ for (const slug of CLOUD_RUN_SLUGS) {
124
136
  const found = await findDeployedService(run, opts, slug);
125
137
  if (!found.ok) return found;
126
138
  const image = await run([
@@ -170,6 +182,15 @@ export async function chooseImageDigests(opts: {
170
182
  readonly readLive: () => ReturnType<typeof readDeployedImageDigests>;
171
183
  readonly resolveLatest: () => Promise<ImageDigests>;
172
184
  }): Promise<DigestChoice> {
185
+ if (opts.recorded && !opts.recorded["mcp-auth-proxy"]) {
186
+ // #1328 — the release it records predates the MCP-Auth-Proxy: this
187
+ // installer's stack would deploy a proxy image of another release.
188
+ return {
189
+ ok: false,
190
+ error:
191
+ "This install runs a release from before the MCP-Auth-Proxy (its install.json records no mcp-auth-proxy image). Run `upgrade` first — it sets the proxy up and records the release — then re-run the installer.",
192
+ };
193
+ }
173
194
  if (opts.recorded) return { ok: true, source: "recorded", digests: opts.recorded };
174
195
  if (!opts.deployed) return { ok: true, source: "latest", digests: await opts.resolveLatest() };
175
196
  const live = await opts.readLive();
@@ -16,6 +16,12 @@
16
16
  * self-hosted the generated compose gateway service: env, secrets,
17
17
  * mounts
18
18
  *
19
+ * Issue #1329 — the same crosswise between the two public databases: the
20
+ * live gateway's env names nothing of the staging database
21
+ * (cms_public_staging, public_staging_role, its password secret), and its
22
+ * identity cannot read the staging login's password, on every adapter. The
23
+ * staging gateway (#1330) gets the mirror-image checks with its service.
24
+ *
19
25
  * The gateway itself refuses to boot on such an env
20
26
  * (apps/api-gateway `adminDatabaseAccessInEnv`); this catches it at review
21
27
  * time, before a release can ship it.
@@ -42,6 +48,12 @@ const ADMIN_ACCESS = /cms_admin|admin_role|gateway_role|ADMIN_DATABASE|GATEWAY_D
42
48
  /** Secrets that unlock a cms_admin login. */
43
49
  const ADMIN_SECRETS = ["postgres-password", "gateway-role-password"];
44
50
 
51
+ /** #1329 — anything in the live gateway's env that reaches the staging database. */
52
+ const STAGING_ACCESS = /cms_public_staging|public_staging_role|PUBLIC_STAGING/;
53
+
54
+ /** #1329 — the secret that unlocks the staging gateway's login. */
55
+ const STAGING_SECRETS = ["public-staging-role-password"];
56
+
45
57
  /** The secrets the gateway reads on GCP: its own login and the staging gate key. */
46
58
  const GCP_GATEWAY_SECRETS = [
47
59
  "public-role-password",
@@ -65,26 +77,39 @@ describe("gcp + gcp-firebase: the gateway env contract (#635)", () => {
65
77
  domain: "example.com",
66
78
  region: "europe-west1",
67
79
  stagingHost: "staging.example.com",
68
- databaseUrls: databaseUrls("10.20.0.3"),
80
+ databaseUrls: databaseUrls("db1.p-label.europe-west1.sql-psa.goog"),
69
81
  });
70
82
 
71
- it("carries no cms_admin URL, role or credential name", () => {
83
+ it("carries no cms_admin URL, role or credential name, and nothing of the staging database (#1329)", () => {
72
84
  for (const v of env) {
73
85
  expect(v.name).not.toMatch(ADMIN_ACCESS);
74
- if ("value" in v && typeof v.value === "string") expect(v.value).not.toMatch(ADMIN_ACCESS);
86
+ expect(v.name).not.toMatch(STAGING_ACCESS);
87
+ if ("value" in v && typeof v.value === "string") {
88
+ expect(v.value).not.toMatch(ADMIN_ACCESS);
89
+ expect(v.value).not.toMatch(STAGING_ACCESS);
90
+ }
75
91
  }
92
+ expect(env.find((v) => v.name === "CAELO_GATEWAY_ENVIRONMENT")).toEqual({
93
+ name: "CAELO_GATEWAY_ENVIRONMENT",
94
+ value: "live",
95
+ });
76
96
  expect(env.find((v) => v.name === "PUBLIC_DATABASE_URL")).toEqual({
77
97
  name: "PUBLIC_DATABASE_URL",
78
- value: "postgresql://public_role@10.20.0.3:5432/cms_public?sslmode=require",
98
+ value:
99
+ "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",
79
100
  });
80
101
  });
81
102
 
82
- it("references no secret that unlocks a cms_admin login", () => {
103
+ it("references no secret that unlocks a cms_admin or the staging login", () => {
83
104
  for (const secret of Object.values(SERVICE_SECRET_ENV.gateway)) {
84
105
  expect(ADMIN_SECRETS).not.toContain(secret);
106
+ expect(STAGING_SECRETS).not.toContain(secret);
85
107
  }
86
108
  const gatewayBindings = runtimeSecretBindings().filter((b) => b.service === "gateway");
87
- for (const b of gatewayBindings) expect(ADMIN_SECRETS).not.toContain(b.secret);
109
+ for (const b of gatewayBindings) {
110
+ expect(ADMIN_SECRETS).not.toContain(b.secret);
111
+ expect(STAGING_SECRETS).not.toContain(b.secret);
112
+ }
88
113
  });
89
114
 
90
115
  for (const provider of ["gcp", "gcp-firebase"] as const) {
@@ -133,8 +158,9 @@ describe("self-hosted: the generated compose gateway service (#635)", () => {
133
158
  const out = generateDockerCompose({
134
159
  domain: "example.com",
135
160
  postgresPassword: "superpw",
136
- rolePasswords: { admin: "adminpw", public: "publicpw" },
161
+ rolePasswords: { admin: "adminpw", public: "publicpw", publicStaging: "stagingpw" },
137
162
  caeloSecretKek: "0".repeat(64),
163
+ dbTlsServerCertSha256: "abababababababababababababababababababababababababababababababab",
138
164
  internalSecret: "i".repeat(48),
139
165
  diskSize: "20Gi",
140
166
  images: {
@@ -150,15 +176,22 @@ describe("self-hosted: the generated compose gateway service (#635)", () => {
150
176
  const gateway = out.slice(start, end === -1 ? undefined : end);
151
177
  expect(gateway).toContain("PUBLIC_DATABASE_URL:");
152
178
  expect(gateway).not.toMatch(ADMIN_ACCESS);
153
- expect(gateway).not.toMatch(/superpw|adminpw|gatewaypw/);
154
- // No other way in: no Compose secrets, no env file, no mounts (a
155
- // mounted file is read by no env check), and exactly its own env.
156
- expect(gateway).not.toMatch(/^ {4}(secrets|env_file|volumes):/m);
179
+ expect(gateway).not.toMatch(STAGING_ACCESS);
180
+ expect(gateway).not.toMatch(/superpw|adminpw|gatewaypw|stagingpw/);
181
+ expect(gateway).toContain('CAELO_GATEWAY_ENVIRONMENT: "live"');
182
+ // No other way in: no Compose secrets, no env file, and exactly one
183
+ // mount — the database CA certificate (#1338), public, no credential.
184
+ expect(gateway).not.toMatch(/^ {4}(secrets|env_file):/m);
185
+ expect(gateway).toContain(
186
+ " volumes:\n - ./postgres-tls/ca.crt:/etc/caelo/db-ca/self-hosted-ca.pem:ro\n restart:",
187
+ );
157
188
  const env = gateway.slice(gateway.indexOf(" environment:"));
158
189
  const keys = [...env.matchAll(/^ {6}([A-Z_][A-Z0-9_]*):/gm)].map((m) => m[1]);
159
190
  expect(keys.sort()).toEqual([
160
191
  // #1254 — its cms_public pool size (pool-budget-stack.ts).
161
192
  "CAELO_DB_PUBLIC_POOL_MAX",
193
+ // #1329 — the live site's gateway.
194
+ "CAELO_GATEWAY_ENVIRONMENT",
162
195
  "CAELO_TRUSTED_PROXY_HOPS",
163
196
  "GATEWAY_PORT",
164
197
  "NODE_ENV",
@@ -176,6 +209,9 @@ describe("azure: the gateway container app (#635)", () => {
176
209
  const block = src.slice(start, src.indexOf("});", start));
177
210
  expect(block).toContain('{ name: "PUBLIC_DATABASE_URL", value: publicDatabaseUrl }');
178
211
  expect(block).not.toMatch(/adminDatabase|gatewayDatabase|ADMIN_DATABASE|GATEWAY_DATABASE/);
212
+ // #1329 — nothing of the staging database, and it says it is the live one.
213
+ expect(block).not.toMatch(/publicStaging|PUBLIC_STAGING/);
214
+ expect(block).toContain('{ name: GATEWAY_ENVIRONMENT_ENV, value: "live" }');
179
215
  });
180
216
 
181
217
  it("builds no cms_admin URL for any role but the admin's", () => {
@@ -201,7 +237,9 @@ describe("aws: the stack (#635)", () => {
201
237
 
202
238
  it("the gateway's task role reads only the public_role password (review finding)", () => {
203
239
  expect(AWS_SERVICE_SECRETS.gateway).toEqual(["public-role-password"]);
204
- for (const secret of ADMIN_SECRETS) expect(AWS_SERVICE_SECRETS.gateway).not.toContain(secret);
240
+ for (const secret of [...ADMIN_SECRETS, ...STAGING_SECRETS]) {
241
+ expect(AWS_SERVICE_SECRETS.gateway).not.toContain(secret);
242
+ }
205
243
  });
206
244
 
207
245
  it("each service gets its own task role whose policy is built from that table", () => {
package/src/gcp-names.ts CHANGED
@@ -56,6 +56,37 @@ export function gatewayServiceAccountEmail(projectId: string, env: string): stri
56
56
  return `${gatewayServiceAccountId(env)}@${projectId}.iam.gserviceaccount.com`;
57
57
  }
58
58
 
59
+ /**
60
+ * #1328 — account id of the service account the MCP-Auth-Proxy runs as. It
61
+ * may read the Google sign-in client and the broker's seal key and nothing
62
+ * else: no database password, no project role (stack-contract.ts
63
+ * `SERVICE_SECRET_ENV` / `SERVICE_SECRET_FILES`). Shortened from the
64
+ * service's name: account ids max out at 30 characters.
65
+ */
66
+ export function mcpAuthProxyServiceAccountId(env: string): string {
67
+ return `${gcpNamePrefix(env)}-mcp-proxy-sa`;
68
+ }
69
+
70
+ /**
71
+ * #1328 — the `gcp` MCP-Auth-Proxy's load-balancer resources, CLI-owned
72
+ * (remote-mcp.ts ensureRemoteMcpEdge): its serverless NEG, its backend
73
+ * service and its own Cloud Armor policy. None matches a prefix the other
74
+ * lookups use (`…-waf`, `…-admin-backend`, `…-gateway-backend`).
75
+ */
76
+ export function mcpAuthProxyEdgeNames(env: string): {
77
+ readonly neg: string;
78
+ readonly backend: string;
79
+ readonly policy: string;
80
+ } {
81
+ const prefix = `${gcpNamePrefix(env)}-mcp-auth-proxy`;
82
+ return { neg: `${prefix}-neg`, backend: `${prefix}-backend`, policy: `${prefix}-armor` };
83
+ }
84
+
85
+ /** Email of the service account the MCP-Auth-Proxy runs as. */
86
+ export function mcpAuthProxyServiceAccountEmail(projectId: string, env: string): string {
87
+ return `${mcpAuthProxyServiceAccountId(env)}@${projectId}.iam.gserviceaccount.com`;
88
+ }
89
+
59
90
  /**
60
91
  * Account id of the `gcp` stack's static-publisher service account. Account
61
92
  * ids max out at 30 chars, so the env is shortened.
@@ -190,6 +221,31 @@ export function captureServiceAccountEmail(projectId: string, env: string): stri
190
221
  return `${captureServiceAccountId(env)}@${projectId}.iam.gserviceaccount.com`;
191
222
  }
192
223
 
224
+ /**
225
+ * #1140 — account id of the scheduler tick's service account: signs its own
226
+ * IAP credential and calls the admin's tick endpoint, nothing else
227
+ * (scheduler-setup.ts). `caelo-production-scheduler` is 26 of the 30
228
+ * characters an account id may have.
229
+ */
230
+ export function schedulerServiceAccountId(env: string): string {
231
+ return `${gcpNamePrefix(env)}-scheduler`;
232
+ }
233
+
234
+ /** Email of the scheduler tick's service account. */
235
+ export function schedulerServiceAccountEmail(projectId: string, env: string): string {
236
+ return `${schedulerServiceAccountId(env)}@${projectId}.iam.gserviceaccount.com`;
237
+ }
238
+
239
+ /** #1140 — the Cloud Workflows workflow one scheduler tick runs (scheduler-setup.ts). */
240
+ export function schedulerWorkflowName(env: string): string {
241
+ return `${gcpNamePrefix(env)}-scheduler-tick`;
242
+ }
243
+
244
+ /** #1140 — the Cloud Scheduler job that starts the tick workflow every N minutes. */
245
+ export function schedulerJobName(env: string): string {
246
+ return `${gcpNamePrefix(env)}-scheduler-tick`;
247
+ }
248
+
193
249
  /**
194
250
  * Account id of the upgrader job's own service account: may roll the two
195
251
  * Cloud Run services, run the migration job and back up Cloud SQL, never
@@ -9,7 +9,7 @@
9
9
  import { beforeAll, describe, expect, it } from "bun:test";
10
10
  import { readFileSync } from "node:fs";
11
11
  import { resolve } from "node:path";
12
- import { GCP_PUBLIC_BASE_PATHS, isGcpRedirectRule, withGcpRedirects } from "@caelo-cms/shared";
12
+ import { GCP_GATEWAY_PATHS, isGcpRedirectRule, withGcpRedirects } from "@caelo-cms/shared";
13
13
  import * as pulumi from "@pulumi/pulumi";
14
14
  import { URL_MAP_REDIRECTS_PERMISSIONS, urlMapRedirectsRoleId } from "./gcp-names.js";
15
15
 
@@ -66,7 +66,8 @@ describe("gcp stack URL map (Pulumi mocks)", () => {
66
66
  {
67
67
  name: PUBLIC_PATH_MATCHER,
68
68
  defaultService: "bucket-id",
69
- pathRules: [{ paths: [...GCP_PUBLIC_BASE_PATHS], service: "gateway-id" }],
69
+ // #1328 — only /api/* here: the MCP-Auth-Proxy's rule is the CLI's.
70
+ pathRules: [{ paths: [...GCP_GATEWAY_PATHS], service: "gateway-id" }],
70
71
  },
71
72
  { name: "admin", defaultService: "admin-id" },
72
73
  ]);
@@ -102,12 +103,9 @@ describe("gcp stack URL map (Pulumi mocks)", () => {
102
103
  );
103
104
  expect(withRedirects.pathRules?.filter((r) => !isGcpRedirectRule(r))).toEqual([
104
105
  {
105
- paths: [
106
- "/api/*",
107
- // #806 — the remote-MCP broker's discovery documents.
108
- "/.well-known/oauth-protected-resource/*",
109
- "/.well-known/oauth-authorization-server/*",
110
- ],
106
+ // #1328 — the remote-MCP discovery paths go to the MCP-Auth-Proxy's
107
+ // own rule, which the CLI adds (remote-mcp.ts planUrlMapPaths).
108
+ paths: ["/api/*"],
111
109
  service: "gateway-id",
112
110
  },
113
111
  ]);
@@ -19,7 +19,7 @@
19
19
  * Imported by the stack from `dist/`; keep it free of CLI dependencies.
20
20
  */
21
21
 
22
- import { GCP_PUBLIC_BASE_PATHS } from "@caelo-cms/shared";
22
+ import { GCP_GATEWAY_PATHS } from "@caelo-cms/shared";
23
23
  import * as gcp from "@pulumi/gcp";
24
24
  import type * as pulumi from "@pulumi/pulumi";
25
25
 
@@ -58,7 +58,11 @@ export function siteUrlMapArgs(i: SiteUrlMapInputs): gcp.compute.URLMapArgs {
58
58
  {
59
59
  name: PUBLIC_PATH_MATCHER,
60
60
  defaultService: i.staticBackend,
61
- pathRules: [{ paths: [...GCP_PUBLIC_BASE_PATHS], service: i.gatewayBackend }],
61
+ // #1328 — the MCP-Auth-Proxy's rule (GCP_MCP_AUTH_PROXY_PATHS) is the
62
+ // CLI's: it creates the proxy's backend after the first `up`
63
+ // (remote-mcp.ts ensureRemoteMcpEdge), and the stack ignores
64
+ // `pathRules` after creation anyway.
65
+ pathRules: [{ paths: [...GCP_GATEWAY_PATHS], service: i.gatewayBackend }],
62
66
  },
63
67
  {
64
68
  name: "admin",