@bongos/core 1.24.64 → 1.24.66

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.
@@ -294,7 +294,9 @@ async function gradeAndRecord({ taskId, task, summary, baseline, committedFiles,
294
294
  // spawned here) so formatGradeOutcome stays unit-testable without a subprocess.
295
295
  function formatOutageDiagnostic(g, cliFinding) {
296
296
  const out = [];
297
- const dead = (g.signals?.workers || []).filter((w) => w && w.error);
297
+ // task 1005101: a single-shot outage (trivial diff / no-artifact) has no
298
+ // panel workers; its dead shots ride signals.outage_shots in the same shape.
299
+ const dead = (g.signals?.workers || g.signals?.outage_shots || []).filter((w) => w && w.error);
298
300
  const detailed = dead.filter((w) => w.exit_code != null || w.stderr_tail);
299
301
  if (detailed.length) {
300
302
  out.push('');
@@ -248,25 +248,13 @@ function prelaunchGate() {
248
248
  if (body.length > 4096) req.destroy(); // a password is not a payload
249
249
  });
250
250
  req.on('end', () => {
251
- const params = new URLSearchParams(body);
252
- const given = params.get('password') || '';
253
- const want = password();
254
- const ok = given.length === want.length
255
- && crypto.timingSafeEqual(Buffer.from(given), Buffer.from(want));
256
- // re-validated on the way back: the field is visitor-supplied
257
- const returnTo = relativeReturn(params.get('return'));
258
- if (!ok) {
259
- return res.status(401).type('html').send(formHtml({ productName: productName(), wrong: true, returnTo }));
251
+ // A plain event-emitter callback: a throw here is uncaught and kills the
252
+ // process, so any failure answers 400 instead (task 1004380).
253
+ try {
254
+ answerSubmit(req, res, body);
255
+ } catch {
256
+ if (!res.headersSent) res.status(400).type('text').send('Bad request');
260
257
  }
261
- res.cookie(COOKIE, expectedToken(), {
262
- httpOnly: true,
263
- secure: req.secure || req.headers['x-forwarded-proto'] === 'https',
264
- sameSite: 'lax',
265
- path: '/',
266
- ...domainOpts(req),
267
- // no maxAge/expires — a SESSION cookie, by the owner's choice
268
- });
269
- res.redirect(303, returnTo || '/');
270
258
  });
271
259
  return undefined;
272
260
  }
@@ -283,6 +271,34 @@ function prelaunchGate() {
283
271
  };
284
272
  }
285
273
 
274
+ // Compare fixed-length digests: a character-length check is not a byte-length
275
+ // check (a non-ASCII guess passed it, then timingSafeEqual threw on the byte
276
+ // mismatch — task 1004380), and digesting both sides also hides the length.
277
+ function passwordMatches(given) {
278
+ const digest = (s) => crypto.createHash('sha256').update(String(s), 'utf8').digest();
279
+ return crypto.timingSafeEqual(digest(given), digest(password()));
280
+ }
281
+
282
+ function answerSubmit(req, res, body) {
283
+ const params = new URLSearchParams(body);
284
+ const given = params.get('password') || '';
285
+ const ok = passwordMatches(given);
286
+ // re-validated on the way back: the field is visitor-supplied
287
+ const returnTo = relativeReturn(params.get('return'));
288
+ if (!ok) {
289
+ return res.status(401).type('html').send(formHtml({ productName: productName(), wrong: true, returnTo }));
290
+ }
291
+ res.cookie(COOKIE, expectedToken(), {
292
+ httpOnly: true,
293
+ secure: req.secure || req.headers['x-forwarded-proto'] === 'https',
294
+ sameSite: 'lax',
295
+ path: '/',
296
+ ...domainOpts(req),
297
+ // no maxAge/expires — a SESSION cookie, by the owner's choice
298
+ });
299
+ return res.redirect(303, returnTo || '/');
300
+ }
301
+
286
302
  module.exports = {
287
303
  prelaunchGate, enabled, isExempt, tokenMatches, expectedToken, warnIfLegacyEnv,
288
304
  COOKIE, HMAC_LABEL, SUBMIT_PATH, LEGACY_SUBMIT_PATH, SUBMIT_PATHS,
package/src/module-api.js CHANGED
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
81
81
  // MAJOR (see allowBoxScope below): passes the request through untouched.
82
82
  function deprecatedNoopMiddleware(_req, _res, next) { next(); }
83
83
 
84
- const CORE_VERSION = '1.24.64'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
84
+ const CORE_VERSION = '1.24.66'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
85
85
 
86
86
  // A namespaced logger so a module's log lines are attributable + consistent.
87
87
  // Usage: const log = api.logger('discord'); log.info('mounted');
@@ -272,3 +272,85 @@ test('login names the other instances and how to move between them', () => {
272
272
  const fnStart = src.indexOf('function logOtherInstances');
273
273
  assert.ok(!/\/builder-/.test(src.slice(fnStart, fnStart + 700)), 'no slash command in terminal guidance');
274
274
  });
275
+
276
+ // ── 4. Every command reads the session for the instance it TARGETS (task 1005117) ───────────
277
+ //
278
+ // THE BUG. The store above kept every instance's session, but every command still read the ONE
279
+ // active pointer. So signing in to a second instance (toast.cloudbongos.com) mid-run rewrote that
280
+ // pointer, and a run steered at the first instance by <PREFIX>_API_BASE then sent the SECOND
281
+ // instance's token to the first — every claim and ship 401'd until a re-paste. The fix keys the
282
+ // read by target: when a command's target host differs from the active pointer's, the store's
283
+ // session for the target wins. With no known target, or nothing stored for it, the single active
284
+ // file is read exactly as before — that is the backward compatibility.
285
+
286
+ test('a run targeting instance A uses A\'s session even after signing in to B', () => {
287
+ const { out, status } = inSandbox(`
288
+ await lib.saveSession({ token:'TOKEN-A', api_base:'https://a.example.com', builder:{github_login:'x'} });
289
+ await lib.saveSession({ token:'TOKEN-B', api_base:'https://b.example.com', builder:{github_login:'x'} });
290
+ process.env[ic.envPrefix() + '_API_BASE'] = 'https://a.example.com';
291
+ const s = await lib.loadSession();
292
+ const sync = lib.loadSessionSync();
293
+ const tok = lib.readSessionToken();
294
+ console.log(JSON.stringify({ async: s.token, sync: sync.token, raw: tok.token, base: tok.apiBase }));`);
295
+ assert.equal(status, 0, out);
296
+ const r = JSON.parse(out.trim().split('\n').pop());
297
+ assert.equal(r.async, 'TOKEN-A', 'loadSession sent the last sign-in\'s token to another instance — the bug');
298
+ assert.equal(r.sync, 'TOKEN-A');
299
+ assert.equal(r.raw, 'TOKEN-A');
300
+ assert.equal(r.base, 'https://a.example.com');
301
+ });
302
+
303
+ test('with no target, or nothing stored for it, the single active file is read as before', () => {
304
+ const { out, status } = inSandbox(`
305
+ // A pre-store session: only the active pointer exists.
306
+ const active = ic.configPath(ic.SESSION_FILENAMES[0]);
307
+ fs.mkdirSync(path.dirname(active), { recursive: true });
308
+ fs.writeFileSync(active, JSON.stringify({ token:'OLD', api_base:'https://a.example.com' }));
309
+ const plain = (await lib.loadSession()).token;
310
+ process.env[ic.envPrefix() + '_API_BASE'] = 'https://elsewhere.example.com';
311
+ const unstored = (await lib.loadSession()).token;
312
+ console.log(JSON.stringify({ plain, unstored }));`);
313
+ assert.equal(status, 0, out);
314
+ const r = JSON.parse(out.trim().split('\n').pop());
315
+ assert.equal(r.plain, 'OLD');
316
+ assert.equal(r.unstored, 'OLD', 'an unstored target must fall back to the active file, never to no session');
317
+ });
318
+
319
+ test('signing in to B still archives the ACTIVE session, not the targeted one', () => {
320
+ const { out, status } = inSandbox(`
321
+ await lib.saveSession({ token:'TOKEN-A', api_base:'https://a.example.com' });
322
+ await lib.saveSession({ token:'TOKEN-B', api_base:'https://b.example.com' });
323
+ process.env[ic.envPrefix() + '_API_BASE'] = 'https://a.example.com';
324
+ await lib.saveSession({ token:'TOKEN-C', api_base:'https://c.example.com' });
325
+ const b = await lib.loadStoredSession('https://b.example.com');
326
+ console.log(JSON.stringify({ b: b && b.token, hosts: lib.listStoredSessions().map(h=>h.host) }));`);
327
+ assert.equal(status, 0, out);
328
+ const r = JSON.parse(out.trim().split('\n').pop());
329
+ assert.equal(r.b, 'TOKEN-B');
330
+ assert.deepEqual(r.hosts, ['a.example.com', 'b.example.com', 'c.example.com']);
331
+ });
332
+
333
+ // The bug report's own case: a BRANDED checkout (config/branding.json → its publicOrigin) whose
334
+ // active pointer was rewritten by a sign-in to another instance. Driven purely, with the brand and
335
+ // the instance-config probe injected, so it never depends on this checkout's real branding.
336
+ const BRAND_A = { domains: { publicOrigin: 'https://a.example.com' }, repo: { envPrefix: 'ZZTEST' } };
337
+
338
+ test('a branded checkout targets its own publicOrigin, a neutral one targets nothing', () => {
339
+ assert.equal(lib.sessionTargetBase({ env: {}, brand: BRAND_A, hasInstanceConfig: () => true }), 'https://a.example.com');
340
+ assert.equal(lib.sessionTargetBase({ env: {}, brand: BRAND_A, hasInstanceConfig: () => false }), null);
341
+ });
342
+
343
+ test('the injected brand\'s OWN env prefix steers the target', () => {
344
+ const prefix = ic.envPrefix(BRAND_A);
345
+ const env = { [`${prefix}_API_BASE`]: 'https://override.example.com' };
346
+ assert.equal(lib.sessionTargetBase({ env, brand: BRAND_A, hasInstanceConfig: () => true }), 'https://override.example.com');
347
+ });
348
+
349
+ test('a branded checkout whose active pointer belongs to another instance reads its OWN stored session', () => {
350
+ const opts = { env: {}, brand: BRAND_A, hasInstanceConfig: () => true };
351
+ const strayB = { token: 'TOKEN-B', api_base: 'https://b.example.com' };
352
+ assert.equal(lib.targetStorePath(strayB, opts), path.join(lib.sessionStoreDir(), 'a.example.com.json'));
353
+ // Already on target, or no target: keep the active session, read nothing.
354
+ assert.equal(lib.targetStorePath({ token: 'A', api_base: 'https://a.example.com' }, opts), null);
355
+ assert.equal(lib.targetStorePath(strayB, { ...opts, hasInstanceConfig: () => false }), null);
356
+ });
@@ -0,0 +1,334 @@
1
+ // tests/community_proof_db.mjs — PROOF: the privacy matrix and the window math on every
2
+ // community surface, against real Postgres (task 1002985, goal 1000110 CT9;
3
+ // docs/specs/bongos-v1-community-tab.md D3, D4, D5, D7 and D8).
4
+ //
5
+ // WHAT THIS ADDS. tests/privacy_matrix.mjs proves every cell DB-free, through an
6
+ // interpreter that reads each statement's WHERE text, and each surface's own _db test
7
+ // proves its arithmetic on a fixture of its own. Neither runs ONE fixture through EVERY
8
+ // surface on real SQL. This does: one account per D2 row with the cells of
9
+ // tests/fixtures/privacy-matrix.mjs, one shared snapshot history, and the real routers
10
+ // over HTTP — the leaderboard in every window, search by name and by craft, the builder
11
+ // of the week, the crowns and the movers — each read signed out and signed in, and the
12
+ // two bodies compared byte for byte.
13
+ //
14
+ // THE HISTORY. PUB's credits step up one hour after every window edge, by 1, 2, 4 … 256,
15
+ // one work a step, so each window's total is a bitmask naming exactly the snapshots it
16
+ // counted (510 = every step after the 365-day edge, 192 = the week's two). Every account
17
+ // the matrix keeps off the boards gains 1000 a step: had any predicate let one through,
18
+ // it would top that board and take that crown, so its absence is never vacuous.
19
+ //
20
+ // THE CLOCK is frozen at NOW, a Wednesday in August 2097, because the routes take no
21
+ // `now`. Every read is global, and those windows hold no real builder's snapshot, so a
22
+ // shared database's own rows move nowhere in them (the crowns and movers _db precedent).
23
+ //
24
+ // Real-DB integration test: self-skips without a reachable Postgres; in the INTEGRATION
25
+ // set (scripts/bongos/run-unit-tests.js) so the DB-free lane never runs it.
26
+ //
27
+ // Run: DATABASE_URL=... node tests/community_proof_db.mjs
28
+
29
+ import { createRequire } from 'node:module';
30
+ import { readFileSync } from 'node:fs';
31
+ import { fileURLToPath } from 'node:url';
32
+ import { dirname, join } from 'node:path';
33
+ import http from 'node:http';
34
+ import { strict as assert } from 'node:assert';
35
+ import { KEYS, STATE, MATRIX } from './fixtures/privacy-matrix.mjs';
36
+
37
+ const require = createRequire(import.meta.url);
38
+ const { Pool } = require('pg');
39
+ const express = require('express');
40
+
41
+ const here = dirname(fileURLToPath(import.meta.url));
42
+ const MIG_DIR = join(here, '..', 'modules', 'platform-identity', 'migrations');
43
+ const migrationSql = (f) => readFileSync(join(MIG_DIR, f), 'utf8');
44
+
45
+ const pool = new Pool({ connectionString: process.env.DATABASE_URL || undefined });
46
+ try {
47
+ await pool.query('SELECT 1');
48
+ } catch (e) {
49
+ console.log(`community_proof_db.mjs SKIPPED — no reachable Postgres (${e.code || e.message}).`);
50
+ await pool.end().catch(() => {});
51
+ process.exit(0);
52
+ }
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // The fixture
56
+ // ---------------------------------------------------------------------------
57
+
58
+ const PREFIX = 't1002985-';
59
+ const TAG = 't1002985-craft'; // every fixture row's only craft; the board reads filter on it
60
+ const ID = Object.fromEntries(KEYS.map((k, i) => [k, 90298501 + i]));
61
+ // '-acct' keeps one handle from being a substring of another (priv / privd / privdh)
62
+ const HANDLE = Object.fromEntries(KEYS.map((k) => [k, `${PREFIX}${k.toLowerCase()}-acct`]));
63
+ const NAME = Object.fromEntries(KEYS.map((k) => [k, `T1002985 ${k} Acct`]));
64
+ const IDS = Object.values(ID);
65
+ const CLIENT = 't1002985-p1';
66
+ const ORIGIN = 'https://t1002985-p1.example';
67
+ const PROJECT = 'T1002985 Alpha';
68
+
69
+ const NOW = '2097-08-14T12:00:00.000Z';
70
+ const HISTORY = '2095-06-01T00:00:00.000Z'; // the first snapshot written: when history began
71
+ // Every edge a surface reads, in time order. The routes' own answers are checked against
72
+ // these below, so a fixture that stopped straddling them would fail, not pass vacuously.
73
+ const EDGE = {
74
+ lastYear: '2096-01-01T00:00:00.000Z',
75
+ d365: '2096-08-14T12:00:00.000Z',
76
+ year: '2097-01-01T00:00:00.000Z',
77
+ month: '2097-07-01T00:00:00.000Z',
78
+ d30: '2097-07-15T12:00:00.000Z',
79
+ monthEnd: '2097-08-01T00:00:00.000Z',
80
+ week: '2097-08-05T00:00:00.000Z',
81
+ d7: '2097-08-07T12:00:00.000Z',
82
+ weekEnd: '2097-08-12T00:00:00.000Z',
83
+ };
84
+ const day = (iso) => iso.slice(0, 10);
85
+
86
+ // PUB's numbers per window, read off its steps: the sum of the steps after the edge.
87
+ const PUB_IN = {
88
+ all: [511, 9], // every step
89
+ '365d': [510, 8], // 2 … 256: the 365-day edge falls after the first step
90
+ '30d': [496, 5], // 16 … 256
91
+ '7d': [384, 2], // 128 + 256
92
+ week: [192, 2], // 64 + 128: the step after the week's end is the next week's
93
+ month: [24, 2], // 8 + 16: the step after the month's end is August's
94
+ year: [508, 7], // 4 … 256, the year so far
95
+ lastYear: [3, 2], // 1 + 2
96
+ };
97
+ // OPTOUT: 5 credits and one work at the 7-day edge plus two hours, so inside every rolling
98
+ // window, the week and the year so far, and in neither July nor last year.
99
+ const OPTOUT_IN = [5, 1];
100
+
101
+ // The shared rule this history is built for: the boards, the crowns and the movers show
102
+ // PUB and OPTOUT and nobody else (a crown or a card IS stats — the board's rule).
103
+ const SHOWN = KEYS.filter((k) => MATRIX[k].board === 'listed');
104
+ assert.deepEqual(SHOWN, ['PUB', 'OPTOUT'], 'the matrix changed — re-plan this history before trusting it');
105
+ for (const k of KEYS) {
106
+ for (const col of ['week', 'crowns', 'movers']) assert.equal(MATRIX[k][col], MATRIX[k].board, `${k}.${col} no longer follows the board`);
107
+ }
108
+ const KEPT_OFF = KEYS.filter((k) => !SHOWN.includes(k));
109
+
110
+ // ---------------------------------------------------------------------------
111
+ // The frozen clock, the doorway and the real routers
112
+ // ---------------------------------------------------------------------------
113
+
114
+ // Every Date the modules make from here on is a FrozenDate. A Date made before the swap
115
+ // would fail pg's `instanceof Date`, so fixture times go to SQL as ISO strings.
116
+ const RealDate = Date;
117
+ const clock = { now: RealDate.parse(NOW) };
118
+ class FrozenDate extends RealDate {
119
+ constructor(...args) {
120
+ if (args.length) super(...args);
121
+ else super(clock.now);
122
+ }
123
+
124
+ static now() {
125
+ return clock.now;
126
+ }
127
+ }
128
+ globalThis.Date = FrozenDate;
129
+ const after = (iso, hours) => new RealDate(RealDate.parse(iso) + hours * 3600 * 1000).toISOString();
130
+
131
+ const moduleApiPath = require.resolve('../src/module-api.js');
132
+ require.cache[moduleApiPath] = {
133
+ id: moduleApiPath,
134
+ filename: moduleApiPath,
135
+ loaded: true,
136
+ exports: {
137
+ pool,
138
+ corsPublicGet: () => (_req, _res, next) => next(),
139
+ logger: () => ({ error: (o, m) => console.error(`route error: ${m}`, o), info: () => {}, warn: () => {} }),
140
+ },
141
+ };
142
+ const { attachFail } = require('../src/bongos/middleware/error-envelope.js');
143
+ const app = express();
144
+ app.use(express.json());
145
+ app.use(attachFail);
146
+ for (const f of ['community-search', 'community-leaderboard']) {
147
+ app.use('/api/bongos', require(`../modules/platform-identity/routes/${f}.js`)());
148
+ }
149
+ const server = app.listen(0, '127.0.0.1');
150
+ await new Promise((r) => server.once('listening', r));
151
+ const port = server.address().port;
152
+
153
+ const SIGNED_IN = { Authorization: 'Bearer t1002985-viewer', Cookie: 'bongos_session=t1002985-viewer' };
154
+ const SEEN = []; // every body served, for the sweep at the end
155
+ function get(path, headers) {
156
+ return new Promise((resolve, reject) => {
157
+ const req = http.request({ host: '127.0.0.1', port, method: 'GET', path, headers, agent: false }, (res) => {
158
+ const chunks = [];
159
+ res.on('data', (c) => chunks.push(c));
160
+ res.on('end', () => resolve({ status: res.statusCode, text: Buffer.concat(chunks).toString('utf8') }));
161
+ });
162
+ req.on('error', reject);
163
+ req.end();
164
+ });
165
+ }
166
+ // A surface read signed out and signed in: the same bytes to both (spec D3, D8).
167
+ async function read(path) {
168
+ const anon = await get(path, {});
169
+ const signed = await get(path, SIGNED_IN);
170
+ assert.equal(anon.status, 200, `${path} → ${anon.status}: ${anon.text.slice(0, 300)}`);
171
+ assert.equal(signed.text, anon.text, `${path}: a signed-in viewer got a different body`);
172
+ SEEN.push({ path, text: anon.text });
173
+ return JSON.parse(anon.text);
174
+ }
175
+
176
+ const q = (sql, params) => pool.query(sql, params);
177
+ let pass = 0;
178
+ const same = (actual, expected, msg) => { assert.deepEqual(actual, expected, msg); pass++; console.log(` ok - ${msg}`); };
179
+ const who = (w) => w && [w.handle, w.credits, w.tasks_shipped];
180
+ const mine = (rows) => rows.filter((r) => r.handle.startsWith(PREFIX));
181
+
182
+ async function cleanup() {
183
+ await q('DELETE FROM platform_identity_activity_snapshots WHERE github_id = ANY($1::bigint[])', [IDS]).catch(() => {});
184
+ await q('DELETE FROM platform_identity_builder_project_activity WHERE github_id = ANY($1::bigint[])', [IDS]).catch(() => {});
185
+ await q('DELETE FROM platform_identity_accounts WHERE github_id = ANY($1::bigint[])', [IDS]).catch(() => {});
186
+ await q('DELETE FROM platform_identity_projects WHERE origin = $1', [ORIGIN]).catch(() => {});
187
+ await q('DELETE FROM platform_identity_sso_clients WHERE client_id = $1', [CLIENT]).catch(() => {});
188
+ await q('DELETE FROM platform_identity_weekly_features WHERE week_start = $1::date', [day(EDGE.week)]).catch(() => {});
189
+ await q(
190
+ `DELETE FROM platform_identity_period_features
191
+ WHERE (period = 'month' AND period_start = $1::date) OR (period = 'year' AND period_start = $2::date)`,
192
+ [day(EDGE.month), day(EDGE.lastYear)],
193
+ ).catch(() => {});
194
+ }
195
+
196
+ // One (project, builder) row, seen since history began, and its cumulative snapshots.
197
+ async function history(k, steps) {
198
+ const [, credits, ships] = steps[steps.length - 1];
199
+ await q(
200
+ `INSERT INTO platform_identity_builder_project_activity (client_id, github_id, credits, tasks_shipped, karma, disciplines, first_seen_at)
201
+ VALUES ($1, $2, $3, $4, 0, $5, $6)`,
202
+ [CLIENT, ID[k], credits, ships, [TAG], HISTORY],
203
+ );
204
+ for (const [at, c, s] of steps) {
205
+ await q(
206
+ `INSERT INTO platform_identity_activity_snapshots (client_id, github_id, credits, tasks_shipped, karma, reported_at)
207
+ VALUES ($1, $2, $3, $4, 0, $5)`,
208
+ [CLIENT, ID[k], c, s, at],
209
+ );
210
+ }
211
+ }
212
+ // From zero at HISTORY, one step `hours` after each edge, `size(i)` credits and one work each.
213
+ function stepsAfterEachEdge(hours, size) {
214
+ let credits = 0;
215
+ return [[HISTORY, 0, 0], ...Object.values(EDGE).map((edge, i) => {
216
+ credits += size(i);
217
+ return [after(edge, hours), credits, i + 1];
218
+ })];
219
+ }
220
+
221
+ try {
222
+ await q('CREATE EXTENSION IF NOT EXISTS pg_trgm');
223
+ for (const f of [
224
+ 'platform_identity_001_tables.sql', 'platform_identity_004_projects.sql', 'platform_identity_005_builder_activity.sql',
225
+ 'platform_identity_007_profile_consent.sql', 'platform_identity_008_handle.sql',
226
+ 'platform_identity_011_project_visibility.sql', 'platform_identity_012_account_privacy.sql',
227
+ 'platform_identity_019_terms_acceptance.sql', 'platform_identity_020_visibility_pull.sql',
228
+ 'platform_identity_022_activity_snapshots.sql', 'platform_identity_023_builder_search_trgm.sql',
229
+ 'platform_identity_032_weekly_features.sql', 'platform_identity_033_period_features.sql',
230
+ ]) await q(migrationSql(f));
231
+ await cleanup();
232
+
233
+ for (const k of KEYS) {
234
+ const s = STATE[k];
235
+ await q(
236
+ `INSERT INTO platform_identity_accounts
237
+ (github_id, github_login, display_name, handle, profile_state, account_visibility, hide_stats, recruiter_discoverable, terms_accepted_at)
238
+ VALUES ($1, $2, $3, $2, $4, $5, $6, $7, now())`,
239
+ [ID[k], HANDLE[k], NAME[k], s.profile_state, s.account_visibility, s.hide_stats, s.recruiter_discoverable],
240
+ );
241
+ }
242
+ await q(`INSERT INTO platform_identity_sso_clients (client_id, secret_hash, name, origin) VALUES ($1, 'x', $2, $3)`, [CLIENT, PROJECT, ORIGIN]);
243
+ await q(
244
+ `INSERT INTO platform_identity_projects (origin, name, description, featured, status, visibility, visibility_source)
245
+ VALUES ($1, $2, 'described', false, 'active', 'public', 'control-plane')`,
246
+ [ORIGIN, PROJECT],
247
+ );
248
+ await history('PUB', stepsAfterEachEdge(1, (i) => 2 ** i)); // first, so its HISTORY row is the first snapshot
249
+ await history('OPTOUT', [[HISTORY, 0, 0], [after(EDGE.d7, 2), ...OPTOUT_IN]]);
250
+ for (const k of KEPT_OFF) await history(k, stepsAfterEachEdge(2, () => 1000));
251
+
252
+ // 1 — The leaderboard in every window (D3): exactly the shown accounts, the numbers exact.
253
+ for (const [w, edge] of [['all', null], ['365d', EDGE.d365], ['30d', EDGE.d30], ['7d', EDGE.d7]]) {
254
+ const board = await read(`/api/bongos/community/leaderboard?window=${w}&discipline=${TAG}&limit=100`);
255
+ same(board.since, edge, `${w}: the window opens on the edge the history straddles`);
256
+ same(board.results.map((r) => [r.place, r.handle, r.credits, r.tasks_shipped]),
257
+ [[1, HANDLE.PUB, ...PUB_IN[w]], [2, HANDLE.OPTOUT, ...OPTOUT_IN]],
258
+ `${w} board: PUB ${PUB_IN[w][0]} and OPTOUT ${OPTOUT_IN[0]}; the five accounts kept off, each ahead of both, are absent`);
259
+ }
260
+
261
+ // 2 — Search (D4): findable by name per the matrix; a craft is stats, so the craft filter
262
+ // drops the hidden account the name search lists.
263
+ const byName = new Map(mine((await read(`/api/bongos/community/search?q=${PREFIX}&limit=50`)).results).map((r) => [r.handle, r]));
264
+ for (const k of KEYS) {
265
+ const r = byName.get(HANDLE[k]);
266
+ const got = !r ? 'absent' : r.disciplines === null ? 'name-only' : 'listed';
267
+ same(got, MATRIX[k].search, `search by name, ${STATE[k].label}: ${MATRIX[k].search}`);
268
+ if (got === 'listed') same(r.disciplines, [TAG], `${k}'s crafts are its own`);
269
+ }
270
+ const byCraft = mine((await read(`/api/bongos/community/search?q=${PREFIX}&discipline=${TAG}&limit=50`)).results);
271
+ same(byCraft.map((r) => r.handle).sort(), KEYS.filter((k) => MATRIX[k].search === 'listed').map((k) => HANDLE[k]).sort(),
272
+ 'search by craft: the hidden account found by name is absent');
273
+
274
+ // 3 — The builder of the week (D5): the last full Monday-to-Monday week, crowned at render.
275
+ const bow = await read('/api/bongos/community/builder-of-the-week');
276
+ same([bow.week.start, bow.week.end], [day(EDGE.week), day(EDGE.weekEnd)], 'the crowned week is the one the history straddles');
277
+ same(who(bow.winner), [HANDLE.PUB, ...PUB_IN.week], 'builder of the week: PUB’s 192 and two works');
278
+ same(mine(bow.past), [], 'no fixture account in the past weeks');
279
+
280
+ // 4 — The crowns: the month and last year crowned at render, the year so far live.
281
+ const crowns = await read('/api/bongos/community/crowns');
282
+ same(who(crowns.week.winner), [HANDLE.PUB, ...PUB_IN.week], 'the home’s week is the same crown');
283
+ same([crowns.month.start, crowns.month.end, crowns.month.first_crown], [day(EDGE.month), day(EDGE.monthEnd), null], 'the crowned month is July, the history straddling both ends');
284
+ same(who(crowns.month.winner), [HANDLE.PUB, ...PUB_IN.month], 'builder of the month: PUB’s 24 and two works');
285
+ same([crowns.year.start, crowns.year.since], [day(EDGE.year), day(EDGE.year)], 'the year so far, history having begun before it');
286
+ same(who(crowns.year.winner), [HANDLE.PUB, ...PUB_IN.year], 'builder of the year so far: PUB’s 508 and seven works');
287
+ same(crowns.year.last && [crowns.year.last.start, ...who(crowns.year.last.winner)], [day(EDGE.lastYear), HANDLE.PUB, ...PUB_IN.lastYear],
288
+ 'builder of last year: PUB’s 3 and two works');
289
+
290
+ // 5 — The movers (D7): the last 30 days, rolling.
291
+ const movers = await read('/api/bongos/community/movers');
292
+ same([movers.window.start, movers.window.end], [EDGE.d30, NOW], 'the 30 days end now and open on the 30-day edge');
293
+ same(who(movers.rising), [HANDLE.PUB, ...PUB_IN['30d']], 'rising builder: PUB, 15 before the 30 days (under 100) and 496 in them');
294
+ same(movers.projects.map((p) => [p.name, p.origin, p.tasks_shipped, p.credits]), [[PROJECT, ORIGIN, 6, 501]],
295
+ 'projects on the move: PUB’s five works and OPTOUT’s one; none of the kept-off accounts’ works count');
296
+
297
+ // 6 — A winner who hides (D8): gone from every surface on the next read, and no pinned
298
+ // period is handed to anyone else; only the live reads move on to OPTOUT.
299
+ await q('UPDATE platform_identity_accounts SET hide_stats = true WHERE github_id = $1', [ID.PUB]);
300
+ for (const w of ['all', '365d', '30d', '7d']) {
301
+ const board = await read(`/api/bongos/community/leaderboard?window=${w}&discipline=${TAG}&limit=100`);
302
+ same(board.results.map((r) => [r.place, r.handle, r.credits, r.tasks_shipped]), [[1, HANDLE.OPTOUT, ...OPTOUT_IN]], `${w} board after PUB hides: OPTOUT alone`);
303
+ }
304
+ const hidName = mine((await read(`/api/bongos/community/search?q=${PREFIX}&limit=50`)).results).find((r) => r.handle === HANDLE.PUB);
305
+ same(hidName && hidName.disciplines, null, 'search by name: PUB is still found, now name-only');
306
+ same(mine((await read(`/api/bongos/community/search?q=${PREFIX}&discipline=${TAG}&limit=50`)).results).map((r) => r.handle), [HANDLE.OPTOUT],
307
+ 'search by craft: PUB is gone');
308
+ same((await read('/api/bongos/community/builder-of-the-week')).winner, null, 'the week PUB won shows nobody, though OPTOUT earned in it');
309
+ const hidCrowns = await read('/api/bongos/community/crowns');
310
+ same([hidCrowns.week.winner, hidCrowns.month.winner, hidCrowns.year.last], [null, null, null], 'the week, July and last year stay PUB’s and show nobody');
311
+ same(hidCrowns.year.winner, null, 'the year so far shows nobody while the leader’s memo is warm');
312
+ const hidMovers = await read('/api/bongos/community/movers');
313
+ same(who(hidMovers.rising), [HANDLE.OPTOUT, ...OPTOUT_IN], 'rising builder: OPTOUT, without waiting for the memo');
314
+ same(hidMovers.projects.map((p) => [p.name, p.tasks_shipped, p.credits]), [[PROJECT, 1, 5]], 'PUB’s works leave the project’s totals');
315
+ clock.now += 31 * 1000; // past every 30s memo; no snapshot sits within 31s of an edge
316
+ same(who((await read('/api/bongos/community/crowns')).year.winner), [HANDLE.OPTOUT, ...OPTOUT_IN], 'once the memo lapses, the year so far is OPTOUT’s');
317
+
318
+ // 7 — The sweep: an account kept off the boards is named by no body served above, but
319
+ // the name search the matrix allows it.
320
+ for (const k of KEPT_OFF) {
321
+ for (const r of SEEN) {
322
+ if (r.path.startsWith('/api/bongos/community/search?') && !r.path.includes('discipline=') && MATRIX[k].search !== 'absent') continue;
323
+ assert.ok(!r.text.includes(HANDLE[k]) && !r.text.includes(NAME[k]), `${k} was named by GET ${r.path}`);
324
+ }
325
+ }
326
+ same(SEEN.length, 19, 'the sweep read all 19 bodies served, each identical signed out and signed in, and no kept-off account is named where the matrix says absent');
327
+
328
+ console.log(`\ncommunity_proof_db.mjs: ${pass} passed`);
329
+ } finally {
330
+ await cleanup();
331
+ globalThis.Date = RealDate;
332
+ await new Promise((r) => server.close(r));
333
+ await pool.end().catch(() => {});
334
+ }