@bongos/core 1.19.803 → 1.19.805

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 (235) hide show
  1. package/.bongos-core.json +261 -236
  2. package/.claude/skills/ask-for-help/SKILL.md +6 -6
  3. package/.claude/skills/backlog-review/SKILL.md +1 -1
  4. package/.claude/skills/blocker-review/SKILL.md +7 -7
  5. package/.claude/skills/blocker-solve/SKILL.md +5 -5
  6. package/.claude/skills/bug-triage/SKILL.md +7 -7
  7. package/.claude/skills/builder-claim/SKILL.md +3 -3
  8. package/.claude/skills/builder-cost/SKILL.md +2 -2
  9. package/.claude/skills/builder-end/SKILL.md +2 -2
  10. package/.claude/skills/builder-exit/SKILL.md +7 -7
  11. package/.claude/skills/builder-key/SKILL.md +2 -2
  12. package/.claude/skills/builder-reauth/SKILL.md +2 -2
  13. package/.claude/skills/builder-redteam/SKILL.md +7 -7
  14. package/.claude/skills/builder-release/SKILL.md +2 -2
  15. package/.claude/skills/builder-sequence/SKILL.md +1 -1
  16. package/.claude/skills/builder-setup/SKILL.md +3 -3
  17. package/.claude/skills/builder-ship/SKILL.md +1 -1
  18. package/.claude/skills/builder-stage/SKILL.md +1 -1
  19. package/.claude/skills/builder-start/SKILL.md +1 -1
  20. package/.claude/skills/fix-task/SKILL.md +4 -4
  21. package/.claude/skills/goal-create/SKILL.md +18 -18
  22. package/.claude/skills/goal-review/SKILL.md +7 -7
  23. package/.claude/skills/grade-audit/SKILL.md +5 -5
  24. package/.claude/skills/grade-recover/SKILL.md +4 -4
  25. package/.claude/skills/grader-health/SKILL.md +6 -6
  26. package/.claude/skills/idea-triage/SKILL.md +14 -14
  27. package/.claude/skills/merge-mode/SKILL.md +3 -3
  28. package/.claude/skills/planning-session/SKILL.md +20 -20
  29. package/.claude/skills/priority-session/SKILL.md +10 -10
  30. package/.claude/skills/recall/SKILL.md +1 -1
  31. package/.claude/skills/scan-before-install/SKILL.md +3 -3
  32. package/.claude/skills/status/SKILL.md +2 -2
  33. package/.claude/skills/strand-fix/SKILL.md +2 -2
  34. package/clients/bongos-client/README.md +1 -1
  35. package/clients/bongos-client/bongos-client.global.js +2 -0
  36. package/clients/bongos-client/index.cjs +2 -0
  37. package/clients/bongos-client/index.d.ts +2 -0
  38. package/clients/bongos-client/index.mjs +2 -0
  39. package/docs/api/openapi.json +45 -11
  40. package/docs/api-reference.md +11 -10
  41. package/docs/module-api-changelog.md +4 -0
  42. package/modules/builder-settings/profile-nudge-prefs.js +1 -1
  43. package/modules/builder-settings/render-prefs.js +1 -1
  44. package/modules/dev-box/app/README.md +1 -1
  45. package/modules/dev-box/app/src/gds-client.js +11 -9
  46. package/modules/dev-box/app/src/vendor/bongos-client.cjs +2 -0
  47. package/modules/dev-box/app/test/unit.mjs +13 -13
  48. package/modules/dev-box/box-access.js +3 -3
  49. package/modules/dev-box/box-onboard.js +7 -7
  50. package/modules/discord/bug-attachments.js +1 -1
  51. package/modules/economy/cost-classify.js +6 -6
  52. package/modules/grading/grader-rubric.json +1 -1
  53. package/modules/grading/grader-score.js +1 -1
  54. package/modules/hall-ui/module.json +1 -1
  55. package/modules/hall-ui/public/drachmae.js +3 -3
  56. package/modules/hall-ui/public/idea.js +1 -1
  57. package/modules/hall-ui/public/md-reader.js +1 -1
  58. package/modules/hall-ui/public/people.html +5 -1
  59. package/modules/hall-ui/public/people.js +44 -5
  60. package/modules/hall-ui/public/primer.js +4 -4
  61. package/modules/hall-ui/public/roster.js +1 -1
  62. package/modules/hall-ui/public/shell.js +1 -1
  63. package/modules/hall-ui/public/task.js +1 -1
  64. package/modules/ideas/inbox.js +2 -2
  65. package/modules/lifecycle/db-analytics.js +1 -1
  66. package/modules/lifecycle/done-when.js +4 -4
  67. package/modules/lifecycle/version-close.js +1 -1
  68. package/modules/memory/learnings.js +2 -2
  69. package/modules/memory/memory.js +1 -1
  70. package/modules/onboarding/onboarding.js +1 -1
  71. package/modules/platform-identity/routes/my-projects.js +1 -1
  72. package/modules/platform-identity/routes/sso.js +2 -2
  73. package/modules/public-landing/public/index.html +2 -2
  74. package/modules/public-landing/public/projects-floor.states.json +1 -1
  75. package/modules/public-landing/public/projects.html +1 -1
  76. package/modules/public-landing/public/projects.states.json +1 -1
  77. package/modules/sessions/db.js +120 -0
  78. package/modules/sessions/module.json +2 -2
  79. package/modules/sessions/pollers/session-upload-health.js +111 -0
  80. package/modules/sessions/pulse.js +2 -2
  81. package/modules/sessions/routes/sessions.js +46 -0
  82. package/modules/sessions/upload-health.js +197 -0
  83. package/modules/status-ui/module.json +1 -1
  84. package/modules/status-ui/public/explore.js +1 -1
  85. package/package-lock.json +2 -2
  86. package/package.json +1 -1
  87. package/scripts/gds/agent-invoke.js +3 -3
  88. package/scripts/gds/api.js +4 -4
  89. package/scripts/gds/api.sh +1 -1
  90. package/scripts/gds/architect-audit.js +1 -1
  91. package/scripts/gds/artifact-audit.js +2 -2
  92. package/scripts/gds/audit-ships.js +2 -2
  93. package/scripts/gds/backfill-cost.js +1 -1
  94. package/scripts/gds/backfill-sessions.js +1 -1
  95. package/scripts/gds/backfill-subagent-rewards.js +1 -1
  96. package/scripts/gds/backfill-task-rewards.js +2 -2
  97. package/scripts/gds/box-auth-check.js +1 -1
  98. package/scripts/gds/box-sync.js +3 -3
  99. package/scripts/gds/box.js +10 -10
  100. package/scripts/gds/build-cli-package.js +3 -3
  101. package/scripts/gds/capture.js +2 -2
  102. package/scripts/gds/ci-grade.js +1 -1
  103. package/scripts/gds/claim-recall.js +2 -2
  104. package/scripts/gds/cli-lib.js +3 -3
  105. package/scripts/gds/context-pack.js +1 -1
  106. package/scripts/gds/copy-apply.js +2 -2
  107. package/scripts/gds/dev-lib.js +2 -2
  108. package/scripts/gds/fetch-art-key.js +2 -2
  109. package/scripts/gds/fetch-sound-prefs.js +3 -3
  110. package/scripts/gds/grade-smoke.js +1 -1
  111. package/scripts/gds/hooks/pre-push.js +1 -1
  112. package/scripts/gds/init.js +9 -9
  113. package/scripts/gds/key-card.js +1 -1
  114. package/scripts/gds/learning-capture.js +2 -2
  115. package/scripts/gds/linkify-refs.js +2 -2
  116. package/scripts/gds/local-preview-lib.js +1 -1
  117. package/scripts/gds/login.js +11 -10
  118. package/scripts/gds/main-audit.js +1 -1
  119. package/scripts/gds/newcomer-floor.config.json +1 -1
  120. package/scripts/gds/newcomer-floor.js +2 -2
  121. package/scripts/gds/newcomer-floor.templates.json +2 -2
  122. package/scripts/gds/onboarding-ship.js +1 -1
  123. package/scripts/gds/paste-token.js +3 -3
  124. package/scripts/gds/portability-smoke-memory.sh +5 -5
  125. package/scripts/gds/prove-strict-versioning.js +2 -2
  126. package/scripts/gds/provision-net.js +2 -2
  127. package/scripts/gds/provision-units.js +3 -3
  128. package/scripts/gds/pulse.js +2 -2
  129. package/scripts/gds/push-path-brief.js +2 -2
  130. package/scripts/gds/reauth.js +1 -1
  131. package/scripts/gds/recall.js +1 -1
  132. package/scripts/gds/redteam-patrol.js +3 -3
  133. package/scripts/gds/render-ideas.js +5 -5
  134. package/scripts/gds/repo-metrics.js +1 -1
  135. package/scripts/gds/sandbox-stage.js +3 -3
  136. package/scripts/gds/seed-adopt-tasks.js +3 -3
  137. package/scripts/gds/seed-board-room-tasks.js +1 -1
  138. package/scripts/gds/seed-bongos-app-tasks.js +2 -2
  139. package/scripts/gds/seed-bongos-coreB-tranche1-tasks.js +3 -3
  140. package/scripts/gds/seed-bongos-coreB-tranche2-tasks.js +4 -4
  141. package/scripts/gds/seed-bongos-tasks.js +5 -5
  142. package/scripts/gds/seed-bongos-v1-idea-routing-tasks.js +1 -1
  143. package/scripts/gds/seed-bongos-v1-seo-tasks.js +1 -1
  144. package/scripts/gds/seed-bv1-community-tasks.js +1 -1
  145. package/scripts/gds/seed-bv1-privacy-invites-tasks.js +1 -1
  146. package/scripts/gds/seed-bv1-profile-page-tasks.js +1 -1
  147. package/scripts/gds/seed-bv1-profiles-tasks.js +1 -1
  148. package/scripts/gds/seed-bv1-r01-delta-tasks.js +1 -1
  149. package/scripts/gds/seed-gds-v4-tasks.js +3 -3
  150. package/scripts/gds/seed-medusa-tasks.js +1 -1
  151. package/scripts/gds/seed-productization-tasks.js +1 -1
  152. package/scripts/gds/seed-project-admin-console-tasks.js +1 -1
  153. package/scripts/gds/seed-provisioning-tasks.js +2 -2
  154. package/scripts/gds/seed-strict-versioning-tasks.js +3 -3
  155. package/scripts/gds/seed-v2-world-tasks.js +2 -2
  156. package/scripts/gds/seed-verify-criteria.js +1 -1
  157. package/scripts/gds/session-digest.js +1 -1
  158. package/scripts/gds/session.js +2 -2
  159. package/scripts/gds/setup.js +7 -7
  160. package/scripts/gds/ship-flow.js +3 -3
  161. package/scripts/gds/ship-grade-steps.js +3 -3
  162. package/scripts/gds/ship-memory-sync.js +1 -1
  163. package/scripts/gds/ship-session-upload.js +3 -3
  164. package/scripts/gds/smoke-batch-claim.sh +10 -10
  165. package/scripts/gds/smoke-batch-release.sh +14 -14
  166. package/scripts/gds/smoke-claim-consistency.sh +19 -19
  167. package/scripts/gds/smoke-dependencies.sh +9 -9
  168. package/scripts/gds/smoke-write.sh +11 -11
  169. package/scripts/gds/start.js +1 -1
  170. package/scripts/gds/status.js +2 -2
  171. package/scripts/gds/strand-watch.js +1 -1
  172. package/scripts/gds/triage.js +4 -4
  173. package/scripts/gds/version-close.js +2 -2
  174. package/scripts/hall-preview/README.md +1 -1
  175. package/scripts/hall-preview/server.js +1 -1
  176. package/src/bongos/auth-admission.js +3 -3
  177. package/src/bongos/auth-idp.js +2 -2
  178. package/src/bongos/auth.js +3 -3
  179. package/src/bongos/db.js +2 -2
  180. package/src/bongos/llm-cache.js +2 -2
  181. package/src/bongos/metrics.js +1 -1
  182. package/src/bongos/middleware/audit.js +3 -3
  183. package/src/bongos/middleware/rate-limit.js +1 -1
  184. package/src/bongos/platform-visibility-gate.js +2 -2
  185. package/src/bongos/routes/_helpers.js +1 -1
  186. package/src/bongos/routes/audit-log.js +1 -1
  187. package/src/bongos/routes/auth.js +5 -5
  188. package/src/bongos/routes/backup.js +1 -1
  189. package/src/bongos/routes/builders.js +1 -1
  190. package/src/bongos/routes/instance.js +1 -1
  191. package/src/bongos/routes/live.js +1 -1
  192. package/src/bongos/routes/me.js +2 -2
  193. package/src/bongos/routes/modules.js +2 -2
  194. package/src/bongos/routes/public.js +1 -1
  195. package/src/bongos/routes/security.js +4 -4
  196. package/src/bongos/routes.js +1 -1
  197. package/src/bongos/serve-internal.js +6 -6
  198. package/src/branding.js +1 -1
  199. package/src/module-api.js +1 -1
  200. package/src/module-seams.js +2 -2
  201. package/src/platform-server.js +2 -2
  202. package/tests/api_alias_exceptions.mjs +67 -0
  203. package/tests/application_echo.mjs +12 -12
  204. package/tests/artifact_audit.mjs +1 -1
  205. package/tests/auth_page_gate.mjs +4 -4
  206. package/tests/auth_web_recovery.mjs +11 -11
  207. package/tests/bongos_login.mjs +7 -4
  208. package/tests/box_env.mjs +6 -6
  209. package/tests/bug_attachments.mjs +1 -1
  210. package/tests/cli_sessions.mjs +1 -1
  211. package/tests/dev_lib.mjs +1 -1
  212. package/tests/discord_bugs.mjs +2 -2
  213. package/tests/discord_inbound.mjs +2 -2
  214. package/tests/explore_visible_states.mjs +1 -1
  215. package/tests/government_page_gates.mjs +1 -1
  216. package/tests/hall_reference_world.mjs +1 -1
  217. package/tests/idea_ideate_full.mjs +1 -1
  218. package/tests/idp_federation.mjs +2 -2
  219. package/tests/llm-cache.mjs +1 -1
  220. package/tests/manage_manifest_shared_read.mjs +3 -3
  221. package/tests/people_directory_clip.mjs +219 -0
  222. package/tests/planning_session_skill.mjs +1 -1
  223. package/tests/platform_visibility_gate.mjs +15 -15
  224. package/tests/profile_route.mjs +37 -26
  225. package/tests/projects_hub.mjs +2 -2
  226. package/tests/provision.mjs +2 -2
  227. package/tests/provision_settings_apply.mjs +4 -4
  228. package/tests/sandbox_stage.mjs +8 -8
  229. package/tests/scouting_owner_access_and_reach.mjs +3 -3
  230. package/tests/session_upload_health.mjs +329 -0
  231. package/tests/setup_device_flow_fallback.mjs +1 -1
  232. package/tests/skill_ask_for_help.mjs +3 -3
  233. package/tests/skill_grade_audit.mjs +2 -2
  234. package/tests/skill_grader_health.mjs +3 -3
  235. package/tests/smoke_write_contract.mjs +1 -1
@@ -98,7 +98,7 @@ const GATE_FLOOR = {
98
98
  // ADR 0157: `page.view.government` floor archon→metic.
99
99
  requireGovernmentPage: 'metic',
100
100
  };
101
- const SIGN_IN = '/api/gds/auth/web/start';
101
+ const SIGN_IN = '/api/bongos/auth/web/start';
102
102
 
103
103
  // --- doubles ----------------------------------------------------------------
104
104
  function fakeRes() {
@@ -241,7 +241,7 @@ const costSummaryFixture = JSON.parse(
241
241
  test('the treasury reads the endpoint that already carried the series — no new route', () => {
242
242
  // The whole point of the task: the payload had both series all along, and the
243
243
  // route's own comment calls daily[] "the treasury chart's cumulative line".
244
- assert.match(drachmaeJs, /TREASURY_URL = '\/api\/gds\/public\/cost-summary'/);
244
+ assert.match(drachmaeJs, /TREASURY_URL = '\/api\/bongos\/public\/cost-summary'/);
245
245
  assert.ok(costSummaryFixture.daily.length, 'the payload really carries daily[]');
246
246
  assert.ok(costSummaryFixture.daily_credits.length, 'and daily_credits[]');
247
247
  assert.ok(Number(costSummaryFixture.total_usd) > 0, 'and the totals the prose states');
@@ -93,7 +93,7 @@ test('it describes NO second write path', () => {
93
93
  // ends up sending a different payload than the form does, and the two records
94
94
  // drift while both claim to be Full Ideas.
95
95
  assert.ok(!/curl/i.test(SKILL), 'no hand-rolled HTTP');
96
- assert.ok(!/api\.js POST \/api\/gds\/inbox/.test(SKILL), 'no raw POST for filing an idea');
96
+ assert.ok(!/api\.js POST \/api\/bongos\/inbox/.test(SKILL), 'no raw POST for filing an idea');
97
97
  const grade = [...SKILL.matchAll(/grade\s*=\s*'([a-z]+)'/g)].map((m) => m[1]);
98
98
  assert.ok(grade.every((g) => g === 'full'), `only the full grade is named: ${JSON.stringify(grade)}`);
99
99
  });
@@ -29,7 +29,7 @@ t('builds the hub /sso/authorize URL with client_id, redirect_uri, state', () =>
29
29
  const url = auth.idpAuthorizeUrl('https://cloudbongos.com', 'client-abc', 'st8', 'https://proj.example.com/api/gds/auth/web/callback');
30
30
  const u = new URL(url);
31
31
  assert.equal(u.origin, 'https://cloudbongos.com');
32
- assert.equal(u.pathname, '/api/gds/sso/authorize');
32
+ assert.equal(u.pathname, '/api/bongos/sso/authorize');
33
33
  assert.equal(u.searchParams.get('client_id'), 'client-abc');
34
34
  assert.equal(u.searchParams.get('redirect_uri'), 'https://proj.example.com/api/gds/auth/web/callback');
35
35
  assert.equal(u.searchParams.get('state'), 'st8');
@@ -37,7 +37,7 @@ t('builds the hub /sso/authorize URL with client_id, redirect_uri, state', () =>
37
37
 
38
38
  t('trims a trailing slash on the hub origin', () => {
39
39
  const url = auth.idpAuthorizeUrl('https://cloudbongos.com/', 'c', 's', 'https://p/cb');
40
- assert.equal(new URL(url).pathname, '/api/gds/sso/authorize');
40
+ assert.equal(new URL(url).pathname, '/api/bongos/sso/authorize');
41
41
  assert.ok(!url.includes('com//api'), 'no double slash between origin and path');
42
42
  });
43
43
 
@@ -88,7 +88,7 @@ await arun('lookup: routes to apiCall on a HIT (right method/path/args)', async
88
88
  const r = await c.lookup(KEY, { tokensSavedEst: 12 });
89
89
  assert.equal(r.result_stdout, 'X');
90
90
  assert.equal(calls[0][0], 'POST');
91
- assert.equal(calls[0][1], '/api/gds/llm-cache/lookup');
91
+ assert.equal(calls[0][1], '/api/bongos/llm-cache/lookup');
92
92
  assert.deepEqual(calls[0][2], { cache_key: KEY, tokens_saved_est: 12 });
93
93
  assert.deepEqual(calls[0][3], { allowUnauthenticated: true });
94
94
  });
@@ -2,7 +2,7 @@
2
2
  // manifest per manage entry, EXECUTED (task 1003554).
3
3
  //
4
4
  // The defect this pins shut: entering /projects?view=manage fetched
5
- // https://<project>/api/gds/instance twice, identically and cross-origin,
5
+ // https://<project>/api/bongos/instance twice, identically and cross-origin,
6
6
  // because the two cards that render a "what it runs right now" line — how the
7
7
  // project is reached (brand.project.platformVisibility) and how people join
8
8
  // (brand.project.joinability) — each asked the project for themselves. Two
@@ -99,7 +99,7 @@ test('the two cards of one manage entry cost ONE round trip, not two', async ()
99
99
 
100
100
  // Assert
101
101
  assert.equal(ui.requests.length, 1, 'one cross-origin GET served both lines');
102
- assert.equal(ui.requests[0].url, 'https://demo.example.com/api/gds/instance');
102
+ assert.equal(ui.requests[0].url, 'https://demo.example.com/api/bongos/instance');
103
103
  assert.equal(ui.requests[0].mode, 'cors', 'the project is somebody else’s origin');
104
104
  for (const a of answers) {
105
105
  assert.equal(a.ok, true);
@@ -151,7 +151,7 @@ test('switching projects is never served the previous project’s manifest', asy
151
151
 
152
152
  // Assert
153
153
  assert.equal(ui.requests.length, 2, 'the memo is keyed on the project, not merely present');
154
- assert.equal(ui.requests[1].url, 'https://two.example.com/api/gds/instance');
154
+ assert.equal(ui.requests[1].url, 'https://two.example.com/api/bongos/instance');
155
155
  });
156
156
 
157
157
  test('a project that answered-and-refused RESOLVES carrying its status — both lines can say so', async () => {
@@ -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\/bongos\/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();
@@ -31,7 +31,7 @@ function test(name, fn) {
31
31
  test('the seeding contract documents the self-check convention', () => {
32
32
  assert.ok(NORM.includes('verifySeedCriteriaLinks'), 'names the helper function');
33
33
  assert.ok(NORM.includes('seed-verify-criteria.js'), 'names the shared helper file');
34
- assert.ok(NORM.includes('GET /api/gds/goals/<id>'), 'names the endpoint the self-check fetches');
34
+ assert.ok(NORM.includes('GET /api/bongos/goals/<id>'), 'names the endpoint the self-check fetches');
35
35
  });
36
36
 
37
37
  test('the self-check is described as failing loudly on any mismatch, zero included', () => {
@@ -85,7 +85,7 @@ test('gated mode: a stranger meets the door on a page, with the way in on it', a
85
85
  assert.equal(nexted, false);
86
86
  assert.equal(res.statusCode, 401, 'a refused knock is a 401, not a 200 or a redirect');
87
87
  assert.match(res.body, /Sign in/, 'the door carries the way in');
88
- assert.match(res.body, /href="\/api\/gds\/auth\/web\/start\?return=%2Fprojects%3Fview%3Dmap"/,
88
+ assert.match(res.body, /href="\/api\/bongos\/auth\/web\/start\?return=%2Fprojects%3Fview%3Dmap"/,
89
89
  'the sign-in start returns the member to the page they knocked on — RELATIVE, which safeReturnTo honours everywhere');
90
90
  assert.match(res.body, /Spike Harbor/, 'it names the project');
91
91
  assert.match(res.body, /noindex/, 'a members-only project is not indexed');
@@ -172,7 +172,7 @@ test('the exempt list: the door, the manifest, the probes, the downloads — on
172
172
  '/api/bongos/v1/public/progress', '/api/gds/instance/anything', '/instance', '/healthzz', '/downloads',
173
173
  '/api/other/instance', '/api/bongos/provisioning/instances',
174
174
  // the apply door is METHOD-scoped, so the bare path — no verb — is not exempt
175
- '/api/gds/access-requests', '/api/gds/access-requests/status',
175
+ '/api/bongos/access-requests', '/api/bongos/access-requests/status',
176
176
  ]) assert.equal(isExempt(p), false, `${p} must be gated`);
177
177
  });
178
178
 
@@ -184,7 +184,7 @@ test('the apply door: POST <api>/access-requests answers a stranger — and only
184
184
  for (const p of [
185
185
  '/api/gds/access-requests', '/api/bongos/access-requests', '/api/bongos/v1/access-requests',
186
186
  // the router's own tolerances, the same ones the rest of the list is held to
187
- '/API/BONGOS/ACCESS-REQUESTS', '/api/gds/access-requests/',
187
+ '/API/BONGOS/ACCESS-REQUESTS', '/api/bongos/access-requests/',
188
188
  ]) assert.equal(isExempt(p, 'POST'), true, `POST ${p} must reach the apply route`);
189
189
 
190
190
  // The bare GET on that path is the OWNER'S QUEUE (requireBuilder +
@@ -192,15 +192,15 @@ test('the apply door: POST <api>/access-requests answers a stranger — and only
192
192
  // in front of it regardless — an exemption that ignored the verb would have
193
193
  // silently removed a layer from the queue that lists would-be builders by name.
194
194
  for (const m of ['GET', 'HEAD', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', '']) {
195
- assert.equal(isExempt('/api/gds/access-requests', m), false, `${m || '(no verb)'} on the queue must stay gated`);
195
+ assert.equal(isExempt('/api/bongos/access-requests', m), false, `${m || '(no verb)'} on the queue must stay gated`);
196
196
  }
197
197
  // and the exemption does not spread down the path
198
- for (const p of ['/api/gds/access-requests/status', '/api/gds/access-requests/invite', '/api/gds/access-requests/1']) {
198
+ for (const p of ['/api/bongos/access-requests/status', '/api/bongos/access-requests/invite', '/api/bongos/access-requests/1']) {
199
199
  assert.equal(isExempt(p, 'POST'), false, `POST ${p} must stay gated`);
200
200
  }
201
201
  // a method-BLIND entry is unchanged by the new shape: it still exempts any verb
202
202
  assert.equal(isExempt('/healthz', 'POST'), true, 'a bare-RegExp entry ignores the verb');
203
- assert.equal(isExempt('/api/gds/instance', 'GET'), true);
203
+ assert.equal(isExempt('/api/bongos/instance', 'GET'), true);
204
204
  });
205
205
 
206
206
  // task 1003525 — the other half of the apply door. `bongos login` cannot re-poll the
@@ -210,15 +210,15 @@ test('the status poll: GET <api>/access-requests/status answers a stranger — t
210
210
  for (const p of [
211
211
  '/api/gds/access-requests/status', '/api/bongos/access-requests/status',
212
212
  '/api/bongos/v1/access-requests/status',
213
- '/API/BONGOS/ACCESS-REQUESTS/STATUS', '/api/gds/access-requests/status/',
213
+ '/API/BONGOS/ACCESS-REQUESTS/STATUS', '/api/bongos/access-requests/status/',
214
214
  ]) assert.equal(isExempt(p, 'GET'), true, `GET ${p} must reach the status route`);
215
215
 
216
216
  // the two halves do not lend each other their verbs
217
- assert.equal(isExempt('/api/gds/access-requests/status', 'POST'), false, 'the status route takes no write');
218
- assert.equal(isExempt('/api/gds/access-requests', 'GET'), false, 'the owner queue is still not a status poll');
217
+ assert.equal(isExempt('/api/bongos/access-requests/status', 'POST'), false, 'the status route takes no write');
218
+ assert.equal(isExempt('/api/bongos/access-requests', 'GET'), false, 'the owner queue is still not a status poll');
219
219
 
220
220
  // and neither opens the review surfaces that share the prefix
221
- for (const p of ['/api/gds/access-requests/invite', '/api/gds/access-requests/1/approve', '/api/gds/access-requests/1']) {
221
+ for (const p of ['/api/bongos/access-requests/invite', '/api/bongos/access-requests/1/approve', '/api/bongos/access-requests/1']) {
222
222
  for (const m of ['GET', 'POST']) {
223
223
  assert.equal(isExempt(p, m), false, `${m} ${p} must stay gated`);
224
224
  }
@@ -238,10 +238,10 @@ test('gated mode: an exempt path is never looked up, and a CORS preflight passes
238
238
  });
239
239
 
240
240
  test('the sign-in return is the knocked-on page for GET/HEAD only, relative, and never a foreign host', () => {
241
- assert.equal(signInHref(fakeReq({ path: '/builders/roadmap' })), '/api/gds/auth/web/start?return=%2Fbuilders%2Froadmap');
242
- assert.equal(signInHref(fakeReq({ path: '/builders/roadmap', method: 'POST' })), '/api/gds/auth/web/start', 'a write has nothing safe to replay');
243
- assert.equal(signInHref(fakeReq({ path: '//evil.example/x' })), '/api/gds/auth/web/start', 'a protocol-relative path is dropped');
244
- assert.equal(signInHref(fakeReq({ path: '/' })), '/api/gds/auth/web/start', 'the front page needs no return');
241
+ assert.equal(signInHref(fakeReq({ path: '/builders/roadmap' })), '/api/bongos/auth/web/start?return=%2Fbuilders%2Froadmap');
242
+ assert.equal(signInHref(fakeReq({ path: '/builders/roadmap', method: 'POST' })), '/api/bongos/auth/web/start', 'a write has nothing safe to replay');
243
+ assert.equal(signInHref(fakeReq({ path: '//evil.example/x' })), '/api/bongos/auth/web/start', 'a protocol-relative path is dropped');
244
+ assert.equal(signInHref(fakeReq({ path: '/' })), '/api/bongos/auth/web/start', 'the front page needs no return');
245
245
  });
246
246
 
247
247
  // ── 2. the real booted app, pack = gated ─────────────────────────────────────
@@ -365,7 +365,7 @@ test('booted gated + apply: the status poll answers, and the review surfaces bes
365
365
  assert.equal(j.error.code, 'bad_github_login', `GET ${p}: the status route answered, not the member door`);
366
366
  }
367
367
  // the owner's review surfaces share the prefix and stay behind the door
368
- for (const p of ['/api/gds/access-requests', '/api/gds/access-requests/invite']) {
368
+ for (const p of ['/api/bongos/access-requests', '/api/bongos/access-requests/invite']) {
369
369
  const res = await request(p);
370
370
  assert.equal(res.status, 401, `GET ${p} stays gated`);
371
371
  assert.equal(JSON.parse(res.body).error.code, 'unauthenticated');
@@ -198,7 +198,7 @@ function buildApp() {
198
198
  const app = express();
199
199
  app.use(express.json());
200
200
  app.use(attachFail); // res.fail() ahead of the routers, exactly as the real app does
201
- app.use('/api/gds', buildBuildersRouter());
201
+ app.use('/api/bongos', buildBuildersRouter());
202
202
  return app;
203
203
  }
204
204
 
@@ -227,7 +227,7 @@ function get(port, path, headers) {
227
227
  const { server, port } = await listen(buildApp());
228
228
 
229
229
  await t('known id → 200 with the public field subset', async () => {
230
- const r = await get(port, '/api/gds/builders/95/profile');
230
+ const r = await get(port, '/api/bongos/builders/95/profile');
231
231
  assert.equal(r.status, 200);
232
232
  assert.ok(r.json && r.json.builder, 'response has a builder object');
233
233
  for (const k of PUBLIC_KEYS) {
@@ -242,7 +242,7 @@ await t('known id → 200 with the public field subset', async () => {
242
242
  });
243
243
 
244
244
  await t('known id → NO private column leaks on the builder object', async () => {
245
- const r = await get(port, '/api/gds/builders/95/profile');
245
+ const r = await get(port, '/api/bongos/builders/95/profile');
246
246
  assert.equal(r.status, 200);
247
247
  for (const k of PRIVATE_KEYS) {
248
248
  assert.ok(!(k in r.json.builder), `private field "${k}" must NOT be present on the builder`);
@@ -250,7 +250,7 @@ await t('known id → NO private column leaks on the builder object', async () =
250
250
  });
251
251
 
252
252
  await t('known id → no secret sentinel leaks ANYWHERE in the response body', async () => {
253
- const r = await get(port, '/api/gds/builders/95/profile');
253
+ const r = await get(port, '/api/bongos/builders/95/profile');
254
254
  assert.equal(r.status, 200);
255
255
  for (const secret of [SECRET_EMAIL, SECRET_USER, SECRET_REASON]) {
256
256
  assert.ok(!r.text.includes(secret), `response body must not contain the secret "${secret}"`);
@@ -258,13 +258,13 @@ await t('known id → no secret sentinel leaks ANYWHERE in the response body', a
258
258
  });
259
259
 
260
260
  await t('unknown id → 404 builder_not_found', async () => {
261
- const r = await get(port, '/api/gds/builders/99999/profile');
261
+ const r = await get(port, '/api/bongos/builders/99999/profile');
262
262
  assert.equal(r.status, 404);
263
263
  assert.equal(r.json && r.json.error && r.json.error.code, 'builder_not_found');
264
264
  });
265
265
 
266
266
  await t('non-numeric id → 400 bad_builder_id', async () => {
267
- const r = await get(port, '/api/gds/builders/not-a-number/profile');
267
+ const r = await get(port, '/api/bongos/builders/not-a-number/profile');
268
268
  assert.equal(r.status, 400);
269
269
  assert.equal(r.json && r.json.error && r.json.error.code, 'bad_builder_id');
270
270
  });
@@ -275,7 +275,7 @@ await t('non-numeric id → 400 bad_builder_id', async () => {
275
275
  // hub posture for the tests below.
276
276
 
277
277
  await t('hub port ABSENT → no cross_project key + the cacheable posture stays', async () => {
278
- const r = await get(port, '/api/gds/builders/95/profile');
278
+ const r = await get(port, '/api/bongos/builders/95/profile');
279
279
  assert.equal(r.status, 200);
280
280
  assert.ok(!('cross_project' in r.json), 'cross_project must be absent on a non-hub instance');
281
281
  assert.equal(r.headers['cache-control'], 'public, max-age=60');
@@ -292,7 +292,7 @@ seams.registerProvider('platform-identity.hub', {
292
292
  });
293
293
 
294
294
  await t('hub port present → cross_project carries exactly the whitelist shape', async () => {
295
- const r = await get(port, '/api/gds/builders/95/profile');
295
+ const r = await get(port, '/api/bongos/builders/95/profile');
296
296
  assert.equal(r.status, 200);
297
297
  const cp = r.json.cross_project;
298
298
  assert.ok(cp, 'cross_project attached when the port resolves');
@@ -302,7 +302,7 @@ await t('hub port present → cross_project carries exactly the whitelist shape'
302
302
  });
303
303
 
304
304
  await t('hub port present → module-internal sentinels leak NOWHERE in the response', async () => {
305
- const r = await get(port, '/api/gds/builders/95/profile');
305
+ const r = await get(port, '/api/bongos/builders/95/profile');
306
306
  assert.equal(r.status, 200);
307
307
  for (const secret of [SECRET_CLIENT_ID, SECRET_PROJECT_NAME, SECRET_EMAIL, SECRET_USER, SECRET_REASON]) {
308
308
  assert.ok(!r.text.includes(secret), `response body must not contain "${secret}"`);
@@ -311,13 +311,13 @@ await t('hub port present → module-internal sentinels leak NOWHERE in the resp
311
311
  });
312
312
 
313
313
  await t('hub port present → hide must beat the cache: Cache-Control is no-store', async () => {
314
- const r = await get(port, '/api/gds/builders/95/profile');
314
+ const r = await get(port, '/api/bongos/builders/95/profile');
315
315
  assert.equal(r.headers['cache-control'], 'no-store');
316
316
  });
317
317
 
318
318
  await t('hub port returns null (hidden/provisional/none) → cross_project: null, still no-store', async () => {
319
319
  hubExtras = null;
320
- const r = await get(port, '/api/gds/builders/95/profile');
320
+ const r = await get(port, '/api/bongos/builders/95/profile');
321
321
  assert.equal(r.status, 200);
322
322
  assert.ok('cross_project' in r.json, 'the key is present on the hub');
323
323
  assert.equal(r.json.cross_project, null, 'null — no partial numbers, indistinguishable from no-account');
@@ -472,7 +472,7 @@ const buildScoutingRoutes = require('../modules/platform-identity/routes/scoutin
472
472
  hubPublicRead = (githubId) => pi.getPublicProfileExtras(githubId, { pool: engine });
473
473
 
474
474
  await t('R06.1 a two-project builder\'s COMBINED record shows to another account', async () => {
475
- const r = await get(port, '/api/gds/builders/95/profile');
475
+ const r = await get(port, '/api/bongos/builders/95/profile');
476
476
  assert.equal(r.status, 200);
477
477
  const cp = r.json.cross_project;
478
478
  assert.ok(cp, 'the record is served');
@@ -500,7 +500,7 @@ await t('R06.1 a name needs the map AND the Public door — every other shape is
500
500
  try {
501
501
  for (const [label, catalog] of Object.entries(unnamed)) {
502
502
  other.catalog = catalog;
503
- const r = await get(port, '/api/gds/builders/95/profile');
503
+ const r = await get(port, '/api/bongos/builders/95/profile');
504
504
  const cp = r.json.cross_project;
505
505
  assert.deepEqual(cp.projects.map((p) => p.name), [pub.name], `${label}: only the Public project is named`);
506
506
  assert.equal(cp.more_projects, 1, `${label}: counted`);
@@ -509,7 +509,7 @@ await t('R06.1 a name needs the map AND the Public door — every other shape is
509
509
  // The positive control: the same row IS named once Public and on the map, so the
510
510
  // refusals above are the door, not a fixture nothing could ever name.
511
511
  other.catalog = { ...original, visibility: 'public' };
512
- const r = await get(port, '/api/gds/builders/95/profile');
512
+ const r = await get(port, '/api/bongos/builders/95/profile');
513
513
  assert.deepEqual(r.json.cross_project.projects.map((p) => p.name), [pub.name, SECRET_PROJECT_NAME]);
514
514
  } finally {
515
515
  other.catalog = original;
@@ -517,8 +517,8 @@ await t('R06.1 a name needs the map AND the Public door — every other shape is
517
517
  });
518
518
 
519
519
  await t('R06.1 the record is viewer-INDEPENDENT — another account sees the same body', async () => {
520
- const anon = await get(port, '/api/gds/builders/95/profile');
521
- const other = await get(port, '/api/gds/builders/95/profile', { Cookie: 'gds_session=some-other-builder' });
520
+ const anon = await get(port, '/api/bongos/builders/95/profile');
521
+ const other = await get(port, '/api/bongos/builders/95/profile', { Cookie: 'gds_session=some-other-builder' });
522
522
  assert.equal(other.status, 200);
523
523
  assert.equal(other.text, anon.text, 'one response shape for every reader (ADR 0171 D4)');
524
524
  });
@@ -564,7 +564,7 @@ await t('R06.4 NEGATIVE CONTROL: the leak guard goes red when a private field is
564
564
  try {
565
565
  // (a) a module-internal key on the object itself.
566
566
  hubPublicRead = async () => ({ ...(await rollup()), client_id: SECRET_CLIENT_ID });
567
- let r = await get(port, '/api/gds/builders/95/profile');
567
+ let r = await get(port, '/api/bongos/builders/95/profile');
568
568
  assert.throws(() => assertCrossProjectContract(r.json.cross_project, r.text),
569
569
  /cross_project keys are exactly the contract/, 'an extra top-level key must turn the guard red');
570
570
 
@@ -573,7 +573,7 @@ await t('R06.4 NEGATIVE CONTROL: the leak guard goes red when a private field is
573
573
  const cp = await rollup();
574
574
  return { ...cp, projects: cp.projects.map((p) => ({ ...p, is_public: true })) };
575
575
  };
576
- r = await get(port, '/api/gds/builders/95/profile');
576
+ r = await get(port, '/api/bongos/builders/95/profile');
577
577
  assert.throws(() => assertCrossProjectContract(r.json.cross_project, r.text),
578
578
  /project rows carry only the whitelisted keys/, 'an extra project-row key must turn the guard red');
579
579
 
@@ -583,7 +583,7 @@ await t('R06.4 NEGATIVE CONTROL: the leak guard goes red when a private field is
583
583
  const cp = await rollup();
584
584
  return { ...cp, projects: [{ ...cp.projects[0], name: SECRET_PROJECT_NAME }] };
585
585
  };
586
- r = await get(port, '/api/gds/builders/95/profile');
586
+ r = await get(port, '/api/bongos/builders/95/profile');
587
587
  assert.deepEqual(Object.keys(r.json.cross_project).sort(), [...CP_PUBLIC_KEYS].sort(),
588
588
  'the planted leak is shape-legal — only the sentinel scan can catch it');
589
589
  assert.throws(() => assertCrossProjectContract(r.json.cross_project, r.text),
@@ -594,7 +594,7 @@ await t('R06.4 NEGATIVE CONTROL: the leak guard goes red when a private field is
594
594
  });
595
595
 
596
596
  await t('R06.2 hide_stats removes the record on the IMMEDIATE next load', async () => {
597
- const before = await get(port, '/api/gds/builders/95/profile');
597
+ const before = await get(port, '/api/bongos/builders/95/profile');
598
598
  assert.ok(before.json.cross_project, 'public before the flip');
599
599
  assert.equal(before.headers['cache-control'], 'no-store');
600
600
 
@@ -604,7 +604,7 @@ await t('R06.2 hide_stats removes the record on the IMMEDIATE next load', async
604
604
  assert.equal(row.hide_stats, true, 'the write path set the flag');
605
605
 
606
606
  // No sleep, no second server, no cache warm-up: the very next load.
607
- const after = await get(port, '/api/gds/builders/95/profile');
607
+ const after = await get(port, '/api/bongos/builders/95/profile');
608
608
  assert.equal(after.status, 200);
609
609
  assert.equal(after.json.cross_project, null, 'gone on the next load');
610
610
  assert.equal(after.headers['cache-control'], 'no-store', 'hide must beat the cache (ADR 0171 D5)');
@@ -616,7 +616,7 @@ await t('R06.5 no PARTIAL numbers survive the hide (a count beside a hidden tota
616
616
  // Set the flag here too, so this proof stands on its own rather than on
617
617
  // whatever R06.2 left behind.
618
618
  await pi.setAccountHideStats({ githubId: HUB_GITHUB_ID, githubLogin: 'jaxri', hide: true }, { pool: engine });
619
- const r = await get(port, '/api/gds/builders/95/profile');
619
+ const r = await get(port, '/api/bongos/builders/95/profile');
620
620
  assert.equal(r.json.cross_project, null);
621
621
  for (const key of ['totals', 'works_shipped', 'more_projects', 'last_active', 'rank_held']) {
622
622
  assert.ok(!r.text.includes(key), `nothing derived from the rollup may survive — found "${key}"`);
@@ -632,12 +632,12 @@ await t('R06.5 no PARTIAL numbers survive the hide (a count beside a hidden tota
632
632
 
633
633
  const scoutApp = express();
634
634
  scoutApp.use(attachFail);
635
- scoutApp.use('/api/gds', buildScoutingRoutes());
635
+ scoutApp.use('/api/bongos', buildScoutingRoutes());
636
636
  const scout = await listen(scoutApp);
637
637
 
638
638
  await t('R06.3 GET /scouting drops the hidden, the provisional AND the private builder', async () => {
639
639
  engine.calls.length = 0;
640
- const r = await get(scout.port, '/api/gds/scouting');
640
+ const r = await get(scout.port, '/api/bongos/scouting');
641
641
  assert.equal(r.status, 200);
642
642
  const logins = r.json.builders.map((b) => b.github_login);
643
643
  assert.deepEqual(logins, ['r06-visible'],
@@ -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\/bongos\/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();