@bongos/core 1.24.24 → 1.24.25

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 CHANGED
@@ -2,11 +2,11 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/bongos/package-core.js",
5
- "core_version": "1.24.24",
6
- "core_contract": "1.24.24",
7
- "source_commit": "18d2aee4a5e5be6233f9598d64f4b5f99bfb5c79",
5
+ "core_version": "1.24.25",
6
+ "core_contract": "1.24.25",
7
+ "source_commit": "dee2639f5053216958a0024b2e6af6d54c9b4628",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-11-03T01:18:44.066Z",
9
+ "built_at": "2026-11-03T22:09:15.437Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 632,
@@ -17,7 +17,7 @@
17
17
  "gate": "passed"
18
18
  },
19
19
  "file_count": 4499,
20
- "tree_sha256": "964ede2546fdb0e28e85daf8ebd98ca1d3eb5b2a8df16cb2e38e1e18fe23e0ae",
20
+ "tree_sha256": "5e0b2f24b44dcffe4bd7a9d82e6024bddd12a6be61dc609c02447cb82ba6fc1f",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -3002,7 +3002,7 @@
3002
3002
  {
3003
3003
  "path": "docs/module-api-changelog.md",
3004
3004
  "mode": "0000644",
3005
- "sha256": "fe84d1e80dbc3ac6840e4bd8729ad2fe0828cf3d246c54052b804772e9689adc"
3005
+ "sha256": "abd56f3acf0eed7e0ed62297e2bc5774c165f608c36163d9b3a015429fb9e4cd"
3006
3006
  },
3007
3007
  {
3008
3008
  "path": "docs/modules-contract.md",
@@ -11767,12 +11767,12 @@
11767
11767
  {
11768
11768
  "path": "package-lock.json",
11769
11769
  "mode": "0000644",
11770
- "sha256": "b7a74553a15e3b2a0e569ded78a5b48a61621131805b9994de96868a8f580cd7"
11770
+ "sha256": "52a5166b00e8a5761186ca4689a2481d5ee305cf937c40739b2c8357e90510db"
11771
11771
  },
11772
11772
  {
11773
11773
  "path": "package.json",
11774
11774
  "mode": "0000644",
11775
- "sha256": "6b7d576d2ecb55b9c9315d7424fc2044766cfaa7a1c27f3ee66520c985e6ddd0"
11775
+ "sha256": "14f054f72fe8d56472d2dc94d3f5de9f68e0246533ea992519ff8e03f1628d2d"
11776
11776
  },
11777
11777
  {
11778
11778
  "path": "public-docs/index.html",
@@ -11792,7 +11792,7 @@
11792
11792
  {
11793
11793
  "path": "release-notes.json",
11794
11794
  "mode": "0000644",
11795
- "sha256": "ac6239f92750e39fde341f15d5797dfe8f9c85611060d4a1e06893700d3ebb52"
11795
+ "sha256": "d7dc47351e474cd55f6c2a089146540e628a02976d308c8bcaf4bdf9462f74eb"
11796
11796
  },
11797
11797
  {
11798
11798
  "path": "scripts/bongos/CLAUDE.md",
@@ -15392,7 +15392,7 @@
15392
15392
  {
15393
15393
  "path": "src/bongos/package-registry.js",
15394
15394
  "mode": "0000644",
15395
- "sha256": "47f7872dd523e166246a5207431256a08b80e2faaa861ae9bca368f61b7af3d8"
15395
+ "sha256": "a7a61e07e2aae983fd0a87f748fd4277bed136bb3062b4aabd5d7416dffdefd9"
15396
15396
  },
15397
15397
  {
15398
15398
  "path": "src/bongos/passkeys.js",
@@ -15517,7 +15517,7 @@
15517
15517
  {
15518
15518
  "path": "src/bongos/routes/core-update.js",
15519
15519
  "mode": "0000644",
15520
- "sha256": "3fbb1b2302a21569de90e3b264b47d6b3bf3c2b73c0a45d65649ed25d326ec48"
15520
+ "sha256": "dbb6cc64939a87b3f6c79727d90e3a9948c2c04e650235a3f1d3446c36716685"
15521
15521
  },
15522
15522
  {
15523
15523
  "path": "src/bongos/routes/feedback.js",
@@ -15637,7 +15637,7 @@
15637
15637
  {
15638
15638
  "path": "src/bongos/software-update.js",
15639
15639
  "mode": "0000644",
15640
- "sha256": "77e29f45a383f7e188cf0aa6e0d873f9d6bb02acc955df9312353a11cdfeb002"
15640
+ "sha256": "4805e1c822f1a55cf38fec99af788cc3654f67f9aaa2830fbf853f75583c1244"
15641
15641
  },
15642
15642
  {
15643
15643
  "path": "src/bongos/task-needs.js",
@@ -15682,7 +15682,7 @@
15682
15682
  {
15683
15683
  "path": "src/module-api.js",
15684
15684
  "mode": "0000644",
15685
- "sha256": "addc0ff10b81034f6258ddd7c2784fcbf9f3e3808ad152a5a27d1b27999b2be3"
15685
+ "sha256": "f7b0423511136930dafb0f695c664c581cf2ac50b94c9b1dbd763e455f00a9ce"
15686
15686
  },
15687
15687
  {
15688
15688
  "path": "src/module-loader/catalog.js",
@@ -21772,7 +21772,7 @@
21772
21772
  {
21773
21773
  "path": "tests/software_update.mjs",
21774
21774
  "mode": "0000644",
21775
- "sha256": "0f3b40a9560dc3f8ae7ee84e517316a4b4dce9de47554e443f804942a18909a6"
21775
+ "sha256": "6fe6bb1ebb12a0d2ab06ec98f54f79f9fb91d1c526d7a38bfae2a716a9b35d0f"
21776
21776
  },
21777
21777
  {
21778
21778
  "path": "tests/sound_player.mjs",
@@ -3304,5 +3304,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
3304
3304
  landed since 1.24.22 with no explicit bump. run 41370306880. (task 1002620)
3305
3305
  1.24.24 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3306
3306
  landed since 1.24.23 with no explicit bump. run 41558735984. (task 1002620)
3307
+ 1.24.25 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3308
+ landed since 1.24.24 with no explicit bump. run 41727989005. (task 1002620)
3307
3309
  ---------------------------------------------------------------------------
3308
3310
  ```
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.24.24",
3
+ "version": "1.24.25",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.24.24",
9
+ "version": "1.24.25",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.24.24",
3
+ "version": "1.24.25",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
@@ -11065,5 +11065,11 @@
11065
11065
  "id": "1004908",
11066
11066
  "text": "Contractors are now only paid once a signed NDA is on file and the payment processor has collected their tax details; if either is missing the payment waits and the person is told why. Bongos never stores tax numbers."
11067
11067
  }
11068
+ ],
11069
+ "1.24.25": [
11070
+ {
11071
+ "id": "1004593",
11072
+ "text": "A hosted project's Settings page no longer offers an unreleased version; it now offers exactly what the Upgrade page offers."
11073
+ }
11068
11074
  ]
11069
11075
  }
@@ -255,7 +255,8 @@ async function cached(slot, fresh, load, t) {
255
255
  * registry — `{ ok, versions, published, latest, newest }` or `{ ok:false, error }`
256
256
  * notes — the newest version's release notes, `{ ok, version, notes }` or
257
257
  * `{ ok:false, version, error }`; `{ ok:false, skipped }` when the caller
258
- * already holds notes that reach the newest version (`haveNotesFor`).
258
+ * already holds notes that reach the newest version, or needs none
259
+ * (`haveNotesFor(newest, registry)`).
259
260
  *
260
261
  * `get` swaps the HTTPS transport and `store`/`now` the cache, so tests drive the real
261
262
  * parsing and failure paths rather than a stub standing in front of them.
@@ -267,7 +268,7 @@ async function readRegistry({ pkg, haveNotesFor = () => false, get = https.get,
267
268
  () => fetchPackument({ pkg, get, origin }), t);
268
269
  if (!registry.ok) return { registry, notes: { ok: false, error: 'the registry could not be read' } };
269
270
  const version = registry.newest;
270
- if (haveNotesFor(version)) return { registry, notes: { ok: false, skipped: true, version } };
271
+ if (haveNotesFor(version, registry)) return { registry, notes: { ok: false, skipped: true, version } };
271
272
  const ns = store.notes;
272
273
  if (ns.version !== version) { ns.version = version; ns.value = null; ns.at = 0; }
273
274
  const notes = await cached(ns, (s) => s.value.ok || t - s.at < RETRY_AFTER_MS,
@@ -31,6 +31,7 @@ const { asyncHandler } = require('./_helpers');
31
31
  const auth = require('../auth');
32
32
  const { readCoreUpdate, readCoreUpgrades } = require('../core-update');
33
33
  const { readSoftwareUpdate } = require('../software-update');
34
+ const { followForShape } = require('../../../scripts/bongos/update-channel.js');
34
35
  const seams = require('../../module-seams');
35
36
  const { loadIdpConfig } = require('../auth-config');
36
37
  const upgradeRequest = require('../upgrade-request');
@@ -88,6 +89,13 @@ function updateDoor({ provisioning, idp, hubUpgradeUrl }) {
88
89
  return url ? { kind: 'hub', url } : { kind: 'cli', url: null };
89
90
  }
90
91
 
92
+ // WHICH PUBLISHED VERSIONS THIS HALL MAY BE OFFERED (task 1004593, ADR 0361 D5): the hall that
93
+ // runs the door is the platform's own, the one project that takes every candidate; every other
94
+ // project follows releases, as the runner and /deploy offer them. followForShape is the runner's.
95
+ function followFor(door) {
96
+ return followForShape(door && door.kind === 'local' ? 'control-plane' : null);
97
+ }
98
+
91
99
  function safeHubUpgradeUrl() {
92
100
  try { return require('../../instance-config').resolveEnv('HUB_UPGRADE_URL') || null; } catch { return null; }
93
101
  }
@@ -327,12 +335,12 @@ function buildCoreUpdateRouter({
327
335
  asyncHandler('GET /software-update', async (req, res) => {
328
336
  let running = null;
329
337
  try { running = require('../../module-api').CORE_VERSION; } catch { /* version unknown */ }
338
+ const door = updateDoor(doorInputs());
330
339
  // canUpdate does not depend on the rule, so it runs beside the rule read, not after it.
331
340
  const [update, can] = await Promise.all([
332
- resolveChannel(selfChannel).then(async ({ channel, source }) => ({ ...(await softwareUpdate({ running, channel })), channel_source: source })),
341
+ resolveChannel(selfChannel).then(async ({ channel, source }) => ({ ...(await softwareUpdate({ running, channel, follow: followFor(door) })), channel_source: source })),
333
342
  canUpdate(req),
334
343
  ]);
335
- const door = updateDoor(doorInputs());
336
344
  const ask = await readAsk({ update, door, deps: askDeps() });
337
345
  const held = await safeHold(upgradeHold, { running, offered: update && update.reading === 'ok' ? update.offered : null });
338
346
  // The history only for a viewer who holds core.pin.move (see safeMoved's gate note).
@@ -357,8 +365,8 @@ function buildCoreUpdateRouter({
357
365
  let running = null;
358
366
  try { running = require('../../module-api').CORE_VERSION; } catch { /* version unknown */ }
359
367
  const { channel } = await resolveChannel(selfChannel);
360
- const update = await softwareUpdate({ running, channel });
361
368
  const door = updateDoor(doorInputs());
369
+ const update = await softwareUpdate({ running, channel, follow: followFor(door) });
362
370
  const deps = askDeps();
363
371
  return upgradeRequest.requestUpgrade({
364
372
  host: deps.host,
@@ -22,6 +22,12 @@
22
22
  // /deploy, via the provisioning.selfUpdateChannel seam — task 1004535), else the runner's
23
23
  // default. The banner (core-update.js) is handed the same rule, so the two cannot disagree.
24
24
  //
25
+ // ONLY WHAT THIS PROJECT'S FOLLOW ADMITS IS OFFERED (task 1004593, ADR 0361 D5). Merges publish
26
+ // as candidates above the registry's `latest` label; a project that follows releases is offered
27
+ // nothing above it, through `versionsForFollow`, the function the runner and the banner use.
28
+ // Before this the panel offered a candidate that /deploy then said did not exist. The route
29
+ // names the follow: candidates only on the platform's own hall.
30
+ //
25
31
  // A READING, NEVER A GUESS. `reading` is 'ok' only when the registry answered; "up to date"
26
32
  // is `offered: null` on an ok reading and nothing else. Notes that could not be read say so,
27
33
  // and a version whose notes are missing is shown as missing rather than as empty.
@@ -30,6 +36,8 @@ const {
30
36
  CORE_PKG,
31
37
  DEFAULT_CHANNEL,
32
38
  normalizeChannel,
39
+ normalizeFollow,
40
+ versionsForFollow,
33
41
  resolveChannelTarget,
34
42
  compareSemver,
35
43
  } = require('../../scripts/bongos/update-channel.js');
@@ -62,22 +70,29 @@ function entriesFor(notes, version) {
62
70
  return out;
63
71
  }
64
72
 
73
+ /** The published versions `follow` admits, oldest first; [] when a release follower has no label. */
74
+ function admittedVersions(follow, registry) {
75
+ return versionsForFollow({ follow, available: registry.versions, released: registry.latest });
76
+ }
77
+
65
78
  /**
66
79
  * The panel's whole answer, from one registry reading. PURE — no clock, no I/O.
67
80
  *
68
81
  * `registry` and `notes` are package-registry.js's `readRegistry` result. Versions in the
69
- * update are the published ones above `running` and at or below `offered`, newest first.
82
+ * update are the admitted ones above `running` and at or below `offered`, newest first, and
83
+ * `newest` is the newest admitted version, so a candidate is never named to a release follower.
70
84
  */
71
- function softwareUpdateVerdict({ running, channel, registry, notes, checkedAt }) {
85
+ function softwareUpdateVerdict({ running, channel, follow, registry, notes, checkedAt }) {
72
86
  const ch = normalizeChannel(channel == null ? DEFAULT_CHANNEL : channel);
73
- const base = { checked_at: checkedAt, running: running || null, channel: ch };
74
- if (!running || !VERSION_RE.test(String(running))) {
75
- return { ...base, reading: 'no_answer', error: 'this project could not name the version it runs', offered: null, newest: null, whats_new: null };
76
- }
77
- if (!registry || !registry.ok) {
78
- return { ...base, reading: 'no_answer', error: (registry && registry.error) || 'the registry could not be read', offered: null, newest: null, whats_new: null };
79
- }
80
- const offered = resolveChannelTarget({ installed: running, channel: ch, available: registry.versions }) || null;
87
+ const fol = normalizeFollow(follow);
88
+ const base = { checked_at: checkedAt, running: running || null, channel: ch, follow: fol };
89
+ const noAnswer = (error) => ({ ...base, reading: 'no_answer', error, offered: null, newest: null, whats_new: null });
90
+ if (!running || !VERSION_RE.test(String(running))) return noAnswer('this project could not name the version it runs');
91
+ if (!registry || !registry.ok) return noAnswer((registry && registry.error) || 'the registry could not be read');
92
+ const admitted = admittedVersions(fol, registry);
93
+ // No label, no answer: the runner moves a release follower on nothing it cannot call released.
94
+ if (!admitted.length) return noAnswer('the registry does not say which version is released');
95
+ const offered = resolveChannelTarget({ installed: running, channel: ch, available: admitted }) || null;
81
96
  const published = registry.published || {};
82
97
  const answer = {
83
98
  ...base,
@@ -85,12 +100,12 @@ function softwareUpdateVerdict({ running, channel, registry, notes, checkedAt })
85
100
  error: null,
86
101
  offered,
87
102
  offered_published_at: offered ? published[offered] || null : null,
88
- newest: registry.newest || null,
103
+ newest: admitted.reduce((a, v) => (compareSemver(v, a) > 0 ? v : a)),
89
104
  whats_new: null,
90
105
  };
91
106
  if (!offered) return answer;
92
107
 
93
- const inUpdate = registry.versions
108
+ const inUpdate = admitted
94
109
  .filter((v) => compareSemver(v, running) > 0 && compareSemver(v, offered) <= 0)
95
110
  .sort((a, b) => compareSemver(b, a));
96
111
  const shown = inUpdate.slice(0, VERSIONS_MAX);
@@ -115,14 +130,19 @@ function softwareUpdateVerdict({ running, channel, registry, notes, checkedAt })
115
130
  * `get`/`now`/`store` pass through to the registry reader, so a test drives the real
116
131
  * packument parsing, the real tarball stream and the real cache rather than a stub.
117
132
  */
118
- async function readSoftwareUpdate({ running, channel, get, now = Date.now, store } = {}) {
133
+ async function readSoftwareUpdate({ running, channel, follow, get, now = Date.now, store } = {}) {
119
134
  const t = now();
135
+ const fol = normalizeFollow(follow);
120
136
  const { registry, notes } = await readRegistry({
121
137
  pkg: CORE_PKG, get, now, store,
122
- // Nothing to describe when nothing newer is published: skip the download.
123
- haveNotesFor: (newest) => !VERSION_RE.test(String(running || '')) || compareSemver(newest, running) <= 0,
138
+ // Nothing to describe when nothing this project could take is newer: skip the download.
139
+ haveNotesFor: (_newest, facts) => {
140
+ if (!VERSION_RE.test(String(running || ''))) return true;
141
+ const admitted = admittedVersions(fol, facts);
142
+ return !admitted.some((v) => compareSemver(v, running) > 0);
143
+ },
124
144
  });
125
- return softwareUpdateVerdict({ running, channel, registry, notes, checkedAt: new Date(t).toISOString() });
145
+ return softwareUpdateVerdict({ running, channel, follow: fol, registry, notes, checkedAt: new Date(t).toISOString() });
126
146
  }
127
147
 
128
148
  module.exports = { readSoftwareUpdate };
package/src/module-api.js CHANGED
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
81
81
  // MAJOR (see allowBoxScope below): passes the request through untouched.
82
82
  function deprecatedNoopMiddleware(_req, _res, next) { next(); }
83
83
 
84
- const CORE_VERSION = '1.24.24'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
84
+ const CORE_VERSION = '1.24.25'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
85
85
 
86
86
  // A namespaced logger so a module's log lines are attributable + consistent.
87
87
  // Usage: const log = api.logger('discord'); log.info('mounted');
@@ -39,11 +39,12 @@ const NOTES = {
39
39
  '1.20.0': [{ id: 200, text: 'The start of a new line.' }],
40
40
  };
41
41
 
42
- /** A registry that answers the packument and the NEWEST version's tarball. */
43
- function registry({ versions = VERSIONS, notes = NOTES, pkStatus = 200, tgzStatus = 200, tgz } = {}) {
42
+ /** A registry that answers the packument and the NEWEST version's tarball. `latest` is the
43
+ * release label (ADR 0361): every version above it is a candidate. */
44
+ function registry({ versions = VERSIONS, notes = NOTES, pkStatus = 200, tgzStatus = 200, tgz, latest } = {}) {
44
45
  const newest = versions[versions.length - 1];
45
46
  return fakeHttpsGet({
46
- [PACKUMENT_URL]: { status: pkStatus, body: npmPackument({ versions, latest: newest, times: TIMES }) },
47
+ [PACKUMENT_URL]: { status: pkStatus, body: npmPackument({ versions, latest: latest === undefined ? newest : latest, times: TIMES }) },
47
48
  [tgzUrl(newest)]: { status: tgzStatus, body: tgz || npmTarball({ notes }) },
48
49
  });
49
50
  }
@@ -148,6 +149,44 @@ test('a project far behind lists the newest forty and counts the rest', async ()
148
149
  assert.equal(u.whats_new.versions[0].version, '1.19.51');
149
150
  });
150
151
 
152
+ // ---- released vs candidate (task 1004593, ADR 0361 D5) --------------------------------
153
+ // The live defect, 2026-10-06, uattest (1.21.66, patch): npm `latest` was 1.21.66 and 1.21.69
154
+ // sat above it as a candidate. Settings offered 1.21.69; /deploy and the banner offered
155
+ // nothing. A project that follows releases must be offered what the runner would move it to.
156
+
157
+ test('a project that follows releases is offered nothing above the release label', async () => {
158
+ const reg = registry({ latest: '1.19.11' });
159
+ const u = await read('1.19.10', reg);
160
+ assert.equal(u.reading, 'ok');
161
+ assert.equal(u.follow, 'released', 'no follow given: the default, exactly as the runner');
162
+ assert.equal(u.offered, '1.19.11', '1.19.12 and 1.19.13 are candidates, never offered here');
163
+ assert.equal(u.newest, '1.19.11', 'and the newest it reports is the newest it could take, not a candidate');
164
+ assert.deepEqual(u.whats_new.versions.map((v) => v.version), ['1.19.11'], 'what\'s new stops at the offer');
165
+ });
166
+
167
+ test('the uattest case: running the released version with only candidates above it is up to date', async () => {
168
+ const reg = registry({ latest: '1.19.10' });
169
+ const u = await read('1.19.10', reg);
170
+ assert.equal(u.reading, 'ok');
171
+ assert.equal(u.offered, null, 'Settings now agrees with /deploy: nothing it can take');
172
+ assert.equal(u.whats_new, null);
173
+ assert.deepEqual(reg.asked, [PACKUMENT_URL], 'and nothing is downloaded for a version it will never offer');
174
+ });
175
+
176
+ test('the platform\'s own hall follows candidates and is offered them', async () => {
177
+ const u = await read('1.19.10', registry({ latest: '1.19.10' }), { follow: 'candidates' });
178
+ assert.equal(u.follow, 'candidates');
179
+ assert.equal(u.offered, '1.19.13');
180
+ assert.equal(u.newest, '1.20.0');
181
+ });
182
+
183
+ test('a registry with no release label is no answer for a release follower — never "up to date"', async () => {
184
+ const u = await read('1.19.10', registry({ latest: null }));
185
+ assert.equal(u.reading, 'no_answer');
186
+ assert.match(u.error, /released/);
187
+ assert.equal(u.offered, null);
188
+ });
189
+
151
190
  test('a second look within minutes asks the registry nothing new', async () => {
152
191
  const reg = registry();
153
192
  const store = fresh();
@@ -510,6 +549,25 @@ test('the REAL default reads the provisioning.selfUpdateChannel port — and wit
510
549
  }
511
550
  });
512
551
 
552
+ test('the route hands the reader the follow of the hall it serves: candidates only where the door runs (task 1004593)', async () => {
553
+ const cases = [
554
+ [{ provisioning: true, idp: null }, 'candidates', 'the platform\'s own hall, the one project that takes every candidate'],
555
+ [{ provisioning: false, idp: { origin: 'https://cloudbongos.com', clientId: 'uattest' } }, 'released', 'a hosted project'],
556
+ [{ provisioning: false, idp: null }, 'released', 'a self-hosted instance'],
557
+ ];
558
+ for (const [inputs, want, what] of cases) {
559
+ let seen;
560
+ const r = await drive({ softwareUpdate: async (a) => { seen = a; return STUB_UPDATE; }, canUpdate: async () => true, doorInputs: () => inputs });
561
+ assert.equal(r.status, 200, what);
562
+ assert.equal(seen.follow, want, what);
563
+ }
564
+ });
565
+
566
+ test('[source] the upgrade request asks with the same follow as the panel (task 1004593)', () => {
567
+ const seg = ROUTE_SRC.slice(ROUTE_SRC.indexOf("'/software-update/request'"), ROUTE_SRC.indexOf("'/project/manage-link'"));
568
+ assert.match(seg, /softwareUpdate\(\{ running, channel, follow: followFor\(door\) \}\)/, 'the version asked for is one the runner would take');
569
+ });
570
+
513
571
  test('[source] the port the route reads is the port the provisioning module registers and declares (task 1004535)', () => {
514
572
  const port = 'provisioning.selfUpdateChannel';
515
573
  const route = fs.readFileSync(path.join(ROOT, 'src/bongos/routes/core-update.js'), 'utf8');