@bongos/core 1.19.1074 → 1.19.1075

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 (114) hide show
  1. package/.bongos-core.json +122 -122
  2. package/bin/bongos.js +1 -3
  3. package/docs/api/openapi.json +4 -4
  4. package/docs/module-api-changelog.md +2 -0
  5. package/modules/copy-desk/routes/copy-desk.js +1 -1
  6. package/modules/discord/ship-broadcast.js +3 -3
  7. package/modules/economy/credits.js +2 -2
  8. package/modules/ideas/projection.js +1 -1
  9. package/modules/lifecycle/db-ship.js +4 -4
  10. package/modules/lifecycle/github-push.js +12 -14
  11. package/modules/lifecycle/publish-reconciler.js +10 -10
  12. package/modules/lifecycle/routes/tasks.js +8 -8
  13. package/modules/lifecycle/ship-card.js +1 -1
  14. package/modules/lifecycle/task-visuals.js +2 -2
  15. package/modules/memory/routes/memory.js +2 -2
  16. package/modules/platform-identity/routes/sso.js +3 -2
  17. package/modules/provisioning/capacity.js +1 -1
  18. package/modules/provisioning/provisioning.js +4 -3
  19. package/modules/provisioning/routes/provisioning.js +4 -4
  20. package/modules/sessions/db.js +1 -1
  21. package/package-lock.json +2 -2
  22. package/package.json +1 -1
  23. package/release-notes.json +6 -0
  24. package/scripts/gds/artifact-staleness.js +2 -2
  25. package/scripts/gds/backfill-task-rewards.js +5 -5
  26. package/scripts/gds/claude-materialize.js +2 -2
  27. package/scripts/gds/cli-lib.js +7 -7
  28. package/scripts/gds/codemod-rename-src-gds.js +1 -1
  29. package/scripts/gds/context-pack.js +5 -7
  30. package/scripts/gds/doctor.js +3 -3
  31. package/scripts/gds/fitness-checks-packaging.js +4 -4
  32. package/scripts/gds/fitness-ratchets.js +1 -1
  33. package/scripts/gds/gen-api-client.js +4 -4
  34. package/scripts/gds/gen-api-docs.js +1 -1
  35. package/scripts/gds/gen-repo-map.js +13 -14
  36. package/scripts/gds/gen-session-index.js +8 -8
  37. package/scripts/gds/leak-scan-allowlist.js +1 -1
  38. package/scripts/gds/plain-cards.js +1 -1
  39. package/scripts/gds/push-path-brief.js +10 -10
  40. package/scripts/gds/rename-history-check.js +1 -1
  41. package/scripts/gds/session-digest-build.js +2 -2
  42. package/scripts/gds/ship-deploy-target.js +13 -13
  43. package/scripts/gds/ship-flow.js +1 -1
  44. package/scripts/gds/ship-land.js +6 -6
  45. package/scripts/gds/ship-merge.js +4 -4
  46. package/scripts/gds/ship-preflight-steps.js +4 -4
  47. package/scripts/gds/ship-regen.js +9 -12
  48. package/scripts/gds/ship.js +9 -9
  49. package/scripts/gds/skill-preflight.js +9 -8
  50. package/src/bongos/api-prefix.js +4 -4
  51. package/src/bongos/platform-visibility-gate.js +1 -1
  52. package/src/bongos/routes/me.js +1 -1
  53. package/src/bongos/routes/security.js +3 -3
  54. package/src/bongos/routes.js +2 -2
  55. package/src/bongos/serve-internal.js +5 -6
  56. package/src/bongos-downloads.js +1 -6
  57. package/src/branding.js +2 -2
  58. package/src/build-info.js +4 -6
  59. package/src/instance-config.js +2 -2
  60. package/src/module-api.js +3 -3
  61. package/src/module-loader/manifest-schema.js +1 -1
  62. package/tests/api_alias_caller_ratchet.mjs +1 -1
  63. package/tests/api_alias_exceptions.mjs +1 -1
  64. package/tests/api_client.mjs +1 -1
  65. package/tests/api_path_404.mjs +1 -1
  66. package/tests/backfill_task_rewards.mjs +1 -1
  67. package/tests/{box_ship_permissions.mjs → cli_allowlist_permissions.mjs} +5 -5
  68. package/tests/cli_sessions.mjs +1 -1
  69. package/tests/context_pack.mjs +13 -13
  70. package/tests/copy_inventory.mjs +7 -7
  71. package/tests/credit_grant.mjs +2 -2
  72. package/tests/criterion_suggest.mjs +1 -1
  73. package/tests/design_tokens_sync.mjs +3 -2
  74. package/tests/fitness_ratchets.mjs +2 -2
  75. package/tests/github_push_land_proof.mjs +1 -1
  76. package/tests/goal_advisory.mjs +3 -3
  77. package/tests/goal_suggest.mjs +3 -3
  78. package/tests/government_abuse_matrix.mjs +4 -3
  79. package/tests/hall_audit.mjs +1 -1
  80. package/tests/helpers.mjs +1 -1
  81. package/tests/host_topology_skips.mjs +2 -2
  82. package/tests/migration_namespace.mjs +6 -6
  83. package/tests/module_api.mjs +2 -2
  84. package/tests/module_cli.mjs +5 -5
  85. package/tests/module_manifest.mjs +13 -13
  86. package/tests/module_route_rank.mjs +2 -2
  87. package/tests/modules_route_wiring.mjs +1 -1
  88. package/tests/onboarding_route_signals.mjs +1 -1
  89. package/tests/paste_token_session_store.mjs +1 -1
  90. package/tests/platform_visibility_gate.mjs +1 -1
  91. package/tests/project_modules_ui.mjs +17 -17
  92. package/tests/projects_hub_module_picker.mjs +75 -74
  93. package/tests/provisioning_capacity.mjs +1 -1
  94. package/tests/provisioning_recommendations.mjs +2 -1
  95. package/tests/publish_branch_route.mjs +1 -1
  96. package/tests/publish_manifest.mjs +1 -1
  97. package/tests/publish_reconciler.mjs +8 -8
  98. package/tests/push_path_brief.mjs +10 -9
  99. package/tests/regrade_eligibility.mjs +1 -1
  100. package/tests/rename_history_restraint.mjs +1 -1
  101. package/tests/repo_map.mjs +9 -9
  102. package/tests/seam_wiring_guard.mjs +11 -11
  103. package/tests/search_isolation.mjs +3 -3
  104. package/tests/session_records.mjs +1 -1
  105. package/tests/session_rename_fallback.mjs +1 -1
  106. package/tests/session_start_freshness.mjs +3 -3
  107. package/tests/session_token_hash_db.mjs +1 -1
  108. package/tests/ship_card.mjs +1 -1
  109. package/tests/ship_ci_deploy.mjs +17 -17
  110. package/tests/ship_premerge.mjs +1 -1
  111. package/tests/ship_resume.mjs +1 -1
  112. package/tests/skill_preflight.mjs +2 -2
  113. package/tests/skip_is_not_pass.mjs +1 -1
  114. package/tests/terms_acceptance.mjs +1 -1
@@ -3,7 +3,7 @@
3
3
  // tests/ is publishable, but some tests assert paths the neutral core deliberately does
4
4
  // not carry: publish-manifest.js's PROTECTED_TOPOLOGY_EXCLUDE (`.claude/hooks/`,
5
5
  // `.claude/settings.json`, `infra/`, `.github/`, `scripts/deploy/`) plus instance-only
6
- // content (`devbox-app/`, `config/<instance>.json`, an uninstalled module). Those files
6
+ // content (`config/<instance>.json`, an uninstalled module). Those files
7
7
  // used to CRASH at import and red-X the unit gate for every consumer; they now skip
8
8
  // cleanly via the helpers in tests/helpers.mjs and run in full wherever the topology is
9
9
  // materialized.
@@ -33,7 +33,7 @@ const ALLOWED = {
33
33
  allow_rule_guard: '.claude/hooks/allow-rule-guard.js (package-excluded)',
34
34
  art_stage: 'scripts/gds/art-stage.js (art-pipeline tooling, not in the core)',
35
35
  bongos_downloads: 'config/bongos-cli.json (instance content)',
36
- box_ship_permissions: '.claude/settings.json (package-excluded)',
36
+ cli_allowlist_permissions: '.claude/settings.json (package-excluded)',
37
37
  conductor: '.claude/hooks/conductor.js (package-excluded)',
38
38
  deploy_instance: 'scripts/deploy/deploy-instance.sh (host topology)',
39
39
  eviction_policy: '.claude/hooks/eviction-policy.js (package-excluded)',
@@ -46,8 +46,8 @@ t('parseMigrationName: core_NNN_slug.sql', () => {
46
46
  });
47
47
 
48
48
  t('parseMigrationName: multi-word module prefix splits at the number, not greedily', () => {
49
- const p = ns.parseMigrationName('dev_box_003_add_col.sql');
50
- assert.equal(p.prefix, 'dev_box', 'dev_box_003 → prefix dev_box, not dev');
49
+ const p = ns.parseMigrationName('platform_identity_003_add_col.sql');
50
+ assert.equal(p.prefix, 'platform_identity', 'platform_identity_003 → prefix platform_identity, not platform');
51
51
  assert.equal(p.number, 3);
52
52
  assert.equal(p.slug, 'add_col');
53
53
  });
@@ -114,10 +114,10 @@ t('filenameViolation: NEW module migration must be <key>_-prefixed', () => {
114
114
  });
115
115
 
116
116
  t('filenameViolation: hyphenated module key maps to underscore prefix', () => {
117
- assert.equal(ns.sqlNamespace('dev-box'), 'dev_box');
118
- assert.equal(ns.filenameViolation('module', 'dev_box_003_x.sql', { moduleKey: 'dev-box' }), null);
119
- const v = ns.filenameViolation('module', '003_x.sql', { moduleKey: 'dev-box' });
120
- assert.ok(v && /dev_box_-prefixed|dev_box_003_x/.test(v));
117
+ assert.equal(ns.sqlNamespace('platform-identity'), 'platform_identity');
118
+ assert.equal(ns.filenameViolation('module', 'platform_identity_003_x.sql', { moduleKey: 'platform-identity' }), null);
119
+ const v = ns.filenameViolation('module', '003_x.sql', { moduleKey: 'platform-identity' });
120
+ assert.ok(v && /platform_identity_-prefixed|platform_identity_003_x/.test(v));
121
121
  });
122
122
 
123
123
  // ---- stemCollisions --------------------------------------------------------
@@ -16,8 +16,8 @@ const seams = require(path.join(ROOT, 'src', 'module-seams.js'));
16
16
  // removing/renaming a key is normally a MAJOR bump (a breaking change to every
17
17
  // module). Exception (BV1.R68 / ADR 0091 §3): relocating a DOMAIN query whose
18
18
  // sole in-repo consumer moves in lockstep is treated as MINOR — e.g.
19
- // `setBuilderBoxBlocked` left for modules/dev-box/db.js at 1.5.0, when `withTx`
20
- // was added; no enabled module at its pinned coreVersion lost a capability.
19
+ // a per-builder setter left the kernel for its own module's db.js at 1.5.0, when
20
+ // `withTx` was added; no enabled module at its pinned coreVersion lost a capability.
21
21
  const PUBLISHED_SURFACE = [
22
22
  'CORE_VERSION',
23
23
  'requireBuilder', 'requireRank', 'rankMeetsThreshold', 'requireBfgPrincipal', 'allowBoxScope', 'resolveSession',
@@ -63,11 +63,11 @@ test('scaffoldModule: writes a valid, loader-discoverable module with no core ed
63
63
 
64
64
  test('scaffoldModule: a kebab-case key produces matching dir, route file, and title', () => {
65
65
  const root = tmpModulesRoot();
66
- const { dir, manifest } = mod.scaffoldModule({ key: 'dev-box', modulesDir: root, core: '1.0.0' });
67
- assert.equal(manifest.title, 'Dev Box');
68
- assert.deepEqual(manifest.contributes.routes, ['dev-box']);
69
- assert.ok(fs.existsSync(path.join(dir, 'routes', 'dev-box.js')));
70
- assert.match(fs.readFileSync(path.join(dir, 'routes', 'dev-box.js'), 'utf8'), /devBoxRoutes/);
66
+ const { dir, manifest } = mod.scaffoldModule({ key: 'copy-desk', modulesDir: root, core: '1.0.0' });
67
+ assert.equal(manifest.title, 'Copy Desk');
68
+ assert.deepEqual(manifest.contributes.routes, ['copy-desk']);
69
+ assert.ok(fs.existsSync(path.join(dir, 'routes', 'copy-desk.js')));
70
+ assert.match(fs.readFileSync(path.join(dir, 'routes', 'copy-desk.js'), 'utf8'), /copyDeskRoutes/);
71
71
  });
72
72
 
73
73
  test('scaffoldModule: rejects an invalid key', () => {
@@ -12,11 +12,11 @@ const { satisfies, isValidRange, parseVersion } = require(path.join(ROOT, 'src',
12
12
  const { validateManifest, assertValidManifest } = require(path.join(ROOT, 'src', 'module-loader', 'manifest-schema.js'));
13
13
 
14
14
  const baseManifest = () => ({
15
- key: 'dev-box',
16
- title: 'Dev boxes',
17
- description: 'Per-builder cloud dev environments.',
15
+ key: 'discord',
16
+ title: 'Discord',
17
+ description: 'A bridge to the team chat.',
18
18
  version: '1.0.0', coreVersion: '^1.0.0',
19
- contributes: { routes: ['box'], pollers: ['box-lifecycle'], uiSections: ['boxes'] },
19
+ contributes: { routes: ['discord'], pollers: ['discord-bot'], uiSections: ['chat'] },
20
20
  });
21
21
 
22
22
  // --- semver ---
@@ -91,7 +91,7 @@ test('missing required fields each produce an error', () => {
91
91
  });
92
92
 
93
93
  test('bad key format is rejected', () => {
94
- const r = validateManifest({ ...baseManifest(), key: 'Dev_Box' });
94
+ const r = validateManifest({ ...baseManifest(), key: 'Discord_Bot' });
95
95
  assert.equal(r.valid, false);
96
96
  assert.ok(r.errors.some((e) => e.startsWith('key')));
97
97
  });
@@ -112,13 +112,13 @@ test('version must be an exact X.Y.Z — a range or junk is rejected (task 10037
112
112
  });
113
113
 
114
114
  test('unknown contributes key is rejected (typo guard)', () => {
115
- const r = validateManifest({ ...baseManifest(), contributes: { routs: ['box'] } });
115
+ const r = validateManifest({ ...baseManifest(), contributes: { routs: ['discord'] } });
116
116
  assert.equal(r.valid, false);
117
117
  assert.ok(r.errors.some((e) => e.includes('contributes.routs')));
118
118
  });
119
119
 
120
120
  test('non-array contributes.routes is rejected', () => {
121
- const r = validateManifest({ ...baseManifest(), contributes: { routes: 'box' } });
121
+ const r = validateManifest({ ...baseManifest(), contributes: { routes: 'discord' } });
122
122
  assert.equal(r.valid, false);
123
123
  assert.ok(r.errors.some((e) => e.includes('contributes.routes')));
124
124
  });
@@ -296,7 +296,7 @@ test('every bundled module declares a maintenance posture (the catalog has no un
296
296
  test('declarativeSeams: a valid exemption naming a declared seam + entry passes', () => {
297
297
  const r = validateManifest({
298
298
  ...baseManifest(),
299
- declarativeSeams: { pollers: ['box-lifecycle'], uiSections: ['boxes'] },
299
+ declarativeSeams: { pollers: ['discord-bot'], uiSections: ['chat'] },
300
300
  });
301
301
  assert.equal(r.valid, true, r.errors.join('; '));
302
302
  });
@@ -304,8 +304,8 @@ test('declarativeSeams: a valid exemption naming a declared seam + entry passes'
304
304
  test('declarativeSeams: an exemption for a seam the module does not declare is an ERROR', () => {
305
305
  const r = validateManifest({
306
306
  ...baseManifest(),
307
- contributes: { routes: ['box'] }, // no pollers declared
308
- declarativeSeams: { pollers: ['box-lifecycle'] },
307
+ contributes: { routes: ['discord'] }, // no pollers declared
308
+ declarativeSeams: { pollers: ['discord-bot'] },
309
309
  });
310
310
  assert.equal(r.valid, false);
311
311
  assert.ok(r.errors.some((e) => /does not declare contributes\.pollers/.test(e)), r.errors.join('; '));
@@ -314,10 +314,10 @@ test('declarativeSeams: an exemption for a seam the module does not declare is a
314
314
  test('declarativeSeams: an exemption naming an ENTRY the module does not list is an ERROR (the typo case)', () => {
315
315
  const r = validateManifest({
316
316
  ...baseManifest(),
317
- declarativeSeams: { pollers: ['box-lifecyle'] }, // transposed letters
317
+ declarativeSeams: { pollers: ['discord-bto'] }, // transposed letters
318
318
  });
319
319
  assert.equal(r.valid, false);
320
- assert.ok(r.errors.some((e) => /"box-lifecyle" is not in contributes\.pollers/.test(e)), r.errors.join('; '));
320
+ assert.ok(r.errors.some((e) => /"discord-bto" is not in contributes\.pollers/.test(e)), r.errors.join('; '));
321
321
  });
322
322
 
323
323
  test('declarativeSeams: the per-entry check spans EVERY seam, not just bare string arrays', () => {
@@ -344,7 +344,7 @@ test('declarativeSeams: an unknown seam key is an ERROR, not a silent no-op', ()
344
344
 
345
345
  test('declarativeSeams: shape errors — non-object block, non-string-array entries', () => {
346
346
  assert.equal(validateManifest({ ...baseManifest(), declarativeSeams: ['pollers'] }).valid, false);
347
- assert.equal(validateManifest({ ...baseManifest(), declarativeSeams: { pollers: 'box-lifecycle' } }).valid, false);
347
+ assert.equal(validateManifest({ ...baseManifest(), declarativeSeams: { pollers: 'discord-bot' } }).valid, false);
348
348
  assert.equal(validateManifest({ ...baseManifest(), declarativeSeams: { pollers: [''] } }).valid, false);
349
349
  });
350
350
 
@@ -91,9 +91,9 @@ test('checkRouteRanks: an absent modules/ dir leaves the core result clean (byte
91
91
 
92
92
  // ---- the grade-time trigger fires on a module route change ----
93
93
  test('ROUTE_FILE_RE matches a module route path (a module route change triggers the rank pre-pass)', () => {
94
- assert.match('modules/dev-box/routes/box.js', rrc.ROUTE_FILE_RE);
94
+ assert.match('modules/discord/routes/discord.js', rrc.ROUTE_FILE_RE);
95
95
  assert.match('src/bongos/routes/tasks.js', rrc.ROUTE_FILE_RE); // core still matches
96
- assert.ok(!rrc.ROUTE_FILE_RE.test('modules/dev-box/module.json'), 'a non-route module file does not trigger the pre-pass');
96
+ assert.ok(!rrc.ROUTE_FILE_RE.test('modules/discord/module.json'), 'a non-route module file does not trigger the pre-pass');
97
97
  });
98
98
 
99
99
  // ---- a rank DRIFT on a pinned MODULE route reds the build (BV1.R61) ----
@@ -79,7 +79,7 @@ test('1002857: POST /modules/:key/enable rejects a non-boolean before touching t
79
79
  // is why the next assertion matters more than this one.
80
80
  const router = buildModulesRouter();
81
81
  const res = await invoke(handlerFor(router, 'post', '/modules/:key/enable'), {
82
- params: { key: 'dev-box' }, query: {}, body: { enabled: 'yes-please' }, builder: { id: 1 },
82
+ params: { key: 'discord' }, query: {}, body: { enabled: 'yes-please' }, builder: { id: 1 },
83
83
  });
84
84
  assert.equal(res.body.error, 'invalid_body');
85
85
  });
@@ -137,7 +137,7 @@ await t('no live signals → discord/cli both uncleared, and no retired box step
137
137
  assert.equal(r.status, 200);
138
138
  assert.equal(stepCleared(r.json, 'join_discord'), false);
139
139
  assert.equal(stepCleared(r.json, 'cli_setup'), false);
140
- assert.equal(stepCleared(r.json, 'box_connect'), undefined, 'the dev-box step left the checklist (task 1004310)');
140
+ assert.equal(stepCleared(r.json, 'box_connect'), undefined, 'the retired box_connect step is no longer on the checklist (task 1004310)');
141
141
  });
142
142
 
143
143
  await t('discord linked → join_discord clears from ITS OWN signal [the scootermack regression]', async () => {
@@ -139,7 +139,7 @@ test('pasting a token for instance B PRESERVES instance A — the bug this close
139
139
  assert.equal(stored.token, 'B'.repeat(64));
140
140
 
141
141
  // 3. The active pointer still follows the paste, in the same place every existing
142
- // reader (skills, hooks, the dev box) looks. The fix is additive or it is a
142
+ // reader (skills, hooks) looks. The fix is additive or it is a
143
143
  // different, quieter outage.
144
144
  const pointer = readJson(active);
145
145
  assert.equal(pointer.api_base, base, 'the active pointer must follow the paste');
@@ -162,7 +162,7 @@ test('the exempt list: the door, the manifest, the probes, the downloads — on
162
162
  '/api/gds/instance', '/api/bongos/instance', '/api/gds/healthz',
163
163
  '/api/bongos/provisioning/tls-check', '/api/gds/provisioning/health',
164
164
  '/healthz', '/version', '/metrics', '/__stealth',
165
- '/downloads/bongos/install.sh', '/downloads/devbox/latest.json',
165
+ '/downloads/bongos/install.sh', '/downloads/bongos/latest.json',
166
166
  // Express routes case-insensitively and with a trailing slash — the list sees what the router sees
167
167
  '/API/GDS/INSTANCE', '/api/gds/instance/', '/HEALTHZ', '/api/bongos/auth/',
168
168
  ]) assert.equal(isExempt(p), true, `${p} must answer a stranger`);
@@ -56,7 +56,7 @@ for (const fn of ['function ensureModsCatalog(', 'function renderMods(', 'functi
56
56
  const CATALOG = {
57
57
  core: ['memory', 'lifecycle', 'hall-ui'],
58
58
  optional: [
59
- { key: 'dev-box', label: 'Dev box', summary: 'A shared machine your builders work on.' },
59
+ { key: 'npm-release', label: 'npm release', summary: 'Publishes each release of your project to npm.' },
60
60
  { key: 'discord', label: 'Discord', summary: 'Mirrors what ships into a Discord channel.' },
61
61
  { key: 'agents', label: 'Agents', summary: 'Lets AI builders claim and ship work on their own.' },
62
62
  ],
@@ -76,7 +76,7 @@ function parseRows(html) {
76
76
 
77
77
  // Boot the region against a scripted catalog + detail channel and hand back the
78
78
  // handles a test drives it with.
79
- function boot({ selected = ['dev-box'], source = 'chosen', catalog = CATALOG, catalogOk = true, patchAnswer } = {}) {
79
+ function boot({ selected = ['npm-release'], source = 'chosen', catalog = CATALOG, catalogOk = true, patchAnswer } = {}) {
80
80
  const requests = [];
81
81
  const els = new Map();
82
82
  const listeners = new Map();
@@ -156,14 +156,14 @@ function boot({ selected = ['dev-box'], source = 'chosen', catalog = CATALOG, ca
156
156
 
157
157
  test('the band renders the optional catalog the server offered — a row and a sentence each', async () => {
158
158
  // Arrange
159
- const ui = boot({ selected: ['dev-box'] });
159
+ const ui = boot({ selected: ['npm-release'] });
160
160
 
161
161
  // Act
162
162
  await ui.render();
163
163
 
164
164
  // Assert
165
- assert.deepEqual(ui.rows().map((r) => r.key), ['dev-box', 'discord', 'agents']);
166
- assert.deepEqual(ui.rows().map((r) => r.label), ['Dev box', 'Discord', 'Agents']);
165
+ assert.deepEqual(ui.rows().map((r) => r.key), ['npm-release', 'discord', 'agents']);
166
+ assert.deepEqual(ui.rows().map((r) => r.label), ['npm release', 'Discord', 'Agents']);
167
167
  for (const r of ui.rows()) assert.ok(r.summary.length > 20, `${r.label} says what it is for`);
168
168
  });
169
169
 
@@ -171,11 +171,11 @@ test('what the project runs today is what is switched on — resolved by the ser
171
171
  // A project that SKIPPED the picker reads as its own starter bundle, and the
172
172
  // band must show that set rather than an empty one: the whole point of
173
173
  // effectiveModules resolving `source: 'bundle'` on the way out.
174
- const ui = boot({ selected: ['dev-box', 'discord'], source: 'bundle' });
174
+ const ui = boot({ selected: ['npm-release', 'discord'], source: 'bundle' });
175
175
 
176
176
  await ui.render();
177
177
 
178
- assert.deepEqual(ui.rows().filter((r) => r.on).map((r) => r.key), ['dev-box', 'discord']);
178
+ assert.deepEqual(ui.rows().filter((r) => r.on).map((r) => r.key), ['npm-release', 'discord']);
179
179
  assert.deepEqual(ui.rows().filter((r) => !r.on).map((r) => r.key), ['agents']);
180
180
  });
181
181
 
@@ -185,7 +185,7 @@ test('the always-on core is COUNTED and stated, and is not a row anyone can swit
185
185
  await ui.render();
186
186
 
187
187
  assert.match(ui.slot(), /the same 3 core parts/, 'the clause names how many, from the server\'s own list');
188
- assert.deepEqual(ui.rows().map((r) => r.key), ['dev-box', 'discord', 'agents'], 'and no core key is on the band');
188
+ assert.deepEqual(ui.rows().map((r) => r.key), ['npm-release', 'discord', 'agents'], 'and no core key is on the band');
189
189
  for (const key of CATALOG.core) {
190
190
  assert.equal(ui.rows().some((r) => r.key === key), false, `${key} is not a choice`);
191
191
  }
@@ -196,26 +196,26 @@ test('a toggle issues the write — the WHOLE list, in catalog order, on the own
196
196
  const ui = boot({ selected: ['discord'] });
197
197
  await ui.render();
198
198
 
199
- // Act — switch the dev box ON
200
- await ui.click('dev-box');
199
+ // Act — switch npm release ON
200
+ await ui.click('npm-release');
201
201
 
202
202
  // Assert
203
203
  const patch = ui.requests.find((r) => r.method === 'PATCH');
204
204
  assert.ok(patch, 'a PATCH was sent');
205
205
  assert.equal(patch.url, '/api/bongos/provisioning/instances/18/detail');
206
- assert.deepEqual(JSON.parse(patch.body), { modules: ['dev-box', 'discord'] },
206
+ assert.deepEqual(JSON.parse(patch.body), { modules: ['npm-release', 'discord'] },
207
207
  'catalog order, exactly as the column stores it — never append order');
208
208
  assert.equal(patch.credentials, 'same-origin', 'the cookie session is the auth — this page carries no token');
209
209
  });
210
210
 
211
211
  test('a toggle can REMOVE one too — the same list, one key shorter', async () => {
212
- const ui = boot({ selected: ['dev-box', 'agents'] });
212
+ const ui = boot({ selected: ['npm-release', 'agents'] });
213
213
  await ui.render();
214
214
 
215
215
  await ui.click('agents');
216
216
 
217
217
  const patch = ui.requests.find((r) => r.method === 'PATCH');
218
- assert.deepEqual(JSON.parse(patch.body), { modules: ['dev-box'] });
218
+ assert.deepEqual(JSON.parse(patch.body), { modules: ['npm-release'] });
219
219
  });
220
220
 
221
221
  test('switching the last one off sends an empty list — a real answer, not a skipped write', async () => {
@@ -243,8 +243,8 @@ test('the band repaints from the PLATFORM’S answer, and says the save changed
243
243
 
244
244
  test('a refusal is SAID in the server’s own words, and the band goes back to what the row still holds', async () => {
245
245
  const ui = boot({
246
- selected: ['dev-box'],
247
- patchAnswer: Promise.resolve({ ok: false, status: 400, _json: { error: { code: 'bad_modules', message: 'Unknown module "discrod" — the choices are: dev-box, discord, agents.' } } }),
246
+ selected: ['npm-release'],
247
+ patchAnswer: Promise.resolve({ ok: false, status: 400, _json: { error: { code: 'bad_modules', message: 'Unknown module "discrod" — the choices are: npm-release, discord, agents.' } } }),
248
248
  });
249
249
  await ui.render();
250
250
 
@@ -252,7 +252,7 @@ test('a refusal is SAID in the server’s own words, and the band goes back to w
252
252
 
253
253
  assert.equal(ui.msg().className, 'projMsg err');
254
254
  assert.match(ui.msg().textContent, /Unknown module "discrod"/);
255
- assert.deepEqual(ui.rows().filter((r) => r.on).map((r) => r.key), ['dev-box'],
255
+ assert.deepEqual(ui.rows().filter((r) => r.on).map((r) => r.key), ['npm-release'],
256
256
  'a refused write leaves the band showing the project’s real set');
257
257
  });
258
258
 
@@ -269,7 +269,7 @@ test('a signed-out save offers a way back in rather than a dead end', async () =
269
269
  });
270
270
 
271
271
  test('a catalog that ANSWERED badly says so; nothing about the project is claimed to have changed', async () => {
272
- const ui = boot({ selected: ['dev-box'], catalogOk: false });
272
+ const ui = boot({ selected: ['npm-release'], catalogOk: false });
273
273
 
274
274
  await ui.render();
275
275
  await ui.settle();