@caelo-cms/provisioning 0.10.29 → 0.10.31

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 (99) hide show
  1. package/dist/admin-domain.d.ts +54 -0
  2. package/dist/admin-domain.d.ts.map +1 -0
  3. package/dist/admin-domain.js +122 -0
  4. package/dist/admin-domain.js.map +1 -0
  5. package/dist/cli.js +71 -7
  6. package/dist/cli.js.map +1 -1
  7. package/dist/compose.d.ts +7 -9
  8. package/dist/compose.d.ts.map +1 -1
  9. package/dist/compose.js +17 -1
  10. package/dist/compose.js.map +1 -1
  11. package/dist/deployed-release.d.ts +84 -0
  12. package/dist/deployed-release.d.ts.map +1 -0
  13. package/dist/deployed-release.js +182 -0
  14. package/dist/deployed-release.js.map +1 -0
  15. package/dist/gcloud-retry.d.ts +16 -0
  16. package/dist/gcloud-retry.d.ts.map +1 -0
  17. package/dist/gcloud-retry.js +17 -0
  18. package/dist/gcloud-retry.js.map +1 -0
  19. package/dist/gcloud.d.ts.map +1 -1
  20. package/dist/gcloud.js +2 -0
  21. package/dist/gcloud.js.map +1 -1
  22. package/dist/gcp-names.d.ts +58 -0
  23. package/dist/gcp-names.d.ts.map +1 -0
  24. package/dist/gcp-names.js +88 -0
  25. package/dist/gcp-names.js.map +1 -0
  26. package/dist/install-state.d.ts +31 -0
  27. package/dist/install-state.d.ts.map +1 -1
  28. package/dist/install-state.js +38 -0
  29. package/dist/install-state.js.map +1 -1
  30. package/dist/lifecycle.d.ts +5 -0
  31. package/dist/lifecycle.d.ts.map +1 -1
  32. package/dist/lifecycle.js +340 -50
  33. package/dist/lifecycle.js.map +1 -1
  34. package/dist/mcp-iap.d.ts +5 -26
  35. package/dist/mcp-iap.d.ts.map +1 -1
  36. package/dist/mcp-iap.js +6 -18
  37. package/dist/mcp-iap.js.map +1 -1
  38. package/dist/migration-runner.d.ts +25 -0
  39. package/dist/migration-runner.d.ts.map +1 -1
  40. package/dist/migration-runner.js +60 -19
  41. package/dist/migration-runner.js.map +1 -1
  42. package/dist/operator-access.d.ts +83 -0
  43. package/dist/operator-access.d.ts.map +1 -0
  44. package/dist/operator-access.js +562 -0
  45. package/dist/operator-access.js.map +1 -0
  46. package/dist/region-data.d.ts +30 -0
  47. package/dist/region-data.d.ts.map +1 -0
  48. package/dist/region-data.js +406 -0
  49. package/dist/region-data.js.map +1 -0
  50. package/dist/regions.d.ts +130 -0
  51. package/dist/regions.d.ts.map +1 -0
  52. package/dist/regions.js +247 -0
  53. package/dist/regions.js.map +1 -0
  54. package/dist/runtime-secrets.d.ts +128 -0
  55. package/dist/runtime-secrets.d.ts.map +1 -0
  56. package/dist/runtime-secrets.js +444 -0
  57. package/dist/runtime-secrets.js.map +1 -0
  58. package/dist/stack-contract.d.ts +231 -0
  59. package/dist/stack-contract.d.ts.map +1 -0
  60. package/dist/stack-contract.js +455 -0
  61. package/dist/stack-contract.js.map +1 -0
  62. package/dist/stack-converge.d.ts +183 -0
  63. package/dist/stack-converge.d.ts.map +1 -0
  64. package/dist/stack-converge.js +513 -0
  65. package/dist/stack-converge.js.map +1 -0
  66. package/dist/wizard.d.ts +7 -0
  67. package/dist/wizard.d.ts.map +1 -1
  68. package/dist/wizard.js +11 -1
  69. package/dist/wizard.js.map +1 -1
  70. package/dist/wizards/gcp-cost.d.ts +10 -10
  71. package/dist/wizards/gcp-cost.d.ts.map +1 -1
  72. package/dist/wizards/gcp-cost.js +40 -3
  73. package/dist/wizards/gcp-cost.js.map +1 -1
  74. package/dist/wizards/gcp-firebase.d.ts +2 -0
  75. package/dist/wizards/gcp-firebase.d.ts.map +1 -1
  76. package/dist/wizards/gcp-firebase.js +1 -0
  77. package/dist/wizards/gcp-firebase.js.map +1 -1
  78. package/dist/wizards/gcp-pulumi.d.ts +51 -1
  79. package/dist/wizards/gcp-pulumi.d.ts.map +1 -1
  80. package/dist/wizards/gcp-pulumi.js +89 -6
  81. package/dist/wizards/gcp-pulumi.js.map +1 -1
  82. package/dist/wizards/gcp.d.ts +2 -0
  83. package/dist/wizards/gcp.d.ts.map +1 -1
  84. package/dist/wizards/gcp.js +171 -23
  85. package/dist/wizards/gcp.js.map +1 -1
  86. package/package.json +3 -3
  87. package/stacks/aws/Pulumi.yaml +5 -2
  88. package/stacks/aws/README.md +3 -1
  89. package/stacks/aws/index.ts +21 -1
  90. package/stacks/azure/Pulumi.yaml +6 -3
  91. package/stacks/azure/README.md +1 -1
  92. package/stacks/azure/index.ts +22 -3
  93. package/stacks/gcp/Pulumi.yaml +5 -5
  94. package/stacks/gcp/README.md +15 -3
  95. package/stacks/gcp/index.ts +204 -141
  96. package/stacks/gcp-firebase/Pulumi.yaml +5 -2
  97. package/stacks/gcp-firebase/index.ts +169 -76
  98. package/stacks/self-hosted/Pulumi.yaml +4 -0
  99. package/stacks/self-hosted/index.ts +3 -0
@@ -0,0 +1,455 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+ /**
3
+ * What a GCP install looks like, declared once for both ways of producing
4
+ * one: the Pulumi stacks (`stacks/gcp`, `stacks/gcp-firebase`) for new
5
+ * installs and `pulumi up`, and `cms-provision upgrade` (stack-converge.ts)
6
+ * for existing installs, which rolls images through gcloud without running
7
+ * Pulumi.
8
+ *
9
+ * Before this contract, everything added to the stacks after an install was
10
+ * created never reached that install: `CAELO_SITE_URL` (so `site_base_url`
11
+ * stayed unset and Stage failed), the run SA's `run.viewer` on the gateway
12
+ * (Firebase Hosting deploys 403'd), the telemetry roles, the one-year CDN
13
+ * TTLs. Three parts close that gap:
14
+ *
15
+ * - {@link adminEnvContract} / {@link gatewayEnvContract}: the complete
16
+ * env of the two Cloud Run services — plain values and Secret Manager
17
+ * references. The stacks deploy them as the Cloud Run `envs`; upgrade
18
+ * applies them (`--update-env-vars` / `--update-secrets`) in the same
19
+ * `services update` that rolls the image.
20
+ * - {@link stackIamInvariants} + {@link STATIC_CDN_POLICY}: the IAM
21
+ * bindings and CDN settings upgrade ensures with gcloud.
22
+ * - {@link STACK_IAM_NOT_ENSURED}: bindings upgrade deliberately leaves to
23
+ * Pulumi, each with the reason. The parity test
24
+ * (stack-contract-parity.test.ts) fails when a stack declares an IAM
25
+ * binding that is in neither list, so a new binding cannot ship without
26
+ * a decision about existing installs.
27
+ *
28
+ * No secret value is ever a plain env var: database URLs carry no password
29
+ * (the apps add it from the `<NAME>_PASSWORD` var, @caelo-cms/shared
30
+ * `databaseUrlFromEnv`), and every secret is a `secretKeyRef` to Secret
31
+ * Manager. Anyone with `run.services.get` sees secret names, never values.
32
+ *
33
+ * Pure module — the stacks import it from `dist/`.
34
+ */
35
+ import { gatewayServiceAccountEmail, gcpBucketName, gcpNamePrefix, gcpSecretId, mcpIapServiceAccountEmail, operatorAccessJobResource, runServiceAccountEmail, staticPublisherServiceAccountId, } from "./gcp-names.js";
36
+ /** Port Cloud SQL Postgres listens on (private IP). */
37
+ const DATABASE_PORT = 5432;
38
+ /**
39
+ * Password-less connection URLs for a Cloud SQL instance at `host`. The
40
+ * password reaches the apps separately, as a Secret Manager reference
41
+ * ({@link SERVICE_SECRET_ENV}).
42
+ */
43
+ export function databaseUrls(host) {
44
+ const url = (role, db) => `postgresql://${role}@${host}:${DATABASE_PORT}/${db}?sslmode=require`;
45
+ return {
46
+ admin: url("admin_role", "cms_admin"),
47
+ publicAdmin: url("admin_role", "cms_public"),
48
+ public: url("public_role", "cms_public"),
49
+ };
50
+ }
51
+ /** The public site's base URL, as the admin seeds `site_base_url` from it (#551). */
52
+ export function publicSiteUrl(domain) {
53
+ return `https://${domain}`;
54
+ }
55
+ /**
56
+ * The admin env var naming the MCP service account (issue #37). `upgrade`
57
+ * leaves it untouched when it could not set that account up.
58
+ */
59
+ export const MCP_ENV_VAR = "CAELO_MCP_IAP_SERVICE_ACCOUNT";
60
+ /**
61
+ * The admin env var naming the operator-access sync job. `upgrade` leaves it
62
+ * untouched when it could not set the job up.
63
+ */
64
+ export const OPERATOR_ACCESS_JOB_ENV_VAR = "CAELO_OPERATOR_ACCESS_JOB";
65
+ /** Where the admin image ships the static-generator CLI. */
66
+ const GENERATOR_CLI = "/app/apps/static-generator/src/cli.ts";
67
+ /**
68
+ * Secrets the CLI generates and stores (runtime-secrets.ts
69
+ * `ensureGeneratedSecrets`) rather than Pulumi: the wizard creates them
70
+ * before `pulumi up`, upgrade creates them on installs that predate them.
71
+ * Pulumi only references them, so a `pulumi up` after an upgrade never
72
+ * collides with a secret upgrade created.
73
+ */
74
+ export const CLI_GENERATED_SECRETS = [
75
+ "internal-secret",
76
+ "tool-approval-secret",
77
+ ];
78
+ /**
79
+ * Each service's Secret Manager-backed env vars and the secret each reads.
80
+ * The services' run SAs get `secretAccessor` on exactly these secrets
81
+ * ({@link runtimeSecretBindings}), so this table is also the least-privilege
82
+ * boundary between the two services.
83
+ */
84
+ export const SERVICE_SECRET_ENV = {
85
+ admin: {
86
+ ADMIN_DATABASE_PASSWORD: "postgres-password",
87
+ PUBLIC_ADMIN_DATABASE_PASSWORD: "postgres-password",
88
+ // P18 — encrypts AI provider keys and other at-rest secrets in cms_admin.
89
+ CAELO_SECRET_KEK: "secret-kek",
90
+ // P15.1 — HMAC key of the /api/internal/* bearer tokens (internal-jwt.ts).
91
+ CAELO_INTERNAL_SECRET: "internal-secret",
92
+ // Binds an AI tool-approval to the exact call the operator approved
93
+ // (admin-core ai/providers/_sdk-shared.ts).
94
+ CAELO_TOOL_APPROVAL_SECRET: "tool-approval-secret",
95
+ },
96
+ gateway: {
97
+ // Known gap against CLAUDE.md §2 ("never let the API Gateway hold
98
+ // admin_role credentials"): the gateway reads site_settings, rate
99
+ // limits, captcha challenges, the request log and the plugin registry
100
+ // through an admin_role pool (apps/api-gateway/src/server.ts), and fails
101
+ // to boot without it. Until that moves behind a narrower role the
102
+ // gateway needs this credential; it is no longer a plain env var.
103
+ ADMIN_DATABASE_PASSWORD: "postgres-password",
104
+ PUBLIC_DATABASE_PASSWORD: "postgres-password",
105
+ },
106
+ };
107
+ /** The secrets `service` reads (deduplicated, table order). */
108
+ export function serviceSecrets(service) {
109
+ return [...new Set(Object.values(SERVICE_SECRET_ENV[service]))];
110
+ }
111
+ /**
112
+ * Env vars an older stack set that the contract no longer gives a service;
113
+ * upgrade removes them. The gateway never used the KEK, and its run SA
114
+ * cannot read it.
115
+ */
116
+ export const RETIRED_SERVICE_ENV = {
117
+ admin: [],
118
+ gateway: ["CAELO_SECRET_KEK"],
119
+ };
120
+ function secretEnv(service, env) {
121
+ return Object.entries(SERVICE_SECRET_ENV[service]).map(([name, secret]) => ({
122
+ name,
123
+ valueSource: { secretKeyRef: { secret: gcpSecretId(env, secret), version: "latest" } },
124
+ }));
125
+ }
126
+ // ===========================================================================
127
+ // Env contracts
128
+ // ===========================================================================
129
+ /** Env vars both services carry. */
130
+ function commonEnv(inputs) {
131
+ return [
132
+ { name: "CAELO_PROVIDER", value: inputs.provider },
133
+ { name: "CAELO_ENV", value: inputs.env },
134
+ {
135
+ name: "MEDIA_STORAGE_URL",
136
+ value: `gs://${gcpBucketName(inputs.projectId, inputs.env, "media")}`,
137
+ },
138
+ ];
139
+ }
140
+ /**
141
+ * The env of the gateway service. Order is stable so the stacks see no
142
+ * spurious diffs.
143
+ */
144
+ export function gatewayEnvContract(inputs) {
145
+ return [
146
+ ...commonEnv(inputs),
147
+ { name: "PUBLIC_DATABASE_URL", value: inputs.databaseUrls.public },
148
+ // See the known gap noted on SERVICE_SECRET_ENV.gateway.
149
+ { name: "ADMIN_DATABASE_URL", value: inputs.databaseUrls.admin },
150
+ ...secretEnv("gateway", inputs.env),
151
+ ];
152
+ }
153
+ /**
154
+ * The env of the admin service.
155
+ *
156
+ * @example
157
+ * adminEnvContract({ provider: "gcp", projectId: "p", env: "production",
158
+ * domain: "example.com", region: "europe-west1",
159
+ * databaseUrls: databaseUrls("10.20.0.3") })
160
+ * // → [{ name: "CAELO_PROVIDER", value: "gcp" }, …,
161
+ * // { name: "CAELO_SITE_URL", value: "https://example.com" }, …,
162
+ * // { name: "CAELO_SECRET_KEK", valueSource: { secretKeyRef: … } }, …]
163
+ */
164
+ export function adminEnvContract(inputs) {
165
+ const { projectId, env, domain } = inputs;
166
+ const admin = [
167
+ ...commonEnv(inputs),
168
+ { name: "ADMIN_DATABASE_URL", value: inputs.databaseUrls.admin },
169
+ // admin_role on cms_public: cross-DB reads, plugin DDL, migrations.
170
+ // Not public_role (write-limited).
171
+ { name: "PUBLIC_ADMIN_DATABASE_URL", value: inputs.databaseUrls.publicAdmin },
172
+ // #551 — the admin seeds site_defaults.site_base_url from it (canonical,
173
+ // og:url, sitemap) when it is not configured yet.
174
+ { name: "CAELO_SITE_URL", value: publicSiteUrl(domain) },
175
+ { name: "CAELO_GENERATOR_CLI", value: GENERATOR_CLI },
176
+ // Issue #37 — shown in the /security/mcp `claude mcp add` command.
177
+ { name: MCP_ENV_VAR, value: mcpIapServiceAccountEmail(projectId) },
178
+ // The operator-access sync job the admin starts after user/role changes
179
+ // (admin-core security/operator-access/gcp-job-trigger.ts).
180
+ {
181
+ name: OPERATOR_ACCESS_JOB_ENV_VAR,
182
+ value: operatorAccessJobResource(projectId, inputs.region, env),
183
+ },
184
+ ];
185
+ if (inputs.provider === "gcp") {
186
+ // v0.2.78 — the GCS StaticPublisher: Stage uploads to staging,
187
+ // Confirm-publish copies to static.
188
+ admin.push({ name: "CAELO_STATIC_BUCKET", value: gcpBucketName(projectId, env, "static") }, { name: "CAELO_STAGING_BUCKET", value: gcpBucketName(projectId, env, "staging") });
189
+ }
190
+ else {
191
+ // v0.3.1 — the Firebase publisher deploys to this site and declares
192
+ // the /api/** rewrite to the gateway service.
193
+ admin.push({ name: "CAELO_FIREBASE_SITE", value: inputs.firebaseSiteId }, { name: "CAELO_GATEWAY_SERVICE", value: inputs.gatewayService }, { name: "CAELO_GATEWAY_REGION", value: inputs.region });
194
+ }
195
+ admin.push(...secretEnv("admin", env));
196
+ return admin;
197
+ }
198
+ // ===========================================================================
199
+ // IAM + CDN invariants
200
+ // ===========================================================================
201
+ /**
202
+ * Cloud CDN policy of the `gcp` stack's static backend bucket. The GCS
203
+ * publisher sets an explicit Cache-Control on every object: content-hashed
204
+ * assets `max-age=31536000, immutable`, pages `max-age=60` + SWR. In
205
+ * CACHE_ALL_STATIC mode `clientTtl` clamps the max-age browsers see and
206
+ * `maxTtl` caps the edge TTL, so both must allow a year or the immutable
207
+ * policy is cut back to 1h (#555). `defaultTtl` applies only to responses
208
+ * without a max-age.
209
+ */
210
+ export const STATIC_CDN_POLICY = {
211
+ cacheMode: "CACHE_ALL_STATIC",
212
+ defaultTtl: 3600,
213
+ maxTtl: 31536000,
214
+ clientTtl: 31536000,
215
+ };
216
+ const SECRET_ACCESSOR = "roles/secretmanager.secretAccessor";
217
+ /**
218
+ * The `secretAccessor` bindings both stacks declare: each service's run SA
219
+ * on exactly the secrets its env reads ({@link SERVICE_SECRET_ENV}).
220
+ */
221
+ export function runtimeSecretBindings() {
222
+ return ["admin", "gateway"].flatMap((service) => serviceSecrets(service).map((secret) => ({
223
+ service,
224
+ secret,
225
+ // The admin's keep the names they had when one SA served both.
226
+ stackResource: service === "admin" ? `${secret}-binding` : `gateway-${secret}-binding`,
227
+ })));
228
+ }
229
+ function secretAccessors() {
230
+ return runtimeSecretBindings().map((b) => ({
231
+ stackResource: b.stackResource,
232
+ role: SECRET_ACCESSOR,
233
+ member: b.service === "admin" ? "run-sa" : "gateway-sa",
234
+ target: { kind: "secret", name: b.secret },
235
+ onFailure: "abort",
236
+ why: `the ${b.service} reads ${b.secret} from Secret Manager at boot`,
237
+ }));
238
+ }
239
+ /** v0.6.6 — a custom runtime SA gets no telemetry roles implicitly. */
240
+ const TELEMETRY = [
241
+ {
242
+ stackResource: "run-log-writer",
243
+ role: "roles/logging.logWriter",
244
+ member: "run-sa",
245
+ target: { kind: "project" },
246
+ onFailure: "warn",
247
+ why: "container logs reach Cloud Logging",
248
+ },
249
+ {
250
+ stackResource: "run-metric-writer",
251
+ role: "roles/monitoring.metricWriter",
252
+ member: "run-sa",
253
+ target: { kind: "project" },
254
+ onFailure: "warn",
255
+ why: "CPU/memory metrics reach Cloud Monitoring (autoscaling signals)",
256
+ },
257
+ {
258
+ stackResource: "gateway-log-writer",
259
+ role: "roles/logging.logWriter",
260
+ member: "gateway-sa",
261
+ target: { kind: "project" },
262
+ onFailure: "warn",
263
+ why: "gateway container logs reach Cloud Logging",
264
+ },
265
+ {
266
+ stackResource: "gateway-metric-writer",
267
+ role: "roles/monitoring.metricWriter",
268
+ member: "gateway-sa",
269
+ target: { kind: "project" },
270
+ onFailure: "warn",
271
+ why: "gateway CPU/memory metrics reach Cloud Monitoring (autoscaling signals)",
272
+ },
273
+ ];
274
+ const GCP_ONLY = [
275
+ {
276
+ stackResource: "static-public-read",
277
+ role: "roles/storage.objectViewer",
278
+ member: "allUsers",
279
+ target: { kind: "bucket", bucket: "static" },
280
+ onFailure: "abort",
281
+ why: "Cloud CDN fetches the public site from the static bucket",
282
+ },
283
+ {
284
+ stackResource: "media-rw",
285
+ role: "roles/storage.objectAdmin",
286
+ member: "run-sa",
287
+ target: { kind: "bucket", bucket: "media" },
288
+ onFailure: "abort",
289
+ why: "the admin stores and serves media",
290
+ },
291
+ {
292
+ stackResource: "admin-static-rw",
293
+ role: "roles/storage.objectAdmin",
294
+ member: "run-sa",
295
+ target: { kind: "bucket", bucket: "static" },
296
+ onFailure: "abort",
297
+ why: "Confirm-publish copies the staged build into the static bucket",
298
+ },
299
+ {
300
+ stackResource: "admin-staging-rw",
301
+ role: "roles/storage.objectAdmin",
302
+ member: "run-sa",
303
+ target: { kind: "bucket", bucket: "staging" },
304
+ onFailure: "abort",
305
+ why: "Stage uploads builds to the private staging bucket (v0.2.78)",
306
+ },
307
+ {
308
+ stackResource: "static-publisher-rw",
309
+ role: "roles/storage.objectAdmin",
310
+ member: "static-publisher-sa",
311
+ target: { kind: "bucket", bucket: "static" },
312
+ onFailure: "warn",
313
+ why: "manual `deploy` uploads as the static-publisher SA (the admin publishes without it)",
314
+ },
315
+ {
316
+ stackResource: "iap-invoke-admin",
317
+ role: "roles/run.invoker",
318
+ member: "iap-service-agent",
319
+ target: { kind: "run-service", service: "admin" },
320
+ onFailure: "abort",
321
+ why: "IAP forwards admin traffic to Cloud Run as its service agent",
322
+ },
323
+ ];
324
+ const GCP_FIREBASE_ONLY = [
325
+ {
326
+ stackResource: "media-rw",
327
+ role: "roles/storage.objectAdmin",
328
+ member: "run-sa",
329
+ target: { kind: "bucket", bucket: "media" },
330
+ onFailure: "abort",
331
+ why: "the admin stores and serves media",
332
+ },
333
+ {
334
+ stackResource: "run-firebase-hosting",
335
+ role: "roles/firebasehosting.admin",
336
+ member: "run-sa",
337
+ target: { kind: "project" },
338
+ onFailure: "abort",
339
+ why: "the admin publishes the site to Firebase Hosting",
340
+ },
341
+ {
342
+ stackResource: "gateway-public-invoker",
343
+ role: "roles/run.invoker",
344
+ member: "allUsers",
345
+ target: { kind: "run-service", service: "gateway" },
346
+ onFailure: "abort",
347
+ why: "Firebase Hosting rewrites /api/** to the public gateway",
348
+ },
349
+ {
350
+ stackResource: "gateway-viewer-for-admin-sa",
351
+ role: "roles/run.viewer",
352
+ member: "run-sa",
353
+ target: { kind: "run-service", service: "gateway" },
354
+ onFailure: "abort",
355
+ why: "Firebase Hosting checks run.services.get on the rewrite target at deploy time (v0.6.5; without it every publish 403s)",
356
+ },
357
+ {
358
+ stackResource: "admin-iap-sa-invoker",
359
+ role: "roles/run.invoker",
360
+ member: "iap-service-agent",
361
+ target: { kind: "run-service", service: "admin" },
362
+ onFailure: "abort",
363
+ why: "IAP forwards admin traffic to Cloud Run as its service agent",
364
+ },
365
+ ];
366
+ /** Every IAM binding upgrade ensures on an install of `provider`. */
367
+ export function stackIamInvariants(provider) {
368
+ return [
369
+ ...secretAccessors(),
370
+ ...(provider === "gcp" ? GCP_ONLY : GCP_FIREBASE_ONLY),
371
+ ...TELEMETRY,
372
+ ];
373
+ }
374
+ /**
375
+ * IAM bindings the stacks declare that upgrade deliberately does not ensure,
376
+ * keyed by provider and the stack resource's logical name as written in the
377
+ * stack source, with each `${…}` placeholder written as `*`.
378
+ */
379
+ export const STACK_IAM_NOT_ENSURED = {
380
+ gcp: {
381
+ "admin-iap-allow-*": "The IAP allowlist is operator-managed membership from stack config; upgrade must not re-add principals an Owner removed.",
382
+ "mcp-token-creator-*": "Ensured by ensureMcpIapAccess (mcp-iap.ts).",
383
+ "admin-iap-mcp": "Ensured by ensureMcpIapAccess (mcp-iap.ts).",
384
+ "edge-log-sink-bq-perms": "The member is the sink's generated writer identity, which only Pulumi knows; analytics only.",
385
+ },
386
+ "gcp-firebase": {
387
+ "admin-iap-*": "The IAP allowlist is operator-managed membership from stack config; upgrade must not re-add principals an Owner removed.",
388
+ "admin-invoker-*": "Per-principal invoker for the IAP allowlist (operator-managed, see admin-iap-*).",
389
+ "mcp-token-creator-*": "Ensured by ensureMcpIapAccess (mcp-iap.ts).",
390
+ "admin-iap-mcp": "Ensured by ensureMcpIapAccess (mcp-iap.ts).",
391
+ },
392
+ };
393
+ /** The IAM member string for a principal on a concrete install. */
394
+ export function iamMember(principal, install) {
395
+ switch (principal) {
396
+ case "run-sa":
397
+ return `serviceAccount:${runServiceAccountEmail(install.projectId, install.env)}`;
398
+ case "gateway-sa":
399
+ return `serviceAccount:${gatewayServiceAccountEmail(install.projectId, install.env)}`;
400
+ case "static-publisher-sa":
401
+ return `serviceAccount:${staticPublisherServiceAccountId(install.env)}@${install.projectId}.iam.gserviceaccount.com`;
402
+ case "iap-service-agent":
403
+ return `serviceAccount:service-${install.projectNumber}@gcp-sa-iap.iam.gserviceaccount.com`;
404
+ case "allUsers":
405
+ return "allUsers";
406
+ }
407
+ }
408
+ /** Name prefix of the `gcp` stack's static backend bucket (Pulumi auto-suffixed). */
409
+ export function staticBackendBucketPrefix(env) {
410
+ return `${gcpNamePrefix(env)}-static-backend`;
411
+ }
412
+ /**
413
+ * #553 — the admin's memory, in Cloud Run / Kubernetes quantity notation.
414
+ * The admin runs the quality audit (Lighthouse driving the bundled
415
+ * Chromium in a child process) next to the editor's requests; 1 GiB left
416
+ * no headroom for both, 2 GiB does. Every adapter exposes the same
417
+ * `adminMemory` knob with this default (CLAUDE.md §11.B), and `upgrade`
418
+ * raises an existing install's admin to it (never lowers a larger value).
419
+ */
420
+ export const ADMIN_MEMORY_DEFAULT = "2Gi";
421
+ /**
422
+ * Parse a memory quantity (`512Mi`, `2Gi`, `2G`, `1.5Gi`) to MiB; null when
423
+ * it is not a quantity this contract understands.
424
+ */
425
+ /**
426
+ * #553 — Azure Container Apps (Consumption) only accepts fixed CPU/memory
427
+ * pairs: 0.25 vCPU / 0.5Gi up to 4 vCPU / 8Gi in 0.25 vCPU steps, memory
428
+ * always 2 GiB per vCPU. Map `adminMemory` onto that pair, or throw with
429
+ * the accepted values — an unsupported size must fail before `pulumi up`
430
+ * does, not deploy something else.
431
+ */
432
+ export function azureContainerAppResources(quantity) {
433
+ const mib = memoryQuantityMiB(quantity);
434
+ if (mib === null || mib % 512 !== 0 || mib < 512 || mib > 8192) {
435
+ throw new Error(`caelo-azure:adminMemory "${quantity}" is not a Container Apps size; use 0.5Gi to 8Gi in 0.5Gi steps (e.g. 2Gi = 1 vCPU)`);
436
+ }
437
+ return { cpu: mib / 2048, memory: `${(mib / 1024).toFixed(1)}Gi` };
438
+ }
439
+ export function memoryQuantityMiB(quantity) {
440
+ const m = /^(\d+(?:\.\d+)?)\s*(Mi|Gi|M|G)$/.exec(quantity.trim());
441
+ if (!m)
442
+ return null;
443
+ const n = Number(m[1]);
444
+ switch (m[2]) {
445
+ case "Mi":
446
+ return Math.round(n);
447
+ case "Gi":
448
+ return Math.round(n * 1024);
449
+ case "M":
450
+ return Math.round((n * 1000 * 1000) / (1024 * 1024));
451
+ default:
452
+ return Math.round((n * 1000 * 1000 * 1000) / (1024 * 1024));
453
+ }
454
+ }
455
+ //# sourceMappingURL=stack-contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stack-contract.js","sourceRoot":"","sources":["../src/stack-contract.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Converge an existing GCP install to what its Pulumi stack would produce,
3
+ * through gcloud (`cms-provision upgrade` does not run Pulumi). The desired
4
+ * state comes from stack-contract.ts, which the stacks use too.
5
+ *
6
+ * - {@link planEnvUpdate}: the env-var flags for the `services update` that
7
+ * rolls the image, so env changes ride the same new revision — including
8
+ * moving a plain var to a Secret Manager reference.
9
+ * - {@link ensureStackInvariants}: IAM bindings + CDN settings. Each binding
10
+ * is checked against the resource's policy first and only added when
11
+ * missing, so an operator without IAM-admin rights can still upgrade an
12
+ * install that is already in shape.
13
+ */
14
+ import { type GcloudResult } from "./gcloud.js";
15
+ import { type GcloudRunner, type Sleep } from "./gcloud-retry.js";
16
+ import { type CloudRunEnvVar, type CloudRunSlug, type GcpProvider, type ServiceEnvInputs } from "./stack-contract.js";
17
+ /** An env var as a deployed Cloud Run service carries it. */
18
+ export type LiveEnvValue = {
19
+ readonly kind: "value";
20
+ readonly value: string;
21
+ } | {
22
+ readonly kind: "secret";
23
+ readonly secret: string;
24
+ readonly version: string;
25
+ };
26
+ /**
27
+ * Env vars of the first container in `gcloud run services describe
28
+ * --format=json` output (Knative `serving.knative.dev/v1` Service).
29
+ */
30
+ export declare function liveContainerEnv(serviceJson: string): Map<string, LiveEnvValue>;
31
+ /** The service account the service's revisions run as (`undefined`: the default compute SA). */
32
+ export declare function liveServiceAccount(serviceJson: string): string | undefined;
33
+ export interface EnvChange {
34
+ readonly name: string;
35
+ /** `undefined` when the service does not have the var yet. */
36
+ readonly from: string | undefined;
37
+ /** `undefined` when the var is removed. */
38
+ readonly to: string | undefined;
39
+ }
40
+ export type EnvUpdatePlan = {
41
+ readonly ok: true;
42
+ readonly changes: EnvChange[];
43
+ readonly flags: string[];
44
+ } | {
45
+ readonly ok: false;
46
+ readonly error: string;
47
+ };
48
+ /**
49
+ * Whether a service still carries a URL with an inline password in a plain
50
+ * var — the pre-Secret Manager shape. Such a password is in every revision
51
+ * Cloud Run keeps, so it should be rotated once the services moved off it.
52
+ */
53
+ export declare function liveEnvHasInlinePassword(liveEnv: ReadonlyMap<string, LiveEnvValue>): boolean;
54
+ /**
55
+ * The `gcloud run services update` flags that bring `live` to `desired`.
56
+ * Only vars that differ are touched; vars the contract does not own
57
+ * (operator additions) are left alone, and the `retired` ones are removed.
58
+ *
59
+ * A plain var that becomes a Secret Manager reference moves in the same
60
+ * update: `--remove-env-vars=X --update-secrets=X=…`. gcloud applies all
61
+ * literal env changes (removals included) before the secret changes of one
62
+ * `services update` (googlecloudsdk/command_lib/run/flags.py
63
+ * `_GetConfigurationChanges`), so the new revision has X as a secret and no
64
+ * revision is ever without X. The reverse — a secret becoming a plain value
65
+ * — cannot be expressed in one update (the literal is set while X is still
66
+ * a secret, which gcloud rejects), so it is refused rather than attempted.
67
+ */
68
+ export declare function planEnvUpdate(live: ReadonlyMap<string, LiveEnvValue>, desired: readonly CloudRunEnvVar[], retired?: readonly string[]): EnvUpdatePlan;
69
+ /** A deployed Cloud Run service as upgrade found it. */
70
+ export interface DeployedService {
71
+ /** Pulumi-suffixed service name. */
72
+ readonly serviceName: string;
73
+ readonly liveEnv: ReadonlyMap<string, LiveEnvValue>;
74
+ }
75
+ /**
76
+ * The Cloud SQL host the admin connects to, from its live `ADMIN_DATABASE_URL`
77
+ * (with or without an inline password — before and after the move to Secret
78
+ * Manager).
79
+ */
80
+ export declare function liveDatabaseHost(liveEnv: ReadonlyMap<string, LiveEnvValue>): {
81
+ ok: true;
82
+ host: string;
83
+ } | {
84
+ ok: false;
85
+ error: string;
86
+ };
87
+ /**
88
+ * The env changes that bring both deployed services to their env contract
89
+ * (stack-contract.ts — the contract the stacks deploy). Values Pulumi
90
+ * generated at install time (database host, Firebase site) are read from the
91
+ * live services. Fails instead of guessing when one can't be determined.
92
+ */
93
+ export declare function planContractEnv(install: ServiceEnvInputs, services: Readonly<Record<CloudRunSlug, DeployedService>>, opts?: {
94
+ /** Contract vars to leave exactly as the services have them. */
95
+ readonly leaveUntouched?: readonly string[];
96
+ }): {
97
+ readonly ok: true;
98
+ readonly services: Record<CloudRunSlug, {
99
+ flags: string[];
100
+ changes: EnvChange[];
101
+ }>;
102
+ } | {
103
+ readonly ok: false;
104
+ readonly error: string;
105
+ };
106
+ /** The deployed install `ensureStackInvariants` works on. */
107
+ export interface InstallTarget {
108
+ readonly provider: GcpProvider;
109
+ readonly projectId: string;
110
+ readonly region: string;
111
+ /** Pulumi stack name (`production`). */
112
+ readonly env: string;
113
+ /** Deployed (Pulumi-suffixed) Cloud Run service names. */
114
+ readonly services: Readonly<Record<CloudRunSlug, string>>;
115
+ }
116
+ export interface InvariantOutcome {
117
+ /** Human-readable identity, e.g. `roles/run.viewer → serviceAccount:… on gateway`. */
118
+ readonly id: string;
119
+ readonly status: "present" | "applied" | "failed";
120
+ /** What a failure means for the upgrade (from the invariant). */
121
+ readonly onFailure: "abort" | "warn";
122
+ readonly why: string;
123
+ readonly error?: string;
124
+ }
125
+ export interface InvariantsReport {
126
+ readonly outcomes: InvariantOutcome[];
127
+ /** True when a failed invariant is one the install cannot work without. */
128
+ readonly mustAbort: boolean;
129
+ }
130
+ /** Whether an IAM policy (JSON) grants `role` to `member` unconditionally. */
131
+ export declare function policyGrants(policyJson: string, role: string, member: string): boolean;
132
+ /**
133
+ * Idempotently ensure every IAM binding and CDN setting the install's stack
134
+ * declares (stack-contract.ts). Additive only — nothing is removed. Never
135
+ * throws for a gcloud failure; the report says what failed and whether the
136
+ * upgrade must stop.
137
+ */
138
+ export declare function ensureStackInvariants(install: InstallTarget, deps?: {
139
+ run?: GcloudRunner;
140
+ sleep?: Sleep;
141
+ }): Promise<InvariantsReport>;
142
+ /** The memory limit of the service's container (`512Mi`, `2Gi`), or null
143
+ * when the service declares none (Cloud Run then runs it with 512Mi). */
144
+ export declare function liveContainerMemory(serviceJson: string): string | null;
145
+ /**
146
+ * #553 — raise the admin's memory to the stack's default (it runs the
147
+ * Lighthouse quality audit). Upgrade only ever RAISES: an operator who set
148
+ * more keeps it, and a value this contract cannot parse is left alone and
149
+ * reported, never overwritten blindly.
150
+ */
151
+ export declare function planAdminMemory(live: string | null, desired?: string): {
152
+ readonly ok: true;
153
+ readonly flags: readonly string[];
154
+ readonly from: string | null;
155
+ } | {
156
+ readonly ok: false;
157
+ readonly error: string;
158
+ };
159
+ /**
160
+ * The single `gcloud run services update` that rolls a service to a new
161
+ * image, its run SA and its env changes, so all land in one new revision.
162
+ */
163
+ export declare function serviceRollArgs(roll: {
164
+ readonly serviceName: string;
165
+ readonly region: string;
166
+ readonly projectId: string;
167
+ readonly imageRef: string;
168
+ /** The SA the new revision runs as (stack-contract.ts per-service SAs). */
169
+ readonly serviceAccount: string;
170
+ readonly envFlags: readonly string[];
171
+ /** Resource changes (`--memory=…`) that ride the same revision. */
172
+ readonly resourceFlags?: readonly string[];
173
+ }): string[];
174
+ /**
175
+ * Run a roll (`serviceRollArgs`), retrying while it fails only because a
176
+ * fresh secret binding has not propagated yet. Any other failure returns at
177
+ * once.
178
+ */
179
+ export declare function rollService(args: string[], deps?: {
180
+ run?: GcloudRunner;
181
+ sleep?: Sleep;
182
+ }): Promise<GcloudResult>;
183
+ //# sourceMappingURL=stack-converge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stack-converge.d.ts","sourceRoot":"","sources":["../src/stack-converge.ts"],"names":[],"mappings":"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"}