@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
@@ -25,7 +25,9 @@
25
25
  * 4. A second preview: what the review shows and `up` applies. An adopted
26
26
  * resource that is still planned as a create stops the run.
27
27
  *
28
- * Why only service accounts ({@link ADOPTABLE_KINDS}). A kind is adopted
28
+ * Why service accounts, since #1338 the Cloud SQL private DNS zone and its
29
+ * record (cloudsql-tls.ts creates both on upgrade), and since #1328
30
+ * explicitly named Cloud Run services — {@link ADOPTABLE_KINDS}. A kind is adopted
29
31
  * when the stack declares it AND the CLI also creates it through gcloud, and
30
32
  * its name is unique in the project. Checked per kind against both stacks:
31
33
  *
@@ -34,6 +36,9 @@
34
36
  * (stack-contract.ts CLI_GENERATED_SECRETS) the stack only references by
35
37
  * id. Adopting a secret without its value resource would also make `up`
36
38
  * add a version with a freshly generated value — a new KEK.
39
+ * - Cloud Run services: only the ones the stack names explicitly (#1328:
40
+ * the gcp-firebase data gateway, which the converge creates on older
41
+ * installs); the auto-named admin and proxy are Pulumi's alone.
37
42
  * - Buckets, Cloud Run jobs, custom roles: the stack's buckets are made by
38
43
  * Pulumi alone; the CLI's own (ops, audit results, the upgrader, converge,
39
44
  * audit and operator-access jobs, their custom roles) are not declared.
@@ -44,12 +49,17 @@
44
49
  * Their create is a read-modify-write that merges the member into the
45
50
  * policy and succeeds when it is there already, so a planned create of
46
51
  * one is an adoption already.
47
- * - Cloud SQL users: the CLI's are IAM users named after service accounts,
48
- * the stack's are the built-in `admin_role` / `public_role`.
52
+ * - Cloud SQL databases and users: only the staging database
53
+ * `cms_public_staging` and its login `public_staging_role` (#1329), which
54
+ * the converge creates on installs from before them (staging-database.ts).
55
+ * Every other one is Pulumi's alone: the CLI's other users are IAM users
56
+ * named after service accounts, the stack's the built-in `admin_role` /
57
+ * `public_role`.
49
58
  */
50
59
 
51
60
  import type { GcloudRunner } from "./gcloud-retry.js";
52
61
  import type { PlannedStep } from "./preview-plan.js";
62
+ import { STAGING_DATABASE, STAGING_DATABASE_ROLE } from "./staging-database.js";
53
63
 
54
64
  /** The cloud identity of a resource a planned create would make. */
55
65
  export interface AdoptionTarget {
@@ -72,6 +82,9 @@ export interface AdoptableKind {
72
82
  target(inputs: Readonly<Record<string, unknown>>, projectId: string): AdoptionTarget | null;
73
83
  }
74
84
 
85
+ /** The Cloud SQL objects the converge creates that the stack declares too (#1329). */
86
+ const CONVERGE_CREATED_SQL = { database: STAGING_DATABASE, user: STAGING_DATABASE_ROLE } as const;
87
+
75
88
  /** Kinds adopted before `up`, by Pulumi type token. See the file comment for the rest. */
76
89
  export const ADOPTABLE_KINDS: Readonly<Record<string, AdoptableKind>> = {
77
90
  "gcp:serviceaccount/account:Account": {
@@ -102,6 +115,131 @@ export const ADOPTABLE_KINDS: Readonly<Record<string, AdoptableKind>> = {
102
115
  };
103
116
  },
104
117
  },
118
+ // #1338 — the private zone + A record that resolve the Cloud SQL
119
+ // instance's DNS name in the VPC. `cms-provision upgrade` creates both
120
+ // through gcloud (cloudsql-tls.ts) on installs that predate them; the
121
+ // stacks declare them with literal names so the preview pins them down.
122
+ "gcp:dns/managedZone:ManagedZone": {
123
+ label: "private DNS zone",
124
+ target(inputs, projectId) {
125
+ const name = inputs.name;
126
+ if (typeof name !== "string" || !/^[a-z][a-z0-9-]{0,62}$/.test(name)) return null;
127
+ if ((inputs.project ?? projectId) !== projectId) return null;
128
+ return {
129
+ display: name,
130
+ describeArgs: [
131
+ "dns",
132
+ "managed-zones",
133
+ "describe",
134
+ name,
135
+ `--project=${projectId}`,
136
+ "--format=value(name)",
137
+ ],
138
+ importId: `projects/${projectId}/managedZones/${name}`,
139
+ };
140
+ },
141
+ },
142
+ "gcp:dns/recordSet:RecordSet": {
143
+ label: "DNS record",
144
+ target(inputs, projectId) {
145
+ const { managedZone: zone, name, type } = inputs;
146
+ if (typeof zone !== "string" || !/^[a-z][a-z0-9-]{0,62}$/.test(zone)) return null;
147
+ if (typeof name !== "string" || !/^[a-z0-9.-]{1,253}\.$/.test(name)) return null;
148
+ if (typeof type !== "string" || !/^[A-Z]{1,10}$/.test(type)) return null;
149
+ if ((inputs.project ?? projectId) !== projectId) return null;
150
+ return {
151
+ display: `${name} ${type}`,
152
+ describeArgs: [
153
+ "dns",
154
+ "record-sets",
155
+ "describe",
156
+ name,
157
+ `--zone=${zone}`,
158
+ `--type=${type}`,
159
+ `--project=${projectId}`,
160
+ "--format=value(name)",
161
+ ],
162
+ importId: `projects/${projectId}/managedZones/${zone}/rrsets/${name}/${type}`,
163
+ };
164
+ },
165
+ },
166
+ // #1328 — the gcp-firebase data gateway: the stack declares it under a
167
+ // fixed name (stack-contract.ts fixedCloudRunServiceName), and the converge
168
+ // creates the same service on installs whose stack predates it. Only a
169
+ // service the stack names explicitly is a candidate: an auto-named one has
170
+ // no name until Pulumi picks it.
171
+ "gcp:cloudrunv2/service:Service": {
172
+ label: "Cloud Run service",
173
+ target(inputs, projectId) {
174
+ const name = inputs.name;
175
+ const location = inputs.location;
176
+ if (typeof name !== "string" || !/^[a-z][a-z0-9-]{0,47}[a-z0-9]$/.test(name)) return null;
177
+ if (typeof location !== "string" || !/^[a-z]+-[a-z]+\d+$/.test(location)) return null;
178
+ const project = inputs.project ?? projectId;
179
+ if (project !== projectId) return null;
180
+ return {
181
+ display: `${name} (${location})`,
182
+ describeArgs: [
183
+ "run",
184
+ "services",
185
+ "describe",
186
+ name,
187
+ `--region=${location}`,
188
+ `--project=${projectId}`,
189
+ "--format=value(metadata.name)",
190
+ ],
191
+ importId: `projects/${projectId}/locations/${location}/services/${name}`,
192
+ };
193
+ },
194
+ },
195
+ // #1329 — the staging database and its login: created by the converge on
196
+ // an install whose stack predates them (staging-database.ts), declared by
197
+ // the stack with literal names. Only those two names are candidates.
198
+ "gcp:sql/database:Database": {
199
+ label: "Cloud SQL database",
200
+ target(inputs, projectId) {
201
+ const { instance, name } = inputs;
202
+ if (name !== CONVERGE_CREATED_SQL.database) return null;
203
+ if (typeof instance !== "string" || !/^[a-z][a-z0-9-]{0,97}$/.test(instance)) return null;
204
+ if ((inputs.project ?? projectId) !== projectId) return null;
205
+ return {
206
+ display: `${name} (${instance})`,
207
+ describeArgs: [
208
+ "sql",
209
+ "databases",
210
+ "describe",
211
+ name,
212
+ `--instance=${instance}`,
213
+ `--project=${projectId}`,
214
+ "--format=value(name)",
215
+ ],
216
+ importId: `projects/${projectId}/instances/${instance}/databases/${name}`,
217
+ };
218
+ },
219
+ },
220
+ "gcp:sql/user:User": {
221
+ label: "Cloud SQL user",
222
+ target(inputs, projectId) {
223
+ const { instance, name } = inputs;
224
+ if (name !== CONVERGE_CREATED_SQL.user) return null;
225
+ if (typeof instance !== "string" || !/^[a-z][a-z0-9-]{0,97}$/.test(instance)) return null;
226
+ if ((inputs.project ?? projectId) !== projectId) return null;
227
+ return {
228
+ display: `${name} (${instance})`,
229
+ describeArgs: [
230
+ "sql",
231
+ "users",
232
+ "describe",
233
+ name,
234
+ `--instance=${instance}`,
235
+ `--project=${projectId}`,
236
+ "--format=value(name)",
237
+ ],
238
+ // The provider's import id for a Postgres built-in user.
239
+ importId: `${projectId}/${instance}/${name}`,
240
+ };
241
+ },
242
+ },
105
243
  };
106
244
 
107
245
  /** A planned create the installer may adopt instead. */
@@ -8,7 +8,12 @@
8
8
 
9
9
  import { describe, expect, it } from "bun:test";
10
10
  import type { CommandResult, HostRunner } from "../self-hosted-release.js";
11
- import { applyDatabaseSide, POSTGRES_RESTART_NOTICE } from "./command.js";
11
+ import {
12
+ applyArgv,
13
+ applyDatabaseSide,
14
+ POSTGRES_RESTART_NOTICE,
15
+ parseApplyArgs,
16
+ } from "./command.js";
12
17
 
13
18
  function runner(published: string | null): HostRunner & { calls: string[] } {
14
19
  const calls: string[] = [];
@@ -75,3 +80,46 @@ describe("applyDatabaseSide", () => {
75
80
  expect(r.calls).toEqual([]);
76
81
  });
77
82
  });
83
+
84
+ describe("agent apply argv (#1328)", () => {
85
+ const D = (c: string) => `sha256:${c.repeat(64)}`;
86
+ const request = {
87
+ requestId: "11111111-1111-4111-8111-111111111111",
88
+ targetVersion: "0.12.0",
89
+ trigger: "manual" as const,
90
+ cliPath: "/srv/.caelo/agent/cli/caelo-0.12.0/node_modules/@caelo-cms/provisioning/dist/cli.js",
91
+ };
92
+
93
+ it("passes the MCP-Auth-Proxy digest when the manifest names one, and the apply reads it back", () => {
94
+ const argv = applyArgv("/usr/bin/bun", {
95
+ ...request,
96
+ digests: { admin: D("a"), gateway: D("b"), "mcp-auth-proxy": D("d") },
97
+ });
98
+ expect(argv.slice(argv.indexOf("--mcp-auth-proxy-digest"))).toEqual([
99
+ "--mcp-auth-proxy-digest",
100
+ D("d"),
101
+ ]);
102
+ const parsed = parseApplyArgs(argv);
103
+ expect(parsed.success && parsed.data.mcpAuthProxyDigest).toBe(D("d"));
104
+ });
105
+
106
+ it("an agent of a release from before the proxy starts the apply without it", () => {
107
+ const argv = applyArgv("/usr/bin/bun", {
108
+ ...request,
109
+ digests: { admin: D("a"), gateway: D("b") },
110
+ });
111
+ expect(argv).not.toContain("--mcp-auth-proxy-digest");
112
+ const parsed = parseApplyArgs(argv);
113
+ expect(parsed.success).toBe(true);
114
+ expect(parsed.success && parsed.data.mcpAuthProxyDigest).toBeUndefined();
115
+ });
116
+
117
+ it("refuses a malformed proxy digest", () => {
118
+ const argv = [
119
+ ...applyArgv("/usr/bin/bun", { ...request, digests: { admin: D("a"), gateway: D("b") } }),
120
+ "--mcp-auth-proxy-digest",
121
+ "latest",
122
+ ];
123
+ expect(parseApplyArgs(argv).success).toBe(false);
124
+ });
125
+ });
@@ -57,6 +57,11 @@ export interface AgentCliContext {
57
57
  emitConfig(): void;
58
58
  /** The role convergence as the superuser, including the agent's login. */
59
59
  convergeRoles(): Promise<ConvergeOutcome>;
60
+ /**
61
+ * #1338 / #1329 — as the superuser, before an upgrade's migrations: the
62
+ * staging database with its gateway login, and both gateway logins' limits.
63
+ */
64
+ applyPreMigrationRoles(): Promise<ConvergeOutcome>;
60
65
  /** The Compose file for `images` from config.json. */
61
66
  renderCompose(images: ReleaseImages): string;
62
67
  /** Re-write the Caddyfile (config + domains table) and reload Caddy (#891). */
@@ -417,12 +422,15 @@ function pruneVerifiedClis(caeloDir: string, keep: readonly (string | undefined)
417
422
  }
418
423
  }
419
424
 
420
- /** Start the target release's `agent apply` and wait for it. */
421
- async function spawnApply(request: AgentApplyRequest): Promise<AgentApplyOutcome> {
422
- // The verified install (agent/cli-provenance.ts) — never `bun x`, which
423
- // would fetch an unverified copy.
424
- const argv = [
425
- process.execPath,
425
+ /**
426
+ * The argv that starts the target release's `agent apply` with `bun` at
427
+ * `execPath`: the verified install (agent/cli-provenance.ts) — never
428
+ * `bun x`, which would fetch an unverified copy — and the digests the signed
429
+ * manifest names, the MCP-Auth-Proxy's only when it names one (#1328).
430
+ */
431
+ export function applyArgv(execPath: string, request: AgentApplyRequest): string[] {
432
+ return [
433
+ execPath,
426
434
  request.cliPath,
427
435
  "agent",
428
436
  "apply",
@@ -436,7 +444,15 @@ async function spawnApply(request: AgentApplyRequest): Promise<AgentApplyOutcome
436
444
  request.digests.admin,
437
445
  "--gateway-digest",
438
446
  request.digests.gateway,
447
+ ...(request.digests["mcp-auth-proxy"]
448
+ ? ["--mcp-auth-proxy-digest", request.digests["mcp-auth-proxy"]]
449
+ : []),
439
450
  ];
451
+ }
452
+
453
+ /** Start the target release's `agent apply` and wait for it. */
454
+ async function spawnApply(request: AgentApplyRequest): Promise<AgentApplyOutcome> {
455
+ const argv = applyArgv(process.execPath, request);
440
456
  try {
441
457
  const proc = Bun.spawn(argv, {
442
458
  stdout: "pipe",
@@ -504,16 +520,25 @@ const applyArgs = z.object({
504
520
  trigger: z.enum(PLATFORM_UPDATE_TRIGGERS),
505
521
  adminDigest: digest,
506
522
  gatewayDigest: digest,
523
+ // #1328 — an agent of a release before the MCP-Auth-Proxy starts this
524
+ // without it; the signed manifest the apply reads names it anyway.
525
+ mcpAuthProxyDigest: digest.optional(),
507
526
  });
508
527
 
509
- async function agentApply(argv: readonly string[], ctx: AgentCliContext): Promise<number> {
510
- const parsed = applyArgs.safeParse({
528
+ /** The `agent apply` flags {@link applyArgv} writes, validated. */
529
+ export function parseApplyArgs(argv: readonly string[]) {
530
+ return applyArgs.safeParse({
511
531
  request: flag(argv, "request"),
512
532
  version: flag(argv, "version"),
513
533
  trigger: flag(argv, "trigger"),
514
534
  adminDigest: flag(argv, "admin-digest"),
515
535
  gatewayDigest: flag(argv, "gateway-digest"),
536
+ mcpAuthProxyDigest: flag(argv, "mcp-auth-proxy-digest"),
516
537
  });
538
+ }
539
+
540
+ async function agentApply(argv: readonly string[], ctx: AgentCliContext): Promise<number> {
541
+ const parsed = parseApplyArgs(argv);
517
542
  if (!parsed.success) {
518
543
  console.error(
519
544
  `agent apply is started by the update agent, not by hand (${parsed.error.issues.map((i) => i.path.join(".")).join(", ")}).`,
@@ -539,13 +564,18 @@ async function agentApply(argv: readonly string[], ctx: AgentCliContext): Promis
539
564
  upgradeSelfHosted(
540
565
  {
541
566
  version: args.version,
542
- expectedDigests: { admin: args.adminDigest, gateway: args.gatewayDigest },
567
+ expectedDigests: {
568
+ admin: args.adminDigest,
569
+ gateway: args.gatewayDigest,
570
+ ...(args.mcpAuthProxyDigest ? { "mcp-auth-proxy": args.mcpAuthProxyDigest } : {}),
571
+ },
543
572
  },
544
573
  {
545
574
  caeloDir: ctx.caeloDir,
546
575
  runner: bunHostRunner,
547
576
  renderCompose: (images) => ctx.renderCompose(images),
548
577
  convergeRoles: () => ctx.convergeRoles(),
578
+ applyPreMigrationRoles: () => ctx.applyPreMigrationRoles(),
549
579
  refreshCaddy: () => ctx.refreshCaddy(),
550
580
  log,
551
581
  },
@@ -198,6 +198,25 @@ describe("runAgentOnce", () => {
198
198
  expect(locks).toEqual({ taken: 1, released: 1 });
199
199
  });
200
200
 
201
+ it("#1328: hands over the MCP-Auth-Proxy digest the signed manifest names", async () => {
202
+ const withProxy: ReleaseManifest = {
203
+ ...MANIFEST,
204
+ digests: { ...MANIFEST.digests, "mcp-auth-proxy": D("d") },
205
+ };
206
+ const { deps, applied } = setup({
207
+ fetchManifest: async (): Promise<ReleaseManifestResult> => ({
208
+ ok: true,
209
+ manifest: withProxy,
210
+ }),
211
+ });
212
+ await runAgentOnce(deps);
213
+ expect(applied[0]?.digests).toEqual({
214
+ admin: D("a"),
215
+ gateway: D("b"),
216
+ "mcp-auth-proxy": D("d"),
217
+ });
218
+ });
219
+
201
220
  interface RefusalRow {
202
221
  readonly name: string;
203
222
  readonly over: Partial<AgentRunDeps>;
package/src/agent/run.ts CHANGED
@@ -48,7 +48,12 @@ export interface AgentApplyRequest {
48
48
  readonly targetVersion: string;
49
49
  readonly trigger: PlatformUpdateTrigger;
50
50
  /** From the verified manifest: the images the target must resolve to. */
51
- readonly digests: { readonly admin: string; readonly gateway: string };
51
+ readonly digests: {
52
+ readonly admin: string;
53
+ readonly gateway: string;
54
+ /** #1328 — absent in manifests of releases from before the MCP-Auth-Proxy. */
55
+ readonly "mcp-auth-proxy"?: string;
56
+ };
52
57
  /** The target's CLI, installed and provenance-verified (agent/cli-provenance.ts). */
53
58
  readonly cliPath: string;
54
59
  }
@@ -254,7 +259,11 @@ export async function runAgentOnce(deps: AgentRunDeps): Promise<AgentRunResult>
254
259
  requestId: request.id,
255
260
  targetVersion: m.version,
256
261
  trigger: request.trigger,
257
- digests: { admin: m.digests.admin, gateway: m.digests.gateway },
262
+ digests: {
263
+ admin: m.digests.admin,
264
+ gateway: m.digests.gateway,
265
+ ...(m.digests["mcp-auth-proxy"] ? { "mcp-auth-proxy": m.digests["mcp-auth-proxy"] } : {}),
266
+ },
258
267
  cliPath: cli.cliPath,
259
268
  });
260
269
  } finally {
@@ -20,12 +20,14 @@ export type AwsSecret =
20
20
  | "resend-api-key";
21
21
 
22
22
  /** A service with its own ECS task role. */
23
- export type AwsTaskService = "admin" | "gateway";
23
+ export type AwsTaskService = "admin" | "gateway" | "mcp-auth-proxy";
24
24
 
25
25
  /**
26
26
  * The secrets each service's task role may read. The admin side (admin and
27
27
  * its in-process workers) reads all of them; the gateway reads only
28
- * public_role's password — its one database login.
28
+ * public_role's password — its one database login; the MCP-Auth-Proxy
29
+ * (#1328) reads none: remote MCP is not available on AWS, so it holds
30
+ * neither a database login nor the broker's keys.
29
31
  */
30
32
  export const AWS_SERVICE_SECRETS: Readonly<Record<AwsTaskService, readonly AwsSecret[]>> = {
31
33
  admin: [
@@ -37,6 +39,7 @@ export const AWS_SERVICE_SECRETS: Readonly<Record<AwsTaskService, readonly AwsSe
37
39
  "resend-api-key",
38
40
  ],
39
41
  gateway: ["public-role-password"],
42
+ "mcp-auth-proxy": [],
40
43
  };
41
44
 
42
45
  /**
@@ -31,6 +31,7 @@ import {
31
31
  CADDY_IMAGE,
32
32
  COMPOSE_ADMIN_UPSTREAM,
33
33
  COMPOSE_GATEWAY_UPSTREAM,
34
+ COMPOSE_MCP_AUTH_PROXY_UPSTREAM,
34
35
  COMPOSE_REDIRECT_SNIPPETS,
35
36
  SITE_OUTPUT_ROOT,
36
37
  } from "./compose.js";
@@ -149,6 +150,7 @@ describe.skipIf(!dockerUp)(`self-hosted redirects through ${CADDY_IMAGE}`, () =>
149
150
  stagingSiteRoot: `${SITE_OUTPUT_ROOT}/output/staging/current`,
150
151
  adminUpstream: COMPOSE_ADMIN_UPSTREAM,
151
152
  gatewayUpstream: COMPOSE_GATEWAY_UPSTREAM,
153
+ mcpAuthProxyUpstream: COMPOSE_MCP_AUTH_PROXY_UPSTREAM,
152
154
  domains: selfHostedSeedDomains(LIVE, STAGING),
153
155
  }),
154
156
  );
@@ -15,6 +15,7 @@ import { caddyRedirectReloadCommand, caddyRedirectReloadScript } from "./caddy-r
15
15
  import {
16
16
  COMPOSE_ADMIN_UPSTREAM,
17
17
  COMPOSE_GATEWAY_UPSTREAM,
18
+ COMPOSE_MCP_AUTH_PROXY_UPSTREAM,
18
19
  COMPOSE_REDIRECT_SNIPPETS,
19
20
  generateDockerCompose,
20
21
  } from "./compose.js";
@@ -26,6 +27,7 @@ const caddyfile = generateCaddyfile({
26
27
  stagingSiteRoot: "/srv/caelo/output/staging/current",
27
28
  adminUpstream: COMPOSE_ADMIN_UPSTREAM,
28
29
  gatewayUpstream: COMPOSE_GATEWAY_UPSTREAM,
30
+ mcpAuthProxyUpstream: COMPOSE_MCP_AUTH_PROXY_UPSTREAM,
29
31
  domains: selfHostedSeedDomains("example.com", "staging.example.com"),
30
32
  });
31
33
 
@@ -61,8 +63,9 @@ describe("Compose — Caddy picks up new redirects (#587)", () => {
61
63
  const yaml = generateDockerCompose({
62
64
  domain: "example.com",
63
65
  postgresPassword: "pgpw",
64
- rolePasswords: { admin: "a", public: "p" },
66
+ rolePasswords: { admin: "a", public: "p", publicStaging: "ps" },
65
67
  caeloSecretKek: "0".repeat(64),
68
+ dbTlsServerCertSha256: "abababababababababababababababababababababababababababababababab",
66
69
  internalSecret: "i".repeat(48),
67
70
  diskSize: "20Gi",
68
71
  ports: { http: 80, https: 443 },
@@ -16,7 +16,12 @@ import { describe, expect, it } from "bun:test";
16
16
  import { readFileSync } from "node:fs";
17
17
  import { join } from "node:path";
18
18
  import { type CaddyfileSpec, generateCaddyfile, selfHostedSeedDomains } from "./caddy.js";
19
- import { CADDY_IMAGE, COMPOSE_ADMIN_UPSTREAM, COMPOSE_GATEWAY_UPSTREAM } from "./compose.js";
19
+ import {
20
+ CADDY_IMAGE,
21
+ COMPOSE_ADMIN_UPSTREAM,
22
+ COMPOSE_GATEWAY_UPSTREAM,
23
+ COMPOSE_MCP_AUTH_PROXY_UPSTREAM,
24
+ } from "./compose.js";
20
25
 
21
26
  const REQUIRED = process.env.CAELO_CADDY_VALIDATE === "required";
22
27
 
@@ -62,6 +67,7 @@ const base: CaddyfileSpec = {
62
67
  stagingSiteRoot: "/srv/caelo/output/staging/current",
63
68
  adminUpstream: COMPOSE_ADMIN_UPSTREAM,
64
69
  gatewayUpstream: COMPOSE_GATEWAY_UPSTREAM,
70
+ mcpAuthProxyUpstream: COMPOSE_MCP_AUTH_PROXY_UPSTREAM,
65
71
  domains: selfHostedSeedDomains("example.com", "staging.example.com"),
66
72
  };
67
73
 
package/src/caddy.test.ts CHANGED
@@ -6,9 +6,14 @@ import {
6
6
  caddyTokenProblem,
7
7
  generateCaddyfile,
8
8
  selfHostedAdminHost,
9
+ selfHostedMcpHost,
9
10
  selfHostedSeedDomains,
10
11
  } from "./caddy.js";
11
- import { COMPOSE_ADMIN_UPSTREAM, COMPOSE_GATEWAY_UPSTREAM } from "./compose.js";
12
+ import {
13
+ COMPOSE_ADMIN_UPSTREAM,
14
+ COMPOSE_GATEWAY_UPSTREAM,
15
+ COMPOSE_MCP_AUTH_PROXY_UPSTREAM,
16
+ } from "./compose.js";
12
17
 
13
18
  /** The inputs `cms-provision init` passes for example.com. */
14
19
  const spec: CaddyfileSpec = {
@@ -18,6 +23,7 @@ const spec: CaddyfileSpec = {
18
23
  stagingSiteRoot: "/srv/caelo/output/staging/current",
19
24
  adminUpstream: COMPOSE_ADMIN_UPSTREAM,
20
25
  gatewayUpstream: COMPOSE_GATEWAY_UPSTREAM,
26
+ mcpAuthProxyUpstream: COMPOSE_MCP_AUTH_PROXY_UPSTREAM,
21
27
  domains: selfHostedSeedDomains("example.com", "staging.example.com"),
22
28
  };
23
29
 
@@ -35,7 +41,39 @@ describe("self-hosted seed vhosts", () => {
35
41
  { hostname: "example.com", kind: "public", env: "production" },
36
42
  { hostname: "staging.example.com", kind: "public", env: "staging" },
37
43
  ]);
44
+ // #1328 — no MCP vhost until remote MCP exists on self-hosted: Caddy would
45
+ // otherwise chase an ACME certificate for a host without a DNS record.
46
+ expect(
47
+ selfHostedSeedDomains("example.com", "staging.example.com").some((d) => d.kind === "mcp"),
48
+ ).toBe(false);
38
49
  expect(selfHostedAdminHost("example.com")).toBe("admin.example.com");
50
+ expect(selfHostedMcpHost("example.com")).toBe("mcp.example.com");
51
+ });
52
+
53
+ // #1328 — the MCP-Auth-Proxy never shares an origin with the site's module JS.
54
+ it("serves the MCP-Auth-Proxy on its own vhost, nothing else of the site", () => {
55
+ const out = generateCaddyfile({
56
+ ...spec,
57
+ domains: [
58
+ ...selfHostedSeedDomains("example.com", "staging.example.com"),
59
+ { hostname: selfHostedMcpHost("example.com"), kind: "mcp", env: "production" },
60
+ ],
61
+ });
62
+ const block = out.slice(out.indexOf("mcp.example.com {"));
63
+ const mcp = block.slice(0, block.indexOf("\n}\n") + 3);
64
+ expect(mcp).toContain(`reverse_proxy ${COMPOSE_MCP_AUTH_PROXY_UPSTREAM}`);
65
+ expect(mcp).toContain('X-Robots-Tag "noindex"');
66
+ expect(mcp).not.toContain("file_server");
67
+ expect(mcp).not.toContain(COMPOSE_GATEWAY_UPSTREAM);
68
+ // ... and the site's vhost does not route the proxy.
69
+ const site = out.slice(out.indexOf("\nexample.com {"));
70
+ expect(site.slice(0, site.indexOf("\n}\n"))).not.toContain(COMPOSE_MCP_AUTH_PROXY_UPSTREAM);
71
+ });
72
+
73
+ it("refuses an MCP-Auth-Proxy upstream that would break out of its token", () => {
74
+ expect(() => generateCaddyfile({ ...spec, mcpAuthProxyUpstream: "x }\nevil {" })).toThrow(
75
+ /mcpAuthProxyUpstream/,
76
+ );
39
77
  });
40
78
 
41
79
  it("refuses two vhosts for one host — Caddy would reject the whole file", () => {
package/src/caddy.ts CHANGED
@@ -11,6 +11,8 @@
11
11
  * Per-vhost shape:
12
12
  * <hostname> {
13
13
  * # admin → reverse_proxy <adminUpstream>
14
+ * # mcp → reverse_proxy <mcpAuthProxyUpstream> (#1328: the
15
+ * # MCP-Auth-Proxy on its own host, never the site's origin)
14
16
  * # public → /api/* → <gatewayUpstream>; everything else is the
15
17
  * # static site (try_files + file_server, 404 → /404.html)
16
18
  * }
@@ -68,7 +70,12 @@ import {
68
70
 
69
71
  export interface CaddyDomainSpec {
70
72
  readonly hostname: string;
71
- readonly kind: "admin" | "public";
73
+ /**
74
+ * `mcp` (#1328) is the MCP-Auth-Proxy's own host: remote MCP's sign-in
75
+ * pages and its forwarder must never share an origin with the site, whose
76
+ * module JS could otherwise read them same-origin or toss their cookies.
77
+ */
78
+ readonly kind: "admin" | "public" | "mcp";
72
79
  readonly env: "production" | "staging";
73
80
  }
74
81
 
@@ -89,6 +96,8 @@ export interface CaddyfileSpec {
89
96
  readonly adminUpstream: string;
90
97
  /** `host:port` of the API gateway as Caddy reaches it (Compose: `caelo-gateway:8090`). */
91
98
  readonly gatewayUpstream: string;
99
+ /** `host:port` of the MCP-Auth-Proxy as Caddy reaches it (Compose: `caelo-mcp-auth-proxy:8095`). */
100
+ readonly mcpAuthProxyUpstream: string;
92
101
  readonly domains: ReadonlyArray<CaddyDomainSpec>;
93
102
  }
94
103
 
@@ -97,12 +106,25 @@ export function selfHostedAdminHost(domain: string): string {
97
106
  return `admin.${domain}`;
98
107
  }
99
108
 
109
+ /** #1328 — the host the MCP-Auth-Proxy of a self-hosted install answers on. */
110
+ export function selfHostedMcpHost(domain: string): string {
111
+ return `mcp.${domain}`;
112
+ }
113
+
100
114
  /**
101
115
  * The vhosts every self-hosted install serves before an Owner adds domains
102
116
  * in the admin: the admin on `admin.<domain>`, the live site on `<domain>`,
103
- * the staging site on its own host. The admin needs its own host — it
104
- * routes from `/` (`/setup`, `/login`, …), so it cannot share one with the
105
- * public site, and Caddy refuses two site blocks for the same address.
117
+ * the staging site on its own host. The admin needs its own host — it routes
118
+ * from `/` (`/setup`, `/login`, …), so it cannot share one with the public
119
+ * site, and Caddy refuses two site blocks for the same address.
120
+ *
121
+ * #1328 — the MCP-Auth-Proxy's vhost (`mcp.<domain>`, kind `mcp`) is not
122
+ * seeded: remote MCP needs an identity proxy in front of the admin (§11.B),
123
+ * which a self-hosted install does not have yet, so the proxy runs
124
+ * unexposed on the Compose network (E14 a: provisioned, fixed 404) — the
125
+ * same honest refusal as on aws and azure. Seeding it would make Caddy
126
+ * fetch and retry an ACME certificate for a host nobody has a DNS record
127
+ * for. The vhost comes with remote MCP on self-hosted.
106
128
  */
107
129
  export function selfHostedSeedDomains(domain: string, stagingDomain: string): CaddyDomainSpec[] {
108
130
  return [
@@ -154,6 +176,7 @@ export function generateCaddyfile(spec: CaddyfileSpec): string {
154
176
  assertSafeCaddyToken("stagingSiteRoot", spec.stagingSiteRoot, "token");
155
177
  assertSafeCaddyToken("adminUpstream", spec.adminUpstream, "token");
156
178
  assertSafeCaddyToken("gatewayUpstream", spec.gatewayUpstream, "token");
179
+ assertSafeCaddyToken("mcpAuthProxyUpstream", spec.mcpAuthProxyUpstream, "token");
157
180
 
158
181
  const hostnames = new Set<string>();
159
182
  for (const d of spec.domains) {
@@ -200,6 +223,15 @@ function vhost(d: CaddyDomainSpec, spec: CaddyfileSpec): string {
200
223
  return `${d.hostname} {${noindex}
201
224
  reverse_proxy ${spec.adminUpstream}
202
225
  }
226
+ `;
227
+ }
228
+ if (d.kind === "mcp") {
229
+ // Everything on this host goes to the proxy: it answers its own 404s.
230
+ return `${d.hostname} {
231
+ header X-Robots-Tag "noindex"
232
+ header X-Content-Type-Options "${NOSNIFF}"
233
+ reverse_proxy ${spec.mcpAuthProxyUpstream}
234
+ }
203
235
  `;
204
236
  }
205
237
  // public — API routes go to the gateway, the rest is the static site.