@bongos/core 1.19.514 → 1.19.516
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.
- package/.bongos-core.json +81 -36
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +12 -0
- package/clients/bongos-client/index.cjs +12 -0
- package/clients/bongos-client/index.d.ts +22 -2
- package/clients/bongos-client/index.mjs +12 -0
- package/docs/adr/0240-a-skipped-module-picker-stores-nothing-and-resolves-the-bundle-on-read.md +157 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +362 -6
- package/docs/api-reference.md +12 -2
- package/docs/copy-inventory.md +224 -218
- package/docs/copy-registry.json +357 -291
- package/docs/module-api-changelog.md +4 -0
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +12 -0
- package/modules/government/catalog.js +6 -0
- package/modules/government/migrations/government_009_mingle_manage.sql +55 -0
- package/modules/lifecycle/migrations/lifecycle_007_mingle.sql +134 -0
- package/modules/lifecycle/mingle.js +337 -0
- package/modules/lifecycle/module.json +1 -0
- package/modules/lifecycle/routes/mingle.js +116 -0
- package/modules/provisioning/migrations/provisioning_014_module_selection.sql +60 -0
- package/modules/provisioning/provisioning.js +24 -4
- package/modules/provisioning/routes/provisioning.js +59 -2
- package/modules/provisioning/starter-bundles.js +91 -0
- package/modules/public-landing/public/assets/cosmos.css +1 -1
- package/modules/public-landing/public/projects.html +230 -35
- package/modules/public-landing/public/projects.probes.json +2 -2
- package/modules/public-landing/public/projects.states.json +5 -4
- package/modules/ui-design/kit/serve.js +32 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/module-api.js +1 -1
- package/tests/government_seed.mjs +35 -0
- package/tests/mingle.mjs +370 -0
- package/tests/projects_hub.mjs +72 -7
- package/tests/projects_hub_module_picker.mjs +369 -0
- package/tests/provisioning_module_selection.mjs +286 -0
- package/tests/provisioning_starter_bundles.mjs +131 -0
|
@@ -34,10 +34,13 @@ import { createRequire } from 'node:module';
|
|
|
34
34
|
// the knip ratchet (see the repo's knip learning).
|
|
35
35
|
import {
|
|
36
36
|
ALWAYS_ON_CORE,
|
|
37
|
+
OPTIONAL_MODULES,
|
|
37
38
|
STARTER_BUNDLES,
|
|
38
39
|
HUB_ONLY_MODULES,
|
|
39
40
|
bundleForProjectType,
|
|
40
41
|
allStarterBundles,
|
|
42
|
+
normalizeModuleSelection,
|
|
43
|
+
effectiveModules,
|
|
41
44
|
} from '../modules/provisioning/starter-bundles.js';
|
|
42
45
|
|
|
43
46
|
const require = createRequire(import.meta.url);
|
|
@@ -266,3 +269,131 @@ test('the route never reaches the database — the bundles are a declaration, no
|
|
|
266
269
|
const res = await get('/api/bongos/provisioning/starter-bundles');
|
|
267
270
|
assert.equal(res.status, 200);
|
|
268
271
|
});
|
|
272
|
+
|
|
273
|
+
// ── the opt-in catalog + the selection rules (BV1.R20, task 1002339) ─────────
|
|
274
|
+
|
|
275
|
+
test('the opt-in catalog declared for the picker IS the on-disk opt-in set', () => {
|
|
276
|
+
// Arrange — the manifests are the catalog; OPTIONAL_MODULES is the declaration
|
|
277
|
+
// the picker renders. This is the same link the bundle<->catalog tests above
|
|
278
|
+
// make, applied to the choices: a new default:false module cannot ship without
|
|
279
|
+
// owner-facing copy, and a declared choice cannot outlive its module.
|
|
280
|
+
const onDisk = manifestsOnDisk();
|
|
281
|
+
const optInOnDisk = [...onDisk.values()]
|
|
282
|
+
.filter((m) => m.default !== true && !HUB_ONLY_MODULES.includes(m.key))
|
|
283
|
+
.map((m) => m.key)
|
|
284
|
+
.sort();
|
|
285
|
+
|
|
286
|
+
// Act
|
|
287
|
+
const declared = OPTIONAL_MODULES.map((m) => m.key).sort();
|
|
288
|
+
|
|
289
|
+
// Assert
|
|
290
|
+
assert.deepEqual(declared, optInOnDisk,
|
|
291
|
+
'the picker\'s catalog has drifted from the manifests — a module was added, removed, or flipped its default');
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test('every opt-in module carries owner-facing copy, and no module jargon rides along', () => {
|
|
295
|
+
for (const row of OPTIONAL_MODULES) {
|
|
296
|
+
assert.ok(row.label && row.label.length <= 40, `"${row.key}" needs a short label`);
|
|
297
|
+
assert.ok(row.summary && row.summary.length > 20, `"${row.key}" needs a real one-line summary`);
|
|
298
|
+
// The owner choosing here has met none of these modules; a manifest sentence
|
|
299
|
+
// ("Mirror only, never authority (ADR 0033)") is true and unusable.
|
|
300
|
+
assert.doesNotMatch(`${row.label} ${row.summary}`, /\bADR\b|\bjsonb\b|\bAPI\b|\bwebhook\b|_/i,
|
|
301
|
+
`"${row.key}" reads like a manifest, not like a choice`);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
test('every module a bundle preselects is a row the picker can actually render', () => {
|
|
306
|
+
// A preselect with no catalog row would light up nothing — the panel would
|
|
307
|
+
// silently ship a choice the owner cannot see or switch off.
|
|
308
|
+
const offered = OPTIONAL_MODULES.map((m) => m.key);
|
|
309
|
+
for (const [type, bundle] of Object.entries(STARTER_BUNDLES)) {
|
|
310
|
+
for (const key of bundle.modules) {
|
|
311
|
+
assert.ok(offered.includes(key), `bundle "${type}" preselects "${key}", which the picker does not offer`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test('normalizeModuleSelection: absent is null, an empty answer is [], and order is the catalog\'s', () => {
|
|
317
|
+
// Arrange + Act + Assert — the three-valued contract the nullable column exists for
|
|
318
|
+
assert.equal(normalizeModuleSelection(undefined), null, 'never answered');
|
|
319
|
+
assert.equal(normalizeModuleSelection(null), null, 'never answered');
|
|
320
|
+
assert.equal(normalizeModuleSelection('discord'), null, 'a non-array is not an answer');
|
|
321
|
+
assert.deepEqual(normalizeModuleSelection([]), [], 'the owner said none — a real answer');
|
|
322
|
+
// catalog order, not the caller's
|
|
323
|
+
assert.deepEqual(normalizeModuleSelection(['discord', 'agents']), ['agents', 'discord']);
|
|
324
|
+
assert.deepEqual(normalizeModuleSelection(['discord', 'discord']), ['discord'], 'deduped');
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test('normalizeModuleSelection drops what was never a choice — always-on, hub-only, unknown', () => {
|
|
328
|
+
assert.deepEqual(normalizeModuleSelection(['discord', 'memory']), ['discord'], 'always-on is the floor, not a pick');
|
|
329
|
+
assert.deepEqual(normalizeModuleSelection(['provisioning']), [], 'the control plane is not a project module');
|
|
330
|
+
assert.deepEqual(normalizeModuleSelection(['nope', 42, null]), [], 'nothing is invented from junk');
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
test('effectiveModules: a skipped picker yields exactly the declared type\'s bundle', () => {
|
|
334
|
+
// Arrange — the row a skip writes: type declared, modules NULL
|
|
335
|
+
const row = { type: 'game', modules: null };
|
|
336
|
+
|
|
337
|
+
// Act
|
|
338
|
+
const eff = effectiveModules(row);
|
|
339
|
+
|
|
340
|
+
// Assert — the preselect and the fallback are the SAME declaration, which is
|
|
341
|
+
// what makes "skip = what the panel showed you" true rather than a promise.
|
|
342
|
+
assert.deepEqual(eff.selected, bundleForProjectType('game').bundle);
|
|
343
|
+
assert.deepEqual(eff.modules, bundleForProjectType('game').modules);
|
|
344
|
+
assert.equal(eff.source, 'bundle', 'and a reader can tell nobody answered');
|
|
345
|
+
assert.deepEqual(eff.core, [...ALWAYS_ON_CORE]);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
test('effectiveModules: an answered picker wins over the bundle, including "none of them"', () => {
|
|
349
|
+
assert.deepEqual(effectiveModules({ type: 'game', modules: ['agents'] }).selected, ['agents']);
|
|
350
|
+
assert.equal(effectiveModules({ type: 'game', modules: ['agents'] }).source, 'chosen');
|
|
351
|
+
// the case a NOT NULL DEFAULT '[]' column could never have expressed
|
|
352
|
+
const none = effectiveModules({ type: 'game', modules: [] });
|
|
353
|
+
assert.deepEqual(none.selected, [], 'the owner switched the bundle off, and it stays off');
|
|
354
|
+
assert.deepEqual(none.modules, [...ALWAYS_ON_CORE]);
|
|
355
|
+
assert.equal(none.source, 'chosen');
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
test('effectiveModules: an undeclared type resolves to the core, never a guess', () => {
|
|
359
|
+
for (const row of [{ type: null, modules: null }, { type: 'not-sure', modules: null }, {}]) {
|
|
360
|
+
const eff = effectiveModules(row);
|
|
361
|
+
assert.deepEqual(eff.selected, [], `${JSON.stringify(row)} preselects nothing`);
|
|
362
|
+
assert.deepEqual(eff.modules, [...ALWAYS_ON_CORE]);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test('GET /provisioning/starter-bundles carries the opt-in catalog the picker renders', async () => {
|
|
367
|
+
// Act
|
|
368
|
+
const res = await get('/api/bongos/provisioning/starter-bundles');
|
|
369
|
+
|
|
370
|
+
// Assert — one read gives the picker both halves: what is preselected, and
|
|
371
|
+
// what may be chosen. A second round trip is what lets them disagree.
|
|
372
|
+
assert.equal(res.status, 200, res.raw);
|
|
373
|
+
assert.deepEqual(res.body.optional, OPTIONAL_MODULES.map((m) => ({ ...m })));
|
|
374
|
+
for (const row of res.body.optional) {
|
|
375
|
+
assert.deepEqual(Object.keys(row).sort(), ['key', 'label', 'summary']);
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test('the design kit\'s stub fixture is the same payload this route serves', () => {
|
|
380
|
+
// modules/ui-design/kit/serve.js answers the picker's read with a hand-written
|
|
381
|
+
// fixture (the stub imports no module, by design — see modules/public-landing/
|
|
382
|
+
// CLAUDE.md's "the kit's stub is not the server" trap). This is the pin that
|
|
383
|
+
// stops the picture the kit renders from drifting away from the real payload:
|
|
384
|
+
// task 1003352 shipped a body the real route would have 400'd on, past 47
|
|
385
|
+
// green probes and a clean render, because nothing compared the two.
|
|
386
|
+
const stub = fs.readFileSync(path.join(fileURLToPath(new URL('..', import.meta.url)), 'modules/ui-design/kit/serve.js'), 'utf8');
|
|
387
|
+
const src = (stub.match(/const STARTER_BUNDLES_FIXTURE = (\{[\s\S]*?\n\};)/) || [, ''])[1];
|
|
388
|
+
assert.ok(src, 'the fixture is extractable');
|
|
389
|
+
// eslint-disable-next-line no-new-func -- reading a literal out of our own repo file
|
|
390
|
+
const fixture = new Function(`return ${src.replace(/;$/, '')}`)();
|
|
391
|
+
|
|
392
|
+
assert.deepEqual(fixture.core, [...ALWAYS_ON_CORE], 'the core the panel counts');
|
|
393
|
+
assert.deepEqual(fixture.optional, OPTIONAL_MODULES.map((m) => ({ ...m })), 'the catalog the panel renders');
|
|
394
|
+
assert.deepEqual(
|
|
395
|
+
fixture.bundles.map((b) => ({ type: b.type, bundle: b.bundle })),
|
|
396
|
+
allStarterBundles().map((b) => ({ type: b.type, bundle: b.bundle })),
|
|
397
|
+
'and what each type preselects',
|
|
398
|
+
);
|
|
399
|
+
});
|