@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.
- package/.bongos-core.json +109 -54
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +2 -0
- package/clients/bongos-client/index.cjs +2 -0
- package/clients/bongos-client/index.d.ts +2 -0
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/api/openapi.json +22 -1
- package/docs/api-reference.md +3 -2
- package/docs/copy-inventory.md +985 -934
- package/docs/copy-registry.json +1829 -1309
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +10 -2
- package/docs/page-readings.json +601 -576
- package/modules/copy-desk/auto-close.js +9 -3
- package/modules/copy-desk/page-status.js +25 -13
- package/modules/copy-desk/tests/copy_page_auto_close.mjs +8 -0
- package/modules/copy-desk/tests/copy_page_status.mjs +2 -2
- package/modules/platform-identity/builder-of-the-week.js +48 -20
- package/modules/platform-identity/community-crowns.js +176 -0
- package/modules/platform-identity/community-leaderboard.js +1 -1
- package/modules/platform-identity/migrations/platform_identity_033_period_features.sql +43 -0
- package/modules/platform-identity/routes/community-leaderboard.js +25 -3
- package/modules/public-landing/public/account.html +3 -2
- package/modules/public-landing/public/community-crowns-first.states.json +15 -0
- package/modules/public-landing/public/community-crowns-new-year.states.json +15 -0
- package/modules/public-landing/public/community-crowns-none.states.json +15 -0
- package/modules/public-landing/public/community-empty.states.json +1 -1
- package/modules/public-landing/public/community-owner-two.states.json +2 -2
- package/modules/public-landing/public/community-owner.states.json +1 -1
- package/modules/public-landing/public/community-week-none.states.json +1 -1
- package/modules/public-landing/public/community.html +130 -22
- package/modules/public-landing/public/community.probes.json +20 -3
- package/modules/public-landing/public/community.states.json +8 -6
- package/modules/public-landing/public/connections.html +3 -2
- package/modules/public-landing/public/contact.html +3 -2
- package/modules/public-landing/public/guild.html +3 -2
- package/modules/public-landing/public/index.html +3 -2
- package/modules/public-landing/public/invitations.html +3 -2
- package/modules/public-landing/public/privacy.html +3 -2
- package/modules/public-landing/public/projects.html +3 -2
- package/modules/public-landing/public/terms.html +3 -2
- package/modules/ui-design/kit/fixtures/community-crowns-first.json +6 -0
- package/modules/ui-design/kit/fixtures/community-crowns-new-year.json +6 -0
- package/modules/ui-design/kit/fixtures/community-crowns-none.json +6 -0
- package/modules/ui-design/kit/fixtures/community-crowns.json +6 -0
- package/modules/ui-design/kit/serve.js +14 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/bongos/run-unit-tests.js +4 -0
- package/src/module-api.js +1 -1
- package/src/platform-server.js +4 -3
- package/tests/community_crowns.mjs +369 -0
- package/tests/community_crowns_db.mjs +164 -0
- package/tests/copy_stuck_page_tweaks.mjs +39 -2
- package/tests/hub_account_page.mjs +2 -2
- package/tests/hub_community_page.mjs +144 -29
- package/tests/landing_legal_pages.mjs +2 -2
- package/tests/privacy_matrix.mjs +68 -9
package/tests/privacy_matrix.mjs
CHANGED
|
@@ -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
|
-
//
|
|
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`);
|