@bongos/core 1.23.14 → 1.23.15

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 (43) hide show
  1. package/.bongos-core.json +77 -42
  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 +924 -878
  10. package/docs/copy-registry.json +1796 -1325
  11. package/docs/module-api-changelog.md +2 -0
  12. package/docs/page-inventory.json +8 -3
  13. package/docs/page-readings.json +575 -555
  14. package/modules/platform-identity/builder-of-the-week.js +180 -0
  15. package/modules/platform-identity/migrations/platform_identity_032_weekly_features.sql +53 -0
  16. package/modules/platform-identity/routes/community-leaderboard.js +25 -1
  17. package/modules/public-landing/public/account.html +2 -1
  18. package/modules/public-landing/public/community-week-none.states.json +16 -0
  19. package/modules/public-landing/public/community.html +142 -13
  20. package/modules/public-landing/public/community.states.json +6 -4
  21. package/modules/public-landing/public/connections.html +2 -1
  22. package/modules/public-landing/public/contact.html +2 -1
  23. package/modules/public-landing/public/guild.html +2 -1
  24. package/modules/public-landing/public/index.html +2 -1
  25. package/modules/public-landing/public/invitations.html +2 -1
  26. package/modules/public-landing/public/privacy.html +2 -1
  27. package/modules/public-landing/public/projects.html +2 -1
  28. package/modules/public-landing/public/terms.html +2 -1
  29. package/modules/ui-design/kit/fixtures/community-week-none.json +6 -0
  30. package/modules/ui-design/kit/fixtures/community-week.json +11 -0
  31. package/modules/ui-design/kit/serve.js +8 -0
  32. package/package-lock.json +2 -2
  33. package/package.json +1 -1
  34. package/release-notes.json +6 -0
  35. package/scripts/bongos/run-unit-tests.js +4 -0
  36. package/src/module-api.js +1 -1
  37. package/src/platform-server.js +5 -5
  38. package/tests/builder_of_the_week.mjs +266 -0
  39. package/tests/builder_of_the_week_db.mjs +166 -0
  40. package/tests/hub_account_page.mjs +2 -2
  41. package/tests/hub_community_page.mjs +136 -25
  42. package/tests/landing_legal_pages.mjs +2 -2
  43. package/tests/privacy_matrix.mjs +53 -8
@@ -150,6 +150,10 @@ function identitySentinels(acct) {
150
150
  // 'listed' | 'name-only' (listed, disciplines null) | 'absent'
151
151
  // board GET /community/leaderboard Anon · Builder — the cross-project leaderboard
152
152
  // 'listed' | 'absent'
153
+ // week GET /community/builder-of-the-week Anon · Builder · Recruiter — the crown and
154
+ // the past weeks (task 1002981, spec D5): a row IS stats, so the board's rule.
155
+ // Every fixture account has won a recorded week, so the read's own predicate
156
+ // decides: 'listed' | 'absent'
153
157
  // directory GET /scouting Recruiter — 'full' | 'name-only' | 'absent'
154
158
  // sliver GET /scouting/:handle Recruiter — 'full' (D3) | 'name-only' (D3, hidden) | 404
155
159
  // self GET /me/cross-project Self — 'own' (own numbers, any flags) | 'n/a' (no session exists)
@@ -166,13 +170,13 @@ function identitySentinels(acct) {
166
170
  // people the guild page already shows them — shown_publicly AND existence;
167
171
  // hide_stats never hides membership, and private or provisional is never listed
168
172
  const MATRIX = {
169
- PROV: { profile: 404, search: 'absent', board: 'absent', directory: 'absent', sliver: 404, self: 'n/a', guild: 'absent', members: 'absent', invite: 'absent' },
170
- PUB: { profile: 'full', search: 'listed', board: 'listed', directory: 'full', sliver: 'full', self: 'own', guild: 'listed', members: 'listed', invite: 'listed' },
171
- SHY: { profile: 'identity', search: 'name-only', board: 'absent', directory: 'name-only', sliver: 'name-only', self: 'own', guild: 'listed', members: 'listed', invite: 'listed' },
172
- PRIV: { profile: 404, search: 'absent', board: 'absent', directory: 'absent', sliver: 404, self: 'own', guild: 'absent', members: 'listed', invite: 'absent' },
173
- PRIVD: { profile: 404, search: 'absent', board: 'absent', directory: 'absent', sliver: 'full', self: 'own', guild: 'absent', members: 'listed', invite: 'absent' },
174
- PRIVDH: { profile: 404, search: 'absent', board: 'absent', directory: 'absent', sliver: 'name-only', self: 'own', guild: 'absent', members: 'listed', invite: 'absent' },
175
- OPTOUT: { profile: 'full', search: 'listed', board: 'listed', directory: 'absent', sliver: 404, self: 'own', guild: 'listed', members: 'listed', invite: 'listed' },
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' },
176
180
  };
177
181
 
178
182
  // ===========================================================================
@@ -280,6 +284,19 @@ function guildRosterFilter(sql, params) {
280
284
  // origin the hall links point at.
281
285
  const MX_CLIENT = { client_id: 'mx-proj', secret_hash: '', name: 'MX Project', redirect_uris: [], origin: 'https://mx-proj.example', owner_github_id: null, status: 'active' };
282
286
 
287
+ // THE WEEKS each fixture account won (task 1002981): PUB the week now crowned, every
288
+ // other account a week before it, so the crown and the past list both carry a cell.
289
+ // Lazy: the module may only load after the pool is pinned below, through the routes.
290
+ let weeksWon = null;
291
+ function weekOf(key) {
292
+ if (!weeksWon) {
293
+ const { crownedWeek } = require('../modules/platform-identity/builder-of-the-week.js');
294
+ weeksWon = Object.fromEntries(['PUB', ...KEYS.filter((k) => k !== 'PUB')].map((k, i) =>
295
+ [k, new Date(crownedWeek().start.getTime() - i * 7 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10)]));
296
+ }
297
+ return weeksWon[key];
298
+ }
299
+
283
300
  const pool = {
284
301
  query: async (text, params = []) => {
285
302
  const sql = String(text).replace(/\s+/g, ' ').trim();
@@ -338,6 +355,21 @@ const pool = {
338
355
  // ... and the weekly totals: never computed on a read (guild-totals.js); none stored here.
339
356
  if (/FROM platform_identity_guild_totals WHERE guild_id = \$1$/.test(sql)) return { rows: [], rowCount: 0 };
340
357
 
358
+ // BUILDER OF THE WEEK (task 1002981): every week is already recorded (no crowning
359
+ // here), one per fixture account, PUB the current one — so the read's own predicate
360
+ // decides who is shown. The projection is pinned: a new column is a new disclosure.
361
+ if (/^SELECT 1 FROM platform_identity_weekly_features WHERE week_start = \$1::date$/.test(sql)) return { rows: [{ '?column?': 1 }], rowCount: 1 };
362
+ if (/FROM platform_identity_weekly_features f JOIN platform_identity_accounts pa/.test(sql)) {
363
+ const cols = (/^SELECT (.+?) FROM /.exec(sql) || [, ''])[1];
364
+ if (cols !== "to_char(f.week_start, 'YYYY-MM-DD') AS week_start, f.credits_delta, f.ships_delta, pa.handle, pa.github_login, pa.display_name, pa.avatar_url") throw new Error(`fake pool: the builder-of-the-week read selects something new — ${cols}`);
365
+ const where = lift(sql, /WHERE f\.week_start <= \$1::date AND (.+?) ORDER BY/, 'builder of the week');
366
+ const rows = ACCOUNTS.filter(accountFilter(where, [])).map((a) => ({
367
+ week_start: weekOf(a.key), credits_delta: String(ROLLUP[a.github_id].credits), ships_delta: String(ROLLUP[a.github_id].works_shipped),
368
+ handle: a.handle, github_login: a.github_login, display_name: a.display_name, avatar_url: a.avatar_url,
369
+ })).sort((x, y) => (x.week_start < y.week_start ? 1 : -1)).slice(0, params[1]);
370
+ return { rows, rowCount: rows.length };
371
+ }
372
+
341
373
  // THE LEADERBOARD's totals: the rollup floor sits on the act CTE's join.
342
374
  if (/platform_identity_activity_snapshots/.test(sql)) {
343
375
  const where = lift(sql, /JOIN platform_identity_accounts pa ON pa\.github_id = a\.github_id WHERE (.+?) \), per_project AS/, 'leaderboard totals');
@@ -764,6 +796,19 @@ test('the cross-project leaderboard — only shown, public, active accounts, the
764
796
  }
765
797
  });
766
798
 
799
+ test('the builder of the week — only shown, public, active winners, on the crown and the past weeks, the same for every viewer', async () => {
800
+ const seen = [];
801
+ for (const who of [ANON, STRANGER, RECRUITER]) seen.push(await get(who, '/api/bongos/community/builder-of-the-week'));
802
+ for (const r of seen.slice(1)) assert.equal(r.text, seen[0].text, `${r.who} got a different builder of the week`);
803
+ const { winner, past } = seen[0].json;
804
+ assert.equal(winner && winner.handle, ACCT.PUB.handle, 'control: a shown winner is crowned, or absence below is vacuous');
805
+ const listed = new Set([winner, ...past].filter(Boolean).map((x) => x.handle));
806
+ for (const k of KEYS) {
807
+ assert.equal(listed.has(ACCT[k].handle), MATRIX[k].week === 'listed', `${k}: expected ${MATRIX[k].week}`);
808
+ }
809
+ assert.doesNotMatch(seen[0].text, /github_id/, 'github_id never crosses');
810
+ });
811
+
767
812
  test('the recruiting directory — the recruiter sees the full and name-only tiers; Anon and Builder are refused', async () => {
768
813
  const r = await get(RECRUITER, '/api/bongos/scouting');
769
814
  assert.equal(r.status, 200);
@@ -806,7 +851,7 @@ function disclosed(r) {
806
851
  }
807
852
 
808
853
  test('the walk covered every surface for every row (so the claims below are about something)', () => {
809
- for (const p of ['/profiles/', '/me/cross-project', '/community/search', '/community/leaderboard', '/scouting', '/guilds/mx-guild', '/guilds/mx-guild/members', '/projects/invite-guild']) {
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']) {
810
855
  assert.ok(SEEN.some((r) => r.path.includes(p) && r.status === 200), `no 200 from ${p} — the sweep below would be vacuous for it`);
811
856
  }
812
857
  assert.ok(SEEN.length >= KEYS.length * 6, `only ${SEEN.length} responses recorded`);