@bongos/core 1.19.346 → 1.19.348
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 +57 -22
- package/docs/copy-inventory.md +49 -37
- package/docs/copy-registry.json +170 -42
- package/modules/hall-ui/public/collab-lib.js +77 -0
- package/modules/hall-ui/public/collab.css +36 -0
- package/modules/hall-ui/public/collab.html +66 -0
- package/modules/hall-ui/public/collab.js +139 -0
- package/modules/hall-ui/public/idea.js +9 -1
- package/modules/hall-ui/public/ideas.js +17 -5
- package/modules/hall-ui/public/shell.js +4 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/client-baseurl-guard.js +116 -0
- package/scripts/gds/deploy-divergence.js +34 -3
- package/scripts/gds/fitness.js +1 -0
- package/scripts/gds/update-channel.js +121 -5
- package/src/bongos/serve-internal.js +4 -1
- package/src/module-api.js +5 -1
- package/tests/client_baseurl_guard.mjs +122 -0
- package/tests/collab_page.mjs +172 -0
- package/tests/hall_nav.mjs +7 -1
- package/tests/idea_hall_composer.mjs +11 -0
- package/tests/update_channel.mjs +125 -0
package/tests/update_channel.mjs
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
// 4. parseNpmViewVersions — the array / bare-string / empty shapes npm view emits.
|
|
9
9
|
// 5. listAvailableVersions — ok + fail-soft with an injected runner.
|
|
10
10
|
// 6. loadSubscriptions — missing/malformed → empty; channel default + validate; env kept; dropless.
|
|
11
|
+
// 7. describeRoster — an empty roster REPORTS WHY, and is an alarm on a control plane (task 1003158).
|
|
12
|
+
// 8. subscriptionStaleness — releases the channel was entitled to and did not take = "the upgrader stopped".
|
|
11
13
|
//
|
|
12
14
|
// DB-free + network-free (every external is injected). Run: node tests/update_channel.mjs
|
|
13
15
|
|
|
@@ -182,6 +184,129 @@ t('loadSubscriptions: an entry without a dir is dropped (cannot be upgraded)', (
|
|
|
182
184
|
assert.equal(r.instances[0].slug, 'real');
|
|
183
185
|
});
|
|
184
186
|
|
|
187
|
+
// ---- describeRoster: fails closed, no longer fails quiet (task 1003158) ----
|
|
188
|
+
//
|
|
189
|
+
// The regression these pin: before this, a missing manifest, a malformed manifest and a deliberately
|
|
190
|
+
// empty roster were INDISTINGUISHABLE — all three returned a bare `instances: []` and the routine
|
|
191
|
+
// shrugged. cloudbongos.com sat 36 releases behind for days because "nothing happened" looked
|
|
192
|
+
// identical to "nothing needed to happen". Every empty roster must now say WHY, every run.
|
|
193
|
+
|
|
194
|
+
t('loadSubscriptions: missing file reports reason manifest-absent (not just an empty list)', () => {
|
|
195
|
+
const dir = mkdtempSync(join(tmpdir(), 'otb-subs-none2-'));
|
|
196
|
+
const r = uc.loadSubscriptions({ instanceDir: dir });
|
|
197
|
+
assert.deepEqual(r.instances, []);
|
|
198
|
+
assert.equal(r.reason, uc.ROSTER_ABSENT);
|
|
199
|
+
assert.equal(r.dropped, 0);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
t('loadSubscriptions: malformed JSON reports reason manifest-malformed + keeps the error', () => {
|
|
203
|
+
const dir = scratchWith('{ not valid');
|
|
204
|
+
const r = uc.loadSubscriptions({ instanceDir: dir });
|
|
205
|
+
assert.equal(r.reason, uc.ROSTER_MALFORMED);
|
|
206
|
+
assert.match(r.error, /malformed JSON/);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
t('loadSubscriptions: parsed-but-unsubscribable reports roster-empty and COUNTS the drops', () => {
|
|
210
|
+
const dir = scratchWith({ instances: [{ slug: 'ghost' }, { slug: 'alsoghost' }] });
|
|
211
|
+
const r = uc.loadSubscriptions({ instanceDir: dir });
|
|
212
|
+
assert.deepEqual(r.instances, []);
|
|
213
|
+
assert.equal(r.reason, uc.ROSTER_EMPTY);
|
|
214
|
+
assert.equal(r.dropped, 2);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
t('loadSubscriptions: a usable roster reports ok', () => {
|
|
218
|
+
const dir = scratchWith({ instances: [{ slug: 'cb', dir: '/srv/cb' }] });
|
|
219
|
+
const r = uc.loadSubscriptions({ instanceDir: dir });
|
|
220
|
+
assert.equal(r.reason, uc.ROSTER_OK);
|
|
221
|
+
assert.equal(r.dropped, 0);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
t('describeRoster: an empty roster on a CONTROL PLANE is an alarm, not a shrug', () => {
|
|
225
|
+
const dir = mkdtempSync(join(tmpdir(), 'otb-subs-cp-'));
|
|
226
|
+
const d = uc.describeRoster(uc.loadSubscriptions({ instanceDir: dir }), { controlPlane: true });
|
|
227
|
+
assert.equal(d.level, 'alarm');
|
|
228
|
+
assert.match(d.message, /CONTROL PLANE/);
|
|
229
|
+
assert.match(d.message, /subscribing NOTHING/);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
t('describeRoster: the same empty roster on a plain instance is only a warn (shipped default)', () => {
|
|
233
|
+
const dir = mkdtempSync(join(tmpdir(), 'otb-subs-plain-'));
|
|
234
|
+
const d = uc.describeRoster(uc.loadSubscriptions({ instanceDir: dir }));
|
|
235
|
+
assert.equal(d.level, 'warn');
|
|
236
|
+
assert.match(d.message, /shipped default/);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
t('describeRoster: a malformed manifest is an alarm everywhere', () => {
|
|
240
|
+
const d = uc.describeRoster(uc.loadSubscriptions({ instanceDir: scratchWith('{ nope') }));
|
|
241
|
+
assert.equal(d.level, 'alarm');
|
|
242
|
+
assert.match(d.message, /MALFORMED/);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
t('describeRoster: partial loss is surfaced — a healthy-looking sweep that silently skips a row', () => {
|
|
246
|
+
const dir = scratchWith({ instances: [{ slug: 'cb', dir: '/srv/cb' }, { slug: 'ghost' }] });
|
|
247
|
+
const d = uc.describeRoster(uc.loadSubscriptions({ instanceDir: dir }));
|
|
248
|
+
assert.equal(d.level, 'warn');
|
|
249
|
+
assert.equal(d.dropped, 1);
|
|
250
|
+
assert.match(d.message, /DROPPED/);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
t('describeRoster: a clean roster is ok and says how many it holds', () => {
|
|
254
|
+
const dir = scratchWith({ instances: [{ slug: 'cb', dir: '/srv/cb' }] });
|
|
255
|
+
const d = uc.describeRoster(uc.loadSubscriptions({ instanceDir: dir }), { controlPlane: true });
|
|
256
|
+
assert.equal(d.level, 'ok');
|
|
257
|
+
assert.equal(d.count, 1);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// ---- subscriptionStaleness: watching the watcher ---------------------------
|
|
261
|
+
|
|
262
|
+
const RELEASES = Array.from({ length: 41 }, (_, i) => `1.19.${250 + i}`); // 1.19.250 … 1.19.290
|
|
263
|
+
|
|
264
|
+
t('subscriptionStaleness: reproduces the incident — 1.19.254 on patch is 36 releases behind, stale', () => {
|
|
265
|
+
const st = uc.subscriptionStaleness({ installed: '1.19.254', channel: 'patch', available: RELEASES });
|
|
266
|
+
assert.equal(st.behind, 36);
|
|
267
|
+
assert.equal(st.target, '1.19.290');
|
|
268
|
+
assert.equal(st.stale, true);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
t('subscriptionStaleness: a swept instance is 0 behind and not stale', () => {
|
|
272
|
+
const st = uc.subscriptionStaleness({ installed: '1.19.290', channel: 'patch', available: RELEASES });
|
|
273
|
+
assert.equal(st.behind, 0);
|
|
274
|
+
assert.equal(st.target, null);
|
|
275
|
+
assert.equal(st.stale, false);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
t('subscriptionStaleness: a busy publish-on-merge day under the threshold does NOT cry wolf', () => {
|
|
279
|
+
const st = uc.subscriptionStaleness({ installed: '1.19.287', channel: 'patch', available: RELEASES });
|
|
280
|
+
assert.equal(st.behind, 3);
|
|
281
|
+
assert.equal(st.stale, false);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
t('subscriptionStaleness: a pinned instance is never stale (declining is not neglect)', () => {
|
|
285
|
+
const st = uc.subscriptionStaleness({ installed: '1.19.254', channel: 'pinned', available: RELEASES });
|
|
286
|
+
assert.equal(st.behind, 0);
|
|
287
|
+
assert.equal(st.stale, false);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
t('subscriptionStaleness: counts only what the CHANNEL may take — minors/majors are not a backlog', () => {
|
|
291
|
+
const st = uc.subscriptionStaleness({ installed: '1.19.254', channel: 'patch', available: ['1.20.0', '1.20.7', '2.0.0'] });
|
|
292
|
+
assert.equal(st.behind, 0);
|
|
293
|
+
assert.equal(st.stale, false);
|
|
294
|
+
// the same list on the minor channel IS a backlog
|
|
295
|
+
assert.equal(uc.subscriptionStaleness({ installed: '1.19.254', channel: 'minor', available: ['1.20.0', '1.20.7', '2.0.0'] }).behind, 2);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
t('subscriptionStaleness: threshold is overridable; a bad threshold falls back to the default', () => {
|
|
299
|
+
assert.equal(uc.subscriptionStaleness({ installed: '1.19.287', channel: 'patch', available: RELEASES, threshold: 1 }).stale, true);
|
|
300
|
+
assert.equal(uc.subscriptionStaleness({ installed: '1.19.287', channel: 'patch', available: RELEASES, threshold: 'nonsense' }).threshold, uc.DEFAULT_STALENESS_THRESHOLD);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
t('describeStaleness: silent when healthy, explicit about "the upgrader stopped" when not', () => {
|
|
304
|
+
assert.equal(uc.describeStaleness(uc.subscriptionStaleness({ installed: '1.19.290', channel: 'patch', available: RELEASES })), null);
|
|
305
|
+
const msg = uc.describeStaleness(uc.subscriptionStaleness({ installed: '1.19.254', channel: 'patch', available: RELEASES }), { slug: 'cloudbongos.com' });
|
|
306
|
+
assert.match(msg, /cloudbongos\.com is 36 patch releases behind/);
|
|
307
|
+
assert.match(msg, /upgrader has most likely stopped/);
|
|
308
|
+
});
|
|
309
|
+
|
|
185
310
|
console.log(`\nupdate_channel: ${passed} passed, ${failed} failed`);
|
|
186
311
|
process.exit(failed ? 1 : 0);
|
|
187
312
|
|