@caelo-cms/provisioning 0.11.10 → 0.11.12

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 (228) hide show
  1. package/dist/adapter.d.ts +6 -0
  2. package/dist/adapter.d.ts.map +1 -1
  3. package/dist/adopt-existing.d.ts +6 -2
  4. package/dist/adopt-existing.d.ts.map +1 -1
  5. package/dist/adopt-existing.js +57 -0
  6. package/dist/adopt-existing.js.map +1 -1
  7. package/dist/agent/command.d.ts +5 -2
  8. package/dist/agent/command.d.ts.map +1 -1
  9. package/dist/agent/command.js +1 -1
  10. package/dist/agent/command.js.map +1 -1
  11. package/dist/agent-db.js +196 -23
  12. package/dist/cli-routing.js +1 -1
  13. package/dist/cli-routing.js.map +1 -1
  14. package/dist/cli.js +59 -7
  15. package/dist/cli.js.map +1 -1
  16. package/dist/cloudsql-tls.d.ts +30 -0
  17. package/dist/cloudsql-tls.d.ts.map +1 -1
  18. package/dist/cloudsql-tls.js +7 -1
  19. package/dist/cloudsql-tls.js.map +1 -1
  20. package/dist/compose.d.ts +14 -0
  21. package/dist/compose.d.ts.map +1 -1
  22. package/dist/compose.js +54 -1
  23. package/dist/compose.js.map +1 -1
  24. package/dist/converge-db-trust.d.ts +44 -0
  25. package/dist/converge-db-trust.d.ts.map +1 -0
  26. package/dist/converge-db-trust.js +158 -0
  27. package/dist/converge-db-trust.js.map +1 -0
  28. package/dist/converge-job-gcp.d.ts +12 -2
  29. package/dist/converge-job-gcp.d.ts.map +1 -1
  30. package/dist/converge-job-gcp.js +42 -3
  31. package/dist/converge-job-gcp.js.map +1 -1
  32. package/dist/converge-job-preflight.d.ts +34 -2
  33. package/dist/converge-job-preflight.d.ts.map +1 -1
  34. package/dist/converge-job-preflight.js +76 -1
  35. package/dist/converge-job-preflight.js.map +1 -1
  36. package/dist/converge-job-setup.d.ts.map +1 -1
  37. package/dist/converge-job-setup.js +4 -1
  38. package/dist/converge-job-setup.js.map +1 -1
  39. package/dist/converge-job.d.ts +44 -3
  40. package/dist/converge-job.d.ts.map +1 -1
  41. package/dist/converge-job.js +105 -11
  42. package/dist/converge-job.js.map +1 -1
  43. package/dist/gcp-names.d.ts +13 -0
  44. package/dist/gcp-names.d.ts.map +1 -1
  45. package/dist/gcp-names.js +21 -0
  46. package/dist/gcp-names.js.map +1 -1
  47. package/dist/index.d.ts +2 -1
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +1 -0
  50. package/dist/index.js.map +1 -1
  51. package/dist/infra-contract.d.ts +1 -1
  52. package/dist/infra-contract.d.ts.map +1 -1
  53. package/dist/infra-contract.js +22 -1
  54. package/dist/infra-contract.js.map +1 -1
  55. package/dist/install-job.d.ts +10 -2
  56. package/dist/install-job.d.ts.map +1 -1
  57. package/dist/install-job.js +10 -1
  58. package/dist/install-job.js.map +1 -1
  59. package/dist/install-state.d.ts +7 -0
  60. package/dist/install-state.d.ts.map +1 -1
  61. package/dist/install-state.js.map +1 -1
  62. package/dist/lifecycle.d.ts.map +1 -1
  63. package/dist/lifecycle.js +3 -0
  64. package/dist/lifecycle.js.map +1 -1
  65. package/dist/mcp-auth-proxy-setup.d.ts +3 -1
  66. package/dist/mcp-auth-proxy-setup.d.ts.map +1 -1
  67. package/dist/mcp-auth-proxy-setup.js +9 -4
  68. package/dist/mcp-auth-proxy-setup.js.map +1 -1
  69. package/dist/migration-runner.d.ts.map +1 -1
  70. package/dist/migration-runner.js +8 -3
  71. package/dist/migration-runner.js.map +1 -1
  72. package/dist/migration-targets.d.ts +11 -0
  73. package/dist/migration-targets.d.ts.map +1 -0
  74. package/dist/migration-targets.js +12 -0
  75. package/dist/migration-targets.js.map +1 -0
  76. package/dist/operator-access.d.ts +10 -0
  77. package/dist/operator-access.d.ts.map +1 -1
  78. package/dist/operator-access.js +5 -0
  79. package/dist/operator-access.js.map +1 -1
  80. package/dist/pool-budget-stack.d.ts +13 -1
  81. package/dist/pool-budget-stack.d.ts.map +1 -1
  82. package/dist/pool-budget-stack.js +11 -1
  83. package/dist/pool-budget-stack.js.map +1 -1
  84. package/dist/public-role-isolation.d.ts +7 -3
  85. package/dist/public-role-isolation.d.ts.map +1 -1
  86. package/dist/public-role-isolation.js +25 -13
  87. package/dist/public-role-isolation.js.map +1 -1
  88. package/dist/release-contract.d.ts.map +1 -1
  89. package/dist/release-contract.js +3 -0
  90. package/dist/release-contract.js.map +1 -1
  91. package/dist/runtime-secrets.d.ts +2 -0
  92. package/dist/runtime-secrets.d.ts.map +1 -1
  93. package/dist/runtime-secrets.js +19 -15
  94. package/dist/runtime-secrets.js.map +1 -1
  95. package/dist/scheduler-setup.d.ts +75 -0
  96. package/dist/scheduler-setup.d.ts.map +1 -0
  97. package/dist/scheduler-setup.js +331 -0
  98. package/dist/scheduler-setup.js.map +1 -0
  99. package/dist/self-hosted-roles.d.ts +16 -9
  100. package/dist/self-hosted-roles.d.ts.map +1 -1
  101. package/dist/self-hosted-roles.js +49 -15
  102. package/dist/self-hosted-roles.js.map +1 -1
  103. package/dist/self-hosted-scheduler-interval.d.ts +21 -0
  104. package/dist/self-hosted-scheduler-interval.d.ts.map +1 -0
  105. package/dist/self-hosted-scheduler-interval.js +68 -0
  106. package/dist/self-hosted-scheduler-interval.js.map +1 -0
  107. package/dist/self-hosted-upgrade.d.ts +7 -4
  108. package/dist/self-hosted-upgrade.d.ts.map +1 -1
  109. package/dist/self-hosted-upgrade.js +9 -5
  110. package/dist/self-hosted-upgrade.js.map +1 -1
  111. package/dist/stack-contract.d.ts +67 -6
  112. package/dist/stack-contract.d.ts.map +1 -1
  113. package/dist/stack-contract.js +87 -6
  114. package/dist/stack-contract.js.map +1 -1
  115. package/dist/staging-database.d.ts +22 -0
  116. package/dist/staging-database.d.ts.map +1 -0
  117. package/dist/staging-database.js +117 -0
  118. package/dist/staging-database.js.map +1 -0
  119. package/dist/upgrade/converge.d.ts +5 -0
  120. package/dist/upgrade/converge.d.ts.map +1 -1
  121. package/dist/upgrade/converge.js +110 -10
  122. package/dist/upgrade/converge.js.map +1 -1
  123. package/dist/upgrade/upgrade.d.ts +6 -0
  124. package/dist/upgrade/upgrade.d.ts.map +1 -1
  125. package/dist/upgrade/upgrade.js +3 -0
  126. package/dist/upgrade/upgrade.js.map +1 -1
  127. package/dist/upgrader-gcp.d.ts +5 -4
  128. package/dist/upgrader-gcp.d.ts.map +1 -1
  129. package/dist/upgrader-gcp.js +5 -4
  130. package/dist/upgrader-gcp.js.map +1 -1
  131. package/dist/upgrader-iam-preflight.d.ts +8 -6
  132. package/dist/upgrader-iam-preflight.d.ts.map +1 -1
  133. package/dist/upgrader-iam-preflight.js +5 -1
  134. package/dist/upgrader-iam-preflight.js.map +1 -1
  135. package/dist/wizard.d.ts +7 -0
  136. package/dist/wizard.d.ts.map +1 -1
  137. package/dist/wizard.js +11 -0
  138. package/dist/wizard.js.map +1 -1
  139. package/dist/wizards/gcp-cost.d.ts +31 -0
  140. package/dist/wizards/gcp-cost.d.ts.map +1 -1
  141. package/dist/wizards/gcp-cost.js +82 -12
  142. package/dist/wizards/gcp-cost.js.map +1 -1
  143. package/dist/wizards/gcp.d.ts.map +1 -1
  144. package/dist/wizards/gcp.js +87 -11
  145. package/dist/wizards/gcp.js.map +1 -1
  146. package/package.json +2 -2
  147. package/src/adapter.ts +6 -0
  148. package/src/admin-memory.test.ts +1 -1
  149. package/src/adopt-existing.test.ts +46 -0
  150. package/src/adopt-existing.ts +58 -2
  151. package/src/agent/command.ts +6 -3
  152. package/src/caddy-redirects.test.ts +1 -1
  153. package/src/cli-routing.test.ts +1 -1
  154. package/src/cli-routing.ts +1 -1
  155. package/src/cli.ts +63 -8
  156. package/src/cloudsql-tls.test.ts +30 -0
  157. package/src/cloudsql-tls.ts +10 -3
  158. package/src/compose.ts +68 -0
  159. package/src/converge-db-trust.test.ts +237 -0
  160. package/src/converge-db-trust.ts +212 -0
  161. package/src/converge-job-gcp.ts +42 -2
  162. package/src/converge-job-preflight.test.ts +106 -0
  163. package/src/converge-job-preflight.ts +97 -3
  164. package/src/converge-job-setup.ts +4 -1
  165. package/src/converge-job.test.ts +213 -0
  166. package/src/converge-job.ts +175 -19
  167. package/src/database-tls-builders.test.ts +3 -2
  168. package/src/deployed-release.test.ts +3 -0
  169. package/src/gateway-env-isolation.test.ts +40 -7
  170. package/src/gcp-names.ts +25 -0
  171. package/src/index.test.ts +3 -3
  172. package/src/index.ts +6 -0
  173. package/src/infra-contract.test.ts +5 -3
  174. package/src/infra-contract.ts +22 -1
  175. package/src/install-job.ts +10 -1
  176. package/src/install-state.ts +7 -0
  177. package/src/lifecycle.ts +3 -0
  178. package/src/mcp-auth-proxy-setup.test.ts +33 -1
  179. package/src/mcp-auth-proxy-setup.ts +11 -4
  180. package/src/migration-runner.ts +14 -9
  181. package/src/migration-targets.ts +12 -0
  182. package/src/operator-access.test.ts +15 -0
  183. package/src/operator-access.ts +14 -0
  184. package/src/pool-budget-stack.ts +15 -0
  185. package/src/pool-budget-stacks.test.ts +38 -13
  186. package/src/public-role-isolation.test.ts +18 -8
  187. package/src/public-role-isolation.ts +31 -17
  188. package/src/release-contract.ts +3 -0
  189. package/src/runtime-secrets.test.ts +18 -8
  190. package/src/runtime-secrets.ts +37 -18
  191. package/src/scheduler-setup.test.ts +201 -0
  192. package/src/scheduler-setup.ts +375 -0
  193. package/src/self-hosted-backup.test.ts +1 -0
  194. package/src/self-hosted-compose.test.ts +72 -1
  195. package/src/self-hosted-roles.integration.test.ts +2 -0
  196. package/src/self-hosted-roles.test.ts +49 -19
  197. package/src/self-hosted-roles.ts +60 -20
  198. package/src/self-hosted-scheduler-interval.test.ts +66 -0
  199. package/src/self-hosted-scheduler-interval.ts +79 -0
  200. package/src/self-hosted-start.test.ts +1 -0
  201. package/src/self-hosted-upgrade.test.ts +2 -1
  202. package/src/self-hosted-upgrade.ts +14 -8
  203. package/src/stack-contract.test.ts +31 -3
  204. package/src/stack-contract.ts +113 -6
  205. package/src/stack-converge-secrets.test.ts +2 -0
  206. package/src/stack-converge.test.ts +6 -4
  207. package/src/staging-database.test.ts +114 -0
  208. package/src/staging-database.ts +148 -0
  209. package/src/upgrade/converge.ts +130 -9
  210. package/src/upgrade/upgrade.test.ts +85 -2
  211. package/src/upgrade/upgrade.ts +9 -0
  212. package/src/upgrader-gcp.ts +5 -4
  213. package/src/upgrader-iam-preflight.ts +15 -10
  214. package/src/wizard.ts +19 -0
  215. package/src/wizards/gcp-cost.test.ts +54 -1
  216. package/src/wizards/gcp-cost.ts +102 -12
  217. package/src/wizards/gcp.ts +109 -8
  218. package/stacks/aws/Pulumi.yaml +1 -1
  219. package/stacks/aws/index.ts +41 -1
  220. package/stacks/azure/index.ts +51 -3
  221. package/stacks/gcp/Pulumi.yaml +1 -1
  222. package/stacks/gcp/README.md +1 -0
  223. package/stacks/gcp/index.ts +50 -1
  224. package/stacks/gcp-firebase/Pulumi.yaml +1 -1
  225. package/stacks/gcp-firebase/index.ts +50 -1
  226. package/stacks/self-hosted/Pulumi.yaml +3 -0
  227. package/stacks/self-hosted/README.md +4 -0
  228. package/stacks/self-hosted/index.ts +29 -4
package/dist/agent-db.js CHANGED
@@ -18856,6 +18856,7 @@ async function recordAudit(tx, opts) {
18856
18856
  var SYSTEM_ACTOR_ID = "00000000-0000-0000-0000-00000000ffff";
18857
18857
 
18858
18858
  // ../query-api/src/branch-access.ts
18859
+ import { AsyncLocalStorage } from "async_hooks";
18859
18860
  var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
18860
18861
  function namedBranches(ctx, input) {
18861
18862
  const out = new Set;
@@ -18879,12 +18880,71 @@ function branchNotFound(operation, chatBranchId) {
18879
18880
  function firstMalformedBranch(branches) {
18880
18881
  return branches.find((b) => !UUID_RE2.test(b)) ?? null;
18881
18882
  }
18882
- async function firstInvisibleBranch(tx, branches) {
18883
+ var VERDICT_MAX_AGE_MS = 30000;
18884
+ var visibilityScope = new AsyncLocalStorage;
18885
+ function verdictKey(ctx, branch) {
18886
+ return `${ctx.actorKind}|${ctx.actorId}|${ctx.chatTaskId ?? ""}|${branch}`;
18887
+ }
18888
+ function rememberedVerdict(ctx, branch) {
18889
+ const scope = visibilityScope.getStore();
18890
+ if (!scope)
18891
+ return;
18892
+ const key = verdictKey(ctx, branch);
18893
+ const hit = scope.verdicts.get(key);
18894
+ if (!hit)
18895
+ return;
18896
+ if (Date.now() - hit.at > VERDICT_MAX_AGE_MS) {
18897
+ scope.verdicts.delete(key);
18898
+ return;
18899
+ }
18900
+ return hit.visible;
18901
+ }
18902
+ function rememberVerdict(ctx, branch, visible) {
18903
+ const scope = visibilityScope.getStore();
18904
+ if (!scope)
18905
+ return;
18906
+ const key = verdictKey(ctx, branch);
18907
+ const entry = { visible, at: Date.now() };
18908
+ scope.verdicts.set(key, entry);
18909
+ visible.catch(() => {
18910
+ if (scope.verdicts.get(key) === entry)
18911
+ scope.verdicts.delete(key);
18912
+ });
18913
+ }
18914
+ async function rememberedFirstInvisible(ctx, branches) {
18915
+ const pending = [];
18883
18916
  for (const branch of branches) {
18884
18917
  if (!UUID_RE2.test(branch))
18885
18918
  return branch;
18886
- const rows = await tx.execute(sql`SELECT caelo_branch_visible(${branch}::uuid) AS visible`);
18887
- if (rows[0]?.visible !== true)
18919
+ const visible = rememberedVerdict(ctx, branch);
18920
+ if (!visible)
18921
+ return;
18922
+ pending.push({ branch, visible });
18923
+ }
18924
+ for (const p of pending) {
18925
+ try {
18926
+ if (!await p.visible)
18927
+ return p.branch;
18928
+ } catch {
18929
+ return;
18930
+ }
18931
+ }
18932
+ return null;
18933
+ }
18934
+ async function firstInvisibleBranch(tx, branches, ctx) {
18935
+ for (const branch of branches) {
18936
+ if (!UUID_RE2.test(branch))
18937
+ return branch;
18938
+ let visible = ctx ? rememberedVerdict(ctx, branch) : undefined;
18939
+ if (!visible) {
18940
+ visible = (async () => {
18941
+ const rows = await tx.execute(sql`SELECT caelo_branch_visible(${branch}::uuid) AS visible`);
18942
+ return rows[0]?.visible === true;
18943
+ })();
18944
+ if (ctx)
18945
+ rememberVerdict(ctx, branch, visible);
18946
+ }
18947
+ if (!await visible)
18888
18948
  return branch;
18889
18949
  }
18890
18950
  return null;
@@ -19106,7 +19166,7 @@ async function provisionPluginSchemaInTx(tx, caller, opts) {
19106
19166
  }
19107
19167
 
19108
19168
  // ../query-api/src/pool-guard.ts
19109
- import { AsyncLocalStorage } from "async_hooks";
19169
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
19110
19170
  var POOL_ACQUIRE_TIMEOUT_MS = 1e4;
19111
19171
  var LONG_TRANSACTIONS = [
19112
19172
  {
@@ -19146,13 +19206,14 @@ class PoolAcquireTimeoutError extends Error {
19146
19206
  timeoutMs;
19147
19207
  name = "PoolAcquireTimeoutError";
19148
19208
  constructor(pool, requester, timeoutMs) {
19149
- super(`the ${pool} database pool had no free connection for ${requester} within ${timeoutMs} ms: every connection is busy. ` + "Nothing was changed. Retry in a few seconds; if it keeps happening the admin is overloaded or a request holds its connections \u2014 the server log names it.");
19209
+ super(`the ${pool} database pool had no free connection for ${requester} within ${timeoutMs} ms: every connection is busy. ` + `Nothing was changed. Retry in a few seconds (wait about ${Math.round(POOL_BUSY_RETRY_AFTER_MS / 1000)} s, then send the same call again unchanged); ` + "if it keeps happening the admin is overloaded or a request holds its connections \u2014 the server log names it.");
19150
19210
  this.pool = pool;
19151
19211
  this.requester = requester;
19152
19212
  this.timeoutMs = timeoutMs;
19153
19213
  }
19154
19214
  }
19155
- var joining = new AsyncLocalStorage;
19215
+ var POOL_BUSY_RETRY_AFTER_MS = 5000;
19216
+ var joining = new AsyncLocalStorage2;
19156
19217
  function newMarker(holder, root) {
19157
19218
  return {
19158
19219
  holder,
@@ -19168,8 +19229,8 @@ function newMarker(holder, root) {
19168
19229
 
19169
19230
  class PoolGuard {
19170
19231
  #stores = {
19171
- admin: new AsyncLocalStorage,
19172
- public: new AsyncLocalStorage
19232
+ admin: new AsyncLocalStorage2,
19233
+ public: new AsyncLocalStorage2
19173
19234
  };
19174
19235
  plan(pool, holder) {
19175
19236
  const outer = this.held(pool);
@@ -19231,6 +19292,50 @@ function detachedRunner(operation, open) {
19231
19292
  });
19232
19293
  }
19233
19294
 
19295
+ // ../query-api/src/pool-pressure.ts
19296
+ var BUN_DEFAULT_POOL_MAX = 10;
19297
+
19298
+ class PoolDemand {
19299
+ #pool;
19300
+ #max;
19301
+ #now;
19302
+ #next = 0;
19303
+ #waiting = new Map;
19304
+ #holding = new Map;
19305
+ constructor(pool, max, now = Date.now) {
19306
+ this.#pool = pool;
19307
+ this.#max = max;
19308
+ this.#now = now;
19309
+ }
19310
+ beginWait() {
19311
+ const id = this.#next++;
19312
+ this.#waiting.set(id, this.#now());
19313
+ return () => {
19314
+ this.#waiting.delete(id);
19315
+ };
19316
+ }
19317
+ beginHold(holder) {
19318
+ const id = this.#next++;
19319
+ this.#holding.set(id, { holder, since: this.#now() });
19320
+ return () => {
19321
+ this.#holding.delete(id);
19322
+ };
19323
+ }
19324
+ snapshot() {
19325
+ const now = this.#now();
19326
+ const firstWait = this.#waiting.values().next();
19327
+ const holders = [...this.#holding.values()].map((h) => ({ holder: h.holder, heldMs: now - h.since })).sort((a, b) => b.heldMs - a.heldMs);
19328
+ return {
19329
+ pool: this.#pool,
19330
+ max: this.#max,
19331
+ inUse: this.#holding.size,
19332
+ waiting: this.#waiting.size,
19333
+ oldestWaitMs: firstWait.done ? 0 : now - firstWait.value,
19334
+ holders
19335
+ };
19336
+ }
19337
+ }
19338
+
19234
19339
  // ../query-api/src/post-commit.ts
19235
19340
  var pendingByTx = new WeakMap;
19236
19341
  function collectPostCommitEffects(tx) {
@@ -19367,20 +19472,24 @@ class DatabaseAdapter {
19367
19472
  #adminRaw;
19368
19473
  #publicRaw;
19369
19474
  #adminUrl;
19475
+ #tls;
19370
19476
  #publicUrl;
19371
19477
  #listeners = new Map;
19372
19478
  #listenLost = new Map;
19373
19479
  #listenersClosing = false;
19374
19480
  #skipVerify;
19375
19481
  #roles;
19482
+ #publicDatabase;
19376
19483
  #acquireTimeoutMs;
19377
19484
  #guard = new PoolGuard;
19485
+ #demand;
19378
19486
  #verifyPromise = null;
19379
19487
  #projectionPublisher = null;
19380
19488
  #onStatement;
19381
19489
  #drizzleOptions;
19382
19490
  constructor(config) {
19383
19491
  this.#roles = config.expectedRoles ?? DEFAULT_ROLES;
19492
+ this.#publicDatabase = config.publicDatabase ?? "cms_public";
19384
19493
  if (config.adminDatabaseUrl === null !== (this.#roles.admin === null)) {
19385
19494
  throw new Error("DatabaseAdapter: a public-only adapter (adminDatabaseUrl: null) must expect no admin role, and an adapter with an admin URL must expect one");
19386
19495
  }
@@ -19388,7 +19497,9 @@ class DatabaseAdapter {
19388
19497
  const poolMax = resolvePoolMax(config.poolMax);
19389
19498
  const publicPoolMax = resolvePublicPoolMax(config.publicPoolMax, config.poolMax);
19390
19499
  const idleTimeout = config.poolIdleTimeoutSeconds ?? POOL_IDLE_TIMEOUT_SECONDS;
19391
- const open = (url, max) => new SQL3(url, max === undefined ? { idleTimeout } : { max, idleTimeout });
19500
+ this.#tls = config.tls ?? null;
19501
+ const tls = this.#tls ? { tls: this.#tls } : {};
19502
+ const open = (url, max) => new SQL3(url, max === undefined ? { idleTimeout, ...tls } : { max, idleTimeout, ...tls });
19392
19503
  this.#adminRaw = config.adminDatabaseUrl === null ? null : open(config.adminDatabaseUrl, poolMax);
19393
19504
  this.#publicRaw = open(config.publicDatabaseUrl, publicPoolMax);
19394
19505
  this.#adminUrl = config.adminDatabaseUrl;
@@ -19400,6 +19511,13 @@ class DatabaseAdapter {
19400
19511
  this.#public = drizzle(this.#publicRaw, this.#drizzleOptions);
19401
19512
  this.#skipVerify = config.skipRoleVerification === true;
19402
19513
  this.#acquireTimeoutMs = config.poolAcquireTimeoutMs ?? POOL_ACQUIRE_TIMEOUT_MS;
19514
+ this.#demand = {
19515
+ admin: new PoolDemand("admin", poolMax ?? BUN_DEFAULT_POOL_MAX),
19516
+ public: new PoolDemand("public", publicPoolMax ?? BUN_DEFAULT_POOL_MAX)
19517
+ };
19518
+ }
19519
+ poolPressure(pool) {
19520
+ return this.#demand[pool].snapshot();
19403
19521
  }
19404
19522
  async#transaction(pool, holder, fn) {
19405
19523
  const raw = pool === "admin" ? this.#adminRaw : this.#publicRaw;
@@ -19410,6 +19528,7 @@ class DatabaseAdapter {
19410
19528
  return this.#joinHeld(pool, plan.outer, plan.marker, fn);
19411
19529
  const marker = plan.marker;
19412
19530
  const reserved = await this.#reserve(pool, raw, holder);
19531
+ const endHold = this.#demand[pool].beginHold(holder);
19413
19532
  try {
19414
19533
  const db = drizzle({ client: reserved, ...this.#drizzleOptions });
19415
19534
  this.#onStatement?.("BEGIN");
@@ -19423,6 +19542,7 @@ class DatabaseAdapter {
19423
19542
  return out;
19424
19543
  } finally {
19425
19544
  reserved.release();
19545
+ endHold();
19426
19546
  await this.#runAfterEnd(marker);
19427
19547
  }
19428
19548
  }
@@ -19466,6 +19586,7 @@ class DatabaseAdapter {
19466
19586
  async#reserve(pool, raw, requester) {
19467
19587
  const deadline = new AbortController;
19468
19588
  const timer = setTimeout(() => deadline.abort(), this.#acquireTimeoutMs);
19589
+ const endWait = this.#demand[pool].beginWait();
19469
19590
  try {
19470
19591
  return await raw.reserve({ signal: deadline.signal });
19471
19592
  } catch (e) {
@@ -19474,6 +19595,7 @@ class DatabaseAdapter {
19474
19595
  }
19475
19596
  throw e;
19476
19597
  } finally {
19598
+ endWait();
19477
19599
  clearTimeout(timer);
19478
19600
  }
19479
19601
  }
@@ -19520,8 +19642,8 @@ class DatabaseAdapter {
19520
19642
  }
19521
19643
  return;
19522
19644
  }
19523
- if (id.database !== "cms_public") {
19524
- throw new Error(`DatabaseAdapter public pool expected database cms_public but connected to ${id.database}. Check PUBLIC_DATABASE_URL / PUBLIC_ADMIN_DATABASE_URL.`);
19645
+ if (id.database !== this.#publicDatabase) {
19646
+ throw new Error(`DatabaseAdapter public pool expected database ${this.#publicDatabase} but connected to ${id.database}. Check ${this.#publicDatabase === "cms_public" ? "PUBLIC_DATABASE_URL / PUBLIC_ADMIN_DATABASE_URL" : "PUBLIC_DATABASE_URL / PUBLIC_STAGING_ADMIN_DATABASE_URL"}.`);
19525
19647
  }
19526
19648
  if (!this.#roles.public.includes(id.user)) {
19527
19649
  throw new Error(`DatabaseAdapter public pool expected user ${this.#roles.public.join(" or ")}, got ${id.user}.`);
@@ -19585,7 +19707,7 @@ class DatabaseAdapter {
19585
19707
  postCommit = collectPostCommitEffects(tx);
19586
19708
  await this.#setIdentity(tx, ctx, op.name);
19587
19709
  if (op.database === "cms_admin") {
19588
- const denied = await firstInvisibleBranch(tx, namedBranches(ctx, validatedInput));
19710
+ const denied = await firstInvisibleBranch(tx, namedBranches(ctx, validatedInput), ctx);
19589
19711
  if (denied !== null)
19590
19712
  return err(branchNotFound(op.name, denied));
19591
19713
  }
@@ -19642,9 +19764,10 @@ class DatabaseAdapter {
19642
19764
  try {
19643
19765
  const branches = op.database === "cms_admin" ? namedBranches(ctx, validatedInput) : [];
19644
19766
  if (branches.length > 0) {
19645
- const denied = await this.#transaction("admin", op.name, async (tx) => {
19767
+ const remembered = await rememberedFirstInvisible(ctx, branches);
19768
+ const denied = remembered !== undefined ? remembered : await this.#transaction("admin", op.name, async (tx) => {
19646
19769
  await this.#setIdentity(tx, ctx);
19647
- return firstInvisibleBranch(tx, branches);
19770
+ return firstInvisibleBranch(tx, branches, ctx);
19648
19771
  });
19649
19772
  if (denied !== null)
19650
19773
  return err(branchNotFound(op.name, denied));
@@ -19710,6 +19833,7 @@ class DatabaseAdapter {
19710
19833
  if (!conn) {
19711
19834
  conn = new SQL3(url, {
19712
19835
  max: LISTEN_CONNECTIONS_PER_INSTANCE,
19836
+ ...this.#tls ? { tls: this.#tls } : {},
19713
19837
  onclose: (cause) => {
19714
19838
  if (this.#listenersClosing)
19715
19839
  return;
@@ -19743,7 +19867,8 @@ class DatabaseAdapter {
19743
19867
  try {
19744
19868
  const branches = namedBranches(ctx, validatedInput);
19745
19869
  if (branches.length > 0) {
19746
- const denied = await this.#shortRead(ctx, op.name, (tx) => firstInvisibleBranch(tx, branches));
19870
+ const remembered = await rememberedFirstInvisible(ctx, branches);
19871
+ const denied = remembered !== undefined ? remembered : await this.#shortRead(ctx, op.name, (tx) => firstInvisibleBranch(tx, branches, ctx));
19747
19872
  if (denied !== null)
19748
19873
  return err(branchNotFound(op.name, denied));
19749
19874
  }
@@ -19823,7 +19948,7 @@ class DatabaseAdapter {
19823
19948
  if (!joined)
19824
19949
  postCommit = collectPostCommitEffects(tx);
19825
19950
  await this.#setIdentity(tx, ctx, options.holder ?? null);
19826
- const denied = await firstInvisibleBranch(tx, namedBranches(ctx, undefined));
19951
+ const denied = await firstInvisibleBranch(tx, namedBranches(ctx, undefined), ctx);
19827
19952
  if (denied !== null) {
19828
19953
  throw new OperationAbortError(branchNotFound("withAdminTransaction", denied));
19829
19954
  }
@@ -19860,11 +19985,40 @@ class DatabaseAdapter {
19860
19985
  if (this.#adminRaw === null)
19861
19986
  throw new AdminDatabaseUnavailableError("rawAdmin");
19862
19987
  this.#guard.assertNoHeldTransaction("admin", "rawAdmin()");
19863
- return this.#adminRaw;
19988
+ return this.#trackedRaw("admin", this.#adminRaw);
19864
19989
  }
19865
19990
  rawPublic() {
19866
19991
  this.#guard.assertNoHeldTransaction("public", "rawPublic()");
19867
- return this.#publicRaw;
19992
+ return this.#trackedRaw("public", this.#publicRaw);
19993
+ }
19994
+ #trackedRaw(pool, raw) {
19995
+ const demand = this.#demand[pool];
19996
+ return new Proxy(raw, {
19997
+ get(target, property, receiver) {
19998
+ const value = Reflect.get(target, property, receiver);
19999
+ if (property !== "begin" || typeof value !== "function") {
20000
+ return typeof value === "function" ? value.bind(target) : value;
20001
+ }
20002
+ return (...args) => {
20003
+ const last = args.length - 1;
20004
+ const fn = args[last];
20005
+ if (typeof fn !== "function")
20006
+ return value.apply(target, args);
20007
+ const endWait = demand.beginWait();
20008
+ const counted = async (tx) => {
20009
+ endWait();
20010
+ const endHold = demand.beginHold("raw.begin");
20011
+ try {
20012
+ return await fn(tx);
20013
+ } finally {
20014
+ endHold();
20015
+ }
20016
+ };
20017
+ const out = value.apply(target, [...args.slice(0, last), counted]);
20018
+ return out.finally(endWait);
20019
+ };
20020
+ }
20021
+ });
19868
20022
  }
19869
20023
  async provisionPluginPublicSchema(opts) {
19870
20024
  if (!/^[0-9a-f-]{36}$/i.test(opts.pluginId)) {
@@ -19875,12 +20029,23 @@ class DatabaseAdapter {
19875
20029
  const outcome = await this.#transaction("public", "provisionPluginPublicSchema", async (tx) => {
19876
20030
  return provisionPluginSchemaInTx(tx, "provisionPluginPublicSchema", opts);
19877
20031
  });
19878
- if (outcome.droppedColumns.length > 0) {
19879
- await this.#transaction("admin", "provisionPluginPublicSchema", async (tx) => {
19880
- await tx.execute(sql.raw("SELECT set_config('caelo.actor_kind', 'system', true)"));
19881
- await auditDroppedColumns(tx, "cms_public", opts.pluginId, outcome);
19882
- });
20032
+ const result = {
20033
+ database: this.#publicDatabase,
20034
+ schemaName: outcome.schemaName,
20035
+ droppedColumns: outcome.droppedColumns
20036
+ };
20037
+ if (outcome.droppedColumns.length > 0 && this.#adminRaw !== null) {
20038
+ await this.auditPluginSchemaDrop(opts.pluginId, result);
19883
20039
  }
20040
+ return result;
20041
+ }
20042
+ async auditPluginSchemaDrop(pluginId, outcome) {
20043
+ if (outcome.droppedColumns.length === 0)
20044
+ return;
20045
+ await this.#transaction("admin", "provisionPluginPublicSchema", async (tx) => {
20046
+ await tx.execute(sql.raw("SELECT set_config('caelo.actor_kind', 'system', true)"));
20047
+ await auditDroppedColumns(tx, outcome.database, pluginId, outcome);
20048
+ });
19884
20049
  }
19885
20050
  async provisionPluginAdminSchema(opts) {
19886
20051
  if (!/^[0-9a-f-]{36}$/i.test(opts.pluginId)) {
@@ -19946,6 +20111,14 @@ async function auditDroppedColumns(tx, database, pluginId, outcome) {
19946
20111
  }
19947
20112
  function thrownToQueryError(operation, thrown, access) {
19948
20113
  const raw = thrown instanceof Error ? thrown.message : String(thrown);
20114
+ if (thrown instanceof PoolAcquireTimeoutError) {
20115
+ return {
20116
+ kind: "HandlerError",
20117
+ operation,
20118
+ message: raw,
20119
+ busy: { retryAfterMs: POOL_BUSY_RETRY_AFTER_MS }
20120
+ };
20121
+ }
19949
20122
  if (isRlsDenial(thrown)) {
19950
20123
  console.warn(`[query-api] ${operation}: RLS denied the ${access}: ${raw}`);
19951
20124
  return { kind: "RLSDenied", operation, detail: rlsDenialMessage(operation, access) };
@@ -136,7 +136,7 @@ export const CLI_COMMANDS = [
136
136
  ];
137
137
  /** The text `cms-provision --help` prints, built from {@link CLI_COMMANDS}. */
138
138
  export function cliUsage() {
139
- return (`Usage: cms-provision [wizard] [--provider <name> --domain <d> [--staging-domain <host>] --owner-email <e> --region <r>] [--local|--install-job] | <${CLI_COMMANDS.map((c) => c.name).join("|")}> [options]\n` +
139
+ return (`Usage: cms-provision [wizard] [--provider <name> --domain <d> [--staging-domain <host>] --owner-email <e> --region <r> [--scheduler-interval-minutes 1-15]] [--local|--install-job] | <${CLI_COMMANDS.map((c) => c.name).join("|")}> [options]\n` +
140
140
  "With several installs on this machine, pick one with --install <install-id or domain> (status, upgrade and backup also take --install all); in a terminal you are asked otherwise.\n" +
141
141
  "Pass --no-wizard with no sub-command to print this usage instead of the wizard.");
142
142
  }
@@ -1 +1 @@
1
- {"version":3,"file":"cli-routing.js","sourceRoot":"","sources":["../src/cli-routing.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli-routing.js","sourceRoot":"","sources":["../src/cli-routing.ts"],"names":[],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -47,7 +47,8 @@ import { selfCommand } from "./self-command.js";
47
47
  import { ensureSelfHostedDbTls, selfHostedDbTlsServerCertSha256 } from "./self-hosted-db-tls.js";
48
48
  import { ensureInternalSecret, writeInternalSecret } from "./self-hosted-internal-secret.js";
49
49
  import { bunHostRunner, readRelease, recordRelease, releaseImages, resolveVerifiedRelease, } from "./self-hosted-release.js";
50
- import { applyPublicRoleLimits, convergeSelfHostedRoles, decideUp, selfHostedBootstrapScript, } from "./self-hosted-roles.js";
50
+ import { applyPreMigrationRoles, convergeSelfHostedRoles, decideUp, selfHostedBootstrapScript, } from "./self-hosted-roles.js";
51
+ import { parseSchedulerIntervalMinutes } from "./stack-contract.js";
51
52
  const REPO_ROOT = resolve(import.meta.dir, "../../..");
52
53
  /**
53
54
  * Whether this CLI runs from a Caelo source checkout (contributor) rather
@@ -139,6 +140,15 @@ function loadConfig() {
139
140
  cfg.composeProject ??= "caelo";
140
141
  saveConfig(cfg);
141
142
  }
143
+ // #1329 — the staging gateway's login came with the staging database. An
144
+ // install from before gets its own password now; the next `up` or
145
+ // upgrade creates the role with it (self-hosted-roles.ts) before anything
146
+ // uses it, so the roles count as not converged until then.
147
+ if (cfg.rolePasswords && !cfg.rolePasswords.publicStaging) {
148
+ cfg.rolePasswords = { ...cfg.rolePasswords, publicStaging: randomSecret(32) };
149
+ cfg.rolesConverged = false;
150
+ saveConfig(cfg);
151
+ }
142
152
  return cfg;
143
153
  }
144
154
  function saveConfig(c) {
@@ -152,9 +162,9 @@ function arg(name) {
152
162
  return undefined;
153
163
  return process.argv[idx + 1];
154
164
  }
155
- /** Fresh passwords for the two application roles. */
165
+ /** Fresh passwords for the application roles. */
156
166
  function newRolePasswords() {
157
- return { admin: randomSecret(32), public: randomSecret(32) };
167
+ return { admin: randomSecret(32), public: randomSecret(32), publicStaging: randomSecret(32) };
158
168
  }
159
169
  function emitConfig(cfg, extraDomains = []) {
160
170
  // Back-fill caeloSecretKek for installs created before P18. Persists
@@ -246,6 +256,9 @@ function renderCompose(cfg, images) {
246
256
  images,
247
257
  ports: cfg.ports,
248
258
  project: cfg.composeProject,
259
+ ...(cfg.schedulerIntervalMinutes !== undefined
260
+ ? { schedulerIntervalMinutes: cfg.schedulerIntervalMinutes }
261
+ : {}),
249
262
  ...(activeAgent(cfg) ? { agent: activeAgent(cfg) } : {}),
250
263
  });
251
264
  }
@@ -282,7 +295,20 @@ function agentContext(cfg) {
282
295
  ...(activeAgent(cfg) ? { agentPassword: activeAgent(cfg)?.dbPassword } : {}),
283
296
  });
284
297
  },
285
- applyPublicRoleLimits: () => applyPublicRoleLimits({ probe: postgresIsUp, psql: superuserPsql }),
298
+ applyPreMigrationRoles: () => {
299
+ if (!cfg.rolePasswords) {
300
+ return Promise.resolve({
301
+ ok: false,
302
+ reachable: true,
303
+ error: "config.json has no role passwords — run `cms-provision up` first",
304
+ });
305
+ }
306
+ return applyPreMigrationRoles({
307
+ probe: postgresIsUp,
308
+ psql: superuserPsql,
309
+ stagingPassword: cfg.rolePasswords.publicStaging,
310
+ });
311
+ },
286
312
  renderCompose: (images) => renderCompose(cfg, images),
287
313
  refreshCaddy: () => refreshCaddyfile(cfg),
288
314
  randomSecret: () => randomSecret(32),
@@ -421,7 +447,7 @@ async function init() {
421
447
  return;
422
448
  }
423
449
  if (!domain || !ownerEmail) {
424
- console.error("Usage: cms-provision init [--provider self-hosted] --domain example.com [--staging-domain staging.example.com] --owner-email me@example.com [--version x.y.z] [--skip-verify] [--dev] [--tls acme|internal] [--http-port 80] [--https-port 443]\n" +
450
+ console.error("Usage: cms-provision init [--provider self-hosted] --domain example.com [--staging-domain staging.example.com] --owner-email me@example.com [--version x.y.z] [--skip-verify] [--dev] [--tls acme|internal] [--http-port 80] [--https-port 443] [--scheduler-interval-minutes 5]\n" +
425
451
  "Cloud providers (gcp | gcp-firebase | aws | azure) run the wizard: cms-provision --provider <name>");
426
452
  process.exit(2);
427
453
  }
@@ -435,6 +461,18 @@ async function init() {
435
461
  console.error("--http-port and --https-port must differ");
436
462
  process.exit(2);
437
463
  }
464
+ const schedulerArg = arg("scheduler-interval-minutes");
465
+ let schedulerIntervalMinutes;
466
+ try {
467
+ schedulerIntervalMinutes =
468
+ schedulerArg === undefined
469
+ ? undefined
470
+ : parseSchedulerIntervalMinutes(schedulerArg, "--scheduler-interval-minutes");
471
+ }
472
+ catch (e) {
473
+ console.error(e.message);
474
+ process.exit(2);
475
+ }
438
476
  const stagingArg = arg("staging-domain");
439
477
  const stagingProblem = stagingArg
440
478
  ? stagingHostProblem(stagingArg, {
@@ -502,6 +540,7 @@ async function init() {
502
540
  tls,
503
541
  ports,
504
542
  composeProject: composeProjectName(domain),
543
+ ...(schedulerIntervalMinutes !== undefined ? { schedulerIntervalMinutes } : {}),
505
544
  };
506
545
  saveConfig(cfg);
507
546
  // P15.1 — the shared HMAC secret for /api/internal/* endpoints, read by
@@ -859,7 +898,7 @@ async function upgradeSelfHostedCommand(opts) {
859
898
  runner: bunHostRunner,
860
899
  renderCompose: (images) => renderCompose(cfg, images),
861
900
  convergeRoles: agentContext(cfg).convergeRoles,
862
- applyPublicRoleLimits: agentContext(cfg).applyPublicRoleLimits,
901
+ applyPreMigrationRoles: agentContext(cfg).applyPreMigrationRoles,
863
902
  refreshCaddy: () => refreshCaddyfile(cfg),
864
903
  log: {
865
904
  step: (m) => console.log(`\n==> ${m}`),
@@ -1356,13 +1395,26 @@ async function waitForDomainMappingCert(target) {
1356
1395
  * §11.C — interactive wizard. Loaded lazily so the bare-CLI startup
1357
1396
  * cost stays small (clack + kleur are pulled in only when needed).
1358
1397
  * Reads `--provider`, `--domain`, `--owner-email`, `--project-id`, `--region`,
1359
- * `--non-interactive` and the run mode (`--local` / `--install-job`, default:
1398
+ * `--scheduler-interval-minutes` (#1140), `--non-interactive` and the run mode (`--local` / `--install-job`, default:
1360
1399
  * the install job in Cloud Shell, local elsewhere — run-mode.ts) from argv.
1361
1400
  */
1362
1401
  async function wizardCommand() {
1363
1402
  const { runWizard } = await import("./wizard.js");
1364
1403
  const { chooseRunMode } = await import("./run-mode.js");
1404
+ const schedulerArg = arg("scheduler-interval-minutes");
1405
+ let schedulerIntervalMinutes;
1406
+ try {
1407
+ schedulerIntervalMinutes =
1408
+ schedulerArg === undefined
1409
+ ? undefined
1410
+ : parseSchedulerIntervalMinutes(schedulerArg, "--scheduler-interval-minutes");
1411
+ }
1412
+ catch (e) {
1413
+ console.error(e.message);
1414
+ process.exit(2);
1415
+ }
1365
1416
  await runWizard({
1417
+ ...(schedulerIntervalMinutes !== undefined ? { schedulerIntervalMinutes } : {}),
1366
1418
  runMode: chooseRunMode(process.argv, process.env),
1367
1419
  nonInteractive: process.argv.includes("--non-interactive"),
1368
1420
  provider: arg("provider"),