@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
@@ -89,10 +89,17 @@ t('RESTART_FROM is exactly {active} — every other state is already served by t
89
89
  }
90
90
  });
91
91
 
92
- t('RESTART_SHAPES is the runner-reachable pair — dedicated is out, and the set is a subset of HOSTING_SHAPES', () => {
93
- assert.deepEqual([...provisioning.RESTART_SHAPES].sort(), ['co-tenant', 'standalone']);
94
- assert.ok(!provisioning.RESTART_SHAPES.has('dedicated'));
92
+ t('RESTART_SHAPES is the runner-reachable pair, and the set is a subset of HOSTING_SHAPES', () => {
93
+ assert.deepEqual([...provisioning.RESTART_SHAPES].sort(), ['cloud-host', 'co-tenant']);
95
94
  for (const s of provisioning.RESTART_SHAPES) assert.ok(provisioning.HOSTING_SHAPES.includes(s), `${s} is a real shape`);
95
+ // Was `!has('dedicated')`. Task 1004182 removed that value from HOSTING_SHAPES, which made
96
+ // the assertion vacuously true — it would now pass against any set at all. Name the shapes
97
+ // that still EXIST and are still unreachable, and assert they are real first, so this
98
+ // cannot rot the same way twice.
99
+ for (const unreachable of ['byo-host', 'control-plane']) {
100
+ assert.ok(provisioning.HOSTING_SHAPES.includes(unreachable), `${unreachable} must be a real shape or this assertion is vacuous`);
101
+ assert.ok(!provisioning.RESTART_SHAPES.has(unreachable), unreachable);
102
+ }
96
103
  });
97
104
 
98
105
  await ta('enqueueRestart: a free slot creates the intent', async () => {
@@ -190,7 +197,7 @@ function post(path) {
190
197
  });
191
198
  }
192
199
 
193
- const ACTIVE = { owner_builder_id: 7, slug: 'spike', status: 'active', hosting_shape: 'standalone', domain: 'spike.cloudbongos.com', settings: {} };
200
+ const ACTIVE = { owner_builder_id: 7, slug: 'spike', status: 'active', hosting_shape: 'cloud-host', domain: 'spike.cloudbongos.com', settings: {} };
194
201
  function reset(patch = {}) {
195
202
  instance = { ...ACTIVE, ...patch };
196
203
  enqueueCalls = []; enqueueResult = null; events = [];
@@ -72,10 +72,17 @@ t('SETTINGS_APPLY_FROM is exactly {active} — the provision intent cannot stand
72
72
  assert.ok(!provisioning.PROVISION_ENQUEUE_FROM.has('active'), 'the premise the gate rests on');
73
73
  });
74
74
 
75
- t('SETTINGS_APPLY_SHAPES is the reachable pair — dedicated is out, and the set is a subset of HOSTING_SHAPES', () => {
76
- assert.deepEqual([...provisioning.SETTINGS_APPLY_SHAPES].sort(), ['co-tenant', 'standalone']);
75
+ t('SETTINGS_APPLY_SHAPES is the reachable pair, and the set is a subset of HOSTING_SHAPES', () => {
76
+ assert.deepEqual([...provisioning.SETTINGS_APPLY_SHAPES].sort(), ['cloud-host', 'co-tenant']);
77
77
  for (const s of provisioning.SETTINGS_APPLY_SHAPES) assert.ok(provisioning.HOSTING_SHAPES.includes(s), s);
78
- assert.ok(!provisioning.SETTINGS_APPLY_SHAPES.has('dedicated'));
78
+ // The exclusion this used to assert was 'dedicated', which task 1004182 removed from
79
+ // HOSTING_SHAPES entirely — so that assertion became vacuously true and stopped testing
80
+ // anything. Name the shapes that DO exist and are still deliberately unreachable: nothing
81
+ // of ours runs a byo-host, and the control plane is not pushed settings by this path.
82
+ for (const unreachable of ['byo-host', 'control-plane']) {
83
+ assert.ok(provisioning.HOSTING_SHAPES.includes(unreachable), `${unreachable} must be a real shape or this assertion is vacuous`);
84
+ assert.ok(!provisioning.SETTINGS_APPLY_SHAPES.has(unreachable), unreachable);
85
+ }
79
86
  });
80
87
 
81
88
  await ta('enqueueSettingsApply: a fresh intent → created:true; the INSERT carries the settings-apply action', async () => {
@@ -208,7 +215,7 @@ function patch(path, body) {
208
215
  });
209
216
  }
210
217
 
211
- const ACTIVE = { owner_builder_id: 7, slug: 'spike', status: 'active', hosting_shape: 'standalone', domain: 'spike.cloudbongos.com', settings: {} };
218
+ const ACTIVE = { owner_builder_id: 7, slug: 'spike', status: 'active', hosting_shape: 'cloud-host', domain: 'spike.cloudbongos.com', settings: {} };
212
219
  function reset({ inst, as } = {}) {
213
220
  instance = inst !== undefined ? inst : { ...ACTIVE };
214
221
  builder = as || { id: 7, github_login: 'owner', rank: 'thetes' };
@@ -230,7 +237,7 @@ function receiptShape(push, { queued, restart, reachable }) {
230
237
 
231
238
  console.log('\nPATCH /provisioning/instances/:id/settings — the enqueue:');
232
239
 
233
- await ta('ACTIVE standalone: the row is written FIRST, then a settings-apply intent queues; the receipt says a restart is coming', async () => {
240
+ await ta('ACTIVE cloud-host: the row is written FIRST, then a settings-apply intent queues; the receipt says a restart is coming', async () => {
234
241
  reset();
235
242
  const r = await patch('/provisioning/instances/18/settings', { platform_visibility: 'gated' });
236
243
  assert.equal(r.status, 200, r.raw);
@@ -168,7 +168,7 @@ t('the runner writes them into web.env alongside PORT and PGDATABASE', () => {
168
168
  const provision = require('../scripts/gds/provision.js');
169
169
  const body = provision.webEnvBody(
170
170
  { slug: 'spike-harbor', port: 3010, db_name: 'spike-harbor', target_ref: 'jaxri/spike-harbor',
171
- hosting_shape: 'standalone', settings: { platform_visibility: 'gated' } }, {});
171
+ hosting_shape: 'cloud-host', settings: { platform_visibility: 'gated' } }, {});
172
172
  assert.match(body, /_PLATFORM_VISIBILITY=gated/);
173
173
  assert.match(body, /_JOINABILITY=apply/);
174
174
  assert.match(body, /PORT=3010/, 'the rest of the file is untouched');
@@ -250,7 +250,7 @@ console.log('\nPOST /provisioning/instances — no provision intent for an insta
250
250
  await ta('re-requesting an ACTIVE instance does NOT enqueue a provision intent (the enabling misstep, fixed)', async () => {
251
251
  createResult = { instance: { id: 42, owner_builder_id: 7, slug: 'spike', status: 'active' }, created: false };
252
252
  enqueueIntentCalls = [];
253
- const res = await post('/provisioning/instances', { slug: 'spike', hosting_shape: 'standalone' });
253
+ const res = await post('/provisioning/instances', { slug: 'spike', hosting_shape: 'cloud-host' });
254
254
  assert.equal(res.status, 200, res.raw);
255
255
  assert.equal(enqueueIntentCalls.length, 0, 'no provision intent was enqueued');
256
256
  assert.equal(res.body.queued, false);
@@ -260,7 +260,7 @@ await ta('re-requesting an ACTIVE instance does NOT enqueue a provision intent (
260
260
  await ta('re-requesting an ERRORED instance still enqueues (the retry path is preserved)', async () => {
261
261
  createResult = { instance: { id: 42, owner_builder_id: 7, slug: 'spike', status: 'error' }, created: false };
262
262
  enqueueIntentCalls = [];
263
- const res = await post('/provisioning/instances', { slug: 'spike', hosting_shape: 'standalone' });
263
+ const res = await post('/provisioning/instances', { slug: 'spike', hosting_shape: 'cloud-host' });
264
264
  assert.equal(res.status, 200, res.raw);
265
265
  assert.equal(enqueueIntentCalls.length, 1, 'the provision retry was enqueued');
266
266
  assert.equal(enqueueIntentCalls[0].action, 'provision');
@@ -269,7 +269,7 @@ await ta('re-requesting an ERRORED instance still enqueues (the retry path is pr
269
269
  await ta('a FRESH instance always enqueues its provision intent (unchanged happy path)', async () => {
270
270
  createResult = { instance: { id: 43, owner_builder_id: 7, slug: 'fresh', status: 'requested' }, created: true };
271
271
  enqueueIntentCalls = [];
272
- const res = await post('/provisioning/instances', { slug: 'fresh', hosting_shape: 'standalone' });
272
+ const res = await post('/provisioning/instances', { slug: 'fresh', hosting_shape: 'cloud-host' });
273
273
  assert.equal(res.status, 201, res.raw);
274
274
  assert.equal(enqueueIntentCalls.length, 1);
275
275
  assert.equal(enqueueIntentCalls[0].action, 'provision');
@@ -193,14 +193,21 @@ test('the manual-repo step states placement and visibility — the owner never h
193
193
  assert.match(body, /public or private/i, 'visibility must be stated in our copy, not first seen on GitHub');
194
194
  });
195
195
 
196
- test('teardown copy states what the runner actually does, per hosting shape (F17)', async () => {
196
+ test('teardown copy states what the runner actually does (F17)', async () => {
197
197
  const { body } = await request('/projects', 'game.test.local');
198
- // the DECIDED policy: shared shapes keep the database; a dedicated machine
199
- // takes its database with it — the copy branches on the shape field
200
- assert.match(body, /The database is kept/, 'the kept-shape promise is present');
201
- assert.match(body, /database goes with (it|the machine)/, 'the dedicated branch exists');
202
- assert.match(body, /hosting_shape === 'dedicated'/, 'shape-driven, not flat prose');
198
+ // The DECIDED policy is unchanged — teardown keeps the database — but it is no longer a
199
+ // PER-SHAPE claim. This used to branch: every shared shape kept the database while a
200
+ // 'dedicated' machine took its database with it. ADR 0327 decision 4 removed that shape
201
+ // and task 1004182 folded its rows into 'cloud-host', so the branch could never be taken
202
+ // again and the page now states the surviving outcome flatly.
203
+ //
204
+ // The important part is the NEGATIVE: the copy must no longer offer the other outcome at
205
+ // all. A conditional that can't fire is not merely dead here — it is a promise to owners
206
+ // about a machine none of them have.
207
+ assert.match(body, /The database is kept/, 'the kept promise is present');
203
208
  assert.match(body, /repository is untouched|repository stays yours|repository is still yours/i);
209
+ assert.doesNotMatch(body, /database goes with (it|the machine)/, 'the removed shape\'s copy is gone with it');
210
+ assert.doesNotMatch(body, /hosting_shape === 'dedicated'/, 'and so is the branch that selected it');
204
211
  // the pre-walk over-claim can never come back
205
212
  assert.doesNotMatch(body, /server and database go away/i);
206
213
  assert.doesNotMatch(body, /database (is|will be) (deleted|dropped|destroyed|removed)/i);
package/tests/upgrade.mjs CHANGED
@@ -1621,7 +1621,7 @@ t('pinnedDeployKey / remoteGithubRepo: read the ADR 0176 wiring off the checkout
1621
1621
  });
1622
1622
 
1623
1623
  t('resolvePinPushAuth: the owner token is spent only when one is stored AND the provisioning row names the remote repo', async () => {
1624
- const inst = { slug: 'acme', owner_builder_id: 7, target_ref: 'acme/widgets', hosting_shape: 'standalone' };
1624
+ const inst = { slug: 'acme', owner_builder_id: 7, target_ref: 'acme/widgets', hosting_shape: 'cloud-host' };
1625
1625
  const at = (url) => pinRun({ remote: { status: 0, stdout: `${url}\n` } });
1626
1626
  const dir = { instanceDir: '/srv/bongos/acme' };
1627
1627
 
@@ -1683,7 +1683,7 @@ t('runUpgrade --commit-pin: on a wired private instance the pin lands via the ow
1683
1683
  materialize: () => ({ skills: 35, settings: true, hooks: 3 }),
1684
1684
  regenerateApiArtifacts: () => ({ ok: true, ran: ['gen-api-docs.js', 'gen-api-client.js'] }),
1685
1685
  databaseUrl: '', pgDatabase: '',
1686
- instance: { slug: 'acme', owner_builder_id: 7, target_ref: 'acme/widgets', hosting_shape: 'standalone' },
1686
+ instance: { slug: 'acme', owner_builder_id: 7, target_ref: 'acme/widgets', hosting_shape: 'cloud-host' },
1687
1687
  getGithubToken: async () => ({ token, scope: 'read:user,repo' }),
1688
1688
  },
1689
1689
  );