@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
@@ -6,7 +6,7 @@ import type { HttpFetch } from "../runtime-secrets.js";
6
6
  import {
7
7
  createIapHealthProbe,
8
8
  evaluateAdminDeepHealth,
9
- evaluateGatewayDeepHealth,
9
+ evaluatePublicServiceDeepHealth,
10
10
  type HealthTarget,
11
11
  healthOrigins,
12
12
  IAM_PROPAGATION_WINDOW_MS,
@@ -72,27 +72,29 @@ describe("evaluateAdminDeepHealth", () => {
72
72
  });
73
73
  });
74
74
 
75
- describe("evaluateGatewayDeepHealth", () => {
76
- it("accepts a healthy gateway on the admin's version", () => {
77
- expect(
78
- evaluateGatewayDeepHealth(200, JSON.stringify({ ok: true, version: "0.11.0" }), "0.11.0"),
79
- ).toEqual({ ok: true, detail: "gateway 0.11.0" });
75
+ describe("evaluatePublicServiceDeepHealth", () => {
76
+ it("accepts a healthy gateway; its version is not public any more (#1326)", () => {
77
+ expect(evaluatePublicServiceDeepHealth("gateway", 200, JSON.stringify({ ok: true }))).toEqual({
78
+ ok: true,
79
+ detail: "gateway healthy",
80
+ });
80
81
  });
81
82
 
82
- it("refuses a gateway on another version than the admin", () => {
83
- const r = evaluateGatewayDeepHealth(
84
- 200,
85
- JSON.stringify({ ok: true, version: "0.10.0" }),
86
- "0.11.0",
87
- );
88
- expect(r.ok).toBe(false);
83
+ it("ignores a version an older gateway still reports", () => {
84
+ expect(
85
+ evaluatePublicServiceDeepHealth(
86
+ "gateway",
87
+ 200,
88
+ JSON.stringify({ ok: true, version: "0.10.0" }),
89
+ ).ok,
90
+ ).toBe(true);
89
91
  });
90
92
 
91
93
  it("refuses a gateway that can't reach its database", () => {
92
- const r = evaluateGatewayDeepHealth(
94
+ const r = evaluatePublicServiceDeepHealth(
95
+ "gateway",
93
96
  503,
94
- JSON.stringify({ ok: false, version: "0.11.0", error: { kind: "DbUnreachable" } }),
95
- "0.11.0",
97
+ JSON.stringify({ ok: false, error: { kind: "DbUnreachable" } }),
96
98
  );
97
99
  expect(r).toEqual({ ok: false, error: "gateway reports unhealthy (HTTP 503, DbUnreachable)" });
98
100
  });
@@ -103,7 +105,8 @@ const FIREBASE: HealthTarget = {
103
105
  projectId: "acme",
104
106
  domain: "acme.com",
105
107
  adminServiceUrl: "https://caelo-production-admin-aaa-xyz.a.run.app",
106
- gatewayServiceUrl: "https://caelo-production-gateway-bbb-xyz.a.run.app",
108
+ gatewayServiceUrl: "https://caelo-production-data-gateway-ddd-xyz.a.run.app",
109
+ mcpAuthProxyServiceUrl: "https://caelo-production-gateway-bbb-xyz.a.run.app",
107
110
  };
108
111
 
109
112
  describe("healthOrigins", () => {
@@ -111,13 +114,15 @@ describe("healthOrigins", () => {
111
114
  expect(healthOrigins({ ...FIREBASE, provider: "gcp" })).toEqual({
112
115
  admin: "https://admin.acme.com",
113
116
  gateway: "https://acme.com",
117
+ mcpAuthProxy: "https://acme.com",
114
118
  });
115
119
  });
116
120
 
117
121
  it("uses the run.app URLs on gcp-firebase", () => {
118
122
  expect(healthOrigins(FIREBASE)).toEqual({
119
123
  admin: "https://caelo-production-admin-aaa-xyz.a.run.app",
120
- gateway: "https://caelo-production-gateway-bbb-xyz.a.run.app",
124
+ gateway: "https://caelo-production-data-gateway-ddd-xyz.a.run.app",
125
+ mcpAuthProxy: "https://caelo-production-gateway-bbb-xyz.a.run.app",
121
126
  });
122
127
  });
123
128
  });
@@ -130,8 +135,12 @@ describe("createIapHealthProbe", () => {
130
135
  exitCode: 0,
131
136
  });
132
137
 
133
- /** A fake network: signJwt + the two health endpoints. */
134
- function network(answers: { admin: () => [number, string]; gateway: () => [number, string] }) {
138
+ /** A fake network: signJwt + the three health endpoints (the proxy answers healthy unless given). */
139
+ function network(answers: {
140
+ admin: () => [number, string];
141
+ gateway: () => [number, string];
142
+ proxy?: () => [number, string];
143
+ }) {
135
144
  const requests: { url: string; headers: Record<string, string>; body?: string }[] = [];
136
145
  const http: HttpFetch = async (url, init) => {
137
146
  requests.push({ url, headers: init.headers, ...(init.body ? { body: init.body } : {}) });
@@ -139,7 +148,9 @@ describe("createIapHealthProbe", () => {
139
148
  ? [200, JSON.stringify({ signedJwt: "signed.jwt" })]
140
149
  : url.includes("admin")
141
150
  ? answers.admin()
142
- : answers.gateway();
151
+ : url.endsWith("/api/mcp/health")
152
+ ? (answers.proxy ?? (() => [200, JSON.stringify({ ok: true, version: "0.11.0" })]))()
153
+ : answers.gateway();
143
154
  return { ok: status < 400, status, text: async () => text };
144
155
  };
145
156
  return { http, requests };
@@ -148,7 +159,7 @@ describe("createIapHealthProbe", () => {
148
159
  it("signs an IAP credential for the admin origin and sends it to the admin only", async () => {
149
160
  const net = network({
150
161
  admin: () => [200, adminBody()],
151
- gateway: () => [200, JSON.stringify({ ok: true, version: "0.11.0" })],
162
+ gateway: () => [200, JSON.stringify({ ok: true })],
152
163
  });
153
164
  const probe = createIapHealthProbe(FIREBASE, {
154
165
  run: gcloudOk,
@@ -160,7 +171,7 @@ describe("createIapHealthProbe", () => {
160
171
  expect(await probe.deep({ version: "0.11.0" })).toEqual({
161
172
  ok: true,
162
173
  detail:
163
- "admin 0.11.0, migrations at 0262_tool_approval_branch.sql / 0040_x.sql; gateway 0.11.0",
174
+ "admin 0.11.0, migrations at 0262_tool_approval_branch.sql / 0040_x.sql; gateway healthy; MCP-Auth-Proxy healthy",
164
175
  });
165
176
  const sign = net.requests[0];
166
177
  expect(sign?.url).toBe(
@@ -180,9 +191,36 @@ describe("createIapHealthProbe", () => {
180
191
  expect(admin?.headers.Authorization).toBe("Bearer signed.jwt");
181
192
  const gateway = net.requests.find((r) => r.url.endsWith("/api/_caelo/health?deep=1"));
182
193
  expect(gateway?.url).toBe(
183
- "https://caelo-production-gateway-bbb-xyz.a.run.app/api/_caelo/health?deep=1",
194
+ "https://caelo-production-data-gateway-ddd-xyz.a.run.app/api/_caelo/health?deep=1",
184
195
  );
185
196
  expect(gateway?.headers.Authorization).toBeUndefined();
197
+ // #1328 — the proxy, on the service that was the gateway, by version.
198
+ const proxy = net.requests.find((r) => r.url.endsWith("/api/mcp/health"));
199
+ expect(proxy?.url).toBe("https://caelo-production-gateway-bbb-xyz.a.run.app/api/mcp/health");
200
+ expect(proxy?.headers.Authorization).toBeUndefined();
201
+ });
202
+
203
+ it("#1328 — fails the deep check while the MCP-Auth-Proxy is unhealthy or does not answer", async () => {
204
+ const deepWith = (proxy: () => [number, string]) =>
205
+ createIapHealthProbe(FIREBASE, {
206
+ run: gcloudOk,
207
+ http: network({
208
+ admin: () => [200, adminBody()],
209
+ gateway: () => [200, JSON.stringify({ ok: true, version: "0.11.0" })],
210
+ proxy,
211
+ }).http,
212
+ sleep: async () => {},
213
+ accessJustChanged: false,
214
+ attempts: 1,
215
+ }).deep({ version: "0.11.0" });
216
+ expect(await deepWith(() => [503, JSON.stringify({ ok: false })])).toEqual({
217
+ ok: false,
218
+ error: "MCP-Auth-Proxy reports unhealthy (HTTP 503, no reason)",
219
+ });
220
+ expect(await deepWith(() => [404, "<html>Not Found</html>"])).toEqual({
221
+ ok: false,
222
+ error: "MCP-Auth-Proxy answered HTTP 404 without JSON",
223
+ });
186
224
  });
187
225
 
188
226
  it("retries while the new revisions warm up, then gives the last error", async () => {
@@ -194,7 +232,7 @@ describe("createIapHealthProbe", () => {
194
232
  ? [503, adminBody({ ok: false, migrations: migrations(1) })]
195
233
  : [200, adminBody()];
196
234
  },
197
- gateway: () => [200, JSON.stringify({ ok: true, version: "0.11.0" })],
235
+ gateway: () => [200, JSON.stringify({ ok: true })],
198
236
  });
199
237
  const probe = createIapHealthProbe(FIREBASE, {
200
238
  run: gcloudOk,
@@ -11,9 +11,12 @@
11
11
  * - admin `GET /_caelo/health?deep=1` → `{ ok, version, migrations }`:
12
12
  * both databases reachable as the expected roles, and every
13
13
  * migration the image ships recorded as applied.
14
- * - gateway `GET /api/_caelo/health?deep=1` → `{ ok, version }`: its
15
- * database logins work. Under `/api/` because that is the only
14
+ * - gateway `GET /api/_caelo/health?deep=1` → `{ ok }` (no version since
15
+ * #1326): its database login works. Under `/api/` because that is the only
16
16
  * path the gcp load balancer routes to the gateway.
17
+ * - MCP-Auth-Proxy `GET /api/mcp/health` → `{ ok }` (#1328, no version
18
+ * either): it has no database, so it answers once it runs; under
19
+ * `/api/mcp/`, the prefix the gcp load balancer routes to it.
17
20
  *
18
21
  * Both answer with no secrets and no raw database errors. The admin sits
19
22
  * behind IAP, so the probe signs an IAP credential as the install's MCP
@@ -134,29 +137,41 @@ export function evaluateAdminDeepHealth(
134
137
  };
135
138
  }
136
139
 
137
- /** Judge the gateway's deep health answer against the version the admin runs. */
138
- export function evaluateGatewayDeepHealth(
140
+ /**
141
+ * Judge a public service's deep health answer: the gateway's, or the
142
+ * MCP-Auth-Proxy's (#1328). Since #1326 the public services report no
143
+ * release version (a public version string names the flaws to try), so
144
+ * this proves only that the service answers healthy — for the gateway,
145
+ * that its database login works. Which release it runs the roll itself
146
+ * establishes: `update --image` with the pinned digest succeeded and
147
+ * traffic moved to that revision (app-roll.ts), or the roll already failed.
148
+ *
149
+ * @param service how the errors name it (`gateway`, `MCP-Auth-Proxy`)
150
+ */
151
+ export function evaluatePublicServiceDeepHealth(
152
+ service: string,
139
153
  status: number,
140
154
  text: string,
141
- expectedVersion: string,
142
155
  ): HealthOutcome {
143
- const body = parseJson<{ ok?: boolean; version?: string; error?: { kind?: string } }>(text);
144
- if (!body) return { ok: false, error: `gateway answered HTTP ${status} without JSON` };
156
+ const body = parseJson<{ ok?: boolean; error?: { kind?: string } }>(text);
157
+ if (!body) return { ok: false, error: `${service} answered HTTP ${status} without JSON` };
145
158
  if (status !== 200 || body.ok !== true) {
146
159
  return {
147
160
  ok: false,
148
- error: `gateway reports unhealthy (HTTP ${status}, ${body.error?.kind ?? "no reason"})`,
161
+ error: `${service} reports unhealthy (HTTP ${status}, ${body.error?.kind ?? "no reason"})`,
149
162
  };
150
163
  }
151
- if (body.version !== expectedVersion) {
152
- return {
153
- ok: false,
154
- error: `gateway runs ${body.version ?? "an unknown version"}, the admin runs ${expectedVersion}`,
155
- };
156
- }
157
- return { ok: true, detail: `gateway ${body.version}` };
164
+ return { ok: true, detail: `${service} healthy` };
158
165
  }
159
166
 
167
+ /**
168
+ * The MCP-Auth-Proxy's health path (apps/mcp-auth-proxy server.ts). Under
169
+ * `/api/mcp/` because on gcp that is the prefix the load balancer routes to
170
+ * the proxy; it answers before any other routing, also while remote MCP is
171
+ * off.
172
+ */
173
+ export const MCP_AUTH_PROXY_HEALTH_PATH = "/api/mcp/health";
174
+
160
175
  /** Where the probe reaches the install. */
161
176
  export interface HealthTarget {
162
177
  readonly provider: "gcp" | "gcp-firebase";
@@ -166,20 +181,32 @@ export interface HealthTarget {
166
181
  readonly adminServiceUrl: string;
167
182
  /** The gateway service's `run.app` URL (public on gcp-firebase). */
168
183
  readonly gatewayServiceUrl: string;
184
+ /** The MCP-Auth-Proxy service's `run.app` URL (public on gcp-firebase). */
185
+ readonly mcpAuthProxyServiceUrl: string;
169
186
  }
170
187
 
171
188
  /**
172
189
  * The origins the probe calls. On gcp the admin's IAP lives on the load
173
- * balancer (`admin.<domain>`) and the gateway's ingress is load-balancer-only
174
- * (`<domain>/api/*`); on gcp-firebase both `run.app` URLs are reachable — the
175
- * admin's through Cloud Run's own IAP, the gateway's publicly.
190
+ * balancer (`admin.<domain>`), and the gateway's and the proxy's ingress is
191
+ * load-balancer-only (`<domain>/api/*`, `<domain>/api/mcp/*`); on
192
+ * gcp-firebase every `run.app` URL is reachable — the admin's through Cloud
193
+ * Run's own IAP, the others publicly.
176
194
  */
177
- export function healthOrigins(target: HealthTarget): { admin: string; gateway: string } {
195
+ export function healthOrigins(target: HealthTarget): {
196
+ admin: string;
197
+ gateway: string;
198
+ mcpAuthProxy: string;
199
+ } {
178
200
  return target.provider === "gcp"
179
- ? { admin: `https://admin.${target.domain}`, gateway: `https://${target.domain}` }
201
+ ? {
202
+ admin: `https://admin.${target.domain}`,
203
+ gateway: `https://${target.domain}`,
204
+ mcpAuthProxy: `https://${target.domain}`,
205
+ }
180
206
  : {
181
207
  admin: new URL(target.adminServiceUrl).origin,
182
208
  gateway: new URL(target.gatewayServiceUrl).origin,
209
+ mcpAuthProxy: new URL(target.mcpAuthProxyServiceUrl).origin,
183
210
  };
184
211
  }
185
212
 
@@ -437,7 +464,12 @@ export function createIapHealthProbe(target: HealthTarget, deps: HealthProbeDeps
437
464
  if (!a.ok) return a;
438
465
  const gateway = await get(`${origins.gateway}/api/_caelo/health?deep=1`, {});
439
466
  if (!gateway.reached) return { ok: false, error: `gateway unreachable: ${gateway.error}` };
440
- const g = evaluateGatewayDeepHealth(gateway.status, gateway.text, a.version);
441
- return g.ok ? { ok: true, detail: `${a.detail}; ${g.detail}` } : g;
467
+ const g = evaluatePublicServiceDeepHealth("gateway", gateway.status, gateway.text);
468
+ if (!g.ok) return g;
469
+ // #1328 — the proxy holds no database login: healthy once it answers.
470
+ const proxy = await get(`${origins.mcpAuthProxy}${MCP_AUTH_PROXY_HEALTH_PATH}`, {});
471
+ if (!proxy.reached) return { ok: false, error: `MCP-Auth-Proxy unreachable: ${proxy.error}` };
472
+ const p = evaluatePublicServiceDeepHealth("MCP-Auth-Proxy", proxy.status, proxy.text);
473
+ return p.ok ? { ok: true, detail: `${a.detail}; ${g.detail}; ${p.detail}` } : p;
442
474
  }
443
475
  }
@@ -184,6 +184,40 @@ describe("platformDigestsOf", () => {
184
184
  expect(calls).toEqual([]);
185
185
  });
186
186
 
187
+ it("#1328: carries the MCP-Auth-Proxy's signed form, and skips a proxy digest install.json lacks", async () => {
188
+ const calls: string[] = [];
189
+ const forms = createReleaseImageForms(fakeRegistryFetch(calls));
190
+ const m = {
191
+ version: "0.12.0",
192
+ digests: { admin: D("a"), gateway: D("b"), ops: D("c"), "mcp-auth-proxy": D("d") },
193
+ platformDigests: { admin: D("1"), gateway: D("2"), ops: D("3"), "mcp-auth-proxy": D("4") },
194
+ };
195
+ expect(
196
+ await platformDigestsOf(
197
+ { admin: D("a"), gateway: D("b"), "mcp-auth-proxy": D("d") },
198
+ forms,
199
+ m,
200
+ ),
201
+ ).toEqual({
202
+ ok: true,
203
+ platformDigests: { admin: D("1"), gateway: D("2"), "mcp-auth-proxy": D("4") },
204
+ });
205
+ // Recorded before the proxy existed: no proxy form, and nothing looked up for it.
206
+ expect(await platformDigestsOf({ admin: D("a"), gateway: D("b") }, forms, m)).toEqual({
207
+ ok: true,
208
+ platformDigests: { admin: D("1"), gateway: D("2") },
209
+ });
210
+ expect(calls).toEqual([]);
211
+ });
212
+
213
+ it("#1328: a release from before the proxy ships no proxy image form", async () => {
214
+ const forms = createReleaseImageForms(fakeRegistryFetch([]));
215
+ const m = { version: "0.11.0", digests: { admin: D("a"), gateway: D("b"), ops: D("c") } };
216
+ const r = await forms.of(m, "mcp-auth-proxy");
217
+ expect(r).toMatchObject({ ok: false, transient: false });
218
+ expect(r.ok ? "" : r.message).toContain("ships no mcp-auth-proxy image");
219
+ });
220
+
187
221
  it("says why when an index cannot be read", async () => {
188
222
  const forms = createReleaseImageForms(fakeRegistryFetch([], { status: 503 }));
189
223
  const r = await platformDigestsOf({ admin: ADMIN_INDEX, gateway: GATEWAY_INDEX }, forms);
@@ -30,7 +30,7 @@
30
30
  import { z } from "zod";
31
31
  import type { ImageDigests } from "../install-state.js";
32
32
  import type { ReleaseManifest } from "../release-manifest.js";
33
- import type { RollSlug } from "./app-roll.js";
33
+ import { ROLL_ORDER, type RollSlug } from "./app-roll.js";
34
34
  import { fetchRegistryManifest, IMAGE_DIGEST, RELEASE_REGISTRY } from "./release-images.js";
35
35
 
36
36
  /** The digests one release image is known by. */
@@ -178,12 +178,20 @@ export function createReleaseImageForms(fetchImpl: typeof fetch = fetch): Releas
178
178
  return {
179
179
  ofIndex,
180
180
  of: async (m, slug) => {
181
+ const index = m.digests[slug];
182
+ if (!index) {
183
+ // #1328 — a release from before the MCP-Auth-Proxy signs no image of it.
184
+ return {
185
+ ok: false,
186
+ transient: false,
187
+ message: `release ${m.version} ships no ${slug} image`,
188
+ };
189
+ }
181
190
  const signed = m.platformDigests?.[slug];
182
191
  if (signed) {
183
- const index = m.digests[slug];
184
192
  return { ok: true, digests: [index, ...(signed === index ? [] : [signed])], amd64: signed };
185
193
  }
186
- return ofIndex(slug, m.digests[slug]);
194
+ return ofIndex(slug, index);
187
195
  },
188
196
  };
189
197
  }
@@ -205,22 +213,29 @@ export async function platformDigestsOf(
205
213
  | { readonly ok: false; readonly message: string }
206
214
  > {
207
215
  const out: Partial<Record<RollSlug, string>> = {};
208
- for (const slug of ["admin", "gateway"] as const) {
216
+ for (const slug of ROLL_ORDER) {
217
+ const index = digests[slug];
218
+ // #1328 — digests recorded before the MCP-Auth-Proxy existed carry none.
219
+ if (!index) continue;
209
220
  const found =
210
- manifest && manifest.digests[slug] === digests[slug]
221
+ manifest && manifest.digests[slug] === index
211
222
  ? await forms.of(manifest, slug)
212
- : await forms.ofIndex(slug, digests[slug]);
223
+ : await forms.ofIndex(slug, index);
213
224
  if (!found.ok) return { ok: false, message: found.message };
214
225
  if (!found.amd64) {
215
226
  return {
216
227
  ok: false,
217
- message: `the ${slug} image index ${digests[slug]} lists no single linux/amd64 manifest`,
228
+ message: `the ${slug} image index ${index} lists no single linux/amd64 manifest`,
218
229
  };
219
230
  }
220
231
  out[slug] = found.amd64;
221
232
  }
222
233
  const { admin, gateway } = out;
234
+ const proxy = out["mcp-auth-proxy"];
223
235
  return admin && gateway
224
- ? { ok: true, platformDigests: { admin, gateway } }
236
+ ? {
237
+ ok: true,
238
+ platformDigests: { admin, gateway, ...(proxy ? { "mcp-auth-proxy": proxy } : {}) },
239
+ }
225
240
  : { ok: false, message: "the platform digests could not be read" };
226
241
  }
@@ -70,7 +70,11 @@ describe("resolveRegistryDigest", () => {
70
70
  const missing = fakeFetch(() => new Response(null, { status: 404, statusText: "Not Found" }));
71
71
  expect(
72
72
  await resolveRegistryDigest("ghcr.io", "caelo-cms/admin", "9.9.9", missing.impl),
73
- ).toEqual({ ok: false, reason: "HTTP 404 Not Found" });
73
+ ).toEqual({ ok: false, reason: "HTTP 404 Not Found", notFound: true });
74
+ // Only a 404 is the verdict "no such tag" (#1328); an outage is not.
75
+ const down = fakeFetch(() => new Response(null, { status: 503 }));
76
+ const outage = await resolveRegistryDigest("ghcr.io", "caelo-cms/admin", "9.9.9", down.impl);
77
+ expect(outage.ok === false && "notFound" in outage).toBe(false);
74
78
  const bad = fakeFetch(
75
79
  () => new Response(null, { status: 200, headers: { "docker-content-digest": "md5:x" } }),
76
80
  );
@@ -17,11 +17,15 @@ import { IMAGE_DIGEST } from "@caelo-cms/shared";
17
17
  import { confirm, isCancel, log, spinner } from "@clack/prompts";
18
18
  import { green, red } from "kleur/colors";
19
19
 
20
- /** The two Caelo services shipped as release images. */
21
- export type ReleaseService = "admin" | "gateway";
20
+ /** The Caelo services shipped as release images (#1328 added the MCP-Auth-Proxy). */
21
+ export type ReleaseService = "admin" | "gateway" | "mcp-auth-proxy";
22
22
 
23
- /** The services every install runs, in roll order (admin first, then gateway). */
24
- export const RELEASE_SERVICES: readonly ReleaseService[] = ["admin", "gateway"];
23
+ /**
24
+ * The services every install runs, in roll order: the admin first, then the
25
+ * gateway, then the MCP-Auth-Proxy (its own service since #1328, so a hole in
26
+ * a visitor handler never shares a process with the operators' tokens).
27
+ */
28
+ export const RELEASE_SERVICES: readonly ReleaseService[] = ["admin", "gateway", "mcp-auth-proxy"];
25
29
 
26
30
  /** GHCR — the canonical registry (release-images.yml), used by self-hosted Compose. */
27
31
  export const GHCR_REGISTRY = "ghcr.io";
@@ -116,7 +120,13 @@ export function releaseTag(opts: {
116
120
  return "latest";
117
121
  }
118
122
 
119
- export type DigestResult = { ok: true; digest: string } | { ok: false; reason: string };
123
+ /**
124
+ * `notFound`: the registry answered that the tag does not exist (HTTP 404)
125
+ * — a verdict, unlike an outage, a refused token or a malformed answer.
126
+ */
127
+ export type DigestResult =
128
+ | { ok: true; digest: string }
129
+ | { ok: false; reason: string; notFound?: true };
120
130
 
121
131
  /** Manifest media types: single-arch manifests and multi-arch indexes, Docker + OCI. */
122
132
  const MANIFEST_ACCEPT = [
@@ -155,7 +165,13 @@ export async function resolveRegistryDigest(
155
165
  headers: { Accept: MANIFEST_ACCEPT, Authorization: `Bearer ${token.token}` },
156
166
  });
157
167
  }
158
- if (!res.ok) return { ok: false, reason: `HTTP ${res.status} ${res.statusText}` };
168
+ if (!res.ok) {
169
+ return {
170
+ ok: false,
171
+ reason: `HTTP ${res.status} ${res.statusText}`,
172
+ ...(res.status === 404 ? { notFound: true as const } : {}),
173
+ };
174
+ }
159
175
  const header = res.headers.get("docker-content-digest");
160
176
  const digest = header ? canonicalDigest(header) : null;
161
177
  if (!digest) {