@bongos/core 1.23.17 → 1.23.19

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 (59) hide show
  1. package/.bongos-core.json +109 -54
  2. package/clients/bongos-client/README.md +1 -1
  3. package/clients/bongos-client/bongos-client.global.js +2 -0
  4. package/clients/bongos-client/index.cjs +2 -0
  5. package/clients/bongos-client/index.d.ts +2 -0
  6. package/clients/bongos-client/index.mjs +2 -0
  7. package/docs/api/openapi.json +22 -1
  8. package/docs/api-reference.md +3 -2
  9. package/docs/copy-inventory.md +985 -934
  10. package/docs/copy-registry.json +1829 -1309
  11. package/docs/module-api-changelog.md +4 -0
  12. package/docs/page-inventory.json +10 -2
  13. package/docs/page-readings.json +601 -576
  14. package/modules/copy-desk/auto-close.js +9 -3
  15. package/modules/copy-desk/page-status.js +25 -13
  16. package/modules/copy-desk/tests/copy_page_auto_close.mjs +8 -0
  17. package/modules/copy-desk/tests/copy_page_status.mjs +2 -2
  18. package/modules/platform-identity/builder-of-the-week.js +48 -20
  19. package/modules/platform-identity/community-crowns.js +176 -0
  20. package/modules/platform-identity/community-leaderboard.js +1 -1
  21. package/modules/platform-identity/migrations/platform_identity_033_period_features.sql +43 -0
  22. package/modules/platform-identity/routes/community-leaderboard.js +25 -3
  23. package/modules/public-landing/public/account.html +3 -2
  24. package/modules/public-landing/public/community-crowns-first.states.json +15 -0
  25. package/modules/public-landing/public/community-crowns-new-year.states.json +15 -0
  26. package/modules/public-landing/public/community-crowns-none.states.json +15 -0
  27. package/modules/public-landing/public/community-empty.states.json +1 -1
  28. package/modules/public-landing/public/community-owner-two.states.json +2 -2
  29. package/modules/public-landing/public/community-owner.states.json +1 -1
  30. package/modules/public-landing/public/community-week-none.states.json +1 -1
  31. package/modules/public-landing/public/community.html +130 -22
  32. package/modules/public-landing/public/community.probes.json +20 -3
  33. package/modules/public-landing/public/community.states.json +8 -6
  34. package/modules/public-landing/public/connections.html +3 -2
  35. package/modules/public-landing/public/contact.html +3 -2
  36. package/modules/public-landing/public/guild.html +3 -2
  37. package/modules/public-landing/public/index.html +3 -2
  38. package/modules/public-landing/public/invitations.html +3 -2
  39. package/modules/public-landing/public/privacy.html +3 -2
  40. package/modules/public-landing/public/projects.html +3 -2
  41. package/modules/public-landing/public/terms.html +3 -2
  42. package/modules/ui-design/kit/fixtures/community-crowns-first.json +6 -0
  43. package/modules/ui-design/kit/fixtures/community-crowns-new-year.json +6 -0
  44. package/modules/ui-design/kit/fixtures/community-crowns-none.json +6 -0
  45. package/modules/ui-design/kit/fixtures/community-crowns.json +6 -0
  46. package/modules/ui-design/kit/serve.js +14 -0
  47. package/package-lock.json +2 -2
  48. package/package.json +1 -1
  49. package/release-notes.json +12 -0
  50. package/scripts/bongos/run-unit-tests.js +4 -0
  51. package/src/module-api.js +1 -1
  52. package/src/platform-server.js +4 -3
  53. package/tests/community_crowns.mjs +369 -0
  54. package/tests/community_crowns_db.mjs +164 -0
  55. package/tests/copy_stuck_page_tweaks.mjs +39 -2
  56. package/tests/hub_account_page.mjs +2 -2
  57. package/tests/hub_community_page.mjs +144 -29
  58. package/tests/landing_legal_pages.mjs +2 -2
  59. package/tests/privacy_matrix.mjs +68 -9
@@ -154,7 +154,11 @@ function identitySentinels(acct) {
154
154
  // the past weeks (task 1002981, spec D5): a row IS stats, so the board's rule.
155
155
  // Every fixture account has won a recorded week, so the read's own predicate
156
156
  // decides: 'listed' | 'absent'
157
- // directory GET /scouting Recruiter — 'full' | 'name-only' | 'absent'
157
+ // crowns GET /community/crowns Anon · Builder · Recruiter — the builders of the month,
158
+ // the year so far and last year (task 1004902): a crown IS stats, so the board's
159
+ // rule. Each account in turn is made the winner of all three, so the reads'
160
+ // own predicates decide: 'listed' | 'absent'
161
+ // directory GET /scouting Recruiter — 'full' | 'name-only' | 'absent'
158
162
  // sliver GET /scouting/:handle Recruiter — 'full' (D3) | 'name-only' (D3, hidden) | 404
159
163
  // self GET /me/cross-project Self — 'own' (own numbers, any flags) | 'n/a' (no session exists)
160
164
  // guild GET /guilds/:slug Anon · Builder · Recruiter · Self — the guild page's PUBLIC
@@ -170,13 +174,13 @@ function identitySentinels(acct) {
170
174
  // people the guild page already shows them — shown_publicly AND existence;
171
175
  // hide_stats never hides membership, and private or provisional is never listed
172
176
  const MATRIX = {
173
- PROV: { profile: 404, search: 'absent', board: 'absent', week: 'absent', directory: 'absent', sliver: 404, self: 'n/a', guild: 'absent', members: 'absent', invite: 'absent' },
174
- PUB: { profile: 'full', search: 'listed', board: 'listed', week: 'listed', directory: 'full', sliver: 'full', self: 'own', guild: 'listed', members: 'listed', invite: 'listed' },
175
- SHY: { profile: 'identity', search: 'name-only', board: 'absent', week: 'absent', directory: 'name-only', sliver: 'name-only', self: 'own', guild: 'listed', members: 'listed', invite: 'listed' },
176
- PRIV: { profile: 404, search: 'absent', board: 'absent', week: 'absent', directory: 'absent', sliver: 404, self: 'own', guild: 'absent', members: 'listed', invite: 'absent' },
177
- PRIVD: { profile: 404, search: 'absent', board: 'absent', week: 'absent', directory: 'absent', sliver: 'full', self: 'own', guild: 'absent', members: 'listed', invite: 'absent' },
178
- PRIVDH: { profile: 404, search: 'absent', board: 'absent', week: 'absent', directory: 'absent', sliver: 'name-only', self: 'own', guild: 'absent', members: 'listed', invite: 'absent' },
179
- OPTOUT: { profile: 'full', search: 'listed', board: 'listed', week: 'listed', directory: 'absent', sliver: 404, self: 'own', guild: 'listed', members: 'listed', invite: 'listed' },
177
+ PROV: { profile: 404, search: 'absent', board: 'absent', week: 'absent', crowns: 'absent', directory: 'absent', sliver: 404, self: 'n/a', guild: 'absent', members: 'absent', invite: 'absent' },
178
+ PUB: { profile: 'full', search: 'listed', board: 'listed', week: 'listed', crowns: 'listed', directory: 'full', sliver: 'full', self: 'own', guild: 'listed', members: 'listed', invite: 'listed' },
179
+ SHY: { profile: 'identity', search: 'name-only', board: 'absent', week: 'absent', crowns: 'absent', directory: 'name-only', sliver: 'name-only', self: 'own', guild: 'listed', members: 'listed', invite: 'listed' },
180
+ PRIV: { profile: 404, search: 'absent', board: 'absent', week: 'absent', crowns: 'absent', directory: 'absent', sliver: 404, self: 'own', guild: 'absent', members: 'listed', invite: 'absent' },
181
+ PRIVD: { profile: 404, search: 'absent', board: 'absent', week: 'absent', crowns: 'absent', directory: 'absent', sliver: 'full', self: 'own', guild: 'absent', members: 'listed', invite: 'absent' },
182
+ PRIVDH: { profile: 404, search: 'absent', board: 'absent', week: 'absent', crowns: 'absent', directory: 'absent', sliver: 'name-only', self: 'own', guild: 'absent', members: 'listed', invite: 'absent' },
183
+ OPTOUT: { profile: 'full', search: 'listed', board: 'listed', week: 'listed', crowns: 'listed', directory: 'absent', sliver: 404, self: 'own', guild: 'listed', members: 'listed', invite: 'listed' },
180
184
  };
181
185
 
182
186
  // ===========================================================================
@@ -296,6 +300,9 @@ function weekOf(key) {
296
300
  }
297
301
  return weeksWon[key];
298
302
  }
303
+ // THE ACCOUNT that won the month, the year so far and last year (task 1004902); the
304
+ // crowns test makes each fixture account the winner in turn.
305
+ let CROWNED = 'PUB';
299
306
 
300
307
  const pool = {
301
308
  query: async (text, params = []) => {
@@ -370,6 +377,39 @@ const pool = {
370
377
  return { rows, rowCount: rows.length };
371
378
  }
372
379
 
380
+ // THE CROWNS (task 1004902): history began long ago, every month and year is already
381
+ // recorded, and CROWNED names the account that won all three — so each read's own
382
+ // predicate decides who is shown. The projections are pinned: a new column is a new
383
+ // disclosure.
384
+ if (/^SELECT reported_at FROM platform_identity_activity_snapshots ORDER BY id ASC LIMIT 1$/.test(sql)) return { rows: [{ reported_at: '2020-01-01T00:00:00.000Z' }], rowCount: 1 };
385
+ if (/^SELECT 1 FROM platform_identity_period_features WHERE /.test(sql)) return { rows: [{ '?column?': 1 }], rowCount: 1 };
386
+ if (/FROM platform_identity_period_features f JOIN platform_identity_accounts pa/.test(sql)) {
387
+ const cols = (/^SELECT (.+?) FROM /.exec(sql) || [, ''])[1];
388
+ if (cols !== 'f.credits_delta, f.ships_delta, pa.handle, pa.github_login, pa.display_name, pa.avatar_url') throw new Error(`fake pool: the crowned-period read selects something new — ${cols}`);
389
+ const where = lift(sql, /AND f\.period_start = \$2::date AND (.+)$/, 'crowned period');
390
+ const rows = ACCOUNTS.filter((a) => a.key === CROWNED).filter(accountFilter(where, [])).map((a) => ({
391
+ credits_delta: String(ROLLUP[a.github_id].credits), ships_delta: String(ROLLUP[a.github_id].works_shipped),
392
+ handle: a.handle, github_login: a.github_login, display_name: a.display_name, avatar_url: a.avatar_url,
393
+ }));
394
+ return { rows, rowCount: rows.length };
395
+ }
396
+ // ... the year so far: computeWinner's act CTE carries the floor, then the leader's re-read
397
+ if (/^WITH act AS/.test(sql) && /\) f ON true/.test(sql)) {
398
+ const where = lift(sql, /JOIN platform_identity_accounts pa ON pa\.github_id = a\.github_id WHERE (.+?) \), per_project AS/, 'year so far');
399
+ const rows = ACCOUNTS.filter((a) => a.key === CROWNED).filter(accountFilter(where, [])).map((a) => ({
400
+ github_id: a.github_id, credits: String(ROLLUP[a.github_id].credits), tasks_shipped: String(ROLLUP[a.github_id].works_shipped),
401
+ }));
402
+ return { rows, rowCount: rows.length };
403
+ }
404
+ if (/FROM platform_identity_accounts pa WHERE pa\.github_id = \$1::bigint AND /.test(sql)) {
405
+ const cols = (/^SELECT (.+?) FROM /.exec(sql) || [, ''])[1];
406
+ if (cols !== 'pa.handle, pa.github_login, pa.display_name, pa.avatar_url') throw new Error(`fake pool: the year leader read selects something new — ${cols}`);
407
+ const where = lift(sql, /WHERE pa\.github_id = \$1::bigint AND (.+)$/, 'year leader');
408
+ const rows = ACCOUNTS.filter((a) => a.github_id === String(params[0])).filter(accountFilter(where, []))
409
+ .map((a) => ({ handle: a.handle, github_login: a.github_login, display_name: a.display_name, avatar_url: a.avatar_url }));
410
+ return { rows, rowCount: rows.length };
411
+ }
412
+
373
413
  // THE LEADERBOARD's totals: the rollup floor sits on the act CTE's join.
374
414
  if (/platform_identity_activity_snapshots/.test(sql)) {
375
415
  const where = lift(sql, /JOIN platform_identity_accounts pa ON pa\.github_id = a\.github_id WHERE (.+?) \), per_project AS/, 'leaderboard totals');
@@ -809,6 +849,25 @@ test('the builder of the week — only shown, public, active winners, on the cro
809
849
  assert.doesNotMatch(seen[0].text, /github_id/, 'github_id never crosses');
810
850
  });
811
851
 
852
+ test('the crowns — the month, the year so far and last year only for shown, public, active winners, the same for every viewer', async () => {
853
+ const { clearCrownsMemo } = require('../modules/platform-identity/community-crowns.js');
854
+ try {
855
+ for (const k of KEYS) {
856
+ CROWNED = k;
857
+ clearCrownsMemo(); // the year's leader is memoised: a new winner is a new computation
858
+ const seen = [];
859
+ for (const who of [ANON, STRANGER, RECRUITER]) seen.push(await get(who, '/api/bongos/community/crowns'));
860
+ for (const r of seen.slice(1)) assert.equal(r.text, seen[0].text, `${r.who} got different crowns with ${k} crowned`);
861
+ const { month, year } = seen[0].json;
862
+ const shown = { month: month.winner, year: year.winner, last: year.last && year.last.winner };
863
+ for (const [which, w] of Object.entries(shown)) {
864
+ assert.equal(!!w && w.handle === ACCT[k].handle, MATRIX[k].crowns === 'listed', `${k} as ${which}: expected ${MATRIX[k].crowns}`);
865
+ }
866
+ assert.doesNotMatch(seen[0].text, /github_id/, 'github_id never crosses');
867
+ }
868
+ } finally { CROWNED = 'PUB'; }
869
+ });
870
+
812
871
  test('the recruiting directory — the recruiter sees the full and name-only tiers; Anon and Builder are refused', async () => {
813
872
  const r = await get(RECRUITER, '/api/bongos/scouting');
814
873
  assert.equal(r.status, 200);
@@ -851,7 +910,7 @@ function disclosed(r) {
851
910
  }
852
911
 
853
912
  test('the walk covered every surface for every row (so the claims below are about something)', () => {
854
- for (const p of ['/profiles/', '/me/cross-project', '/community/search', '/community/leaderboard', '/community/builder-of-the-week', '/scouting', '/guilds/mx-guild', '/guilds/mx-guild/members', '/projects/invite-guild']) {
913
+ for (const p of ['/profiles/', '/me/cross-project', '/community/search', '/community/leaderboard', '/community/builder-of-the-week', '/community/crowns', '/scouting', '/guilds/mx-guild', '/guilds/mx-guild/members', '/projects/invite-guild']) {
855
914
  assert.ok(SEEN.some((r) => r.path.includes(p) && r.status === 200), `no 200 from ${p} — the sweep below would be vacuous for it`);
856
915
  }
857
916
  assert.ok(SEEN.length >= KEYS.length * 6, `only ${SEEN.length} responses recorded`);