@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
@@ -172,6 +172,48 @@ describe("fetchVerifiedReleaseManifest", () => {
172
172
  expect(r.ok ? "" : r.error.message).toContain("HTTP 404");
173
173
  });
174
174
 
175
+ it("regression #865: a missing manifest or blob (404) is not transient — a retry cannot find it", async () => {
176
+ const missing = await fetchVerifiedReleaseManifest(
177
+ "0.11.0",
178
+ fixture({ manifestStatus: 404 }).deps,
179
+ );
180
+ expect(missing.ok ? null : missing.error).toMatchObject({ code: "registry" });
181
+ expect(missing.ok ? null : missing.error.transient).toBeUndefined();
182
+ const { deps } = fixture();
183
+ const noBlob = await fetchVerifiedReleaseManifest("0.11.0", {
184
+ ...deps,
185
+ fetch: async (url, init) =>
186
+ url.includes("/blobs/") ? new Response("", { status: 404 }) : deps.fetch(url, init),
187
+ });
188
+ expect(noBlob.ok ? null : noBlob.error.code).toBe("registry");
189
+ expect(noBlob.ok ? null : noBlob.error.transient).toBeUndefined();
190
+ });
191
+
192
+ it("regression #865: 5xx, 429 and network failures are transient", async () => {
193
+ for (const status of [500, 503, 429]) {
194
+ const r = await fetchVerifiedReleaseManifest(
195
+ "0.11.0",
196
+ fixture({ manifestStatus: status }).deps,
197
+ );
198
+ expect(r.ok ? null : r.error).toMatchObject({ code: "registry", transient: true });
199
+ }
200
+ const { deps } = fixture();
201
+ const blob503 = await fetchVerifiedReleaseManifest("0.11.0", {
202
+ ...deps,
203
+ fetch: async (url, init) =>
204
+ url.includes("/blobs/") ? new Response("", { status: 503 }) : deps.fetch(url, init),
205
+ });
206
+ expect(blob503.ok ? null : blob503.error).toMatchObject({ code: "registry", transient: true });
207
+ const down = await fetchVerifiedReleaseManifest("0.11.0", {
208
+ ...deps,
209
+ fetch: async () => {
210
+ throw new TypeError("fetch failed: ECONNRESET");
211
+ },
212
+ });
213
+ expect(down.ok ? null : down.error).toMatchObject({ code: "registry", transient: true });
214
+ expect(down.ok ? "" : down.error.message).toContain("ECONNRESET");
215
+ });
216
+
175
217
  it("rejects a non-SemVer version without touching the network", async () => {
176
218
  const { deps, urls } = fixture();
177
219
  const r = await fetchVerifiedReleaseManifest("v0.11.0", deps);
@@ -129,6 +129,8 @@ export type ReleaseManifestErrorCode =
129
129
  export interface ReleaseManifestError {
130
130
  readonly code: ReleaseManifestErrorCode;
131
131
  readonly message: string;
132
+ /** On `registry` errors a retry may cure; absent for not-found (see @caelo-cms/shared ReleaseArtifactError). */
133
+ readonly transient?: true;
132
134
  }
133
135
 
134
136
  export type ReleaseManifestResult =
@@ -0,0 +1,137 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+
3
+ /**
4
+ * #806 — what the `gcp` stack's load balancer does for remote MCP, declared
5
+ * once for the stack (new installs, imported from `dist/`) and for
6
+ * `cms-provision upgrade` (remote-mcp.ts `ensureRemoteMcpEdge`, existing
7
+ * installs). Pure module: no CLI or Pulumi imports.
8
+ *
9
+ * Routing: the broker's discovery documents live at the host root
10
+ * (`/.well-known/oauth-*`); the URL map sends those prefixes to the gateway
11
+ * together with `/api/*` (`GCP_PUBLIC_BASE_PATHS` in @caelo-cms/shared).
12
+ *
13
+ * Cloud Armor: the stack's policy bans an IP for 10 minutes after 100
14
+ * requests a minute and denies anything the OWASP sqli/xss rule packs flag.
15
+ * Both are wrong for `/api/mcp/*`:
16
+ *
17
+ * - MCP bodies carry HTML legitimately (module markup, page content the
18
+ * agent writes through Caelo's tools), which the xss/sqli packs flag.
19
+ * The broker accepts only bearer-authenticated JSON-RPC that the
20
+ * IAP-gated admin validates with Zod; Cloud Armor's signature match
21
+ * adds false positives there, not protection.
22
+ * - claude.ai and ChatGPT call from their own shared egress IPs, so every
23
+ * user of an install arrives from a handful of addresses. One agent
24
+ * session issues a JSON-RPC POST per tool call (plus initialize and
25
+ * notifications) — a busy Power-MCP agent sustains 1–3 a second. A
26
+ * 100/min ban would cut every user behind the same connector for 10
27
+ * minutes after one busy session.
28
+ *
29
+ * So the MCP paths ({@link MCP_ARMOR_PATH_PATTERN}) get their own rule, evaluated before the general ones
30
+ * (lower priority number = earlier; the first matching rule decides): a
31
+ * per-IP *throttle* of 600 requests a minute — about five busy agent
32
+ * sessions per egress IP — answering 429 above it without a ban, so a burst
33
+ * slows down instead of locking everyone out. Below the threshold the
34
+ * rule's action is `allow`, which ends evaluation: the OWASP packs and the
35
+ * general ban do not run for these paths. Abuse stays bounded by the
36
+ * gateway's max instances (100) and by IAP, which rejects every request
37
+ * without a valid token of an allowlisted user before it reaches the admin.
38
+ */
39
+
40
+ /**
41
+ * The paths the MCP rule covers, as an anchored RE2 pattern (Cloud Armor's
42
+ * `matches`): the two MCP endpoints and the broker's `oauth/<name>` and
43
+ * `oauth/<name>/<surface>` routes — lowercase letters and hyphens only.
44
+ *
45
+ * Deliberately not a prefix match. A `startsWith('/api/mcp/')` rule also
46
+ * matches `/api/mcp/../forms/submit` or `/api/mcp/%2e%2e/comments`, which
47
+ * the gateway's URL parser normalises to `/api/forms/submit` — so any
48
+ * public write could borrow the MCP rule's 600/min throttle and skip the
49
+ * OWASP packs and the ban. No dot, no percent sign and no extra segment can
50
+ * match this pattern, so a traversal falls through to the general rules.
51
+ * The dotted metadata aliases (`/.well-known/…`, `oauth/.well-known/…`) are
52
+ * one-off GETs per connection and stay under the general rules.
53
+ */
54
+ export const MCP_ARMOR_PATH_PATTERN = "^/api/mcp/(chat|admin|oauth/[a-z-]+(/[a-z-]+)?)$";
55
+
56
+ /** The general per-IP ban rule and the OWASP rule packs the MCP rule must precede. */
57
+ export const GENERAL_RATE_LIMIT_PRIORITY = 1000;
58
+ export const OWASP_RULE_PRIORITIES: readonly number[] = [2000, 2001];
59
+
60
+ /** The `/api/mcp/*` rule of the gateway's Cloud Armor policy (see the module comment). */
61
+ export const MCP_ARMOR_RULE = {
62
+ priority: 900,
63
+ description: "Caelo remote MCP: per-IP throttle sized for agent traffic; skips the OWASP packs",
64
+ expression: `request.path.matches('${MCP_ARMOR_PATH_PATTERN}')`,
65
+ action: "throttle",
66
+ /** Requests per {@link MCP_ARMOR_RULE.intervalSec} per source IP. */
67
+ count: 600,
68
+ intervalSec: 60,
69
+ conformAction: "allow",
70
+ exceedAction: "deny(429)",
71
+ enforceOnKey: "IP",
72
+ } as const;
73
+
74
+ /** {@link MCP_ARMOR_RULE} in the shape `gcp.compute.SecurityPolicy` `rules` take. */
75
+ export function mcpArmorRuleArgs() {
76
+ const r = MCP_ARMOR_RULE;
77
+ return {
78
+ action: r.action,
79
+ priority: r.priority,
80
+ description: r.description,
81
+ match: { expr: { expression: r.expression } },
82
+ rateLimitOptions: {
83
+ rateLimitThreshold: { count: r.count, intervalSec: r.intervalSec },
84
+ conformAction: r.conformAction,
85
+ exceedAction: r.exceedAction,
86
+ enforceOnKey: r.enforceOnKey,
87
+ },
88
+ };
89
+ }
90
+
91
+ /**
92
+ * The `gcloud compute security-policies rules create|update` flags for
93
+ * {@link MCP_ARMOR_RULE} (without the verb, priority and policy).
94
+ */
95
+ export function mcpArmorRuleFlags(): string[] {
96
+ const r = MCP_ARMOR_RULE;
97
+ return [
98
+ `--description=${r.description}`,
99
+ `--expression=${r.expression}`,
100
+ `--action=${r.action}`,
101
+ `--rate-limit-threshold-count=${r.count}`,
102
+ `--rate-limit-threshold-interval-sec=${r.intervalSec}`,
103
+ `--conform-action=${r.conformAction}`,
104
+ // gcloud spells deny(429) as deny-429.
105
+ "--exceed-action=deny-429",
106
+ `--enforce-on-key=${r.enforceOnKey}`,
107
+ ];
108
+ }
109
+
110
+ /** A Cloud Armor rule as `gcloud compute security-policies describe --format=json` lists it. */
111
+ export interface LiveArmorRule {
112
+ readonly priority?: number;
113
+ readonly action?: string;
114
+ readonly match?: { readonly expr?: { readonly expression?: string } };
115
+ readonly rateLimitOptions?: {
116
+ readonly rateLimitThreshold?: { readonly count?: number; readonly intervalSec?: number };
117
+ readonly conformAction?: string;
118
+ readonly exceedAction?: string;
119
+ readonly enforceOnKey?: string;
120
+ };
121
+ }
122
+
123
+ /** Whether a live rule is exactly {@link MCP_ARMOR_RULE}. */
124
+ export function isMcpArmorRule(rule: LiveArmorRule): boolean {
125
+ const r = MCP_ARMOR_RULE;
126
+ const o = rule.rateLimitOptions;
127
+ return (
128
+ rule.priority === r.priority &&
129
+ rule.action === r.action &&
130
+ rule.match?.expr?.expression === r.expression &&
131
+ o?.rateLimitThreshold?.count === r.count &&
132
+ o?.rateLimitThreshold?.intervalSec === r.intervalSec &&
133
+ o?.conformAction === r.conformAction &&
134
+ o?.exceedAction === r.exceedAction &&
135
+ (o?.enforceOnKey ?? "ALL") === r.enforceOnKey
136
+ );
137
+ }