@bongos/core 1.19.804 → 1.19.806

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 (241) hide show
  1. package/.bongos-core.json +267 -242
  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/copy-inventory.md +47 -34
  42. package/docs/copy-registry.json +200 -72
  43. package/docs/module-api-changelog.md +4 -0
  44. package/modules/builder-settings/profile-nudge-prefs.js +1 -1
  45. package/modules/builder-settings/render-prefs.js +1 -1
  46. package/modules/dev-box/app/README.md +1 -1
  47. package/modules/dev-box/app/src/gds-client.js +11 -9
  48. package/modules/dev-box/app/src/vendor/bongos-client.cjs +2 -0
  49. package/modules/dev-box/app/test/unit.mjs +13 -13
  50. package/modules/dev-box/box-access.js +3 -3
  51. package/modules/dev-box/box-onboard.js +7 -7
  52. package/modules/discord/bug-attachments.js +1 -1
  53. package/modules/economy/cost-classify.js +6 -6
  54. package/modules/grading/grader-rubric.json +1 -1
  55. package/modules/grading/grader-score.js +1 -1
  56. package/modules/hall-ui/module.json +1 -1
  57. package/modules/hall-ui/public/drachmae.js +3 -3
  58. package/modules/hall-ui/public/goals-page.js +1 -1
  59. package/modules/hall-ui/public/goals-render.js +144 -22
  60. package/modules/hall-ui/public/goals.css +16 -1
  61. package/modules/hall-ui/public/idea.js +1 -1
  62. package/modules/hall-ui/public/md-reader.js +1 -1
  63. package/modules/hall-ui/public/people.html +1 -1
  64. package/modules/hall-ui/public/people.js +2 -2
  65. package/modules/hall-ui/public/primer.js +4 -4
  66. package/modules/hall-ui/public/roster.js +1 -1
  67. package/modules/hall-ui/public/shell.js +1 -1
  68. package/modules/hall-ui/public/task.js +1 -1
  69. package/modules/ideas/inbox.js +2 -2
  70. package/modules/lifecycle/db-analytics.js +1 -1
  71. package/modules/lifecycle/done-when.js +4 -4
  72. package/modules/lifecycle/version-close.js +1 -1
  73. package/modules/memory/learnings.js +2 -2
  74. package/modules/memory/memory.js +1 -1
  75. package/modules/onboarding/onboarding.js +1 -1
  76. package/modules/platform-identity/routes/my-projects.js +1 -1
  77. package/modules/platform-identity/routes/sso.js +2 -2
  78. package/modules/public-landing/public/index.html +2 -2
  79. package/modules/public-landing/public/projects-floor.states.json +1 -1
  80. package/modules/public-landing/public/projects.html +1 -1
  81. package/modules/public-landing/public/projects.states.json +1 -1
  82. package/modules/sessions/db.js +120 -0
  83. package/modules/sessions/module.json +2 -2
  84. package/modules/sessions/pollers/session-upload-health.js +111 -0
  85. package/modules/sessions/pulse.js +2 -2
  86. package/modules/sessions/routes/sessions.js +46 -0
  87. package/modules/sessions/upload-health.js +197 -0
  88. package/modules/status-ui/module.json +1 -1
  89. package/modules/status-ui/public/explore.js +1 -1
  90. package/package-lock.json +2 -2
  91. package/package.json +1 -1
  92. package/scripts/gds/agent-invoke.js +3 -3
  93. package/scripts/gds/api.js +4 -4
  94. package/scripts/gds/api.sh +1 -1
  95. package/scripts/gds/architect-audit.js +1 -1
  96. package/scripts/gds/artifact-audit.js +2 -2
  97. package/scripts/gds/audit-ships.js +2 -2
  98. package/scripts/gds/backfill-cost.js +1 -1
  99. package/scripts/gds/backfill-sessions.js +1 -1
  100. package/scripts/gds/backfill-subagent-rewards.js +1 -1
  101. package/scripts/gds/backfill-task-rewards.js +2 -2
  102. package/scripts/gds/box-auth-check.js +1 -1
  103. package/scripts/gds/box-sync.js +3 -3
  104. package/scripts/gds/box.js +10 -10
  105. package/scripts/gds/build-cli-package.js +3 -3
  106. package/scripts/gds/capture.js +2 -2
  107. package/scripts/gds/ci-grade.js +1 -1
  108. package/scripts/gds/claim-recall.js +2 -2
  109. package/scripts/gds/cli-lib.js +3 -3
  110. package/scripts/gds/context-pack.js +1 -1
  111. package/scripts/gds/copy-apply.js +2 -2
  112. package/scripts/gds/dev-lib.js +2 -2
  113. package/scripts/gds/fetch-art-key.js +2 -2
  114. package/scripts/gds/fetch-sound-prefs.js +3 -3
  115. package/scripts/gds/grade-smoke.js +1 -1
  116. package/scripts/gds/hooks/pre-push.js +1 -1
  117. package/scripts/gds/init.js +9 -9
  118. package/scripts/gds/key-card.js +1 -1
  119. package/scripts/gds/learning-capture.js +2 -2
  120. package/scripts/gds/linkify-refs.js +2 -2
  121. package/scripts/gds/local-preview-lib.js +1 -1
  122. package/scripts/gds/login.js +11 -10
  123. package/scripts/gds/main-audit.js +1 -1
  124. package/scripts/gds/newcomer-floor.config.json +1 -1
  125. package/scripts/gds/newcomer-floor.js +2 -2
  126. package/scripts/gds/newcomer-floor.templates.json +2 -2
  127. package/scripts/gds/onboarding-ship.js +1 -1
  128. package/scripts/gds/paste-token.js +3 -3
  129. package/scripts/gds/portability-smoke-memory.sh +5 -5
  130. package/scripts/gds/prove-strict-versioning.js +2 -2
  131. package/scripts/gds/provision-net.js +2 -2
  132. package/scripts/gds/provision-units.js +3 -3
  133. package/scripts/gds/pulse.js +2 -2
  134. package/scripts/gds/push-path-brief.js +2 -2
  135. package/scripts/gds/reauth.js +1 -1
  136. package/scripts/gds/recall.js +1 -1
  137. package/scripts/gds/redteam-patrol.js +3 -3
  138. package/scripts/gds/render-ideas.js +5 -5
  139. package/scripts/gds/repo-metrics.js +1 -1
  140. package/scripts/gds/sandbox-stage.js +3 -3
  141. package/scripts/gds/seed-adopt-tasks.js +3 -3
  142. package/scripts/gds/seed-board-room-tasks.js +1 -1
  143. package/scripts/gds/seed-bongos-app-tasks.js +2 -2
  144. package/scripts/gds/seed-bongos-coreB-tranche1-tasks.js +3 -3
  145. package/scripts/gds/seed-bongos-coreB-tranche2-tasks.js +4 -4
  146. package/scripts/gds/seed-bongos-tasks.js +5 -5
  147. package/scripts/gds/seed-bongos-v1-idea-routing-tasks.js +1 -1
  148. package/scripts/gds/seed-bongos-v1-seo-tasks.js +1 -1
  149. package/scripts/gds/seed-bv1-community-tasks.js +1 -1
  150. package/scripts/gds/seed-bv1-privacy-invites-tasks.js +1 -1
  151. package/scripts/gds/seed-bv1-profile-page-tasks.js +1 -1
  152. package/scripts/gds/seed-bv1-profiles-tasks.js +1 -1
  153. package/scripts/gds/seed-bv1-r01-delta-tasks.js +1 -1
  154. package/scripts/gds/seed-gds-v4-tasks.js +3 -3
  155. package/scripts/gds/seed-medusa-tasks.js +1 -1
  156. package/scripts/gds/seed-productization-tasks.js +1 -1
  157. package/scripts/gds/seed-project-admin-console-tasks.js +1 -1
  158. package/scripts/gds/seed-provisioning-tasks.js +2 -2
  159. package/scripts/gds/seed-strict-versioning-tasks.js +3 -3
  160. package/scripts/gds/seed-v2-world-tasks.js +2 -2
  161. package/scripts/gds/seed-verify-criteria.js +1 -1
  162. package/scripts/gds/session-digest.js +1 -1
  163. package/scripts/gds/session.js +2 -2
  164. package/scripts/gds/setup.js +7 -7
  165. package/scripts/gds/ship-flow.js +3 -3
  166. package/scripts/gds/ship-grade-steps.js +3 -3
  167. package/scripts/gds/ship-memory-sync.js +1 -1
  168. package/scripts/gds/ship-session-upload.js +3 -3
  169. package/scripts/gds/smoke-batch-claim.sh +10 -10
  170. package/scripts/gds/smoke-batch-release.sh +14 -14
  171. package/scripts/gds/smoke-claim-consistency.sh +19 -19
  172. package/scripts/gds/smoke-dependencies.sh +9 -9
  173. package/scripts/gds/smoke-write.sh +11 -11
  174. package/scripts/gds/start.js +1 -1
  175. package/scripts/gds/status.js +2 -2
  176. package/scripts/gds/strand-watch.js +1 -1
  177. package/scripts/gds/triage.js +4 -4
  178. package/scripts/gds/version-close.js +2 -2
  179. package/scripts/hall-preview/README.md +1 -1
  180. package/scripts/hall-preview/server.js +1 -1
  181. package/src/bongos/auth-admission.js +3 -3
  182. package/src/bongos/auth-idp.js +2 -2
  183. package/src/bongos/auth.js +3 -3
  184. package/src/bongos/db.js +2 -2
  185. package/src/bongos/llm-cache.js +2 -2
  186. package/src/bongos/metrics.js +1 -1
  187. package/src/bongos/middleware/audit.js +3 -3
  188. package/src/bongos/middleware/rate-limit.js +1 -1
  189. package/src/bongos/platform-visibility-gate.js +2 -2
  190. package/src/bongos/routes/_helpers.js +1 -1
  191. package/src/bongos/routes/audit-log.js +1 -1
  192. package/src/bongos/routes/auth.js +5 -5
  193. package/src/bongos/routes/backup.js +1 -1
  194. package/src/bongos/routes/builders.js +1 -1
  195. package/src/bongos/routes/instance.js +1 -1
  196. package/src/bongos/routes/live.js +1 -1
  197. package/src/bongos/routes/me.js +2 -2
  198. package/src/bongos/routes/modules.js +2 -2
  199. package/src/bongos/routes/public.js +1 -1
  200. package/src/bongos/routes/security.js +4 -4
  201. package/src/bongos/routes.js +1 -1
  202. package/src/bongos/serve-internal.js +6 -6
  203. package/src/branding.js +1 -1
  204. package/src/module-api.js +1 -1
  205. package/src/module-seams.js +2 -2
  206. package/src/platform-server.js +2 -2
  207. package/tests/api_alias_exceptions.mjs +67 -0
  208. package/tests/application_echo.mjs +12 -12
  209. package/tests/artifact_audit.mjs +1 -1
  210. package/tests/auth_page_gate.mjs +4 -4
  211. package/tests/auth_web_recovery.mjs +11 -11
  212. package/tests/bongos_login.mjs +7 -4
  213. package/tests/box_env.mjs +6 -6
  214. package/tests/bug_attachments.mjs +1 -1
  215. package/tests/cli_sessions.mjs +1 -1
  216. package/tests/dev_lib.mjs +1 -1
  217. package/tests/discord_bugs.mjs +2 -2
  218. package/tests/discord_inbound.mjs +2 -2
  219. package/tests/explore_visible_states.mjs +1 -1
  220. package/tests/goal_invite_consent.mjs +307 -0
  221. package/tests/government_page_gates.mjs +1 -1
  222. package/tests/hall_reference_world.mjs +1 -1
  223. package/tests/idea_ideate_full.mjs +1 -1
  224. package/tests/idp_federation.mjs +2 -2
  225. package/tests/llm-cache.mjs +1 -1
  226. package/tests/manage_manifest_shared_read.mjs +3 -3
  227. package/tests/people_directory_clip.mjs +1 -1
  228. package/tests/planning_session_skill.mjs +1 -1
  229. package/tests/platform_visibility_gate.mjs +15 -15
  230. package/tests/profile_route.mjs +25 -25
  231. package/tests/projects_hub.mjs +2 -2
  232. package/tests/provision.mjs +2 -2
  233. package/tests/provision_settings_apply.mjs +4 -4
  234. package/tests/sandbox_stage.mjs +8 -8
  235. package/tests/scouting_owner_access_and_reach.mjs +3 -3
  236. package/tests/session_upload_health.mjs +329 -0
  237. package/tests/setup_device_flow_fallback.mjs +1 -1
  238. package/tests/skill_ask_for_help.mjs +3 -3
  239. package/tests/skill_grade_audit.mjs +2 -2
  240. package/tests/skill_grader_health.mjs +3 -3
  241. package/tests/smoke_write_contract.mjs +1 -1
@@ -74,7 +74,7 @@ await t('unauthenticated request → 302 to sign-in, next() NOT called', async (
74
74
  assert.equal(nexted, false, 'next() must not run for an unauthenticated request');
75
75
  assert.ok(res._redirect, 'a redirect must be issued');
76
76
  assert.equal(res._redirect.code, 302);
77
- assert.equal(res._redirect.loc, '/api/gds/auth/web/start');
77
+ assert.equal(res._redirect.loc, '/api/bongos/auth/web/start');
78
78
  });
79
79
 
80
80
  await t('authenticated request → next() called, no redirect', async () => {
@@ -95,7 +95,7 @@ await t('session lookup error → fails CLOSED (redirect, next() NOT called)', a
95
95
  let nexted = false;
96
96
  await auth.requireBuilderPage(req, res, () => { nexted = true; });
97
97
  assert.equal(nexted, false, 'must not serve the page when the lookup errors');
98
- assert.ok(res._redirect && res._redirect.loc === '/api/gds/auth/web/start',
98
+ assert.ok(res._redirect && res._redirect.loc === '/api/bongos/auth/web/start',
99
99
  'must redirect to sign-in on a lookup error (fail closed)');
100
100
  });
101
101
 
@@ -118,7 +118,7 @@ await t('box-scoped session → 302 to sign-in even though its builder holds the
118
118
  let nexted = false;
119
119
  await auth.requireBuilderPage(req, res, () => { nexted = true; });
120
120
  assert.equal(nexted, false, 'a box session must not be served a gated page shell');
121
- assert.ok(res._redirect && res._redirect.loc === '/api/gds/auth/web/start',
121
+ assert.ok(res._redirect && res._redirect.loc === '/api/bongos/auth/web/start',
122
122
  'the remedy is a different session (/builder-reauth), so sign-in — not deniedTo');
123
123
  });
124
124
 
@@ -129,7 +129,7 @@ await t('a source the registry does not declare → denied, never treated as uns
129
129
  let nexted = false;
130
130
  await auth.requireBuilderPage(req, res, () => { nexted = true; });
131
131
  assert.equal(nexted, false, 'an unrecognised scope must fail closed, exactly as requireBuilder does');
132
- assert.ok(res._redirect && res._redirect.loc === '/api/gds/auth/web/start');
132
+ assert.ok(res._redirect && res._redirect.loc === '/api/bongos/auth/web/start');
133
133
  });
134
134
 
135
135
  await t('cli and web sessions are untouched by the scope check', async () => {
@@ -21,22 +21,22 @@ const req = (cookie) => ({ headers: cookie ? { cookie } : {} });
21
21
 
22
22
  test('the retry door carries the wizard return and the repo-scope opt-in while their cookies are valid', () => {
23
23
  assert.equal(recoveryStartHref(req('gds_return=%2Fprojects%3Fview%3Dnew; gds_oauth_repos=1')),
24
- '/api/gds/auth/web/start?return=%2Fprojects%3Fview%3Dnew&repos=1');
24
+ '/api/bongos/auth/web/start?return=%2Fprojects%3Fview%3Dnew&repos=1');
25
25
  assert.equal(recoveryStartHref(req('gds_return=%2Fprojects%3Fview%3Dnew')),
26
- '/api/gds/auth/web/start?return=%2Fprojects%3Fview%3Dnew', 'a hall sign-in never asks for repos');
27
- assert.equal(recoveryStartHref(req('gds_oauth_repos=1')), '/api/gds/auth/web/start?repos=1');
26
+ '/api/bongos/auth/web/start?return=%2Fprojects%3Fview%3Dnew', 'a hall sign-in never asks for repos');
27
+ assert.equal(recoveryStartHref(req('gds_oauth_repos=1')), '/api/bongos/auth/web/start?repos=1');
28
28
  });
29
29
 
30
30
  test('with nothing left to rebuild from, the door is the bare start — never a guess', () => {
31
- assert.equal(recoveryStartHref(req('')), '/api/gds/auth/web/start');
32
- assert.equal(recoveryStartHref(req('gds_session=abc')), '/api/gds/auth/web/start');
33
- assert.equal(recoveryStartHref(req('gds_oauth_repos=0')), '/api/gds/auth/web/start', 'only the literal opt-in counts');
31
+ assert.equal(recoveryStartHref(req('')), '/api/bongos/auth/web/start');
32
+ assert.equal(recoveryStartHref(req('gds_session=abc')), '/api/bongos/auth/web/start');
33
+ assert.equal(recoveryStartHref(req('gds_oauth_repos=0')), '/api/bongos/auth/web/start', 'only the literal opt-in counts');
34
34
  });
35
35
 
36
36
  test('the door is under the same open-redirect allowlist as the callback', () => {
37
- assert.equal(recoveryStartHref(req('gds_return=https%3A%2F%2Fevil.example%2Fx')), '/api/gds/auth/web/start');
38
- assert.equal(recoveryStartHref(req('gds_return=%2F%2Fevil.example')), '/api/gds/auth/web/start');
39
- assert.equal(recoveryStartHref(req('gds_return=javascript%3Aalert(1)')), '/api/gds/auth/web/start');
37
+ assert.equal(recoveryStartHref(req('gds_return=https%3A%2F%2Fevil.example%2Fx')), '/api/bongos/auth/web/start');
38
+ assert.equal(recoveryStartHref(req('gds_return=%2F%2Fevil.example')), '/api/bongos/auth/web/start');
39
+ assert.equal(recoveryStartHref(req('gds_return=javascript%3Aalert(1)')), '/api/bongos/auth/web/start');
40
40
  });
41
41
 
42
42
  test('every OAuth error page and the pending page rebuild their door from the cookies', () => {
@@ -47,8 +47,8 @@ test('every OAuth error page and the pending page rebuild their door from the co
47
47
  'GitHub refused · State mismatch · handshake threw — three pages, three doors');
48
48
  assert.match(callback, /renderAccessPendingPage\(err\.login, recoveryStartHref\(req\)\)/, 'the auto-walk-in lands where the founder was going');
49
49
  assert.match(callback, /const onWayToProject = founderReturn\.startsWith\('\/projects'\);/, 'the founder-shaped NOT_APPROVED note');
50
- assert.match(SRC, /const cta = opts\.cta \|\| \{ href: opts\.startHref \|\| '\/api\/gds\/auth\/web\/start', label: copy\.ctaLabel \};/);
51
- assert.match(SRC, /data-start="\$\{escapeHtml\(startHref \|\| '\/api\/gds\/auth\/web\/start'\)\}"/, 'the pending page reads its start from a data attribute, never string-built into script');
50
+ assert.match(SRC, /const cta = opts\.cta \|\| \{ href: opts\.startHref \|\| '\/api\/bongos\/auth\/web\/start', label: copy\.ctaLabel \};/);
51
+ assert.match(SRC, /data-start="\$\{escapeHtml\(startHref \|\| '\/api\/bongos\/auth\/web\/start'\)\}"/, 'the pending page reads its start from a data attribute, never string-built into script');
52
52
  assert.match(SRC, /function land\(\) \{ window\.location\.href = start; \}/);
53
53
  });
54
54
 
@@ -16,12 +16,15 @@ const require = createRequire(import.meta.url);
16
16
  const { normalizeInstanceBase, pollDecision } = require('../scripts/gds/login.js');
17
17
  const LOGIN_JS = path.join(path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'), 'scripts', 'gds', 'login.js');
18
18
 
19
- test('normalizeInstanceBase adds https, strips trailing slashes + a pasted /api/gds', () => {
19
+ test('normalizeInstanceBase adds https, strips trailing slashes + a pasted /api/bongos', () => {
20
20
  assert.equal(normalizeInstanceBase('example.com'), 'https://example.com');
21
21
  assert.equal(normalizeInstanceBase('https://example.com/'), 'https://example.com');
22
22
  assert.equal(normalizeInstanceBase('https://example.com///'), 'https://example.com');
23
+ assert.equal(normalizeInstanceBase('https://example.com/api/bongos'), 'https://example.com');
24
+ assert.equal(normalizeInstanceBase('https://example.com/api/bongos/'), 'https://example.com');
25
+ assert.equal(normalizeInstanceBase('https://example.com/api/bongos/v1'), 'https://example.com');
26
+ // the permanent alias is still a legal paste — task 1003707 moved the callers, not the door
23
27
  assert.equal(normalizeInstanceBase('https://example.com/api/gds'), 'https://example.com');
24
- assert.equal(normalizeInstanceBase('https://example.com/api/gds/'), 'https://example.com');
25
28
  assert.equal(normalizeInstanceBase('http://localhost:3000'), 'http://localhost:3000');
26
29
  });
27
30
 
@@ -58,10 +61,10 @@ test('login on a Device-Flow-disabled instance explains the cause and the cure',
58
61
  res.writeHead(code, { 'Content-Type': 'application/json' });
59
62
  res.end(JSON.stringify(body));
60
63
  };
61
- if (req.url.endsWith('/api/gds/instance')) {
64
+ if (req.url.endsWith('/api/bongos/instance')) {
62
65
  return send(200, { schema: 'instance-manifest/v1', name: 'Stub', admission: { policy: 'invite-only' } });
63
66
  }
64
- if (req.url.endsWith('/api/gds/auth/device/start')) {
67
+ if (req.url.endsWith('/api/bongos/auth/device/start')) {
65
68
  // Byte-for-byte what a real instance returned when its App had the box unticked.
66
69
  return send(424, { error: { code: 'device_start_failed', message: 'device/code failed (400): {"error":"device_flow_disabled","error_description":"Device Flow must be explicitly enabled for this App"}' } });
67
70
  }
package/tests/box_env.mjs CHANGED
@@ -20,15 +20,15 @@ const box = require('../scripts/gds/box.js');
20
20
 
21
21
  const { test, summary } = makeRunner();
22
22
 
23
- await test('bakes GDS_BASE as the FULL api base (origin + /api/gds) — matches box-report-*.sh, not box-heartbeat.sh\'s bare-origin BASE', () => {
23
+ await test('bakes GDS_BASE as the FULL api base (origin + /api/bongos) — matches box-report-*.sh, not box-heartbeat.sh\'s bare-origin BASE', () => {
24
24
  const env = box.buildBoxEnv({ apiOrigin: 'https://cloudbongos.com' });
25
- assert.match(env, /^GDS_BASE=https:\/\/cloudbongos\.com\/api\/gds$/m);
25
+ assert.match(env, /^GDS_BASE=https:\/\/cloudbongos\.com\/api\/bongos$/m);
26
26
  });
27
27
 
28
- await test('strips a trailing slash off the origin before appending /api/gds', () => {
28
+ await test('strips a trailing slash off the origin before appending /api/bongos', () => {
29
29
  const env = box.buildBoxEnv({ apiOrigin: 'https://cloudbongos.com/' });
30
- assert.match(env, /^GDS_BASE=https:\/\/cloudbongos\.com\/api\/gds$/m);
31
- assert.ok(!env.includes('//api/gds'), 'must not double the slash');
30
+ assert.match(env, /^GDS_BASE=https:\/\/cloudbongos\.com\/api\/bongos$/m);
31
+ assert.ok(!env.includes('//api/bongos'), 'must not double the slash');
32
32
  });
33
33
 
34
34
  await test('no named tunnel: box.env carries GDS_BASE and nothing else', () => {
@@ -44,7 +44,7 @@ await test('named tunnel: box.env carries GDS_BASE AND all four tunnel vars', ()
44
44
  credential: 'cred456', previewHostname: 'sandbox-alice.example.com',
45
45
  },
46
46
  });
47
- assert.match(env, /^GDS_BASE=https:\/\/cloudbongos\.com\/api\/gds$/m);
47
+ assert.match(env, /^GDS_BASE=https:\/\/cloudbongos\.com\/api\/bongos$/m);
48
48
  assert.match(env, /^BOX_TUNNEL_TOKEN=tok123$/m);
49
49
  assert.match(env, /^BOX_TERMINAL_HOSTNAME=term-alice\.example\.com$/m);
50
50
  assert.match(env, /^BOX_TERMINAL_CREDENTIAL=cred456$/m);
@@ -101,7 +101,7 @@ await t('persistImageFromUrl: content-type says png but bytes are not → sniff_
101
101
  await t('persistImageFromUrl: valid PNG → written to disk, returns durable url', async () => {
102
102
  const r = await at.persistImageFromUrl('https://cdn.discordapp.com/a.png', { messageId: 'm42', fetchImpl: fakeFetch(PNG), dir: TMP });
103
103
  assert.equal(r.ok, true);
104
- assert.match(r.url, /^\/api\/gds\/bug-attachments\/bug-m42-[0-9a-f]{16}\.png$/);
104
+ assert.match(r.url, /^\/api\/bongos\/bug-attachments\/bug-m42-[0-9a-f]{16}\.png$/);
105
105
  assert.equal(at.isSafeName(r.file), true);
106
106
  const onDisk = await fs.readFile(path.join(TMP, r.file));
107
107
  assert.equal(onDisk.length, PNG.length);
@@ -259,7 +259,7 @@ test('login VERIFIES a stored token before trusting it, and can be forced past',
259
259
  // A stored token may be expired or revoked. Reinstating one unchecked would leave the builder
260
260
  // "signed in" to a session every later command then fails on.
261
261
  assert.match(src, /loadStoredSession\(base\)/);
262
- assert.match(src, /\/api\/gds\/me`, \{ headers: \{ Authorization/);
262
+ assert.match(src, /\/api\/bongos\/me`, \{ headers: \{ Authorization/);
263
263
  assert.match(src, /if \(me\.ok && me\.data && me\.data\.builder\)/);
264
264
  assert.match(src, /--force/, 'there must be a way to sign in as somebody else');
265
265
  });
package/tests/dev_lib.mjs CHANGED
@@ -211,7 +211,7 @@ t('stop: kills a live pid, reports not-running otherwise', () => {
211
211
  await ta('health: up + authConfigured parsed from the two healthz routes', async () => {
212
212
  const params = { port: 3006 };
213
213
  const deps = { fetchImpl: async (url) => {
214
- if (url.endsWith('/api/gds/healthz')) return { status: 200, json: async () => ({ ok: true, auth_configured: true }) };
214
+ if (url.endsWith('/api/bongos/healthz')) return { status: 200, json: async () => ({ ok: true, auth_configured: true }) };
215
215
  if (url.endsWith('/healthz')) return { status: 200 };
216
216
  return { status: 404 };
217
217
  } };
@@ -99,7 +99,7 @@ function makeDeps(over = {}) {
99
99
  calls.persist.push({ urls, opts });
100
100
  if (over.persistThrows) throw new Error('persist boom');
101
101
  // default: echo each url back as a stored ref unless overridden
102
- return over.stored ?? urls.map((u, i) => ({ file: `bug-x-${i}.png`, url: `/api/gds/bug-attachments/bug-x-${i}.png` }));
102
+ return over.stored ?? urls.map((u, i) => ({ file: `bug-x-${i}.png`, url: `/api/bongos/bug-attachments/bug-x-${i}.png` }));
103
103
  },
104
104
  };
105
105
  return { deps, calls };
@@ -282,7 +282,7 @@ await t('recordInboundBug: text + images → persists images + embeds durable li
282
282
  assert.equal(calls.persist.length, 1);
283
283
  assert.deepEqual(calls.persist[0].urls, ['https://cdn.discordapp.com/a.png']);
284
284
  assert.match(calls.create[0].bodyMd, /Attachments \(1\)/);
285
- assert.match(calls.create[0].bodyMd, /!\[attachment 1\]\(\/api\/gds\/bug-attachments\//);
285
+ assert.match(calls.create[0].bodyMd, /!\[attachment 1\]\(\/api\/bongos\/bug-attachments\//);
286
286
  });
287
287
 
288
288
  await t('recordInboundBug: image-only (empty text) → filed with screenshot title', async () => {
@@ -71,7 +71,7 @@ function makeDeps(over = {}) {
71
71
  persistImages: async (urls, opts) => {
72
72
  calls.persist.push({ urls, opts });
73
73
  if (over.persistThrows) throw new Error('persist boom');
74
- return over.stored ?? urls.map((u, i) => ({ file: `bug-x-${i}.png`, url: `/api/gds/bug-attachments/bug-x-${i}.png` }));
74
+ return over.stored ?? urls.map((u, i) => ({ file: `bug-x-${i}.png`, url: `/api/bongos/bug-attachments/bug-x-${i}.png` }));
75
75
  },
76
76
  };
77
77
  return { deps, calls };
@@ -224,7 +224,7 @@ await t('recordInboundIdea: text + images → persists + embeds durable links',
224
224
  assert.equal(r.images, 1);
225
225
  assert.equal(calls.persist.length, 1);
226
226
  assert.match(calls.capture[0].bodyMd, /Attachments \(1\)/);
227
- assert.match(calls.capture[0].bodyMd, /!\[attachment 1\]\(\/api\/gds\/bug-attachments\//);
227
+ assert.match(calls.capture[0].bodyMd, /!\[attachment 1\]\(\/api\/bongos\/bug-attachments\//);
228
228
  });
229
229
 
230
230
  await t('recordInboundIdea: UNLINKED author with image → still re-hosted (guards, not identity)', async () => {
@@ -280,6 +280,6 @@ test('the page reads the public feed and asks for JSON', async () => {
280
280
  const r = render([]);
281
281
  await settle();
282
282
  assert.equal(r.requests.length, 1);
283
- assert.equal(r.requests[0].url, '/api/gds/projects/featured');
283
+ assert.equal(r.requests[0].url, '/api/bongos/projects/featured');
284
284
  assert.equal(r.requests[0].opts.headers.Accept, 'application/json');
285
285
  });
@@ -0,0 +1,307 @@
1
+ // tests/goal_invite_consent.mjs — the goals page INVITES; it does not force-add
2
+ // behind the word "Invited." (task 1002886, goal 1000095).
3
+ //
4
+ // WHY THIS FILE EXISTS. The '+ Invite' control posted to POST /goals/:id/members
5
+ // — the immediate force-add — and then toasted 'Invited.' The person was already
6
+ // in the goal and had never been asked. Meanwhile ADR 0112's consented flow
7
+ // (POST /goals/:id/invitations -> a pending row -> the invitee accepts) had its
8
+ // RECEIVING half fully wired in goal-inbox.js and no sender anywhere in the hall,
9
+ // so nothing in the product ever created an invitation.
10
+ //
11
+ // Nothing in tests/ touched the invite box before this file — which is how a
12
+ // control could claim one thing and do the opposite for as long as it did. The
13
+ // lesson generalises past this button: assert the REQUEST a control issues and
14
+ // the SENTENCE it then shows, because those two drifting apart is invisible to
15
+ // any test that only checks the control renders.
16
+ //
17
+ // It runs the REAL goals-render.js (plus the real goals-lib, task-lib and kit)
18
+ // in a vm against a stub DOM and a recording postJSON, and drives renderDetail —
19
+ // the actual entry point, not the private renderer — so what is asserted is the
20
+ // call the page really makes.
21
+ //
22
+ // Run: node tests/goal_invite_consent.mjs
23
+ import { strict as assert } from 'node:assert';
24
+ import fs from 'node:fs';
25
+ import path from 'node:path';
26
+ import vm from 'node:vm';
27
+ import { fileURLToPath } from 'node:url';
28
+ import { makeRunner } from './helpers.mjs';
29
+
30
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
31
+ const read = (rel) => fs.readFileSync(path.join(ROOT, rel), 'utf8');
32
+ const P = 'modules/hall-ui/public/';
33
+ const RENDER_JS = read(`${P}goals-render.js`);
34
+
35
+ const { test, summary } = makeRunner();
36
+ const tick = () => new Promise((r) => setTimeout(r, 0));
37
+
38
+ const SELF = 7001; // the viewer: the goal's creator, so canManage is true
39
+ const MEMBER_ID = 7002; // already in the goal
40
+ const PENDING_ID = 7003; // holds a pending invitation the goal sent
41
+ const FREE_ID = 7004; // neither — the only genuinely invitable builder
42
+
43
+ function makeEl(id) {
44
+ const el = {
45
+ id,
46
+ innerHTML: '', textContent: '', value: '', className: '',
47
+ hidden: false, disabled: false, checked: false,
48
+ options: [], style: {}, dataset: {},
49
+ children: [],
50
+ classList: { add() {}, remove() {}, toggle() {}, contains: () => false },
51
+ appendChild(c) { el.children.push(c); return c; },
52
+ setAttribute() {}, removeAttribute() {}, focus() {}, scrollIntoView() {},
53
+ _on: new Map(),
54
+ addEventListener(ev, fn) {
55
+ if (!el._on.has(ev)) el._on.set(ev, []);
56
+ el._on.get(ev).push(fn);
57
+ },
58
+ fire(ev, arg) {
59
+ for (const fn of el._on.get(ev) || []) fn(arg || { preventDefault() {} });
60
+ },
61
+ querySelector() { return null; },
62
+ querySelectorAll() { return []; },
63
+ };
64
+ return el;
65
+ }
66
+
67
+ function harness({ pendingRequests = [] } = {}) {
68
+ const els = new Map();
69
+ // The box is re-rendered by replacing innerHTML, and the stub DOM does not
70
+ // parse HTML — so the controls the page writes into #goal-invite are modelled
71
+ // by their ids, which is exactly how the page reaches them ($('#id')).
72
+ // Ids the invite box writes INTO itself. Listed rather than inferred because
73
+ // the stub does not parse HTML — this is the seam where it has to imitate the
74
+ // one thing real innerHTML does that matters here: destroy what was there.
75
+ const INVITE_CHILD_IDS = [
76
+ 'goal-invite-open', 'goal-invite-form', 'goal-invite-builder', 'goal-invite-note',
77
+ 'goal-invite-cancel', 'goal-direct-open', 'goal-direct-form', 'goal-direct-builder',
78
+ 'goal-direct-kind', 'goal-direct-cancel',
79
+ ];
80
+ const getEl = (id) => {
81
+ const key = String(id).replace(/^#/, '');
82
+ if (!els.has(key)) {
83
+ const el = makeEl(key);
84
+ if (key === 'goal-invite') {
85
+ // THE BUG THIS MODELS IS REAL: renderInviteBox rebuilds the box on every
86
+ // call, and a stub that kept the old nodes alive would keep their click
87
+ // handlers too — so a single click would re-enter the renderer once per
88
+ // previous render. That is a defect in the STUB, not the page, but only
89
+ // because the page really does replace these nodes. Keep them in step.
90
+ let html = '';
91
+ Object.defineProperty(el, 'innerHTML', {
92
+ get: () => html,
93
+ set: (v) => { html = String(v); for (const cid of INVITE_CHILD_IDS) els.delete(cid); },
94
+ });
95
+ }
96
+ els.set(key, el);
97
+ }
98
+ return els.get(key);
99
+ };
100
+ const documentObj = {
101
+ documentElement: { dataset: {}, style: {}, classList: { add() {}, remove() {} } },
102
+ body: makeEl('body'),
103
+ getElementById: getEl,
104
+ querySelector: (sel) => getEl(sel),
105
+ querySelectorAll: () => [],
106
+ createElement: () => makeEl(),
107
+ addEventListener() {},
108
+ };
109
+
110
+ const posts = [];
111
+ const toasts = [];
112
+ const state = {
113
+ current: 1,
114
+ view: 'detail',
115
+ details: new Map(),
116
+ directory: [
117
+ { id: MEMBER_ID, display_name: 'Ada Member', github_login: 'ada' },
118
+ { id: PENDING_ID, display_name: 'Ben Pending', github_login: 'ben' },
119
+ { id: FREE_ID, display_name: 'Cleo Free', github_login: 'cleo' },
120
+ ],
121
+ pendingRequests,
122
+ myPendingRequest: null,
123
+ inviteOpen: false,
124
+ addDirectOpen: false,
125
+ // The rest of the page state renderDetail walks through on its way to the
126
+ // invite box. Shapes copied from goals-page.js's initial state — the
127
+ // renderers read several of these unguarded, so a partial stub throws
128
+ // before execution ever reaches what is under test here.
129
+ workSort: { todo: { by: null, dir: 'desc' }, done: { by: null, dir: 'desc' } },
130
+ work: { todo: [], done: [] },
131
+ workLoaded: false,
132
+ claimsByTask: new Map(),
133
+ conflictPairs: null,
134
+ conflictSource: null,
135
+ rollups: new Map(),
136
+ activity: new Map(),
137
+ };
138
+
139
+ const windowObj = {
140
+ location: { assign() {}, href: '/goals' },
141
+ localStorage: { getItem: () => null, setItem() {}, removeItem() {} },
142
+ scrollTo() {}, addEventListener() {},
143
+ matchMedia: () => ({ matches: false, addEventListener() {}, addListener() {} }),
144
+ OTB: {
145
+ escapeHtml: (s) => String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;'),
146
+ $: (sel) => documentObj.querySelector(sel),
147
+ fmtNum: (n) => String(n ?? ''),
148
+ fmtInt: (n) => String(n ?? ''),
149
+ fmtDate: (d) => String(d ?? '').slice(0, 10),
150
+ toast: (m) => toasts.push(String(m)),
151
+ rankLabel: (r) => String(r ?? ''),
152
+ categoryChip: () => '',
153
+ num: (n) => Number(n) || 0,
154
+ },
155
+ };
156
+
157
+ const sandbox = {
158
+ window: windowObj, document: documentObj, location: windowObj.location,
159
+ console: { error() {}, warn() {}, log() {} },
160
+ setTimeout, clearTimeout, URLSearchParams, encodeURIComponent, Date, Math, JSON, Set, Map,
161
+ fetch: () => Promise.resolve({ ok: true, status: 200, json: () => Promise.resolve({}) }),
162
+ };
163
+ sandbox.globalThis = sandbox;
164
+ vm.createContext(sandbox);
165
+ for (const f of ['hall-kit.js', 'task-lib.js', 'goals-lib.js', 'goals-render.js']) {
166
+ vm.runInContext(read(P + f), sandbox, { filename: f });
167
+ }
168
+
169
+ const goal = {
170
+ id: 1, version_id: 'BONGOS-V2', status: 'open', visibility: 'public',
171
+ created_by: SELF, title: 'A goal', scope_modules: [], accepting_requests: true,
172
+ };
173
+ const members = [{ builder_id: MEMBER_ID, display_name: 'Ada Member', membership_kind: 'member' }];
174
+ const detail = { goal, members, criteria: [], satisfied: [], tasks: [] };
175
+ state.details.set('1', detail);
176
+
177
+ const api = sandbox.window.OTBGoalsRender({
178
+ API: '/api/bongos',
179
+ curLabel: () => 'Credits',
180
+ // URL-aware, because the renderers walk several feeds on the way to the
181
+ // invite box and a one-shape-fits-all reply feeds the wrong array into the
182
+ // wrong pager.
183
+ getJSON: async (url) => {
184
+ if (/\/goals\/\d+\/requests$/.test(url)) return { requests: pendingRequests };
185
+ if (/\/goals\/inbox$/.test(url)) return { myPendingRequests: [] };
186
+ if (/\/builders\/directory/.test(url)) return { builders: state.directory };
187
+ if (/\/tasks/.test(url)) return { tasks: [] };
188
+ if (/\/claims/.test(url)) return { claims: [] };
189
+ return {};
190
+ },
191
+ patchJSON: async () => ({}),
192
+ postJSON: async (url, body) => { posts.push({ url, body }); return { ok: true }; },
193
+ sameId: (a, b) => String(a ?? '') === String(b ?? ''),
194
+ selfId: () => SELF,
195
+ state,
196
+ get workTabs() { return { activate() {} }; },
197
+ });
198
+
199
+ return { api, state, posts, toasts, getEl, els };
200
+ }
201
+
202
+ // Open the invite box the way a user does: render the detail, then click
203
+ // "+ Invite". Returns the harness with the form on screen.
204
+ async function openInviteForm(h) {
205
+ h.api.renderDetail(1);
206
+ await tick();
207
+ h.getEl('goal-invite-open').fire('click');
208
+ await tick(); await tick();
209
+ return h;
210
+ }
211
+
212
+ // ---------------------------------------------------------------------------
213
+
214
+ await test('the source no longer names the force-add as an "invite"', () => {
215
+ assert.ok(!/async function inviteMember\b/.test(RENDER_JS),
216
+ 'inviteMember() was the force-add wearing the word invite — it is split in two');
217
+ assert.match(RENDER_JS, /async function sendInvitation\(/, 'the consented sender exists');
218
+ assert.match(RENDER_JS, /async function addMemberDirectly\(/, 'the force-add is named for what it does');
219
+ assert.ok(!/toast\('Invited\.'\)/.test(RENDER_JS), "the 'Invited.' toast on a force-add is gone");
220
+ });
221
+
222
+ await test('the invite form posts to /invitations and never claims they joined', async () => {
223
+ const h = await openInviteForm(harness());
224
+ h.getEl('goal-invite-builder').value = String(FREE_ID);
225
+ h.getEl('goal-invite-note').value = ' we could use your eye on this ';
226
+ h.getEl('goal-invite-form').fire('submit');
227
+ await tick(); await tick();
228
+
229
+ assert.equal(h.posts.length, 1, 'exactly one write');
230
+ const { url, body } = h.posts[0];
231
+ assert.match(url, /\/goals\/1\/invitations$/, 'the CONSENTED endpoint, not /members');
232
+ assert.ok(!/\/members$/.test(url), 'the force-add endpoint is not what Invite calls');
233
+ assert.equal(body.builder_id, FREE_ID);
234
+ assert.equal(body.note, 'we could use your eye on this', 'the note is sent, trimmed');
235
+ assert.ok(!('membership_kind' in body),
236
+ 'the invitations route carries no membership_kind — sending one would be a choice dropped in silence');
237
+
238
+ const said = h.toasts.join(' ');
239
+ assert.match(said, /accept/i, 'the toast turns on their acceptance');
240
+ assert.ok(!/^Invited\.$/.test(h.toasts[0]), 'the old lie is not relocated');
241
+ assert.ok(!/\b(added|joined|is now a member)\b/i.test(said),
242
+ `nobody joined anything yet — got: ${JSON.stringify(h.toasts)}`);
243
+ });
244
+
245
+ await test('an empty note is omitted rather than sent as an empty string', async () => {
246
+ const h = await openInviteForm(harness());
247
+ h.getEl('goal-invite-builder').value = String(FREE_ID);
248
+ h.getEl('goal-invite-note').value = ' ';
249
+ h.getEl('goal-invite-form').fire('submit');
250
+ await tick(); await tick();
251
+ assert.ok(!('note' in h.posts[0].body), 'a blank note is absent, not ""');
252
+ });
253
+
254
+ await test('the invite form offers no membership-kind picker', async () => {
255
+ const h = await openInviteForm(harness());
256
+ const html = h.getEl('goal-invite').innerHTML;
257
+ assert.ok(!/id="goal-invite-kind"/.test(html),
258
+ 'the picker cannot be honoured by an invitation — goal_membership_requests has no such column');
259
+ assert.match(html, /join as a member/i, 'and the form says what they will join as instead');
260
+ });
261
+
262
+ await test('the dropdown excludes members AND anyone already pending', async () => {
263
+ const h = await openInviteForm(harness({
264
+ pendingRequests: [{ id: 55, kind: 'invite', builder_id: PENDING_ID, display_name: 'Ben Pending' }],
265
+ }));
266
+ const html = h.getEl('goal-invite').innerHTML;
267
+ assert.ok(html.includes(`value="${FREE_ID}"`), 'the invitable builder is offered');
268
+ assert.ok(!html.includes(`value="${MEMBER_ID}"`), 'a member would 409 already_a_member');
269
+ // The one that matters: a pending invitation is INVISIBLE in the member list,
270
+ // so without this filter the user picks an ordinary-looking name and is handed
271
+ // an error they had no way to predict.
272
+ assert.ok(!html.includes(`value="${PENDING_ID}"`), 'a pending invitee would 409 invite_pending');
273
+ });
274
+
275
+ await test('a pending JOIN-REQUEST is excluded too, not just an invitation', async () => {
276
+ const h = await openInviteForm(harness({
277
+ pendingRequests: [{ id: 56, kind: 'request', builder_id: PENDING_ID }],
278
+ }));
279
+ const html = h.getEl('goal-invite').innerHTML;
280
+ assert.ok(!html.includes(`value="${PENDING_ID}"`),
281
+ 'the server refuses on EITHER kind, so the filter may not look only at invites');
282
+ });
283
+
284
+ await test('the force-add survives, one click away, and says nobody was asked', async () => {
285
+ const h = await openInviteForm(harness());
286
+ const collapsed = h.getEl('goal-invite').innerHTML;
287
+ assert.match(collapsed, /without asking/i, 'the path is discoverable, not removed');
288
+ assert.ok(!/id="goal-direct-form"/.test(collapsed), 'but it is not the default control');
289
+
290
+ h.getEl('goal-direct-open').fire('click');
291
+ await tick();
292
+ const open = h.getEl('goal-invite').innerHTML;
293
+ assert.match(open, /id="goal-direct-kind"/, 'the membership kind IS honoured on this path, so it is offered here');
294
+
295
+ h.getEl('goal-direct-builder').value = String(FREE_ID);
296
+ h.getEl('goal-direct-kind').value = 'lead';
297
+ h.getEl('goal-direct-form').fire('submit');
298
+ await tick(); await tick();
299
+
300
+ assert.equal(h.posts.length, 1);
301
+ assert.match(h.posts[0].url, /\/goals\/1\/members$/, 'the force-add endpoint');
302
+ assert.equal(h.posts[0].body.membership_kind, 'lead', 'and the chosen kind travels');
303
+ const said = h.toasts.join(' ');
304
+ assert.match(said, /without being asked/i, 'the consequence is stated, not hidden behind "Invited."');
305
+ });
306
+
307
+ summary();
@@ -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 () => {
@@ -150,7 +150,7 @@ await test('the page asks for exactly the ceiling, and asks for it ONCE', async
150
150
  const h = harness({ count: 3 });
151
151
  await tick(); await tick();
152
152
  assert.equal(h.requests.length, 1, 'one directory read');
153
- assert.match(h.requests[0], /\/api\/gds\/scouting\?limit=/);
153
+ assert.match(h.requests[0], /\/api\/bongos\/scouting\?limit=/);
154
154
  const limit = Number(new URLSearchParams(h.requests[0].split('?')[1]).get('limit'));
155
155
  assert.equal(limit, CEILING, `the page must ask for the route ceiling (${CEILING})`);
156
156
  });
@@ -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', () => {