@bongos/core 1.19.803 → 1.19.804

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,22 +2,22 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.19.803",
6
- "core_contract": "1.19.803",
7
- "source_commit": "64756c1e9ea07986f673c430081e2f89b870e038",
5
+ "core_version": "1.19.804",
6
+ "core_contract": "1.19.804",
7
+ "source_commit": "992839692e5a396ca7655a00df02413a3300b72a",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-17T02:45:59.094Z",
9
+ "built_at": "2026-09-17T03:35:15.794Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 487,
13
13
  "agent_docs_stubbed": 25,
14
- "functional_verbatim": 2190,
14
+ "functional_verbatim": 2191,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 2702,
20
- "tree_sha256": "0103e5b2dbd5bc126390f3057c7fe8bbcb65f2f1c76c6e63a108dada00d358b2",
19
+ "file_count": 2703,
20
+ "tree_sha256": "773756af73632e2824a78bf24ff19e17d3c1e2c077d511030de84bda409ed684",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -2532,7 +2532,7 @@
2532
2532
  {
2533
2533
  "path": "docs/module-api-changelog.md",
2534
2534
  "mode": "0000644",
2535
- "sha256": "3eb224e03b2a96ab275cd972eab2c9db4ae8b16b0b7297aa8bc5701857e27660"
2535
+ "sha256": "b1b8571ef8f537feeffbca2a68ee1bb71b5d127bbfcfe1a45b3a3ac0d807e717"
2536
2536
  },
2537
2537
  {
2538
2538
  "path": "docs/modules-contract.md",
@@ -5052,12 +5052,12 @@
5052
5052
  {
5053
5053
  "path": "modules/hall-ui/public/people.html",
5054
5054
  "mode": "0000644",
5055
- "sha256": "5e5c99eab08f89feb05ab4db5015d6db3a78324777ffad245aa03c91a633032e"
5055
+ "sha256": "ee7118a1d61518750d4ec105d9965a92784ad1564f7e6b75496d412b9d54be85"
5056
5056
  },
5057
5057
  {
5058
5058
  "path": "modules/hall-ui/public/people.js",
5059
5059
  "mode": "0000644",
5060
- "sha256": "145f767f473b11c222bde97e5de519c9eb0a9be0ddb2f75969d35dff086b3e01"
5060
+ "sha256": "8834c3f6ec31d669dafc268ffbc12c9375e9ca4a8b00c45b164aa2d103af8db9"
5061
5061
  },
5062
5062
  {
5063
5063
  "path": "modules/hall-ui/public/people.states.json",
@@ -7712,12 +7712,12 @@
7712
7712
  {
7713
7713
  "path": "package-lock.json",
7714
7714
  "mode": "0000644",
7715
- "sha256": "eb3019123b9a5320bb5e3af69404dd3d230788a4fce4746e108f27aa93c05d99"
7715
+ "sha256": "95dd4df126fd7a7617f5a1bc84c6f0ad6403d59edb377102978beab0180f0dca"
7716
7716
  },
7717
7717
  {
7718
7718
  "path": "package.json",
7719
7719
  "mode": "0000644",
7720
- "sha256": "921560195171c3d526e3f0b9b97702949a4cc50035cf910141d7f6e307b28cbb"
7720
+ "sha256": "8cfddd30203037f279a7c1d4e5205bfcda5b464b57022c5af3db7da4ac930e70"
7721
7721
  },
7722
7722
  {
7723
7723
  "path": "public-docs/index.html",
@@ -9607,7 +9607,7 @@
9607
9607
  {
9608
9608
  "path": "src/module-api.js",
9609
9609
  "mode": "0000644",
9610
- "sha256": "ca2eb0aedd6b859a96a1ea7e8762c4694cd0090d55e83864108f575a3ee30345"
9610
+ "sha256": "8778f98197e12eaa8fc397413c13584bc079d72bedb8737ba4a7db15109bb497"
9611
9611
  },
9612
9612
  {
9613
9613
  "path": "src/module-loader/catalog.js",
@@ -12129,6 +12129,11 @@
12129
12129
  "mode": "0000644",
12130
12130
  "sha256": "6c623a5abe9aadf4821ad8ca291fb5d7805a7a5bc874bfc3cd7e51e83de289d2"
12131
12131
  },
12132
+ {
12133
+ "path": "tests/people_directory_clip.mjs",
12134
+ "mode": "0000644",
12135
+ "sha256": "a7d4e29162194a67e2f1b948d0522f1ecdff11218d49fbd2b9d7eb0be1cc624c"
12136
+ },
12132
12137
  {
12133
12138
  "path": "tests/permission_path.mjs",
12134
12139
  "mode": "0000644",
@@ -12217,7 +12222,7 @@
12217
12222
  {
12218
12223
  "path": "tests/profile_route.mjs",
12219
12224
  "mode": "0000644",
12220
- "sha256": "d70924dd2ddfc4e05c4b52d9763d76b0a68cf0ed16db6ffd1b07a1834bef0890"
12225
+ "sha256": "ac7f07f4226ed410012ee2650e1870c109d0f3eb8b9142208d8a339ebff27e6c"
12221
12226
  },
12222
12227
  {
12223
12228
  "path": "tests/profile_ui_cross_project.mjs",
@@ -2080,5 +2080,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
2080
2080
  landed since 1.19.801 with no explicit bump. run 35174865190. (task 1002620)
2081
2081
  1.19.803 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2082
2082
  landed since 1.19.802 with no explicit bump. run 35175617185. (task 1002620)
2083
+ 1.19.804 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2084
+ landed since 1.19.803 with no explicit bump. run 35178689663. (task 1002620)
2083
2085
  ---------------------------------------------------------------------------
2084
2086
  ```
@@ -39,6 +39,10 @@
39
39
  <div id="people-body">
40
40
  <div class="profile__placeholder">Loading the directory…</div>
41
41
  </div>
42
+ <!-- task 1003110: the clipped-directory footnote. Hidden unless the
43
+ response came back FULL, which is the only truncation signal the
44
+ rollup offers; people.js writes it. -->
45
+ <p class="ledger__note" id="people-foot" hidden></p>
42
46
  </section>
43
47
  </main>
44
48
 
@@ -25,6 +25,7 @@
25
25
  var body = document.getElementById('people-body');
26
26
  var sub = document.getElementById('people-sub');
27
27
  var countPill = document.getElementById('people-count');
28
+ var foot = document.getElementById('people-foot');
28
29
 
29
30
  function fmtRecency(iso) {
30
31
  if (!iso) return '—';
@@ -73,6 +74,23 @@
73
74
  }).join('') + '</span>';
74
75
  }
75
76
 
77
+ // THE DIRECTORY ASKS FOR THE ENDPOINT MAXIMUM, so a full response is the only
78
+ // truncation signal that exists. `GET /scouting` clamps at parsePagination
79
+ // maxLimit: 500, takes no offset, and `listScoutingRollup` returns rows with no
80
+ // count of the whole set — so `length === DIRECTORY_LIMIT` means "at least this
81
+ // many", never "exactly this many", and there is nothing else to read it from.
82
+ // ONE constant feeds the request AND every sentence written about it, so the
83
+ // number a reader is shown can never drift from the number that was asked for.
84
+ //
85
+ // This is part 1 of task 1003110 — the honest half, which removes the
86
+ // silent-lie property whatever the real count turns out to be. Part 2 is
87
+ // server-offset paging, and it is deliberately NOT here: the kit has carried
88
+ // the mode since task 1003281 (`makeLedger({ serverPager, onPage })`), but the
89
+ // ROUTE cannot serve it — no offset parameter, no total, a hard 500 ceiling —
90
+ // so paging this page is a server change first. Its trigger is a directory
91
+ // approaching ~400; a read on 2026-09-16 returned far fewer.
92
+ var DIRECTORY_LIMIT = 500;
93
+
76
94
  var all = [];
77
95
  var query = '';
78
96
  var craft = '';
@@ -143,10 +161,31 @@
143
161
  + 'and none are listed without it. Their work still shows on each project’s own ledger.');
144
162
  return;
145
163
  }
146
- if (countPill) { countPill.textContent = String(all.length); countPill.hidden = false; }
164
+ // A FULL PAGE IS A CLIPPED PAGE. Every sentence below has to hedge when this
165
+ // is true, because each of them otherwise states a total the page cannot know.
166
+ var clipped = all.length >= DIRECTORY_LIMIT;
167
+ if (countPill) {
168
+ countPill.textContent = clipped ? fmtInt(DIRECTORY_LIMIT) + '+' : String(all.length);
169
+ countPill.hidden = false;
170
+ }
147
171
  if (sub) {
148
- sub.textContent = fmtInt(all.length) + ' builder' + (all.length === 1 ? '' : 's') + ' listed across hosted projects, ranked by track record. '
149
- + 'Descriptive only — cross-project numbers never carry authority here.';
172
+ sub.textContent = clipped
173
+ ? 'Showing the first ' + fmtInt(DIRECTORY_LIMIT) + ' builders across hosted projects, ranked by track record — there are more than this page can load. '
174
+ + 'Descriptive only — cross-project numbers never carry authority here.'
175
+ : fmtInt(all.length) + ' builder' + (all.length === 1 ? '' : 's') + ' listed across hosted projects, ranked by track record. '
176
+ + 'Descriptive only — cross-project numbers never carry authority here.';
177
+ }
178
+ // THE LENSES LIE HARDER THAN THE COUNT DOES, and that is worth its own
179
+ // sentence. Search, sort and the craft filter all re-order the set this page
180
+ // already loaded (see SORTS above) — so on a clipped directory "most karma"
181
+ // is the most karma AMONG THE TOP 500 BY TRACK RECORD, and a builder outside
182
+ // that 500 cannot be found by any of them. A reader who is told only that the
183
+ // list is long would still trust the search.
184
+ if (foot) {
185
+ foot.textContent = clipped
186
+ ? 'The directory is clipped at the first ' + fmtInt(DIRECTORY_LIMIT) + ' by track record. Search and the sort lenses only re-order what this page loaded, so a builder outside that ' + fmtInt(DIRECTORY_LIMIT) + ' will not appear in them.'
187
+ : '';
188
+ foot.hidden = !clipped;
150
189
  }
151
190
  var crafts = [];
152
191
  all.forEach(function (b) {
@@ -194,7 +233,7 @@
194
233
  body.textContent = msg;
195
234
  }
196
235
 
197
- fetch('/api/gds/scouting?limit=500', { headers: { Accept: 'application/json' } })
236
+ fetch('/api/gds/scouting?limit=' + DIRECTORY_LIMIT, { headers: { Accept: 'application/json' } })
198
237
  .then(function (r) {
199
238
  if (r.status === 403 || r.status === 401) { throw { gated: true }; }
200
239
  if (!r.ok) throw new Error('http ' + r.status);
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.803",
3
+ "version": "1.19.804",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.803",
9
+ "version": "1.19.804",
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.19.803",
3
+ "version": "1.19.804",
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",
package/src/module-api.js CHANGED
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
71
71
  // there. scripts/gds/bump-version.js still rewrites the literal below; it appends
72
72
  // the entry to that file. Look for a version's history there, not here.
73
73
  // ---------------------------------------------------------------------------
74
- const CORE_VERSION = '1.19.803'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
74
+ const CORE_VERSION = '1.19.804'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
75
75
 
76
76
  // A namespaced logger so a module's log lines are attributable + consistent.
77
77
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -0,0 +1,219 @@
1
+ // tests/people_directory_clip.mjs — the People directory says so when it is
2
+ // clipped (task 1003110, goal 1000095).
3
+ //
4
+ // WHY THIS FILE EXISTS. `people.js` asks `GET /scouting` for 500 rows, which is
5
+ // that route's HARD CEILING (parsePagination maxLimit: 500). The rollup returns
6
+ // no count of the whole set and the route takes no offset, so a response that
7
+ // comes back FULL is the only truncation signal the page will ever get — and
8
+ // until this task the page simply printed "500 builders listed" and said nothing
9
+ // else. A directory that lies by omission is worse than one that admits it is
10
+ // incomplete.
11
+ //
12
+ // It runs the REAL people.js in a vm against a stub DOM and a fake fetch, so
13
+ // what is asserted is the sentence a reader actually sees, not a regex agreeing
14
+ // with a regex. A source-text assertion would have passed just as happily with
15
+ // the comparison written the wrong way round.
16
+ //
17
+ // The four behaviours:
18
+ // 1. the page asks for exactly the number its copy then names (no drift)
19
+ // 2. a SHORT response is not hedged — the exact count is the honest one
20
+ // 3. a FULL response hedges every sentence that states a total: the pill, the
21
+ // subtitle, and a footnote about the lenses
22
+ // 4. one row short of the ceiling is NOT clipped (the boundary, in the
23
+ // direction that would otherwise cry wolf on every load)
24
+ //
25
+ // Run: node tests/people_directory_clip.mjs
26
+ import { strict as assert } from 'node:assert';
27
+ import fs from 'node:fs';
28
+ import path from 'node:path';
29
+ import vm from 'node:vm';
30
+ import { fileURLToPath } from 'node:url';
31
+ import { makeRunner } from './helpers.mjs';
32
+
33
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
34
+ const read = (rel) => fs.readFileSync(path.join(ROOT, rel), 'utf8');
35
+ const KIT_JS = read('modules/hall-ui/public/hall-kit.js');
36
+ const PEOPLE_JS = read('modules/hall-ui/public/people.js');
37
+ const PEOPLE_HTML = read('modules/hall-ui/public/people.html');
38
+
39
+ // The route's ceiling, spelled here as the CONTRACT rather than read out of the
40
+ // page — a test that imports the page's own constant cannot notice it changing.
41
+ const CEILING = 500;
42
+
43
+ const { test, summary } = makeRunner();
44
+ const tick = () => new Promise((r) => setTimeout(r, 0));
45
+
46
+ function makeEl(id) {
47
+ return {
48
+ id,
49
+ innerHTML: '', textContent: '', value: '',
50
+ hidden: false, disabled: false, className: '',
51
+ options: [], style: {}, dataset: {},
52
+ classList: { add() {}, remove() {}, toggle() {}, contains: () => false },
53
+ appendChild() {}, setAttribute() {}, removeAttribute() {},
54
+ addEventListener() {},
55
+ querySelector() { return makeEl(); },
56
+ querySelectorAll() { return []; },
57
+ };
58
+ }
59
+
60
+ // `count` builders, each distinct enough that a filter lens has something to bite.
61
+ function buildersFor(count) {
62
+ return Array.from({ length: count }, (_, i) => ({
63
+ github_id: 1000 + i,
64
+ github_login: `builder${i}`,
65
+ display_name: `Builder ${i}`,
66
+ avatar_url: null,
67
+ project_count: 1,
68
+ total_credits: count - i,
69
+ total_tasks_shipped: 1,
70
+ total_karma: 1,
71
+ last_active: '2026-09-01T00:00:00Z',
72
+ disciplines: ['engineer'],
73
+ }));
74
+ }
75
+
76
+ function harness({ count }) {
77
+ const els = new Map();
78
+ const getEl = (id) => {
79
+ if (!els.has(id)) els.set(id, makeEl(id));
80
+ return els.get(id);
81
+ };
82
+ const documentObj = {
83
+ documentElement: { dataset: {}, style: {}, classList: { add() {}, remove() {} } },
84
+ getElementById: getEl,
85
+ querySelector(sel) { return getEl(String(sel).replace(/^#/, '')); },
86
+ querySelectorAll() { return []; },
87
+ createElement() { return makeEl(); },
88
+ addEventListener() {},
89
+ };
90
+
91
+ const requests = [];
92
+ const store = new Map();
93
+ const windowObj = {
94
+ location: { assign() {}, href: '/people' },
95
+ localStorage: {
96
+ getItem: (k) => (store.has(k) ? store.get(k) : null),
97
+ setItem: (k, v) => store.set(k, String(v)),
98
+ removeItem: (k) => store.delete(k),
99
+ },
100
+ scrollTo() {},
101
+ addEventListener() {},
102
+ matchMedia: () => ({ matches: false, addEventListener() {}, addListener() {} }),
103
+ OTBBranding: { currencyLabel: () => 'Credits' },
104
+ OTB: {
105
+ escapeHtml: (s) => String(s == null ? '' : s),
106
+ $: (sel) => documentObj.querySelector(sel),
107
+ num: (n) => Number(n) || 0,
108
+ fmtInt: (n) => String(Number(n) || 0),
109
+ fmtCompact: (n) => String(Number(n) || 0),
110
+ fmtDate: (iso) => String(iso ?? '').slice(0, 10),
111
+ rankLabel: (r) => String(r ?? ''),
112
+ toast() {},
113
+ },
114
+ };
115
+
116
+ const sandbox = {
117
+ window: windowObj, document: documentObj, location: windowObj.location,
118
+ console: { error() {}, warn() {}, log() {} },
119
+ setTimeout, clearTimeout, URLSearchParams, encodeURIComponent, Date, Math, JSON,
120
+ fetch: (url) => {
121
+ requests.push(String(url));
122
+ return Promise.resolve({
123
+ ok: true,
124
+ status: 200,
125
+ json: () => Promise.resolve({ builders: buildersFor(count) }),
126
+ });
127
+ },
128
+ };
129
+ sandbox.globalThis = sandbox;
130
+ vm.createContext(sandbox);
131
+ // The real kit, for the same reason sessions_kit_pager.mjs runs it: the page is
132
+ // held to the shipped contract, not to a stub that agrees with it.
133
+ vm.runInContext(KIT_JS, sandbox, { filename: 'hall-kit.js' });
134
+ vm.runInContext(PEOPLE_JS, sandbox, { filename: 'people.js' });
135
+
136
+ return { getEl, requests };
137
+ }
138
+
139
+ // ---------------------------------------------------------------------------
140
+
141
+ await test('the page markup carries the footnote slot, hidden until the page writes it', () => {
142
+ assert.match(PEOPLE_HTML, /id="people-foot"/, 'the clipped-directory footnote has a mount');
143
+ const tag = PEOPLE_HTML.match(/<p[^>]*id="people-foot"[^>]*>/);
144
+ assert.ok(tag, 'the footnote is a <p>');
145
+ assert.match(tag[0], /\bhidden\b/, 'it starts hidden — an empty note is still a visible gap');
146
+ assert.match(tag[0], /class="ledger__note"/, "the shell's ledger footnote class, not a bespoke one");
147
+ });
148
+
149
+ await test('the page asks for exactly the ceiling, and asks for it ONCE', async () => {
150
+ const h = harness({ count: 3 });
151
+ await tick(); await tick();
152
+ assert.equal(h.requests.length, 1, 'one directory read');
153
+ assert.match(h.requests[0], /\/api\/gds\/scouting\?limit=/);
154
+ const limit = Number(new URLSearchParams(h.requests[0].split('?')[1]).get('limit'));
155
+ assert.equal(limit, CEILING, `the page must ask for the route ceiling (${CEILING})`);
156
+ });
157
+
158
+ await test('a SHORT directory is not hedged — the exact count is the honest one', async () => {
159
+ const h = harness({ count: 3 });
160
+ await tick(); await tick();
161
+ assert.equal(h.getEl('people-count').textContent, '3', 'the pill states the real count');
162
+ assert.equal(h.getEl('people-count').hidden, false);
163
+ assert.match(h.getEl('people-sub').textContent, /^3 builders listed/, 'the subtitle states the real count');
164
+ assert.doesNotMatch(h.getEl('people-sub').textContent, /first|more than/i,
165
+ 'nothing is clipped, so nothing may suggest it is');
166
+ assert.equal(h.getEl('people-foot').hidden, true, 'no footnote on a complete directory');
167
+ assert.equal(h.getEl('people-foot').textContent, '', 'and no stale text left behind it');
168
+ });
169
+
170
+ await test('a FULL response hedges the pill, the subtitle AND the lenses', async () => {
171
+ const h = harness({ count: CEILING });
172
+ await tick(); await tick();
173
+
174
+ const pill = h.getEl('people-count').textContent;
175
+ assert.equal(pill, `${CEILING}+`, 'the pill may not state a total the page cannot know');
176
+
177
+ const sub = h.getEl('people-sub').textContent;
178
+ assert.match(sub, new RegExp(`first ${CEILING}\\b`), 'the subtitle says which slice this is');
179
+ assert.match(sub, /more than this page can load/, 'and says the directory is larger');
180
+ assert.doesNotMatch(sub, new RegExp(`^${CEILING} builders listed`),
181
+ 'the old sentence claimed the slice was the whole set');
182
+
183
+ const foot = h.getEl('people-foot');
184
+ assert.equal(foot.hidden, false, 'the footnote is shown');
185
+ // THE LENSES ARE THE SHARPER LIE. Search/sort/craft re-order only the loaded
186
+ // set, so on a clipped directory a builder outside the top 500 cannot be found
187
+ // by any of them — a reader told only "the list is long" would still trust the
188
+ // search box.
189
+ assert.match(foot.textContent, /search/i, 'the footnote names the search');
190
+ assert.match(foot.textContent, /sort/i, 'and the sort lenses');
191
+ assert.match(foot.textContent, new RegExp(`will not appear`),
192
+ 'and says plainly that a builder outside the slice is unreachable through them');
193
+ });
194
+
195
+ await test('one row short of the ceiling is NOT clipped', async () => {
196
+ const h = harness({ count: CEILING - 1 });
197
+ await tick(); await tick();
198
+ assert.equal(h.getEl('people-count').textContent, String(CEILING - 1),
199
+ 'a nearly-full page is still a complete one');
200
+ assert.equal(h.getEl('people-foot').hidden, true, 'crying wolf here would train the notice away');
201
+ });
202
+
203
+ await test('every number in the clipped copy is the number that was requested', async () => {
204
+ const h = harness({ count: CEILING });
205
+ await tick(); await tick();
206
+ const limit = new URLSearchParams(h.requests[0].split('?')[1]).get('limit');
207
+ // The drift this guards: someone raises the request to 1000 and leaves "500"
208
+ // written in three sentences. Every integer the clipped copy prints must be
209
+ // the one the request carried.
210
+ const copy = [h.getEl('people-count').textContent, h.getEl('people-sub').textContent,
211
+ h.getEl('people-foot').textContent].join(' ');
212
+ const printed = copy.match(/\d+/g) || [];
213
+ assert.ok(printed.length >= 3, 'the clipped copy states the number more than once');
214
+ for (const n of printed) {
215
+ assert.equal(n, limit, `copy prints ${n} but the page requested ${limit}`);
216
+ }
217
+ });
218
+
219
+ summary();
@@ -651,8 +651,19 @@ await t('R06.3 GET /scouting drops the hidden, the provisional AND the private b
651
651
  await t('R06.3 the /people page reads /scouting and nothing else', async () => {
652
652
  // /people is the hall's render of the same endpoint, so whatever /scouting
653
653
  // drops, /people drops. Pin that it has no second data source.
654
- const endpoints = [...sourceOf('modules/hall-ui/public/people.js').matchAll(/fetch\(\s*'([^']+)'/g)].map((m) => m[1]);
655
- assert.deepEqual(endpoints, ['/api/gds/scouting?limit=500'], '/people has exactly one data source');
654
+ //
655
+ // MATCHED AS AN EXPRESSION, NOT A STRING LITERAL. This used to capture the
656
+ // whole URL out of `fetch('…')` and compare it to one hardcoded string, which
657
+ // pinned the row limit as a side effect of pinning the ENDPOINT — so task
658
+ // 1003110 naming that limit (`DIRECTORY_LIMIT`, the one constant that feeds
659
+ // both the request and the "showing the first N" copy) failed a test about
660
+ // data sources. What is load-bearing here is the count and the destination:
661
+ // ONE read, and it goes to the scouting rollup. How the query string is
662
+ // assembled is the page's business.
663
+ const args = [...sourceOf('modules/hall-ui/public/people.js').matchAll(/\bfetch\(\s*([^,)]+)/g)].map((m) => m[1].trim());
664
+ assert.equal(args.length, 1, '/people has exactly one data source');
665
+ assert.match(args[0], /^'\/api\/gds\/scouting\?limit='/,
666
+ 'and it is the scouting rollup — nothing may re-enter through a second endpoint');
656
667
  });
657
668
 
658
669
  scout.server.close();