@caelo-cms/provisioning 0.10.55 → 0.10.58

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 (221) hide show
  1. package/dist/agent-db.js +97 -24
  2. package/dist/cli-routing.js +1 -1
  3. package/dist/cli-routing.js.map +1 -1
  4. package/dist/cli.js +4 -1
  5. package/dist/cli.js.map +1 -1
  6. package/dist/compose.js +5 -0
  7. package/dist/compose.js.map +1 -1
  8. package/dist/converge-checkpoint.d.ts +66 -0
  9. package/dist/converge-checkpoint.d.ts.map +1 -0
  10. package/dist/converge-checkpoint.js +61 -0
  11. package/dist/converge-checkpoint.js.map +1 -0
  12. package/dist/converge-job-gcp.d.ts +46 -0
  13. package/dist/converge-job-gcp.d.ts.map +1 -0
  14. package/dist/converge-job-gcp.js +368 -0
  15. package/dist/converge-job-gcp.js.map +1 -0
  16. package/dist/converge-job-setup.d.ts +62 -0
  17. package/dist/converge-job-setup.d.ts.map +1 -0
  18. package/dist/converge-job-setup.js +228 -0
  19. package/dist/converge-job-setup.js.map +1 -0
  20. package/dist/converge-job.d.ts +141 -0
  21. package/dist/converge-job.d.ts.map +1 -0
  22. package/dist/converge-job.js +325 -0
  23. package/dist/converge-job.js.map +1 -0
  24. package/dist/gcp-names.d.ts +9 -0
  25. package/dist/gcp-names.d.ts.map +1 -1
  26. package/dist/gcp-names.js +13 -0
  27. package/dist/gcp-names.js.map +1 -1
  28. package/dist/index.d.ts +2 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +2 -0
  31. package/dist/index.js.map +1 -1
  32. package/dist/infra-contract.d.ts +1 -1
  33. package/dist/infra-contract.d.ts.map +1 -1
  34. package/dist/infra-contract.js +33 -1
  35. package/dist/infra-contract.js.map +1 -1
  36. package/dist/install-job.d.ts +11 -3
  37. package/dist/install-job.d.ts.map +1 -1
  38. package/dist/install-job.js +22 -12
  39. package/dist/install-job.js.map +1 -1
  40. package/dist/lifecycle.d.ts +13 -1
  41. package/dist/lifecycle.d.ts.map +1 -1
  42. package/dist/lifecycle.js +115 -58
  43. package/dist/lifecycle.js.map +1 -1
  44. package/dist/operator-access.d.ts +4 -0
  45. package/dist/operator-access.d.ts.map +1 -1
  46. package/dist/operator-access.js +43 -7
  47. package/dist/operator-access.js.map +1 -1
  48. package/dist/release-contract.d.ts +3 -2
  49. package/dist/release-contract.d.ts.map +1 -1
  50. package/dist/release-contract.js +5 -2
  51. package/dist/release-contract.js.map +1 -1
  52. package/dist/release-manifest.d.ts +2 -0
  53. package/dist/release-manifest.d.ts.map +1 -1
  54. package/dist/release-manifest.js.map +1 -1
  55. package/dist/remote-mcp-edge.d.ts +114 -0
  56. package/dist/remote-mcp-edge.d.ts.map +1 -0
  57. package/dist/remote-mcp-edge.js +116 -0
  58. package/dist/remote-mcp-edge.js.map +1 -0
  59. package/dist/remote-mcp.d.ts +139 -0
  60. package/dist/remote-mcp.d.ts.map +1 -0
  61. package/dist/remote-mcp.js +434 -0
  62. package/dist/remote-mcp.js.map +1 -0
  63. package/dist/runtime-secrets.d.ts +12 -0
  64. package/dist/runtime-secrets.d.ts.map +1 -1
  65. package/dist/runtime-secrets.js +32 -4
  66. package/dist/runtime-secrets.js.map +1 -1
  67. package/dist/seal-key-rotation.d.ts +38 -0
  68. package/dist/seal-key-rotation.d.ts.map +1 -0
  69. package/dist/seal-key-rotation.js +206 -0
  70. package/dist/seal-key-rotation.js.map +1 -0
  71. package/dist/stack-contract.d.ts +206 -3
  72. package/dist/stack-contract.d.ts.map +1 -1
  73. package/dist/stack-contract.js +219 -4
  74. package/dist/stack-contract.js.map +1 -1
  75. package/dist/stack-converge.d.ts +53 -1
  76. package/dist/stack-converge.d.ts.map +1 -1
  77. package/dist/stack-converge.js +124 -4
  78. package/dist/stack-converge.js.map +1 -1
  79. package/dist/state-lock-lease.d.ts +142 -0
  80. package/dist/state-lock-lease.d.ts.map +1 -0
  81. package/dist/state-lock-lease.js +347 -0
  82. package/dist/state-lock-lease.js.map +1 -0
  83. package/dist/state-store.d.ts +66 -9
  84. package/dist/state-store.d.ts.map +1 -1
  85. package/dist/state-store.js +249 -30
  86. package/dist/state-store.js.map +1 -1
  87. package/dist/upgrade/app-roll.d.ts +36 -7
  88. package/dist/upgrade/app-roll.d.ts.map +1 -1
  89. package/dist/upgrade/app-roll.js +53 -19
  90. package/dist/upgrade/app-roll.js.map +1 -1
  91. package/dist/upgrade/audit-job-roll.d.ts.map +1 -1
  92. package/dist/upgrade/audit-job-roll.js +3 -1
  93. package/dist/upgrade/audit-job-roll.js.map +1 -1
  94. package/dist/upgrade/converge.d.ts.map +1 -1
  95. package/dist/upgrade/converge.js +125 -12
  96. package/dist/upgrade/converge.js.map +1 -1
  97. package/dist/upgrade/gcp-lookup.d.ts +27 -10
  98. package/dist/upgrade/gcp-lookup.d.ts.map +1 -1
  99. package/dist/upgrade/gcp-lookup.js +46 -7
  100. package/dist/upgrade/gcp-lookup.js.map +1 -1
  101. package/dist/upgrade/record-rolled.d.ts +40 -0
  102. package/dist/upgrade/record-rolled.d.ts.map +1 -0
  103. package/dist/upgrade/record-rolled.js +46 -0
  104. package/dist/upgrade/record-rolled.js.map +1 -0
  105. package/dist/upgrade/upgrade.d.ts +45 -1
  106. package/dist/upgrade/upgrade.d.ts.map +1 -1
  107. package/dist/upgrade/upgrade.js +60 -17
  108. package/dist/upgrade/upgrade.js.map +1 -1
  109. package/dist/upgrader-assess.d.ts +15 -1
  110. package/dist/upgrader-assess.d.ts.map +1 -1
  111. package/dist/upgrader-assess.js +36 -6
  112. package/dist/upgrader-assess.js.map +1 -1
  113. package/dist/upgrader-gcp.d.ts +15 -0
  114. package/dist/upgrader-gcp.d.ts.map +1 -1
  115. package/dist/upgrader-gcp.js +42 -7
  116. package/dist/upgrader-gcp.js.map +1 -1
  117. package/dist/upgrader-iam-preflight.d.ts +123 -0
  118. package/dist/upgrader-iam-preflight.d.ts.map +1 -0
  119. package/dist/upgrader-iam-preflight.js +414 -0
  120. package/dist/upgrader-iam-preflight.js.map +1 -0
  121. package/dist/upgrader-job.d.ts +33 -9
  122. package/dist/upgrader-job.d.ts.map +1 -1
  123. package/dist/upgrader-job.js +169 -27
  124. package/dist/upgrader-job.js.map +1 -1
  125. package/dist/upgrader-records.d.ts +58 -0
  126. package/dist/upgrader-records.d.ts.map +1 -0
  127. package/dist/upgrader-records.js +87 -0
  128. package/dist/upgrader-records.js.map +1 -0
  129. package/dist/upgrader-reporter.d.ts +3 -2
  130. package/dist/upgrader-reporter.d.ts.map +1 -1
  131. package/dist/upgrader-reporter.js +10 -2
  132. package/dist/upgrader-reporter.js.map +1 -1
  133. package/dist/upgrader-setup.d.ts +25 -3
  134. package/dist/upgrader-setup.d.ts.map +1 -1
  135. package/dist/upgrader-setup.js +123 -11
  136. package/dist/upgrader-setup.js.map +1 -1
  137. package/dist/wizards/gcp-cost.d.ts.map +1 -1
  138. package/dist/wizards/gcp-cost.js +11 -3
  139. package/dist/wizards/gcp-cost.js.map +1 -1
  140. package/dist/wizards/gcp-pulumi.d.ts +6 -0
  141. package/dist/wizards/gcp-pulumi.d.ts.map +1 -1
  142. package/dist/wizards/gcp-pulumi.js +14 -2
  143. package/dist/wizards/gcp-pulumi.js.map +1 -1
  144. package/dist/wizards/gcp.d.ts.map +1 -1
  145. package/dist/wizards/gcp.js +106 -4
  146. package/dist/wizards/gcp.js.map +1 -1
  147. package/package.json +2 -2
  148. package/src/agent/run.test.ts +14 -1
  149. package/src/audit-job-setup.test.ts +7 -1
  150. package/src/cli-routing.ts +1 -1
  151. package/src/cli.ts +4 -1
  152. package/src/compose.ts +5 -0
  153. package/src/converge-checkpoint.ts +75 -0
  154. package/src/converge-entrypoint.test.ts +213 -0
  155. package/src/converge-job-gcp.test.ts +222 -0
  156. package/src/converge-job-gcp.ts +443 -0
  157. package/src/converge-job-setup.test.ts +152 -0
  158. package/src/converge-job-setup.ts +268 -0
  159. package/src/converge-job.test.ts +486 -0
  160. package/src/converge-job.ts +542 -0
  161. package/src/gateway-env-isolation.test.ts +8 -1
  162. package/src/gcp-names.ts +15 -0
  163. package/src/gcp-url-map.test.ts +9 -1
  164. package/src/index.ts +8 -0
  165. package/src/infra-contract.test.ts +8 -4
  166. package/src/infra-contract.ts +33 -1
  167. package/src/install-job.test.ts +3 -1
  168. package/src/install-job.ts +31 -14
  169. package/src/lifecycle.ts +137 -71
  170. package/src/operator-access.test.ts +24 -1
  171. package/src/operator-access.ts +44 -6
  172. package/src/release-contract.ts +5 -2
  173. package/src/release-manifest.test.ts +42 -0
  174. package/src/release-manifest.ts +2 -0
  175. package/src/remote-mcp-edge.ts +137 -0
  176. package/src/remote-mcp.test.ts +690 -0
  177. package/src/remote-mcp.ts +524 -0
  178. package/src/runtime-secrets.test.ts +31 -6
  179. package/src/runtime-secrets.ts +41 -3
  180. package/src/seal-key-rotation.test.ts +131 -0
  181. package/src/seal-key-rotation.ts +258 -0
  182. package/src/stack-contract-parity.test.ts +20 -3
  183. package/src/stack-contract.test.ts +32 -2
  184. package/src/stack-contract.ts +363 -4
  185. package/src/stack-converge-secrets.test.ts +4 -0
  186. package/src/stack-converge.test.ts +21 -5
  187. package/src/stack-converge.ts +162 -7
  188. package/src/state-attach.test.ts +7 -1
  189. package/src/state-lock-lease.test.ts +302 -0
  190. package/src/state-lock-lease.ts +423 -0
  191. package/src/state-lock.test.ts +568 -0
  192. package/src/state-store.ts +347 -27
  193. package/src/upgrade/app-roll.ts +102 -21
  194. package/src/upgrade/audit-job-roll.ts +3 -1
  195. package/src/upgrade/converge.ts +148 -10
  196. package/src/upgrade/gcp-lookup.test.ts +80 -0
  197. package/src/upgrade/gcp-lookup.ts +65 -8
  198. package/src/upgrade/record-rolled.test.ts +105 -0
  199. package/src/upgrade/record-rolled.ts +91 -0
  200. package/src/upgrade/upgrade.test.ts +203 -3
  201. package/src/upgrade/upgrade.ts +120 -24
  202. package/src/upgrader-assess.test.ts +92 -3
  203. package/src/upgrader-assess.ts +58 -9
  204. package/src/upgrader-gcp.ts +47 -8
  205. package/src/upgrader-iam-preflight.test.ts +393 -0
  206. package/src/upgrader-iam-preflight.ts +510 -0
  207. package/src/upgrader-job.test.ts +586 -15
  208. package/src/upgrader-job.ts +237 -53
  209. package/src/upgrader-records.ts +151 -0
  210. package/src/upgrader-reporter.test.ts +6 -0
  211. package/src/upgrader-reporter.ts +7 -2
  212. package/src/upgrader-setup.test.ts +3 -3
  213. package/src/upgrader-setup.ts +128 -11
  214. package/src/wizards/gcp-cost.ts +12 -3
  215. package/src/wizards/gcp-pulumi.test.ts +15 -0
  216. package/src/wizards/gcp-pulumi.ts +18 -2
  217. package/src/wizards/gcp.ts +134 -4
  218. package/stacks/aws/index.ts +10 -0
  219. package/stacks/azure/index.ts +9 -0
  220. package/stacks/gcp/index.ts +47 -7
  221. package/stacks/gcp-firebase/index.ts +38 -6
@@ -0,0 +1,228 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+ /**
3
+ * Set up (and converge) the converge job on a Google Cloud install (`gcp`,
4
+ * `gcp-firebase`), #819. Run by the wizard (laptop, Cloud Shell or the
5
+ * install job) after migrations and by every converge — `cms-provision
6
+ * upgrade` and the converge job itself — so the job always runs the ops
7
+ * image of the release the install last converged to.
8
+ *
9
+ * What it is. A release that raises the infrastructure contract
10
+ * (infra-contract.ts) needs rights the upgrader job deliberately lacks:
11
+ * IAM bindings, service accounts, secrets. Until #819 that meant the Owner
12
+ * ran the upgrade from a terminal with their own Google account. The
13
+ * converge job runs the very same upgrade (upgrade/upgrade.ts: backup,
14
+ * converge, roll, health check, rollback, records) from inside the
15
+ * install's project, as the install's existing `caelo-provisioner` service
16
+ * account — the account the install job already runs `pulumi up` as and
17
+ * that already holds every right a converge needs (gcloud.ts
18
+ * PROVISIONER_ROLES). No new privileged identity is created.
19
+ *
20
+ * Trust boundary — the same shape as the upgrader job (upgrader-setup.ts)
21
+ * and the operator-access job. The admin's runtime service account gets
22
+ * exactly two roles, both on this one job:
23
+ *
24
+ * - `roles/run.jobsExecutor`: `run.jobs.run` WITHOUT
25
+ * `run.jobs.runWithOverrides` — it may start the job, never change its
26
+ * image, command, args or env (cloud.google.com/run/docs/reference/iam/roles);
27
+ * - `roles/run.viewer`: read its executions (the page's "Continue" for a
28
+ * run that died, and the dead-run check).
29
+ *
30
+ * The job takes no input but the database: its only input is the request
31
+ * row's target version. It fetches that release's manifest from the FIXED
32
+ * registry, verifies its signature with cosign, refuses a downgrade or a
33
+ * release below `minFromVersion`, and then runs only that release's signed
34
+ * ops image, pinned by the digest the manifest names (converge-job.ts). So a
35
+ * compromised admin can at most make the install converge to a newer
36
+ * signed Caelo release — what the Owner's click does anyway.
37
+ *
38
+ * What the provisioner gets here, on top of what it holds since install:
39
+ *
40
+ * - {@link INSTALL_JOB_ROLES} (the operator-access step of a converge
41
+ * creates a custom role and a Cloud Scheduler job, and the job's lines
42
+ * must reach Cloud Logging) — what an install-job install already
43
+ * grants; not an escalation, the provisioner holds projectIamAdmin;
44
+ * - a Cloud SQL IAM database login that is a member of `platform_updater`
45
+ * only (migration 0267): it reads its request and writes the timeline,
46
+ * nothing else in the database.
47
+ */
48
+ import { ensureCloudSqlIamUser } from "./cloudsql-iam-user.js";
49
+ import { gcloud as defaultGcloud } from "./gcloud.js";
50
+ import { realSleep, runWithRetry } from "./gcloud-retry.js";
51
+ import { convergeJobName, runServiceAccountEmail } from "./gcp-names.js";
52
+ import { INSTALL_JOB_ROLES } from "./install-job.js";
53
+ import { provisionerServiceAccountEmail } from "./provisioner-identity.js";
54
+ import { opsBucketName } from "./state-backend.js";
55
+ import { UPGRADER_DB_ROLE } from "./upgrader-setup.js";
56
+ /**
57
+ * The job's task timeout in minutes, and the lease on the state lock it
58
+ * holds (state-lock-lease.ts): after it, Cloud Run has killed the run and a
59
+ * new execution resumes from the checkpoint. A converge plus a roll takes
60
+ * 10–20 minutes; the live 0.10.51 run needed 7 before its roll.
61
+ */
62
+ export const CONVERGE_TASK_TIMEOUT_MINUTES = 60;
63
+ /**
64
+ * Exactly what the admin's runtime service account may do on the converge
65
+ * job — start it (no overrides) and read its runs. The setup grants these
66
+ * two and nothing else; the tests assert it.
67
+ */
68
+ export const CONVERGE_ADMIN_ROLES = ["roles/run.jobsExecutor", "roles/run.viewer"];
69
+ /**
70
+ * The job's entry point and its one argument. `caelo-converge-job`
71
+ * (packages/provisioning/ops/caelo-converge-job, installed by
72
+ * Dockerfile.ops) refuses every other argument and starts the job with a
73
+ * scrubbed environment, so even an execution override could not turn the
74
+ * provisioner's job into `destroy` or point gcloud/Pulumi elsewhere
75
+ * (#883 review). Nobody holds run.jobs.runWithOverrides on it anyway.
76
+ */
77
+ export const CONVERGE_JOB_ENTRYPOINT = "caelo-converge-job";
78
+ export const CONVERGE_JOB_COMMAND = "converge-job";
79
+ /** Resources: the converge runs gcloud + Pulumi's Node language host for the stack pin. */
80
+ const JOB_CPU = "2";
81
+ const JOB_MEMORY = "4Gi";
82
+ /**
83
+ * The `gcloud run jobs deploy` argv of the converge job: the ops image by
84
+ * digest, the provisioner as its identity, no env and no secrets, the one
85
+ * fixed subcommand. Exported for the tests that pin the boundary.
86
+ */
87
+ export function convergeJobDeployArgs(target) {
88
+ return [
89
+ "run",
90
+ "jobs",
91
+ "deploy",
92
+ convergeJobName(target.env),
93
+ `--image=${target.opsImageRef}`,
94
+ `--region=${target.region}`,
95
+ `--project=${target.projectId}`,
96
+ `--service-account=${provisionerServiceAccountEmail(target.projectId)}`,
97
+ `--network=${target.network}`,
98
+ `--subnet=${target.subnet}`,
99
+ "--vpc-egress=private-ranges-only",
100
+ `--command=${CONVERGE_JOB_ENTRYPOINT}`,
101
+ `--args=${CONVERGE_JOB_COMMAND}`,
102
+ // No env and no secrets on the job, whatever an earlier deploy set.
103
+ "--clear-env-vars",
104
+ "--clear-secrets",
105
+ "--tasks=1",
106
+ // A retry would start a second converge inside the same execution; a
107
+ // killed run is resumed by starting the job again (checkpointed).
108
+ "--max-retries=0",
109
+ `--task-timeout=${CONVERGE_TASK_TIMEOUT_MINUTES}m`,
110
+ `--cpu=${JOB_CPU}`,
111
+ `--memory=${JOB_MEMORY}`,
112
+ "--labels=caelo-role=converge",
113
+ "--quiet",
114
+ ];
115
+ }
116
+ /** Ensure the converge job and everything it needs. Never throws for a gcloud failure. */
117
+ export async function ensureConvergeJob(target, deps = {}) {
118
+ const run = deps.run ?? defaultGcloud;
119
+ const sleep = deps.sleep ?? realSleep;
120
+ const retry = (args) => runWithRetry(run, sleep, args);
121
+ const { projectId, region, env } = target;
122
+ const project = `--project=${projectId}`;
123
+ const provisioner = provisionerServiceAccountEmail(projectId);
124
+ const member = `serviceAccount:${provisioner}`;
125
+ const adminMember = `serviceAccount:${runServiceAccountEmail(projectId, env)}`;
126
+ const job = convergeJobName(env);
127
+ const bucket = opsBucketName(projectId);
128
+ const done = [];
129
+ const fail = (step, r) => ({
130
+ ok: false,
131
+ done,
132
+ error: `${step}: ${r.stderr.trim()}`,
133
+ });
134
+ if (!/@sha256:[0-9a-f]{64}$/.test(target.opsImageRef)) {
135
+ return {
136
+ ok: false,
137
+ done,
138
+ error: `the converge job's image must be pinned by digest, got ${target.opsImageRef}`,
139
+ };
140
+ }
141
+ // The job attaches the install from its ops bucket and keeps its
142
+ // checkpoints there; an install whose state is on a laptop has neither.
143
+ const bucketCheck = await run([
144
+ "storage",
145
+ "buckets",
146
+ "describe",
147
+ `gs://${bucket}`,
148
+ project,
149
+ "--format=value(name)",
150
+ ]);
151
+ if (!bucketCheck.ok) {
152
+ return {
153
+ ok: false,
154
+ done,
155
+ error: `the ops bucket gs://${bucket} is not there (${bucketCheck.stderr.trim()}). Move the install's state into its own project first: cms-provision state migrate`,
156
+ };
157
+ }
158
+ // The job's identity is the install's provisioner — never created here:
159
+ // an install without one (installed before it existed) keeps converging
160
+ // from a terminal.
161
+ const describe = await run([
162
+ "iam",
163
+ "service-accounts",
164
+ "describe",
165
+ provisioner,
166
+ project,
167
+ "--format=value(email)",
168
+ ]);
169
+ if (!describe.ok) {
170
+ return {
171
+ ok: false,
172
+ done,
173
+ reason: "no-provisioner",
174
+ error: `this install has no ${provisioner} service account (${describe.stderr.trim()}), which the converge job runs as. Updates that change the cloud setup keep running from a terminal (Settings → Updates shows the command).`,
175
+ };
176
+ }
177
+ // 1. What a converge run as the provisioner needs beyond the install roles.
178
+ for (const role of INSTALL_JOB_ROLES) {
179
+ const r = await retry([
180
+ "projects",
181
+ "add-iam-policy-binding",
182
+ projectId,
183
+ `--member=${member}`,
184
+ `--role=${role}`,
185
+ "--condition=None",
186
+ "--quiet",
187
+ "--format=none",
188
+ ]);
189
+ if (!r.ok)
190
+ return fail(`grant the provisioner ${role}`, r);
191
+ done.push(`provisioner: ${role} on the project`);
192
+ }
193
+ // 2. Its database login: the request row and the timeline, nothing else.
194
+ const sqlUser = await ensureCloudSqlIamUser(run, {
195
+ projectId,
196
+ env,
197
+ serviceAccountEmail: provisioner,
198
+ databaseRole: UPGRADER_DB_ROLE,
199
+ });
200
+ done.push(...sqlUser.done);
201
+ if (!sqlUser.ok)
202
+ return { ok: false, done, error: sqlUser.error };
203
+ // 3. The job itself.
204
+ const deploy = await run(convergeJobDeployArgs(target));
205
+ if (!deploy.ok)
206
+ return fail(`deploy job ${job}`, deploy);
207
+ done.push(`job ${job} (${target.opsImageRef.split("@")[1]?.slice(0, 19)}...)`);
208
+ // 4. The admin may start it (no overrides) and read its runs — nothing else.
209
+ for (const role of CONVERGE_ADMIN_ROLES) {
210
+ const r = await retry([
211
+ "run",
212
+ "jobs",
213
+ "add-iam-policy-binding",
214
+ job,
215
+ `--region=${region}`,
216
+ project,
217
+ `--member=${adminMember}`,
218
+ `--role=${role}`,
219
+ "--quiet",
220
+ "--format=none",
221
+ ]);
222
+ if (!r.ok)
223
+ return fail(`grant the admin ${role} on ${job}`, r);
224
+ done.push(`admin: ${role} on ${job}`);
225
+ }
226
+ return { ok: true, done };
227
+ }
228
+ //# sourceMappingURL=converge-job-setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"converge-job-setup.js","sourceRoot":"","sources":["../src/converge-job-setup.ts"],"names":[],"mappings":"AAAA,mCAAmC;AAEnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,MAAM,IAAI,aAAa,EAAqB,MAAM,aAAa,CAAC;AACzE,OAAO,EAAqB,SAAS,EAAE,YAAY,EAAc,MAAM,mBAAmB,CAAC;AAC3F,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,8BAA8B,EAAE,MAAM,2BAA2B,CAAC;AAC3E,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,EAAE,CAAC;AAEhD;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,wBAAwB,EAAE,kBAAkB,CAAU,CAAC;AAE5F;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,oBAAoB,CAAC;AAC5D,MAAM,CAAC,MAAM,oBAAoB,GAAG,cAAc,CAAC;AAEnD,2FAA2F;AAC3F,MAAM,OAAO,GAAG,GAAG,CAAC;AACpB,MAAM,UAAU,GAAG,KAAK,CAAC;AA6BzB;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAA2B;IAC/D,OAAO;QACL,KAAK;QACL,MAAM;QACN,QAAQ;QACR,eAAe,CAAC,MAAM,CAAC,GAAG,CAAC;QAC3B,WAAW,MAAM,CAAC,WAAW,EAAE;QAC/B,YAAY,MAAM,CAAC,MAAM,EAAE;QAC3B,aAAa,MAAM,CAAC,SAAS,EAAE;QAC/B,qBAAqB,8BAA8B,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE;QACvE,aAAa,MAAM,CAAC,OAAO,EAAE;QAC7B,YAAY,MAAM,CAAC,MAAM,EAAE;QAC3B,kCAAkC;QAClC,aAAa,uBAAuB,EAAE;QACtC,UAAU,oBAAoB,EAAE;QAChC,oEAAoE;QACpE,kBAAkB;QAClB,iBAAiB;QACjB,WAAW;QACX,qEAAqE;QACrE,kEAAkE;QAClE,iBAAiB;QACjB,kBAAkB,6BAA6B,GAAG;QAClD,SAAS,OAAO,EAAE;QAClB,YAAY,UAAU,EAAE;QACxB,8BAA8B;QAC9B,SAAS;KACV,CAAC;AACJ,CAAC;AAED,0FAA0F;AAC1F,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,MAA2B,EAC3B,OAA8C,EAAE;IAEhD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,aAAa,CAAC;IACtC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,SAAS,CAAC;IACtC,MAAM,KAAK,GAAG,CAAC,IAAc,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IACjE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC;IAC1C,MAAM,OAAO,GAAG,aAAa,SAAS,EAAE,CAAC;IACzC,MAAM,WAAW,GAAG,8BAA8B,CAAC,SAAS,CAAC,CAAC;IAC9D,MAAM,MAAM,GAAG,kBAAkB,WAAW,EAAE,CAAC;IAC/C,MAAM,WAAW,GAAG,kBAAkB,sBAAsB,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE,CAAC;IAC/E,MAAM,GAAG,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IACjC,MAAM,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;IACxC,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,CAAe,EAAuB,EAAE,CAAC,CAAC;QACpE,EAAE,EAAE,KAAK;QACT,IAAI;QACJ,KAAK,EAAE,GAAG,IAAI,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE;KACrC,CAAC,CAAC;IAEH,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC;QACtD,OAAO;YACL,EAAE,EAAE,KAAK;YACT,IAAI;YACJ,KAAK,EAAE,0DAA0D,MAAM,CAAC,WAAW,EAAE;SACtF,CAAC;IACJ,CAAC;IAED,iEAAiE;IACjE,wEAAwE;IACxE,MAAM,WAAW,GAAG,MAAM,GAAG,CAAC;QAC5B,SAAS;QACT,SAAS;QACT,UAAU;QACV,QAAQ,MAAM,EAAE;QAChB,OAAO;QACP,sBAAsB;KACvB,CAAC,CAAC;IACH,IAAI,CAAC,WAAW,CAAC,EAAE,EAAE,CAAC;QACpB,OAAO;YACL,EAAE,EAAE,KAAK;YACT,IAAI;YACJ,KAAK,EAAE,uBAAuB,MAAM,kBAAkB,WAAW,CAAC,MAAM,CAAC,IAAI,EAAE,qFAAqF;SACrK,CAAC;IACJ,CAAC;IAED,wEAAwE;IACxE,wEAAwE;IACxE,mBAAmB;IACnB,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC;QACzB,KAAK;QACL,kBAAkB;QAClB,UAAU;QACV,WAAW;QACX,OAAO;QACP,uBAAuB;KACxB,CAAC,CAAC;IACH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,OAAO;YACL,EAAE,EAAE,KAAK;YACT,IAAI;YACJ,MAAM,EAAE,gBAAgB;YACxB,KAAK,EAAE,uBAAuB,WAAW,qBAAqB,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,6IAA6I;SAClO,CAAC;IACJ,CAAC;IAED,4EAA4E;IAC5E,KAAK,MAAM,IAAI,IAAI,iBAAiB,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,MAAM,KAAK,CAAC;YACpB,UAAU;YACV,wBAAwB;YACxB,SAAS;YACT,YAAY,MAAM,EAAE;YACpB,UAAU,IAAI,EAAE;YAChB,kBAAkB;YAClB,SAAS;YACT,eAAe;SAChB,CAAC,CAAC;QACH,IAAI,CAAC,CAAC,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC,yBAAyB,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;QAC3D,IAAI,CAAC,IAAI,CAAC,gBAAgB,IAAI,iBAAiB,CAAC,CAAC;IACnD,CAAC;IAED,yEAAyE;IACzE,MAAM,OAAO,GAAG,MAAM,qBAAqB,CAAC,GAAG,EAAE;QAC/C,SAAS;QACT,GAAG;QACH,mBAAmB,EAAE,WAAW;QAChC,YAAY,EAAE,gBAAgB;KAC/B,CAAC,CAAC;IACH,IAAI,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,CAAC,OAAO,CAAC,EAAE;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;IAElE,qBAAqB;IACrB,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAC,CAAC;IACxD,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC,cAAc,GAAG,EAAE,EAAE,MAAM,CAAC,CAAC;IACzD,IAAI,CAAC,IAAI,CAAC,OAAO,GAAG,KAAK,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC;IAE/E,6EAA6E;IAC7E,KAAK,MAAM,IAAI,IAAI,oBAAoB,EAAE,CAAC;QACxC,MAAM,CAAC,GAAG,MAAM,KAAK,CAAC;YACpB,KAAK;YACL,MAAM;YACN,wBAAwB;YACxB,GAAG;YACH,YAAY,MAAM,EAAE;YACpB,OAAO;YACP,YAAY,WAAW,EAAE;YACzB,UAAU,IAAI,EAAE;YAChB,SAAS;YACT,eAAe;SAChB,CAAC,CAAC;QACH,IAAI,CAAC,CAAC,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC,mBAAmB,IAAI,OAAO,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;QAC/D,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,OAAO,GAAG,EAAE,CAAC,CAAC;IACxC,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,141 @@
1
+ /**
2
+ * The converge job (#819): installs a signed Caelo release that changes the
3
+ * install's cloud setup (raises the infrastructure contract), from inside
4
+ * the install's own project, started by the Owner's Update + password in
5
+ * Settings → Updates. It runs as the install's `caelo-provisioner` account
6
+ * (converge-job-setup.ts has the trust boundary); the upgrader job, which
7
+ * only rolls images, keeps every other release.
8
+ *
9
+ * One job, two stages, told apart by the release this code belongs to:
10
+ *
11
+ * Stage 1 — the job still runs the ops image of the INSTALLED release.
12
+ * It claims the request, reads install.json from the ops bucket,
13
+ * fetches the target's release manifest from the FIXED registry and
14
+ * verifies its signature (release-manifest.ts), and decides
15
+ * (upgrader-assess.ts, `route: "converge"`): no downgrade, nothing
16
+ * below minFromVersion, no automatic converge. Then it verifies the
17
+ * target's ops image, points this job at it BY DIGEST and starts a new
18
+ * execution, handing the request over to it. It changes nothing else:
19
+ * the converge logic always comes from the release it installs.
20
+ *
21
+ * Stage 2 — the job runs the TARGET release's ops image (its own
22
+ * version is the request's). It verifies the manifest again, checks
23
+ * that the image it runs is the one the manifest signs, takes the
24
+ * install's state lock (#815; the upgrader job and the terminal
25
+ * `upgrade` take the same one, so they exclude each other), and runs
26
+ * the very upgrade the terminal runs (upgrade/upgrade.ts): backup →
27
+ * converge + app-roll → deep health check, rollback on failure →
28
+ * install.json + the infrastructure contract → the request's end.
29
+ *
30
+ * Resume. Every stage-2 fact a successor must not recompute (the backup,
31
+ * the revisions that served before, the release it judges from) is in the
32
+ * checkpoint (converge-checkpoint.ts). A killed execution leaves its
33
+ * request `running`; starting the job again (the page's "Continue", or
34
+ * any new request for the same release) adopts it once Cloud Run says the
35
+ * old execution has ended, and resumes from the checkpoint.
36
+ *
37
+ * Inputs. The job takes no argument and no env: the request row (a
38
+ * version), install.json in the ops bucket (which the admin cannot write)
39
+ * and the signed manifest decide everything. All side effects come in
40
+ * through {@link ConvergeJobDeps}, so the run is tested against fakes;
41
+ * converge-job-gcp.ts wires the real ones and `@caelo-cms/platform-updater`
42
+ * the database (`caelo-ops converge-job`).
43
+ */
44
+ import type { ConvergeCheckpoint } from "./converge-checkpoint.js";
45
+ import { type ExecutionState } from "./install-job.js";
46
+ import type { InstallMetadata } from "./install-state.js";
47
+ import { type ReleaseManifest, type ReleaseManifestResult } from "./release-manifest.js";
48
+ import type { UpgradeCheckpoint } from "./upgrade/upgrade.js";
49
+ import { type ClaimedUpdate, type UpdateQueue, type UpgraderRunResult } from "./upgrader-job.js";
50
+ import { type EventReporter, type JobLog } from "./upgrader-reporter.js";
51
+ /** What the converge claim found. */
52
+ export interface ConvergeClaim {
53
+ readonly request: ClaimedUpdate;
54
+ /**
55
+ * The request was already running: stage 1 handed it to this run, or a
56
+ * run that died left it. Fresh claims (pending → running) are false.
57
+ */
58
+ readonly resume: boolean;
59
+ /** The execution the request names now (after a fresh claim: this one). */
60
+ readonly execution: string | null;
61
+ }
62
+ /** The converge job's view of the database (the platform_updater role). */
63
+ export interface ConvergeQueue extends Omit<UpdateQueue, "claim"> {
64
+ /**
65
+ * End runs that died long ago (stale), then return the running converge
66
+ * request, or claim the oldest pending one.
67
+ */
68
+ claimConverge(execution: string | null): Promise<ConvergeClaim | null>;
69
+ /**
70
+ * Point running request `requestId` at execution `to` — or at none
71
+ * (`null`: released for the next run to adopt) — only while it still
72
+ * names `from`. Resolves false when another run got there first.
73
+ */
74
+ handover(requestId: string, from: string | null, to: string | null): Promise<boolean>;
75
+ }
76
+ /** How stage 2's upgrade ended (upgrade/upgrade.ts, seen from the job). */
77
+ export type ConvergeUpgradeOutcome = {
78
+ readonly ok: true;
79
+ /** Every converge step succeeded and the new contract is recorded. */
80
+ readonly converged: boolean;
81
+ } | {
82
+ readonly ok: false;
83
+ /** Traffic went back to the revisions that served before. */
84
+ readonly rolledBack: boolean;
85
+ /** The new release serves but install.json could not record it (#860). */
86
+ readonly unrecorded: boolean;
87
+ };
88
+ export interface ConvergeJobDeps {
89
+ readonly queue: ConvergeQueue;
90
+ /** From the metadata server — never from env or argv. */
91
+ readonly projectId: string;
92
+ /** This Cloud Run execution; null outside Cloud Run. */
93
+ readonly execution: string | null;
94
+ /** The Caelo release this code belongs to (CAELO_VERSION of the image). */
95
+ readonly ownVersion: string;
96
+ /** fetchVerifiedReleaseManifest against the fixed registry. */
97
+ readonly fetchManifest: (version: string) => Promise<ReleaseManifestResult>;
98
+ /** install.json as the ops bucket holds it NOW (null when absent) — never cached. */
99
+ readonly readInstall: () => InstallMetadata | null;
100
+ /** Verify the cosign signature of the release image `slug@digest`. */
101
+ readonly verifyImage: (slug: "ops", digest: string) => Promise<boolean>;
102
+ /** The digest of the image this execution runs (from Cloud Run); null when unreadable. */
103
+ readonly ownImageDigest: () => Promise<string | null>;
104
+ /** Point this job at the ops image with `digest` (its identity, command and args stay). */
105
+ readonly pointJobAt: (digest: string) => Promise<{
106
+ readonly ok: true;
107
+ } | {
108
+ readonly ok: false;
109
+ readonly error: string;
110
+ }>;
111
+ /** Start a new execution of this job; returns its name. */
112
+ readonly startJob: () => Promise<{
113
+ readonly ok: true;
114
+ readonly execution: string;
115
+ } | {
116
+ readonly ok: false;
117
+ readonly error: string;
118
+ }>;
119
+ /** Where an execution of this job stands (Cloud Run). */
120
+ readonly executionState: (execution: string) => Promise<ExecutionState>;
121
+ /**
122
+ * Take the install's state lock (#815); returns its release. Throws
123
+ * StateLockHeldError when a live run holds it, any other error when it
124
+ * cannot be taken or judged.
125
+ */
126
+ readonly lockState: (onBreak: (message: string) => void) => () => void;
127
+ readonly readCheckpoint: () => ConvergeCheckpoint | null;
128
+ readonly writeCheckpoint: (checkpoint: ConvergeCheckpoint) => void;
129
+ /** Stage 2: the terminal's upgrade (upgrade/upgrade.ts) to `manifest`, as this account. */
130
+ readonly upgrade: (input: {
131
+ readonly manifest: ReleaseManifest;
132
+ readonly reporter: EventReporter;
133
+ readonly resume: UpgradeCheckpoint | undefined;
134
+ readonly onCheckpoint: (checkpoint: UpgradeCheckpoint) => void;
135
+ }) => Promise<ConvergeUpgradeOutcome>;
136
+ readonly now: () => Date;
137
+ readonly log: JobLog;
138
+ }
139
+ /** Run the converge job once. Never throws; see the file comment. */
140
+ export declare function runConvergeJob(deps: ConvergeJobDeps): Promise<UpgraderRunResult>;
141
+ //# sourceMappingURL=converge-job.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"converge-job.d.ts","sourceRoot":"","sources":["../src/converge-job.ts"],"names":[],"mappings":"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"}