@bongos/core 1.19.999 → 1.19.1000

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 (86) hide show
  1. package/.bongos-core.json +105 -90
  2. package/.claude/skills/collab-review/SKILL.md +24 -13
  3. package/clients/bongos-client/README.md +1 -1
  4. package/clients/bongos-client/bongos-client.global.js +4 -0
  5. package/clients/bongos-client/index.cjs +4 -0
  6. package/clients/bongos-client/index.d.ts +7 -0
  7. package/clients/bongos-client/index.mjs +4 -0
  8. package/docs/adr/0330-an-ask-is-answered-where-it-was-asked.md +155 -0
  9. package/docs/adr/README.md +1 -0
  10. package/docs/api/openapi.json +154 -4
  11. package/docs/api-reference.md +5 -3
  12. package/docs/copy-inventory.md +18 -13
  13. package/docs/copy-registry.json +70 -15
  14. package/docs/module-api-changelog.md +2 -0
  15. package/modules/hall-ui/public/collab-lib.js +79 -0
  16. package/modules/hall-ui/public/collab.css +60 -0
  17. package/modules/hall-ui/public/collab.js +134 -5
  18. package/modules/lifecycle/help-requests.js +118 -8
  19. package/modules/lifecycle/migrations/lifecycle_011_help_request_replies.sql +113 -0
  20. package/modules/lifecycle/routes/help-requests.js +93 -13
  21. package/modules/provisioning/capacity.js +3 -3
  22. package/modules/provisioning/control-plane-guard.js +1 -1
  23. package/modules/provisioning/core-upgrade.js +7 -7
  24. package/modules/provisioning/migrations/provisioning_027_cloud_host_shape_rename.sql +58 -0
  25. package/modules/provisioning/onboard-plan.js +1 -1
  26. package/modules/provisioning/paid-shape-gate.js +28 -15
  27. package/modules/provisioning/provisioning.js +18 -14
  28. package/modules/provisioning/routes/repo-private.js +1 -1
  29. package/modules/provisioning/tests/liveness.mjs +16 -8
  30. package/modules/provisioning/tests/provisioning.mjs +1 -1
  31. package/modules/public-landing/public/projects.html +26 -27
  32. package/modules/ui-design/kit/fixtures/provisioning-instances.json +1 -1
  33. package/package-lock.json +2 -2
  34. package/package.json +1 -1
  35. package/scripts/gds/init.js +12 -4
  36. package/scripts/gds/onboard.js +1 -1
  37. package/scripts/gds/provision-capacity.js +6 -1
  38. package/scripts/gds/provision-config.js +6 -3
  39. package/scripts/gds/provision-core-upgrade.js +8 -8
  40. package/scripts/gds/provision-disconnect.js +2 -2
  41. package/scripts/gds/provision-process.js +2 -2
  42. package/scripts/gds/provision-repo-private.js +1 -1
  43. package/scripts/gds/provision-repo.js +13 -3
  44. package/scripts/gds/provision-teardown.js +2 -2
  45. package/scripts/gds/provision-units.js +2 -2
  46. package/scripts/gds/provision.js +6 -6
  47. package/src/module-api.js +1 -1
  48. package/tests/collab_page.mjs +140 -7
  49. package/tests/core_upgrade_door.mjs +1 -1
  50. package/tests/core_upgrade_runner.mjs +20 -20
  51. package/tests/deploy_page_projects.mjs +5 -5
  52. package/tests/hall_collab_world.mjs +6 -2
  53. package/tests/help_requests.mjs +201 -6
  54. package/tests/hub_disconnect_ui.mjs +4 -1
  55. package/tests/init.mjs +6 -6
  56. package/tests/onboard.mjs +5 -4
  57. package/tests/project_catalog_backfill.mjs +1 -1
  58. package/tests/project_catalog_bridge.mjs +2 -2
  59. package/tests/project_catalog_routes.mjs +4 -4
  60. package/tests/projects_hub.mjs +1 -1
  61. package/tests/projects_hub_pre_uat.mjs +22 -5
  62. package/tests/projects_hub_repo_private.mjs +1 -1
  63. package/tests/provision.mjs +32 -32
  64. package/tests/provision_byo_host.mjs +1 -1
  65. package/tests/provision_caddy_wiring.mjs +1 -1
  66. package/tests/provision_disconnect.mjs +3 -3
  67. package/tests/provision_repo_private.mjs +1 -1
  68. package/tests/provision_restart.mjs +5 -5
  69. package/tests/provision_settings_apply.mjs +4 -4
  70. package/tests/provision_teardown_address.mjs +2 -2
  71. package/tests/provisioning_capacity.mjs +40 -34
  72. package/tests/provisioning_capacity_gate.mjs +5 -12
  73. package/tests/provisioning_default_domain.mjs +1 -1
  74. package/tests/provisioning_domain_attach.mjs +4 -4
  75. package/tests/provisioning_domain_unique.mjs +2 -2
  76. package/tests/provisioning_error_note_scope.mjs +1 -1
  77. package/tests/provisioning_module_selection.mjs +5 -5
  78. package/tests/provisioning_onboard_mode.mjs +2 -2
  79. package/tests/provisioning_paid_shape_gate.mjs +57 -44
  80. package/tests/provisioning_repo_private_route.mjs +1 -1
  81. package/tests/provisioning_restart.mjs +11 -4
  82. package/tests/provisioning_settings_apply.mjs +12 -5
  83. package/tests/provisioning_settings_env.mjs +1 -1
  84. package/tests/provisioning_teardown_intent.mjs +3 -3
  85. package/tests/public_landing_projects.mjs +13 -6
  86. package/tests/upgrade.mjs +2 -2
@@ -108,7 +108,7 @@ t('serviceUnit binds the instance port + DB + per-instance env file, runs platfo
108
108
  });
109
109
 
110
110
  t('serviceUnit(standalone) runs the CORE PACKAGE server from the instance\'s OWN repo dir (ADR 0108)', () => {
111
- const inst = { ...coTenant, hosting_shape: 'standalone' };
111
+ const inst = { ...coTenant, hosting_shape: 'cloud-host' };
112
112
  const u = P.serviceUnit(inst, { etcBase: '/etc' });
113
113
  const root = P.standaloneRoot(inst);
114
114
  assert.match(u, new RegExp('WorkingDirectory=' + reEsc(root)), 'runs from the instance\'s own repo dir');
@@ -118,7 +118,7 @@ t('serviceUnit(standalone) runs the CORE PACKAGE server from the instance\'s OWN
118
118
  });
119
119
 
120
120
  t('standalone step generators: root under standaloneBase + pull / npm-ci / instance-root migrate', () => {
121
- const inst = { ...coTenant, hosting_shape: 'standalone' };
121
+ const inst = { ...coTenant, hosting_shape: 'cloud-host' };
122
122
  assert.match(P.standaloneRoot(inst), /\/mercury$/);
123
123
  assert.equal(P.standalonePullCmd(), 'git pull --ff-only origin main');
124
124
  assert.equal(P.standaloneInstallCmd(), 'npm ci --omit=dev');
@@ -220,7 +220,7 @@ t('backup units: per-instance names + the DB set on the unit (fixes the task-221
220
220
 
221
221
  t('backupScriptPath resolves the CONSUMER LAYOUT, never the old /home/lars/example monorepo path', () => {
222
222
  // standalone → the script inside the instance's OWN pinned @bongos/core
223
- const standalone = { ...coTenant, hosting_shape: 'standalone' };
223
+ const standalone = { ...coTenant, hosting_shape: 'cloud-host' };
224
224
  const sPath = P.backupScriptPath(standalone);
225
225
  assert.ok(sPath.startsWith(P.standaloneRoot(standalone) + '/'), 'under the instance root');
226
226
  assert.match(sPath, /node_modules\/@bongos\/core\/scripts\/gds\/db-backup-nightly\.sh$/);
@@ -311,7 +311,7 @@ t('webEnvBody: the repo target_ref seats the founding owner via <prefix>_FIRST_A
311
311
  // seat an Archon on first sign-in and the owner can't claim their own instance. Derived
312
312
  // from the repo owner in target_ref — the SAME source instanceInitSpec uses for the
313
313
  // scaffold's branding.firstAdmin, so the env override and config/branding.json agree.
314
- const b = P.webEnvBody({ ...coTenant, hosting_shape: 'standalone', domain: 'mercury.example.com', target_ref: 'octocat/mercury' });
314
+ const b = P.webEnvBody({ ...coTenant, hosting_shape: 'cloud-host', domain: 'mercury.example.com', target_ref: 'octocat/mercury' });
315
315
  assert.match(b, new RegExp(`${reEsc(NEUTRAL_PREFIX)}_FIRST_ADMIN=octocat`));
316
316
  // No target_ref ⇒ the line is omitted (org-owned or unknown owner sets it by hand).
317
317
  assert.doesNotMatch(P.webEnvBody(coTenant), /_FIRST_ADMIN=/);
@@ -320,7 +320,7 @@ t('webEnvBody: the repo target_ref seats the founding owner via <prefix>_FIRST_A
320
320
  t('webEnvBody: a resolved owner login wins over the repo owner — an org-owned repo seats the signed-in builder (task 1003240)', () => {
321
321
  // The org that owns an adopted repo is not a sign-in identity; seating it seats nobody.
322
322
  // Same preference instanceInitSpec already applies to the scaffold's branding.firstAdmin.
323
- const inst = { ...coTenant, hosting_shape: 'standalone', domain: 'mercury.example.com', target_ref: 'acme-org/mercury' };
323
+ const inst = { ...coTenant, hosting_shape: 'cloud-host', domain: 'mercury.example.com', target_ref: 'acme-org/mercury' };
324
324
  const withLogin = P.webEnvBody(inst, { ownerLogin: 'octocat' });
325
325
  assert.match(withLogin, new RegExp(`${reEsc(NEUTRAL_PREFIX)}_FIRST_ADMIN=octocat`));
326
326
  assert.doesNotMatch(withLogin, /_FIRST_ADMIN=acme-org/);
@@ -491,7 +491,7 @@ await ta('provisionInstance(standalone): the backup unit ExecStart points at the
491
491
  // capture the unit BODIES, not just paths, so we can assert the ExecStart path
492
492
  const bodies = {};
493
493
  deps.writeFile = (p, body) => { writes.push(p); bodies[p] = body; return { ok: true }; };
494
- const inst = { ...coTenant, hosting_shape: 'standalone', target_ref: 'example-owner/mercury' };
494
+ const inst = { ...coTenant, hosting_shape: 'cloud-host', target_ref: 'example-owner/mercury' };
495
495
  await P.provisionInstance(inst, deps);
496
496
  const svc = bodies[P.backupServicePath(inst)];
497
497
  assert.ok(svc, 'wrote the backup service unit');
@@ -506,7 +506,7 @@ await ta('provisionInstance(standalone): web.env seats the signed-in builder, no
506
506
  deps.writeFile = (p, body) => { bodies[p] = body; return { ok: true }; };
507
507
  // the builders lookup answers with the signed-in owner; every other query stays empty
508
508
  deps.db = { query: async (sql) => (/FROM builders/.test(String(sql)) ? { rows: [{ github_login: 'octocat' }] } : { rows: [] }) };
509
- const inst = { ...coTenant, hosting_shape: 'standalone', target_ref: 'acme-org/mercury', owner_builder_id: 7 };
509
+ const inst = { ...coTenant, hosting_shape: 'cloud-host', target_ref: 'acme-org/mercury', owner_builder_id: 7 };
510
510
  const r = await P.provisionInstance(inst, deps);
511
511
  assert.equal(r.ok, true);
512
512
  const envPath = Object.keys(bodies).find((p) => p.endsWith('/web.env'));
@@ -517,7 +517,7 @@ await ta('provisionInstance(standalone): web.env seats the signed-in builder, no
517
517
 
518
518
  await ta('provisionInstance(standalone) SCAFFOLDS its repo, then syncs (pull + npm ci) + migrates from the instance root', async () => {
519
519
  const { deps, cmds, writes } = recorder();
520
- const r = await P.provisionInstance({ ...coTenant, hosting_shape: 'standalone', target_ref: 'example-owner/mercury' }, deps);
520
+ const r = await P.provisionInstance({ ...coTenant, hosting_shape: 'cloud-host', target_ref: 'example-owner/mercury' }, deps);
521
521
  assert.equal(r.ok, true);
522
522
  const joined = cmds.join('\n');
523
523
  // scaffold leg (task 2055): git init → package-core → init --vendor-core → push
@@ -612,7 +612,7 @@ function recordIo({ sudoP = '' } = {}) {
612
612
  log: (m) => logs.push(m),
613
613
  };
614
614
  }
615
- const INST = { slug: 'demo', hosting_shape: 'standalone', port: 3003, db_name: 'demo' };
615
+ const INST = { slug: 'demo', hosting_shape: 'cloud-host', port: 3003, db_name: 'demo' };
616
616
 
617
617
  t('resolveProcessManager: explicit knob wins, then the /run/systemd/system probe', () => {
618
618
  const booted = { existsSync: (p) => p === '/run/systemd/system' };
@@ -729,7 +729,7 @@ t('instanceInitSpec: falls back to the repo owner as firstAdmin when no owner lo
729
729
  await ta('scaffoldStandaloneRepo: refuses when target_ref is not owner/repo', async () => {
730
730
  const { deps } = recorder({ apply: true });
731
731
  await assert.rejects(
732
- () => P.scaffoldStandaloneRepo({ slug: 'demo', hosting_shape: 'standalone', target_ref: null }, deps),
732
+ () => P.scaffoldStandaloneRepo({ slug: 'demo', hosting_shape: 'cloud-host', target_ref: null }, deps),
733
733
  /target_ref .* is not an owner\/repo/,
734
734
  );
735
735
  });
@@ -743,7 +743,7 @@ await ta('scaffoldStandaloneRepo: idempotent — a repo that already carries pac
743
743
  writeFileSync(join(tmp, 'demo', 'package.json'), '{}\n'); // sign of a completed scaffold
744
744
  const { deps, cmds } = recorder({ apply: true });
745
745
  deps.exec = (cmd) => { cmds.push(cmd); return { ok: true, stdout: /ls-remote/.test(cmd) ? 'abc123\trefs/heads/main\n' : '' }; };
746
- const res = await P.scaffoldStandaloneRepo({ slug: 'demo', hosting_shape: 'standalone', target_ref: 'example-owner/demo' }, deps);
746
+ const res = await P.scaffoldStandaloneRepo({ slug: 'demo', hosting_shape: 'cloud-host', target_ref: 'example-owner/demo' }, deps);
747
747
  assert.equal(res.already, true);
748
748
  assert.deepEqual(cmds.filter((c) => !/ls-remote/.test(c)), [], 'no init/build/push when already scaffolded AND the remote carries main');
749
749
  } finally {
@@ -770,7 +770,7 @@ async function rerunScaffold({ apply = true, lsRemote, token = null, credWriteTh
770
770
  writes.push({ p, body, opts }); return { ok: true };
771
771
  };
772
772
  if (token) deps.getGithubToken = async () => ({ token, scope: 'repo' });
773
- const res = await P.scaffoldStandaloneRepo({ slug: 'demo', hosting_shape: 'standalone', target_ref: 'example-owner/demo', owner_builder_id: 7 }, deps);
773
+ const res = await P.scaffoldStandaloneRepo({ slug: 'demo', hosting_shape: 'cloud-host', target_ref: 'example-owner/demo', owner_builder_id: 7 }, deps);
774
774
  return { res, cmds, writes };
775
775
  } finally {
776
776
  P.CONFIG.standaloneBase = origBase;
@@ -829,7 +829,7 @@ async function scaffoldWithEnv(env) {
829
829
  // the scaffold rebuilds via package-core.js (task 1002275). A consumer-instance root
830
830
  // (a real pin) takes the reuse path — covered by its own test below.
831
831
  await P.scaffoldStandaloneRepo(
832
- { slug: 'demo', hosting_shape: 'standalone', target_ref: 'example-owner/demo' },
832
+ { slug: 'demo', hosting_shape: 'cloud-host', target_ref: 'example-owner/demo' },
833
833
  { ...deps, env, instanceRoot: tmp },
834
834
  );
835
835
  return cmds.join('\n');
@@ -933,7 +933,7 @@ await ta('scaffoldStandaloneRepo: on a consumer control plane it REUSES the pinn
933
933
  try {
934
934
  const { deps, cmds } = recorder({ apply: true });
935
935
  await P.scaffoldStandaloneRepo(
936
- { slug: 'demo', hosting_shape: 'standalone', target_ref: 'example-owner/demo' },
936
+ { slug: 'demo', hosting_shape: 'cloud-host', target_ref: 'example-owner/demo' },
937
937
  { ...deps, instanceRoot: cp },
938
938
  );
939
939
  const joined = cmds.join('\n');
@@ -1073,7 +1073,7 @@ await ta('scaffoldStandaloneRepo: a REGISTRY-pinned consumer control plane PACKS
1073
1073
  try {
1074
1074
  const { deps, cmds } = recorder({ apply: true });
1075
1075
  await P.scaffoldStandaloneRepo(
1076
- { slug: 'demo', hosting_shape: 'standalone', target_ref: 'example-owner/demo' },
1076
+ { slug: 'demo', hosting_shape: 'cloud-host', target_ref: 'example-owner/demo' },
1077
1077
  { ...deps, instanceRoot: cp },
1078
1078
  );
1079
1079
  const joined = cmds.join('\n');
@@ -1106,7 +1106,7 @@ await ta('scaffoldStandaloneRepo: pushes via the owner OAuth token (not SSH), to
1106
1106
  deps.writeFile = (p, body, opts) => { writes.push({ p, body, opts }); return { ok: true }; };
1107
1107
  deps.getGithubToken = async () => ({ token: 'gho_SECRET_TOKEN_XYZ', scope: 'public_repo' });
1108
1108
  await P.scaffoldStandaloneRepo(
1109
- { slug: 'demo', hosting_shape: 'standalone', target_ref: 'example-owner/demo', owner_builder_id: 7 },
1109
+ { slug: 'demo', hosting_shape: 'cloud-host', target_ref: 'example-owner/demo', owner_builder_id: 7 },
1110
1110
  { ...deps, instanceRoot: base },
1111
1111
  );
1112
1112
  const joined = cmds.join('\n');
@@ -1134,7 +1134,7 @@ await ta('scaffoldStandaloneRepo: no owner token → falls back to the SSH origi
1134
1134
  try {
1135
1135
  const { deps, cmds } = recorder({ apply: true }); // default getGithubToken → null
1136
1136
  await P.scaffoldStandaloneRepo(
1137
- { slug: 'demo', hosting_shape: 'standalone', target_ref: 'example-owner/demo', owner_builder_id: 7 },
1137
+ { slug: 'demo', hosting_shape: 'cloud-host', target_ref: 'example-owner/demo', owner_builder_id: 7 },
1138
1138
  { ...deps, instanceRoot: base },
1139
1139
  );
1140
1140
  const joined = cmds.join('\n');
@@ -1204,7 +1204,7 @@ async function scaffoldInMode(mode, { token = null, seedRoot = null } = {}) {
1204
1204
  deps.writeFile = (p, body, opts) => { writes.push({ p, body, opts }); return { ok: true }; };
1205
1205
  if (token) deps.getGithubToken = async () => ({ token, scope: 'read:user,repo' });
1206
1206
  await P.scaffoldStandaloneRepo(
1207
- { slug: 'demo', hosting_shape: 'standalone', target_ref: 'example-owner/demo', owner_builder_id: 7, onboard_mode: mode },
1207
+ { slug: 'demo', hosting_shape: 'cloud-host', target_ref: 'example-owner/demo', owner_builder_id: 7, onboard_mode: mode },
1208
1208
  { ...deps, instanceRoot: base },
1209
1209
  );
1210
1210
  return { joined: cmds.join('\n'), cmds, writes, root: join(base, 'demo') };
@@ -1279,7 +1279,7 @@ await ta('scaffoldStandaloneRepo(adopt): a default branch that is not `main` fai
1279
1279
  deps.exec = (cmd) => ({ ok: true, stdout: /rev-parse --abbrev-ref HEAD/.test(cmd) ? 'master\n' : '' });
1280
1280
  await assert.rejects(
1281
1281
  () => P.scaffoldStandaloneRepo(
1282
- { slug: 'demo', hosting_shape: 'standalone', target_ref: 'example-owner/demo', owner_builder_id: 7, onboard_mode: 'adopt' },
1282
+ { slug: 'demo', hosting_shape: 'cloud-host', target_ref: 'example-owner/demo', owner_builder_id: 7, onboard_mode: 'adopt' },
1283
1283
  { ...deps, instanceRoot: base },
1284
1284
  ),
1285
1285
  /default branch is "master", not "main"/,
@@ -1304,7 +1304,7 @@ await ta('scaffoldStandaloneRepo(adopt): the credential file is removed even whe
1304
1304
  };
1305
1305
  await assert.rejects(
1306
1306
  () => P.scaffoldStandaloneRepo(
1307
- { slug: 'demo', hosting_shape: 'standalone', target_ref: 'example-owner/demo', owner_builder_id: 7, onboard_mode: 'adopt' },
1307
+ { slug: 'demo', hosting_shape: 'cloud-host', target_ref: 'example-owner/demo', owner_builder_id: 7, onboard_mode: 'adopt' },
1308
1308
  { ...deps, instanceRoot: base },
1309
1309
  ),
1310
1310
  /non-fast-forward/,
@@ -1720,7 +1720,7 @@ t('migrateCmd: privileged runs migrate as postgres via env+bash (survives sudo e
1720
1720
  });
1721
1721
 
1722
1722
  t('standaloneMigrateCmd: privileged runs the instance-root migrate as postgres; default unchanged', () => {
1723
- const inst = { ...coTenant, hosting_shape: 'standalone' };
1723
+ const inst = { ...coTenant, hosting_shape: 'cloud-host' };
1724
1724
  assert.equal(P.standaloneMigrateCmd(inst), 'PGDATABASE=mercury MEDUSA_GDS_ONLY=1 bash node_modules/@bongos/core/scripts/migrate.sh');
1725
1725
  assert.equal(P.standaloneMigrateCmd(inst, { privileged: true }), 'sudo -u postgres env PGDATABASE=mercury MEDUSA_GDS_ONLY=1 bash node_modules/@bongos/core/scripts/migrate.sh');
1726
1726
  });
@@ -1950,7 +1950,7 @@ await ta('provisionInstance(standalone, remote): EVERY box step runs control-pla
1950
1950
  checkDnsTokenScope: async () => ({ reachable: true, zone: 'cloudbongos.com' }),
1951
1951
  checkCaddyWiring: () => null,
1952
1952
  };
1953
- await P.provisionInstance({ ...coTenant, hosting_shape: 'standalone', target_ref: 'example-owner/mercury', domain: 'mercury.cloudbongos.com' }, deps);
1953
+ await P.provisionInstance({ ...coTenant, hosting_shape: 'cloud-host', target_ref: 'example-owner/mercury', domain: 'mercury.cloudbongos.com' }, deps);
1954
1954
  // The bug (task 2074): these landed on the REMOTE box, which never had the scaffolded
1955
1955
  // dir. They must ALL be on the control-plane executor now.
1956
1956
  for (const re of [/git pull --ff-only/, /npm ci/, /createdb mercury/, /migrate\.sh/, /systemctl enable --now/, /systemctl reload caddy/, /healthz/]) {
@@ -1991,7 +1991,7 @@ await ta('provisionInstance(standalone, remote, --apply): FAILS LOUD when the co
1991
1991
  checkCaddyWiring: () => null,
1992
1992
  };
1993
1993
  await assert.rejects(
1994
- () => P.provisionInstance({ ...coTenant, hosting_shape: 'standalone', target_ref: 'example-owner/mercury', domain: 'mercury.cloudbongos.com' }, deps),
1994
+ () => P.provisionInstance({ ...coTenant, hosting_shape: 'cloud-host', target_ref: 'example-owner/mercury', domain: 'mercury.cloudbongos.com' }, deps),
1995
1995
  /PROVISION_CONTROL_PLANE_IP/,
1996
1996
  'must demand the control-plane IP, not fall back to the co-hosting box',
1997
1997
  );
@@ -2008,7 +2008,7 @@ await ta('teardownInstance(standalone, remote): tears down on the CONTROL PLANE
2008
2008
  controlExec: (cmd) => { control.push(cmd); return { ok: true }; },
2009
2009
  writeFile: () => ({ ok: true }), controlWriteFile: () => ({ ok: true }), doApi: null,
2010
2010
  };
2011
- await P.teardownInstance({ ...coTenant, hosting_shape: 'standalone' }, deps);
2011
+ await P.teardownInstance({ ...coTenant, hosting_shape: 'cloud-host' }, deps);
2012
2012
  assert.ok(control.some((c) => /systemctl disable --now mercury/.test(c)), 'disable runs on the control plane');
2013
2013
  assert.ok(control.some((c) => /rm -f .*mercury\.service/.test(c)), 'unit removed on the control plane');
2014
2014
  assert.equal(box.length, 0, 'nothing crosses to the co-hosting box for a standalone teardown');
@@ -2058,7 +2058,7 @@ t('shouldFederate: dormant unless hub-configured AND co-tenant AND a domain is s
2058
2058
  assert.equal(P.shouldFederate(coTenantWithDomain, { platformIdentity: fakePI(), hubKeys: hubKeysOff }).federate, false, 'no signing key → skip');
2059
2059
  assert.equal(P.shouldFederate({ ...coTenantWithDomain, domain: null }, { platformIdentity: fakePI(), hubKeys: hubKeysOn }).federate, false, 'no domain → skip');
2060
2060
  // a standalone (owner-hosted) keeps its own GitHub app; a dedicated droplet has no local web.env bake yet
2061
- assert.equal(P.shouldFederate({ ...coTenantWithDomain, hosting_shape: 'standalone' }, { platformIdentity: fakePI(), hubKeys: hubKeysOn }).federate, false, 'standalone = self-hosted → skip');
2061
+ assert.equal(P.shouldFederate({ ...coTenantWithDomain, hosting_shape: 'cloud-host' }, { platformIdentity: fakePI(), hubKeys: hubKeysOn }).federate, false, 'standalone = self-hosted → skip');
2062
2062
  assert.equal(P.shouldFederate({ ...coTenantWithDomain, hosting_shape: 'dedicated' }, { platformIdentity: fakePI(), hubKeys: hubKeysOn }).federate, false, 'dedicated → skip (no local web.env yet)');
2063
2063
  });
2064
2064
  // no hub origin resolvable (even with a signing key) → skip
@@ -2183,8 +2183,8 @@ await ta('provisionInstance(standalone, hub-configured): still NOT federated —
2183
2183
  deps.controlWriteFile = (p, body) => { bodies[p] = body; return { ok: true }; };
2184
2184
  deps.writeFile = (p, body) => { bodies[p] = body; return { ok: true }; };
2185
2185
  deps.platformIdentity = pi; deps.hubKeys = hubKeysOn;
2186
- await P.provisionInstance({ ...coTenantWithDomain, hosting_shape: 'standalone', target_ref: 'example-owner/mercury' }, deps);
2187
- const env = bodies[P.webEnvPath({ ...coTenantWithDomain, hosting_shape: 'standalone' })];
2186
+ await P.provisionInstance({ ...coTenantWithDomain, hosting_shape: 'cloud-host', target_ref: 'example-owner/mercury' }, deps);
2187
+ const env = bodies[P.webEnvPath({ ...coTenantWithDomain, hosting_shape: 'cloud-host' })];
2188
2188
  assert.ok(env, 'wrote web.env');
2189
2189
  assert.doesNotMatch(env, /_AUTH_IDP=/, 'standalone is the self-hosted path — no federation');
2190
2190
  assert.equal(pi.calls.length, 0, 'no hub registration for a standalone');
@@ -2239,7 +2239,7 @@ function attachRecorder({ apply = false, envBody = WEB_ENV_ON_BOX } = {}) {
2239
2239
  }
2240
2240
 
2241
2241
  const activeStandalone = {
2242
- id: 18, slug: 'spike', hosting_shape: 'standalone', status: 'active', owner_builder_id: 7,
2242
+ id: 18, slug: 'spike', hosting_shape: 'cloud-host', status: 'active', owner_builder_id: 7,
2243
2243
  port: 3010, db_name: 'spike', domain: 'spike.cloudbongos.com', target_ref: 'jaxri/spike-harbor',
2244
2244
  };
2245
2245
 
@@ -2728,7 +2728,7 @@ function repoAuthDeps({ apply = true, priv = true, token = 'gho_TOK', keys = [],
2728
2728
  deps.fsImpl = { existsSync: () => existing, readFileSync: () => `${pub}\n` };
2729
2729
  return { deps, cmds, apiCalls };
2730
2730
  }
2731
- const privInst = { id: 9, slug: 'demo', hosting_shape: 'standalone', status: 'requested', owner_builder_id: 7, target_ref: 'example-owner/demo' };
2731
+ const privInst = { id: 9, slug: 'demo', hosting_shape: 'cloud-host', status: 'requested', owner_builder_id: 7, target_ref: 'example-owner/demo' };
2732
2732
 
2733
2733
  await ta('ensurePrivateRepoAccess(private): registers a READ-ONLY key and wires origin + core.sshCommand into the checkout', async () => {
2734
2734
  const { deps, cmds, apiCalls } = repoAuthDeps({ existing: false });
@@ -2965,7 +2965,7 @@ await ta('intendedPrivate(dry-run): names the pre-flip intent in the plan line,
2965
2965
 
2966
2966
  await ta('provisionInstance(standalone): the private-repo wiring lands BETWEEN the scaffold and the step-2 pull', async () => {
2967
2967
  const { deps, cmds } = repoAuthDeps({ existing: false });
2968
- const r = await P.provisionInstance({ ...coTenant, hosting_shape: 'standalone', target_ref: 'example-owner/mercury' }, deps);
2968
+ const r = await P.provisionInstance({ ...coTenant, hosting_shape: 'cloud-host', target_ref: 'example-owner/mercury' }, deps);
2969
2969
  assert.equal(r.ok, true);
2970
2970
  const idx = (re) => cmds.findIndex((c) => re.test(c));
2971
2971
  assert.ok(idx(/remote set-url origin git@github\.com:example-owner\/mercury\.git/) >= 0, 'wired the private origin');
@@ -3147,7 +3147,7 @@ t('serviceUnit: carries the hardening block, with HOME + ReadWritePaths pointing
3147
3147
  });
3148
3148
 
3149
3149
  t('serviceUnit: ReadWritePaths does NOT make the checkout the service runs writable by it', () => {
3150
- const inst = { ...coTenant, hosting_shape: 'standalone' };
3150
+ const inst = { ...coTenant, hosting_shape: 'cloud-host' };
3151
3151
  const u = P.serviceUnit(inst, { etcBase: '/etc' });
3152
3152
  const root = P.standaloneRoot(inst);
3153
3153
  const rw = u.split('\n').find((l) => l.startsWith('ReadWritePaths='));
@@ -3171,7 +3171,7 @@ t('webEnvBody: NO password ⇒ no PG credential lines — an instance provisione
3171
3171
 
3172
3172
  // ── the Postgres pool cap (task 1003928, ADR 0285 §3) ─────────────────────────
3173
3173
  t('webEnvBody: a CO-LOCATED instance (co-tenant or standalone) carries PGPOOL_MAX at the runner\'s cap, beside PGDATABASE', () => {
3174
- for (const shape of ['co-tenant', 'standalone']) {
3174
+ for (const shape of ['co-tenant', 'cloud-host']) {
3175
3175
  const b = P.webEnvBody({ ...coTenant, hosting_shape: shape });
3176
3176
  assert.match(b, new RegExp(`^PGPOOL_MAX=${P.CONFIG.instancePoolMax}$`, 'm'), `${shape} carries the cap`);
3177
3177
  const lines = b.split('\n');
@@ -115,7 +115,7 @@ test('the shape is absent from the runner shapes that DO get stood up', () => {
115
115
  // Any other shape still reaches the first executor — that is the control.
116
116
  const h = harness();
117
117
  return assert.rejects(
118
- P.provisionInstance({ ...byoHost, hosting_shape: 'standalone', domain: null }, h.deps),
118
+ P.provisionInstance({ ...byoHost, hosting_shape: 'cloud-host', domain: null }, h.deps),
119
119
  /byo-host must not/,
120
120
  'a non-byo-host shape still reaches the box steps — the branch is shape-scoped, not a global off switch',
121
121
  );
@@ -58,7 +58,7 @@ test('output that is not Caddy JSON, or a failed adapt, is a gap rather than a p
58
58
  // -- the runner legs --------------------------------------------------------
59
59
 
60
60
  const coTenant = { id: 1, slug: 'mercury', hosting_shape: 'co-tenant', status: 'requested', owner_builder_id: 7, port: 3003, db_name: 'mercury', domain: DOMAIN };
61
- const activeStandalone = { id: 18, slug: 'spike', hosting_shape: 'standalone', status: 'active', owner_builder_id: 7, port: 3010, db_name: 'spike', domain: 'spike.example.com' };
61
+ const activeStandalone = { id: 18, slug: 'spike', hosting_shape: 'cloud-host', status: 'active', owner_builder_id: 7, port: 3010, db_name: 'spike', domain: 'spike.example.com' };
62
62
  const WEB_ENV = 'PORT=3010\nBONGOS_PUBLIC_ORIGIN=https://old.example.com\n';
63
63
 
64
64
  // `served` = the hosts Caddy's resolved config carries once a snippet is written — the
@@ -13,7 +13,7 @@
13
13
  // • ORDER: nothing is erased until the teardown ran; the row is deleted only after every
14
14
  // erase and the catalog forget succeeded, so a failure leaves a row the runner can retry
15
15
  // • the dump erase matches the nightly filename EXACTLY, so `test` never erases `test-nk`
16
- // • each shape erases where its data lives (standalone: control plane; co-tenant: the
16
+ // • each shape erases where its data lives (cloud-host: control plane; co-tenant: the
17
17
  // co-hosting box; dedicated: nothing — its droplet went whole)
18
18
  // • a dry run erases nothing and deletes nothing; an invalid slug is refused before any command
19
19
  //
@@ -45,7 +45,7 @@ async function ta(name, fn) {
45
45
  catch (e) { failed++; console.log(` FAIL ${name}\n ${e.message}`); }
46
46
  }
47
47
 
48
- const lantern = { id: 40, slug: 'lantern', hosting_shape: 'standalone', status: 'torn_down', owner_builder_id: 7, port: 3021, db_name: 'lantern', domain: 'lantern.cloudbongos.com', target_ref: null };
48
+ const lantern = { id: 40, slug: 'lantern', hosting_shape: 'cloud-host', status: 'torn_down', owner_builder_id: 7, port: 3021, db_name: 'lantern', domain: 'lantern.cloudbongos.com', target_ref: null };
49
49
 
50
50
  function rig({ apply = true, privileged = false, failOn = null, catalogThrows = false, catalog = true } = {}) {
51
51
  const events = [], logs = [];
@@ -88,7 +88,7 @@ P.CONFIG.dnsHook = '/opt/infra/box-dns-cloudflare.sh';
88
88
 
89
89
  console.log('the erase, in order:');
90
90
 
91
- await ta('standalone: teardown legs, then the database, its role, the dumps, env + state, checkout, account — then forget, then the row', async () => {
91
+ await ta('cloud-host: teardown legs, then the database, its role, the dumps, env + state, checkout, account — then forget, then the row', async () => {
92
92
  const r = rig();
93
93
  const res = await D.disconnectInstance(lantern, r.deps);
94
94
  assert.equal(res.ok, true, JSON.stringify(res));
@@ -37,7 +37,7 @@ async function ta(name, fn) {
37
37
  catch (e) { failed++; console.log(` FAIL ${name}\n ${e.message}`); }
38
38
  }
39
39
 
40
- const liveStandalone = { id: 18, slug: 'demo', hosting_shape: 'standalone', status: 'active', owner_builder_id: 7, target_ref: 'example-owner/demo' };
40
+ const liveStandalone = { id: 18, slug: 'demo', hosting_shape: 'cloud-host', status: 'active', owner_builder_id: 7, target_ref: 'example-owner/demo' };
41
41
 
42
42
  // One ordered timeline across the shell AND GitHub, so the wire-before-flip order is
43
43
  // observable as positions in a single list rather than inferred from two.
@@ -70,13 +70,13 @@ function recorder({ apply = true, healthzFails = false, privileged = false, rest
70
70
  }
71
71
 
72
72
  const activeStandalone = {
73
- id: 18, slug: 'spike', hosting_shape: 'standalone', status: 'active', owner_builder_id: 7,
73
+ id: 18, slug: 'spike', hosting_shape: 'cloud-host', status: 'active', owner_builder_id: 7,
74
74
  port: 3010, db_name: 'spike', domain: 'spike.cloudbongos.com', target_ref: 'jaxri/spike-harbor',
75
75
  };
76
76
 
77
77
  console.log('restartInstance — the leg:');
78
78
 
79
- await ta('--apply, active standalone: ONE systemctl restart, then healthz — and web.env is never read or written', async () => {
79
+ await ta('--apply, active cloud-host: ONE systemctl restart, then healthz — and web.env is never read or written', async () => {
80
80
  const { deps, cmds, writes } = recorder();
81
81
  const r = await P.restartInstance(activeStandalone, deps);
82
82
  assert.equal(r.ok, true, JSON.stringify(r));
@@ -176,9 +176,9 @@ await ta('dry-run runs no real work and observes nothing', async () => {
176
176
  assert.equal(writes.length, 0);
177
177
  });
178
178
 
179
- await ta('a co-tenant restarts on the co-hosting box; a standalone on the control plane', async () => {
179
+ await ta('a co-tenant restarts on the co-hosting box; a cloud-host on the control plane', async () => {
180
180
  const seen = {};
181
- for (const shape of ['co-tenant', 'standalone']) {
181
+ for (const shape of ['co-tenant', 'cloud-host']) {
182
182
  const box = [], control = [];
183
183
  const { deps } = recorder();
184
184
  deps.exec = (cmd) => { box.push(cmd); return /healthz/.test(cmd) ? { ok: true, stdout: '{}' } : { ok: true, stdout: '' }; };
@@ -187,7 +187,7 @@ await ta('a co-tenant restarts on the co-hosting box; a standalone on the contro
187
187
  seen[shape] = { box: box.length, control: control.length };
188
188
  }
189
189
  assert.ok(seen['co-tenant'].box > 0 && seen['co-tenant'].control === 0, 'a co-tenant runs on the box');
190
- assert.ok(seen.standalone.control > 0 && seen.standalone.box === 0, 'a standalone ran control-plane-local (task 2074) — restart it THERE');
190
+ assert.ok(seen['cloud-host'].control > 0 && seen['cloud-host'].box === 0, 'a cloud-host runs control-plane-local (task 2074) — restart it THERE');
191
191
  });
192
192
 
193
193
  await ta('privileged mode escalates the restart', async () => {
@@ -121,7 +121,7 @@ t('is PURE: same input, same output; empty body yields just the lines', () => {
121
121
  });
122
122
 
123
123
  t('settingsEnvVarsFor: the runner pushes the SAME pair webEnvBody writes at standup, under the pack prefix', () => {
124
- const inst = { slug: 'spike', port: 3010, db_name: 'spike', hosting_shape: 'standalone', target_ref: 'jaxri/spike', settings: { platform_visibility: 'gated' } };
124
+ const inst = { slug: 'spike', port: 3010, db_name: 'spike', hosting_shape: 'cloud-host', target_ref: 'jaxri/spike', settings: { platform_visibility: 'gated' } };
125
125
  const vars = P.settingsEnvVarsFor(inst);
126
126
  assert.deepEqual(vars, {
127
127
  [`${NEUTRAL_PREFIX}_PLATFORM_VISIBILITY`]: 'gated', [`${NEUTRAL_PREFIX}_JOINABILITY`]: 'apply',
@@ -199,14 +199,14 @@ function recorder({ apply = false, envBody = LIVE_ENV, readFails = false, health
199
199
  }
200
200
 
201
201
  const activeStandalone = {
202
- id: 18, slug: 'spike', hosting_shape: 'standalone', status: 'active', owner_builder_id: 7,
202
+ id: 18, slug: 'spike', hosting_shape: 'cloud-host', status: 'active', owner_builder_id: 7,
203
203
  port: 3010, db_name: 'spike', domain: 'spike.cloudbongos.com', target_ref: 'jaxri/spike-harbor',
204
204
  settings: { platform_visibility: 'gated', joinability: 'invite_only' },
205
205
  };
206
206
 
207
207
  console.log('\nsettingsApplyInstance — the leg:');
208
208
 
209
- await ta('--apply, active standalone: read web.env → patch ONLY the settings lines (control-plane writer) → restart → healthz → manifest; creds survive', async () => {
209
+ await ta('--apply, active cloud-host: read web.env → patch ONLY the settings lines (control-plane writer) → restart → healthz → manifest; creds survive', async () => {
210
210
  const { deps, cmds, writes } = recorder({ apply: true });
211
211
  const r = await P.settingsApplyInstance(activeStandalone, deps);
212
212
  assert.equal(r.ok, true, JSON.stringify(r));
@@ -251,7 +251,7 @@ await ta('privileged: the read and the restart escalate via sudo (the /etc file
251
251
  await ta('co-tenant rides the BOX executor + writer; standalone rides the control-plane pair (task 2074 routing)', async () => {
252
252
  const seen = { box: [], control: [] };
253
253
  const mk = (bucket) => (cmd) => { seen[bucket].push(cmd); return /\bcat /.test(cmd) ? { ok: true, stdout: LIVE_ENV } : { ok: true, stdout: '' }; };
254
- for (const [shape, want] of [['co-tenant', 'box'], ['standalone', 'control']]) {
254
+ for (const [shape, want] of [['co-tenant', 'box'], ['cloud-host', 'control']]) {
255
255
  seen.box = []; seen.control = [];
256
256
  const { deps, writes } = recorder({ apply: true });
257
257
  deps.exec = mk('box'); deps.controlExec = mk('control');
@@ -32,7 +32,7 @@ async function ta(name, fn) {
32
32
  }
33
33
 
34
34
  const HOOK = '/opt/infra/box-dns-cloudflare.sh';
35
- const lantern = { id: 5, slug: 'lantern', hosting_shape: 'standalone', status: 'active', owner_builder_id: 7, port: 3021, db_name: 'lantern', domain: 'lantern.cloudbongos.com', target_ref: null };
35
+ const lantern = { id: 5, slug: 'lantern', hosting_shape: 'cloud-host', status: 'active', owner_builder_id: 7, port: 3021, db_name: 'lantern', domain: 'lantern.cloudbongos.com', target_ref: null };
36
36
 
37
37
  // One ordered ledger across both executors and the status writes, so ordering is assertable.
38
38
  function rig({ apply = true, privileged = false, hookThrows = false } = {}) {
@@ -67,7 +67,7 @@ async function withHook(hook, fn) {
67
67
 
68
68
  console.log('the Caddy leg:');
69
69
 
70
- await ta('standalone: the <slug>.caddy snippet is removed, THEN Caddy reloads — on the control plane, where it ran', async () => {
70
+ await ta('cloud-host: the <slug>.caddy snippet is removed, THEN Caddy reloads — on the control plane, where it ran', async () => {
71
71
  await withHook(HOOK, async () => {
72
72
  const r = rig();
73
73
  await P.teardownInstance(lantern, r.deps);