@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
@@ -27,7 +27,11 @@
27
27
  * is to wire those env vars to AWS-native resources.
28
28
  */
29
29
 
30
- import { cloudFrontRedirectFunctionCode } from "@caelo-cms/shared";
30
+ import {
31
+ AWS_RDS_CA_BUNDLE,
32
+ cloudFrontRedirectFunctionCode,
33
+ verifyFullQuery,
34
+ } from "@caelo-cms/shared";
31
35
  import * as aws from "@pulumi/aws";
32
36
  import * as awsx from "@pulumi/awsx";
33
37
  import * as pulumi from "@pulumi/pulumi";
@@ -40,8 +44,10 @@ import {
40
44
  } from "../../dist/aws-task-roles.js";
41
45
  import { generateBootstrapToken } from "../../dist/bootstrap-token.js";
42
46
  import {
47
+ DEFAULT_ADMIN_MAX_INSTANCES,
43
48
  DEFAULT_GATEWAY_MAX_INSTANCES,
44
49
  DEFAULT_MAX_CONNECTIONS,
50
+ DEFAULT_STAGING_GATEWAY_MAX_INSTANCES,
45
51
  stackPoolBudget,
46
52
  } from "../../dist/pool-budget-stack.js";
47
53
  import { ADMIN_MEMORY_DEFAULT, memoryQuantityMiB } from "../../dist/stack-contract.js";
@@ -82,7 +88,10 @@ const maxConnections = Number.parseInt(
82
88
  cfg.get("maxConnections") ?? String(DEFAULT_MAX_CONNECTIONS.aws),
83
89
  10,
84
90
  );
85
- const adminMaxInstances = Number.parseInt(cfg.get("adminMaxInstances") ?? "5", 10);
91
+ const adminMaxInstances = Number.parseInt(
92
+ cfg.get("adminMaxInstances") ?? String(DEFAULT_ADMIN_MAX_INSTANCES),
93
+ 10,
94
+ );
86
95
  const gatewayMaxInstances = Number.parseInt(
87
96
  cfg.get("gatewayMaxInstances") ?? String(DEFAULT_GATEWAY_MAX_INSTANCES),
88
97
  10,
@@ -91,6 +100,8 @@ const poolBudget = stackPoolBudget("aws", {
91
100
  maxConnections,
92
101
  adminMaxInstances,
93
102
  gatewayMaxInstances,
103
+ // #1329 — the staging gateway joins the budget with the staging database.
104
+ stagingGatewayMaxInstances: DEFAULT_STAGING_GATEWAY_MAX_INSTANCES,
94
105
  });
95
106
  if (_adminMemoryMiB === null) {
96
107
  throw new Error(`caelo-aws:adminMemory "${adminMemory}" is not a memory quantity (e.g. 2Gi)`);
@@ -127,6 +138,9 @@ const postgresPassword = pulumi.secret(randomHex(32));
127
138
  // #635) never holds admin_role's. bootstrap.sh creates the roles with these
128
139
  // (ADMIN_ROLE_PASSWORD / PUBLIC_ROLE_PASSWORD).
129
140
  const publicRolePassword = pulumi.secret(randomHex(32));
141
+ // #1329 — the staging gateway's own login on cms_public_staging (created
142
+ // with the database by bootstrap.sh, PUBLIC_STAGING_ROLE_PASSWORD).
143
+ const publicStagingRolePassword = pulumi.secret(randomHex(32));
130
144
  const csrfSecret = pulumi.secret(randomHex(32));
131
145
  const cookieSecret = pulumi.secret(randomHex(32));
132
146
  const anthropicApiKey = pulumi.secret(process.env.ANTHROPIC_API_KEY ?? "");
@@ -147,6 +161,8 @@ function secret(name: string, value: pulumi.Output<string>): aws.secretsmanager.
147
161
 
148
162
  const pgSecret = secret("postgres-password", postgresPassword);
149
163
  const publicRoleSecret = secret("public-role-password", publicRolePassword);
164
+ // #1329 — read by nothing yet: the staging gateway's task (#749) will.
165
+ const _publicStagingRoleSecret = secret("public-staging-role-password", publicStagingRolePassword);
150
166
  const csrfSecretRes = secret("csrf-secret", csrfSecret);
151
167
  const cookieSecretRes = secret("cookie-secret", cookieSecret);
152
168
  const anthropicSecretRes = secret("anthropic-api-key", anthropicApiKey);
@@ -171,9 +187,15 @@ const dbParameters = new aws.rds.ParameterGroup(`${namePrefix}-pg-params`, {
171
187
  family: "postgres16",
172
188
  parameters: [
173
189
  { name: "max_connections", value: String(maxConnections), applyMethod: "pending-reboot" },
190
+ // #1338 — refuse plaintext; every client connects with verify-full.
191
+ { name: "rds.force_ssl", value: "1", applyMethod: "pending-reboot" },
174
192
  ],
175
193
  });
176
194
 
195
+ // #1338 — verify-full against Amazon's RDS CA bundle (shipped in every image,
196
+ // shared database-tls.ts); the RDS certificate names the endpoint host.
197
+ const RDS_TLS = verifyFullQuery(AWS_RDS_CA_BUNDLE);
198
+
177
199
  const db = new aws.rds.Instance(`${namePrefix}-pg`, {
178
200
  engine: "postgres",
179
201
  engineVersion: "16.4",
@@ -196,14 +218,26 @@ const db = new aws.rds.Instance(`${namePrefix}-pg`, {
196
218
  const adminDatabaseUrl = pulumi
197
219
  .all([db.address, db.port, postgresPassword])
198
220
  .apply(
199
- ([host, port, pw]) =>
200
- `postgresql://caelo_admin:${pw}@${host}:${port}/cms_admin?sslmode=require`,
221
+ ([host, port, pw]) => `postgresql://caelo_admin:${pw}@${host}:${port}/cms_admin?${RDS_TLS}`,
201
222
  );
202
223
  const publicDatabaseUrl = pulumi
203
224
  .all([db.address, db.port, publicRolePassword])
225
+ .apply(
226
+ ([host, port, pw]) => `postgresql://public_role:${pw}@${host}:${port}/cms_public?${RDS_TLS}`,
227
+ );
228
+ // #1329 — the admin's pool on the staging database, and the staging
229
+ // gateway's only login (no task definition reads them before #749).
230
+ const publicStagingAdminDatabaseUrl = pulumi
231
+ .all([db.address, db.port, postgresPassword])
232
+ .apply(
233
+ ([host, port, pw]) =>
234
+ `postgresql://caelo_admin:${pw}@${host}:${port}/cms_public_staging?${RDS_TLS}`,
235
+ );
236
+ const publicStagingDatabaseUrl = pulumi
237
+ .all([db.address, db.port, publicStagingRolePassword])
204
238
  .apply(
205
239
  ([host, port, pw]) =>
206
- `postgresql://public_role:${pw}@${host}:${port}/cms_public?sslmode=require`,
240
+ `postgresql://public_staging_role:${pw}@${host}:${port}/cms_public_staging?${RDS_TLS}`,
207
241
  );
208
242
 
209
243
  // === 4. S3 buckets ===
@@ -441,6 +475,16 @@ new aws.cloudwatch.LogDelivery(
441
475
  // refuses every server-side render with that gap
442
476
  // (capture/capture-backend.ts) instead of running a browser in its own task.
443
477
  //
478
+ // FIXME(issue #749): no scheduler tick yet (#1140). On GCP one Cloud
479
+ // Scheduler job calls the admin's /api/internal/scheduler/tick every
480
+ // `schedulerIntervalMinutes` (src/scheduler-setup.ts), and the admin runs
481
+ // its periodic work and plugin workers only on that tick; self-hosted has a
482
+ // scheduler container. The AWS equivalent is one EventBridge Scheduler
483
+ // schedule that runs a small Fargate task (or Lambda) POSTing the same
484
+ // endpoint through the ALB's Cognito gate, with the same 1–15 minute knob.
485
+ // Nothing calls the tick here because no admin task exists on AWS yet; it
486
+ // lands with the admin task in #749.
487
+ //
444
488
  // FIXME(issue #696): no in-cloud upgrader or converge job (#819) yet. On
445
489
  // GCP Settings → Updates hands app-roll releases to the upgrader job and
446
490
  // cloud-setup releases to the converge job; on AWS the admin refuses a
@@ -450,9 +494,12 @@ const cluster = new aws.ecs.Cluster(`${namePrefix}-ecs`, {
450
494
  settings: [{ name: "containerInsights", value: "enabled" }],
451
495
  });
452
496
 
453
- // #806 — no remote MCP on AWS: it rides Google IAP (gcp stacks only). The
454
- // gateway task definition, once it exists, sets none of its env, so /api/mcp/*
455
- // answers 503 remote_mcp_not_configured and the admin reports it unavailable.
497
+ // #1328 — the MCP-Auth-Proxy (remote MCP's sign-in service) is a service of
498
+ // its own on every provider, with its own task role that may read NO secret.
499
+ // Remote MCP rides Google IAP (gcp stacks only), so on AWS it would run with
500
+ // CAELO_REMOTE_MCP=unavailable and answer a fixed 404. Like the admin and
501
+ // gateway it has no task definition yet: the service lands with #749, in the
502
+ // same shape (own origin, zero minimum tasks, no database env).
456
503
  //
457
504
  // One task role per service (#635): the gateway's may read only the
458
505
  // public_role password, never a secret that unlocks a cms_admin login
@@ -482,18 +529,23 @@ function taskRole(service: AwsTaskService): aws.iam.Role {
482
529
  ],
483
530
  }),
484
531
  });
485
- new aws.iam.RolePolicy(`${prefix}-task-secrets`, {
486
- role: role.id,
487
- policy: pulumi
488
- .all(AWS_SERVICE_SECRETS[service].map((name) => secretArns[name]))
489
- .apply(secretsReadPolicy),
490
- });
532
+ // A service that reads no secret gets no secrets policy at all (an empty
533
+ // policy is malformed, see secretsReadPolicy).
534
+ if (AWS_SERVICE_SECRETS[service].length > 0) {
535
+ new aws.iam.RolePolicy(`${prefix}-task-secrets`, {
536
+ role: role.id,
537
+ policy: pulumi
538
+ .all(AWS_SERVICE_SECRETS[service].map((name) => secretArns[name]))
539
+ .apply(secretsReadPolicy),
540
+ });
541
+ }
491
542
  return role;
492
543
  }
493
544
 
494
545
  const taskRoles: Record<AwsTaskService, aws.iam.Role> = {
495
546
  admin: taskRole("admin"),
496
547
  gateway: taskRole("gateway"),
548
+ "mcp-auth-proxy": taskRole("mcp-auth-proxy"),
497
549
  };
498
550
 
499
551
  const execRole = new aws.iam.Role(`${namePrefix}-exec-role`, {
@@ -535,6 +587,7 @@ const dnsRecordsRequired: DnsRecord[] = [
535
587
  const out: CloudAdapterOutputs = {
536
588
  adminDatabaseUrl: adminDatabaseUrl as unknown as string,
537
589
  publicDatabaseUrl: publicDatabaseUrl as unknown as string,
590
+ publicStagingDatabaseUrl: publicStagingDatabaseUrl as unknown as string,
538
591
  mediaStorageUrl: pulumi.interpolate`s3://${mediaBucket.bucket}` as unknown as string,
539
592
  mediaCdnBaseUrl:
540
593
  pulumi.interpolate`https://${distribution.domainName}/media` as unknown as string,
@@ -549,6 +602,9 @@ const out: CloudAdapterOutputs = {
549
602
 
550
603
  export const adminDatabaseUrlOut = out.adminDatabaseUrl;
551
604
  export const publicDatabaseUrlOut = out.publicDatabaseUrl;
605
+ export const publicStagingDatabaseUrlOut = out.publicStagingDatabaseUrl;
606
+ // #1329 — what the admin task definition (#749) sets as PUBLIC_STAGING_ADMIN_DATABASE_URL.
607
+ export const publicStagingAdminDatabaseUrlOut = publicStagingAdminDatabaseUrl;
552
608
  export const mediaStorageUrlOut = out.mediaStorageUrl;
553
609
  export const mediaCdnBaseUrlOut = out.mediaCdnBaseUrl;
554
610
  export const bootstrapUrlOut = out.bootstrapUrl;
@@ -563,6 +619,8 @@ export const rdsAddressOut = db.address;
563
619
  // The task roles the admin-side and gateway task definitions run as.
564
620
  export const adminTaskRoleArnOut = taskRoles.admin.arn;
565
621
  export const gatewayTaskRoleArnOut = taskRoles.gateway.arn;
622
+ // #1328 — the role the MCP-Auth-Proxy task definition will run as (#749).
623
+ export const mcpAuthProxyTaskRoleArnOut = taskRoles["mcp-auth-proxy"].arn;
566
624
  // #1254 — the pool env the admin and gateway task definitions set.
567
625
  export const poolBudgetEnvOut = poolBudget.env;
568
626
 
@@ -13,7 +13,8 @@
13
13
  * 4. Azure Front Door Standard serving the static website on the primary
14
14
  * domain with a managed certificate — files served as files, no compute
15
15
  * on the request path (CLAUDE.md §11.B Tier 1).
16
- * 5. Azure Container Apps (admin/gateway/orchestrator/runner),
16
+ * 5. Azure Container Apps (admin/gateway/orchestrator/runner, and the
17
+ * MCP-Auth-Proxy without any secret, #1328),
17
18
  * mounted with Key Vault secrets via managed identity.
18
19
  * 6. Key Vault entries (postgres-password, csrf-secret, cookie-secret,
19
20
  * anthropic-api-key, resend-api-key).
@@ -34,16 +35,25 @@
34
35
  * env vars wired by this stack.
35
36
  */
36
37
 
38
+ import { verifyFullQuery } from "@caelo-cms/shared";
37
39
  import * as azure from "@pulumi/azure-native";
38
40
  import * as pulumi from "@pulumi/pulumi";
39
41
  import type { CloudAdapterOutputs, DnsRecord } from "../../dist/adapter.js";
40
42
  import { azureFrontDoorDnsRecords } from "../../dist/azure-front-door.js";
41
43
  import { generateBootstrapToken } from "../../dist/bootstrap-token.js";
42
- import { DEFAULT_MAX_CONNECTIONS, stackPoolBudget } from "../../dist/pool-budget-stack.js";
44
+ import {
45
+ DEFAULT_MAX_CONNECTIONS,
46
+ DEFAULT_STAGING_GATEWAY_MAX_INSTANCES,
47
+ stackPoolBudget,
48
+ } from "../../dist/pool-budget-stack.js";
43
49
  import {
44
50
  ADMIN_MEMORY_DEFAULT,
45
51
  azureContainerAppResources,
52
+ GATEWAY_ENVIRONMENT_ENV,
46
53
  GATEWAY_TRUSTED_PROXY_HOPS,
54
+ MCP_AUTH_PROXY_TRUSTED_PROXY_HOPS,
55
+ REMOTE_MCP_MODE,
56
+ REMOTE_MCP_MODE_ENV,
47
57
  TRUSTED_PROXY_HOPS_ENV,
48
58
  } from "../../dist/stack-contract.js";
49
59
  import {
@@ -83,6 +93,8 @@ const poolBudget = stackPoolBudget("azure", {
83
93
  maxConnections,
84
94
  adminMaxInstances: ADMIN_SIDE_APPS * MAX_REPLICAS,
85
95
  gatewayMaxInstances: MAX_REPLICAS,
96
+ // #1329 — the staging gateway joins the budget with the staging database.
97
+ stagingGatewayMaxInstances: DEFAULT_STAGING_GATEWAY_MAX_INSTANCES,
86
98
  });
87
99
  // #553 — the admin runs the Lighthouse quality audit; same knob + default
88
100
  // as every adapter (stack-contract.ts). Container Apps only accepts fixed
@@ -111,6 +123,9 @@ const postgresPassword = pulumi.secret(randomHex(32));
111
123
  // #635) never holds admin_role's. bootstrap.sh creates the roles with these
112
124
  // (ADMIN_ROLE_PASSWORD / PUBLIC_ROLE_PASSWORD).
113
125
  const publicRolePassword = pulumi.secret(randomHex(32));
126
+ // #1329 — the staging gateway's own login on cms_public_staging (created
127
+ // with the database by bootstrap.sh, PUBLIC_STAGING_ROLE_PASSWORD).
128
+ const publicStagingRolePassword = pulumi.secret(randomHex(32));
114
129
  const csrfSecret = pulumi.secret(randomHex(32));
115
130
  const cookieSecret = pulumi.secret(randomHex(32));
116
131
  const anthropicApiKey = pulumi.secret(process.env.ANTHROPIC_API_KEY ?? "");
@@ -140,6 +155,8 @@ function keyVaultSecret(shortName: string, value: pulumi.Output<string>): azure.
140
155
 
141
156
  const _pgSecret = keyVaultSecret("pg-password", postgresPassword);
142
157
  keyVaultSecret("public-role-password", publicRolePassword);
158
+ // #1329 — read by nothing yet: the staging gateway's app (#750) will.
159
+ keyVaultSecret("public-staging-role-password", publicStagingRolePassword);
143
160
  keyVaultSecret("csrf-secret", csrfSecret);
144
161
  keyVaultSecret("cookie-secret", cookieSecret);
145
162
  keyVaultSecret("anthropic-api-key", anthropicApiKey);
@@ -190,19 +207,44 @@ const _pgPublic = new azure.dbforpostgresql.Database(`${namePrefix}-cms-public-d
190
207
  databaseName: "cms_public",
191
208
  charset: "UTF8",
192
209
  });
210
+ // #1329 — staging's own data, the twin of cms_public on the same server.
211
+ const _pgPublicStaging = new azure.dbforpostgresql.Database(`${namePrefix}-cms-public-staging-db`, {
212
+ resourceGroupName: rg.name,
213
+ serverName: pgServer.name,
214
+ databaseName: "cms_public_staging",
215
+ charset: "UTF8",
216
+ });
217
+
218
+ // #1338 — verify-full to the server's FQDN. Flexible Server's certificate chains
219
+ // to public roots (DigiCert Global Root G2, Microsoft RSA Root CA 2017) that
220
+ // Bun trusts out of the box, so no sslrootcert (shared database-tls.ts).
221
+ const AZURE_TLS = verifyFullQuery();
193
222
 
194
223
  const adminDatabaseUrl = pulumi
195
224
  .all([pgServer.fullyQualifiedDomainName, postgresPassword])
196
- .apply(([host, pw]) => `postgresql://caelo_admin:${pw}@${host}:5432/cms_admin?sslmode=require`);
225
+ .apply(([host, pw]) => `postgresql://caelo_admin:${pw}@${host}:5432/cms_admin?${AZURE_TLS}`);
197
226
  // What the admin connects to cms_public with (DDL, migrations, moderation).
198
227
  const publicAdminDatabaseUrl = pulumi
199
228
  .all([pgServer.fullyQualifiedDomainName, postgresPassword])
200
- .apply(([host, pw]) => `postgresql://caelo_admin:${pw}@${host}:5432/cms_public?sslmode=require`);
229
+ .apply(([host, pw]) => `postgresql://caelo_admin:${pw}@${host}:5432/cms_public?${AZURE_TLS}`);
201
230
  // The gateway's only login (#613, #635): public_role on cms_public. It has
202
231
  // no cms_admin login at all (CLAUDE.md §2).
203
232
  const publicDatabaseUrl = pulumi
204
233
  .all([pgServer.fullyQualifiedDomainName, publicRolePassword])
205
- .apply(([host, pw]) => `postgresql://public_role:${pw}@${host}:5432/cms_public?sslmode=require`);
234
+ .apply(([host, pw]) => `postgresql://public_role:${pw}@${host}:5432/cms_public?${AZURE_TLS}`);
235
+ // #1329 — the admin's pool on the staging database (plugin schemas, the
236
+ // staging half of the projection), and the staging gateway's only login.
237
+ const publicStagingAdminDatabaseUrl = pulumi
238
+ .all([pgServer.fullyQualifiedDomainName, postgresPassword])
239
+ .apply(
240
+ ([host, pw]) => `postgresql://caelo_admin:${pw}@${host}:5432/cms_public_staging?${AZURE_TLS}`,
241
+ );
242
+ const publicStagingDatabaseUrl = pulumi
243
+ .all([pgServer.fullyQualifiedDomainName, publicStagingRolePassword])
244
+ .apply(
245
+ ([host, pw]) =>
246
+ `postgresql://public_staging_role:${pw}@${host}:5432/cms_public_staging?${AZURE_TLS}`,
247
+ );
206
248
 
207
249
  // === 4. Storage account + containers ===
208
250
  const storage = new azure.storage.StorageAccount(`${namePrefix}-st`, {
@@ -399,6 +441,15 @@ const logWorkspace = new azure.operationalinsights.Workspace(`${namePrefix}-logs
399
441
  // refuses every server-side render with that gap
400
442
  // (capture/capture-backend.ts) instead of running a browser in its own app.
401
443
  //
444
+ // FIXME(issue #750): no scheduler tick yet (#1140). On GCP one Cloud
445
+ // Scheduler job calls the admin's /api/internal/scheduler/tick every
446
+ // `schedulerIntervalMinutes` (src/scheduler-setup.ts), and the admin runs
447
+ // its periodic work and plugin workers only on that tick; self-hosted has a
448
+ // scheduler container. The Azure equivalent is a scheduled Container Apps
449
+ // job (or Logic App recurrence) POSTing the same endpoint behind Easy Auth,
450
+ // with the same 1–15 minute knob. Nothing calls the tick here because no
451
+ // admin app exists on Azure yet; it lands with the admin in #750.
452
+ //
402
453
  // FIXME(issue #696): no in-cloud upgrader or converge job (#819) yet. On
403
454
  // GCP Settings → Updates hands app-roll releases to the upgrader job and
404
455
  // cloud-setup releases to the converge job; on Azure the admin refuses a
@@ -437,6 +488,12 @@ interface ContainerAppArgs {
437
488
  readonly extraEnv?: ReadonlyArray<{ name: string; value: pulumi.Input<string> }>;
438
489
  /** Defaults to 0.5 vCPU / 1 GiB. */
439
490
  readonly resources?: { readonly cpu: number; readonly memory: string };
491
+ /**
492
+ * #1328 — a service that reads nothing of the install: no CAELO_ENV, no
493
+ * media URL, only `CAELO_PROVIDER` plus {@link extraEnv}, and zero
494
+ * minimum replicas in every environment (E14 a of #763).
495
+ */
496
+ readonly isolated?: true;
440
497
  }
441
498
 
442
499
  function containerApp(args: ContainerAppArgs): azure.app.ContainerApp {
@@ -454,7 +511,10 @@ function containerApp(args: ContainerAppArgs): azure.app.ContainerApp {
454
511
  },
455
512
  },
456
513
  template: {
457
- scale: { minReplicas: env === "production" ? 1 : 0, maxReplicas: MAX_REPLICAS },
514
+ scale: {
515
+ minReplicas: args.isolated || env !== "production" ? 0 : 1,
516
+ maxReplicas: MAX_REPLICAS,
517
+ },
458
518
  containers: [
459
519
  {
460
520
  name: args.serviceName,
@@ -462,16 +522,18 @@ function containerApp(args: ContainerAppArgs): azure.app.ContainerApp {
462
522
  image:
463
523
  cfg.get(`image-${args.serviceName}`) ??
464
524
  `${namePrefix}.azurecr.io/${args.serviceName}:latest`,
465
- env: [
466
- { name: "CAELO_PROVIDER", value: "azure" },
467
- { name: "CAELO_ENV", value: env },
468
- ...args.databaseEnv,
469
- {
470
- name: "MEDIA_STORAGE_URL",
471
- value: pulumi.interpolate`https://${storage.name}.blob.core.windows.net/media`,
472
- },
473
- ...(args.extraEnv ?? []),
474
- ],
525
+ env: args.isolated
526
+ ? [{ name: "CAELO_PROVIDER", value: "azure" }, ...(args.extraEnv ?? [])]
527
+ : [
528
+ { name: "CAELO_PROVIDER", value: "azure" },
529
+ { name: "CAELO_ENV", value: env },
530
+ ...args.databaseEnv,
531
+ {
532
+ name: "MEDIA_STORAGE_URL",
533
+ value: pulumi.interpolate`https://${storage.name}.blob.core.windows.net/media`,
534
+ },
535
+ ...(args.extraEnv ?? []),
536
+ ],
475
537
  resources: args.resources ?? { cpu: 0.5, memory: "1.0Gi" },
476
538
  },
477
539
  ],
@@ -482,6 +544,7 @@ function containerApp(args: ContainerAppArgs): azure.app.ContainerApp {
482
544
  const adminDatabaseEnv = [
483
545
  { name: "ADMIN_DATABASE_URL", value: adminDatabaseUrl },
484
546
  { name: "PUBLIC_ADMIN_DATABASE_URL", value: publicAdminDatabaseUrl },
547
+ { name: "PUBLIC_STAGING_ADMIN_DATABASE_URL", value: publicStagingAdminDatabaseUrl },
485
548
  ...poolBudget.env.admin,
486
549
  ];
487
550
  const adminApp = containerApp({
@@ -498,11 +561,33 @@ const _gatewayApp = containerApp({
498
561
  { name: "PUBLIC_DATABASE_URL", value: publicDatabaseUrl },
499
562
  ...poolBudget.env.gateway,
500
563
  ],
501
- // Which X-Forwarded-For entry is the visitor (api-gateway client-ip.ts).
502
- // #806 — no remote-MCP env: remote MCP rides Google IAP (gcp stacks
503
- // only); unset, the gateway answers /api/mcp/* with 503
504
- // remote_mcp_not_configured and the admin reports it unavailable.
505
- extraEnv: [{ name: TRUSTED_PROXY_HOPS_ENV, value: String(GATEWAY_TRUSTED_PROXY_HOPS.azure) }],
564
+ // Which X-Forwarded-For entry is the visitor (api-gateway client-ip.ts),
565
+ // and (#1329) that this is the live site's gateway.
566
+ extraEnv: [
567
+ { name: TRUSTED_PROXY_HOPS_ENV, value: String(GATEWAY_TRUSTED_PROXY_HOPS.azure) },
568
+ { name: GATEWAY_ENVIRONMENT_ENV, value: "live" },
569
+ ],
570
+ });
571
+ // #1328 — the MCP-Auth-Proxy, its own Container App and so its own FQDN:
572
+ // remote MCP's sign-in pages never share an origin with the site's module
573
+ // JS. Remote MCP needs the admin behind Google IAP, which Azure does not
574
+ // have, so it runs with CAELO_REMOTE_MCP=unavailable and answers everything
575
+ // but its health check (`/api/mcp/health`) with one fixed 404; the admin
576
+ // reports remote MCP as not available here. No database URL, no secret
577
+ // and no managed identity, because there is nothing it may read. Same
578
+ // shape as on every provider (E11 of #763), at zero replicas until a
579
+ // request comes.
580
+ const _mcpAuthProxyApp = containerApp({
581
+ serviceName: "mcp-auth-proxy",
582
+ databaseEnv: [],
583
+ isolated: true,
584
+ resources: { cpu: 0.25, memory: "0.5Gi" },
585
+ extraEnv: [
586
+ { name: REMOTE_MCP_MODE_ENV, value: REMOTE_MCP_MODE.azure },
587
+ { name: TRUSTED_PROXY_HOPS_ENV, value: String(MCP_AUTH_PROXY_TRUSTED_PROXY_HOPS.azure) },
588
+ // The app's ingress targets 8080 (containerApp above).
589
+ { name: "PORT", value: "8080" },
590
+ ],
506
591
  });
507
592
  const _orchestratorApp = containerApp({
508
593
  serviceName: "orchestrator",
@@ -526,6 +611,7 @@ const dnsRecordsRequired = pulumi
526
611
  const out: CloudAdapterOutputs = {
527
612
  adminDatabaseUrl: adminDatabaseUrl as unknown as string,
528
613
  publicDatabaseUrl: publicDatabaseUrl as unknown as string,
614
+ publicStagingDatabaseUrl: publicStagingDatabaseUrl as unknown as string,
529
615
  mediaStorageUrl:
530
616
  pulumi.interpolate`https://${storage.name}.blob.core.windows.net/media` as unknown as string,
531
617
  mediaCdnBaseUrl: pulumi.interpolate`https://${domain}/media` as unknown as string,
@@ -539,6 +625,7 @@ const out: CloudAdapterOutputs = {
539
625
 
540
626
  export const adminDatabaseUrlOut = out.adminDatabaseUrl;
541
627
  export const publicDatabaseUrlOut = out.publicDatabaseUrl;
628
+ export const publicStagingDatabaseUrlOut = out.publicStagingDatabaseUrl;
542
629
  export const mediaStorageUrlOut = out.mediaStorageUrl;
543
630
  export const mediaCdnBaseUrlOut = out.mediaCdnBaseUrl;
544
631
  export const bootstrapUrlOut = out.bootstrapUrl;
@@ -55,7 +55,7 @@ config:
55
55
  description: Admin Cloud Run memory. 2Gi leaves room for the Lighthouse quality audit next to editor requests; billed only while an instance runs.
56
56
  caelo-gcp:adminMaxInstances:
57
57
  type: string
58
- default: "5"
58
+ default: "4"
59
59
  description: |
60
60
  Admin Cloud Run max instances. Each instance gets an equal share of
61
61
  the connection budget (docs/dev/connection-budget.md): at the
@@ -12,6 +12,7 @@ Pulumi stack provisioning Caelo on GCP managed services. Implements the shared `
12
12
  | Container runtime | Four Cloud Run services (admin / gateway / orchestrator / runner) |
13
13
  | Secret store | Secret Manager: postgres-password (admin_role) + secret-kek (Pulumi); internal-secret, tool-approval-secret and public-role-password (created by the CLI before `pulumi up`, see `src/runtime-secrets.ts`). Cloud Run reads each as a `secretKeyRef`; database URLs carry no password. The gateway's service account reads only `public-role-password` (#613, #635: the gateway has no cms_admin login). |
14
14
  | Edge-log sink | Cloud Logging project sink → BigQuery dataset `<env>_edge_logs` (queryable by P12A analytics plugin) |
15
+ | Scheduler tick (CLI-owned, #1140) | One Cloud Scheduler job `caelo-<env>-scheduler-tick` starts the Workflows workflow of the same name every `schedulerIntervalMinutes` (1–15, default 5; `--scheduler-interval-minutes` on the installer, recorded in install.json). The workflow signs an IAP credential as `caelo-<env>-scheduler` and POSTs the admin's `/api/internal/scheduler/tick`, which runs the due periodic work and plugin workers. Set up by the installer and `upgrade` (`src/scheduler-setup.ts`), not by this stack. |
15
16
 
16
17
  The Caelo runtime never knows it's on GCP — every Cloud Run service consumes plain `DATABASE_URL` / `MEDIA_STORAGE_URL` / `SECRETS_PROVIDER` env vars.
17
18