@bongos/core 1.19.157 → 1.19.158
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 +18 -13
- package/modules/platform-identity/platform-identity.js +31 -12
- package/modules/platform-identity/tests/platform-identity.mjs +18 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/run-unit-tests.js +1 -1
- package/src/module-api.js +3 -1
- package/tests/scouting_rollup_fanout.mjs +113 -0
package/.bongos-core.json
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.158",
|
|
6
|
+
"core_contract": "1.19.158",
|
|
7
|
+
"source_commit": "869be86d5fae7b2efd20ec3022caad72ac07894f",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-16T16:
|
|
9
|
+
"built_at": "2026-08-16T16:29:58.341Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 304,
|
|
13
13
|
"agent_docs_stubbed": 21,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 1257,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 1582,
|
|
20
|
+
"tree_sha256": "93a57750a045530d8d70e167025dec1fd016e18779abe75af330ec33807e0a57",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -3777,7 +3777,7 @@
|
|
|
3777
3777
|
{
|
|
3778
3778
|
"path": "modules/platform-identity/platform-identity.js",
|
|
3779
3779
|
"mode": "0000644",
|
|
3780
|
-
"sha256": "
|
|
3780
|
+
"sha256": "3d20563833a9daf20d14745f2ce1c5fe530fc918eac03427185998ff9b67beed"
|
|
3781
3781
|
},
|
|
3782
3782
|
{
|
|
3783
3783
|
"path": "modules/platform-identity/pollers/code-sweep.js",
|
|
@@ -3812,7 +3812,7 @@
|
|
|
3812
3812
|
{
|
|
3813
3813
|
"path": "modules/platform-identity/tests/platform-identity.mjs",
|
|
3814
3814
|
"mode": "0000644",
|
|
3815
|
-
"sha256": "
|
|
3815
|
+
"sha256": "727fc8c7533eec1e4af477582bdd6428e9842b930b3cc898bb8a13ddead83ec3"
|
|
3816
3816
|
},
|
|
3817
3817
|
{
|
|
3818
3818
|
"path": "modules/platform-identity/ui/.gitkeep",
|
|
@@ -4187,12 +4187,12 @@
|
|
|
4187
4187
|
{
|
|
4188
4188
|
"path": "package-lock.json",
|
|
4189
4189
|
"mode": "0000644",
|
|
4190
|
-
"sha256": "
|
|
4190
|
+
"sha256": "97ede86c0b0225546a5e0a73f17c7229d919363a624d023f7a17e99292cdebd9"
|
|
4191
4191
|
},
|
|
4192
4192
|
{
|
|
4193
4193
|
"path": "package.json",
|
|
4194
4194
|
"mode": "0000644",
|
|
4195
|
-
"sha256": "
|
|
4195
|
+
"sha256": "dd91875b5af28a006fd00eb8c82a54488a3b28b662853bc3d47dfe898d5b48ac"
|
|
4196
4196
|
},
|
|
4197
4197
|
{
|
|
4198
4198
|
"path": "public-docs/index.html",
|
|
@@ -5177,7 +5177,7 @@
|
|
|
5177
5177
|
{
|
|
5178
5178
|
"path": "scripts/gds/run-unit-tests.js",
|
|
5179
5179
|
"mode": "0000644",
|
|
5180
|
-
"sha256": "
|
|
5180
|
+
"sha256": "5e7030e617e4db066136556a43cf030078b0d042ee480c9b7245eea05ab83776"
|
|
5181
5181
|
},
|
|
5182
5182
|
{
|
|
5183
5183
|
"path": "scripts/gds/sandbox-stage.js",
|
|
@@ -5787,7 +5787,7 @@
|
|
|
5787
5787
|
{
|
|
5788
5788
|
"path": "src/module-api.js",
|
|
5789
5789
|
"mode": "0000644",
|
|
5790
|
-
"sha256": "
|
|
5790
|
+
"sha256": "f07273c75bd8064ab6d83e73c6639b74433c95702a5892783dadcf7682d619bd"
|
|
5791
5791
|
},
|
|
5792
5792
|
{
|
|
5793
5793
|
"path": "src/module-loader/catalog.js",
|
|
@@ -7519,6 +7519,11 @@
|
|
|
7519
7519
|
"mode": "0000644",
|
|
7520
7520
|
"sha256": "b499ea62cdc1a615e565ce7b0379b2cab83b9aaa8098361f94d3eb93ebc7f927"
|
|
7521
7521
|
},
|
|
7522
|
+
{
|
|
7523
|
+
"path": "tests/scouting_rollup_fanout.mjs",
|
|
7524
|
+
"mode": "0000644",
|
|
7525
|
+
"sha256": "1b58003a1fac89b05acfc3287e529a9e17e2b7a6ec48259b5700f6cc837cc554"
|
|
7526
|
+
},
|
|
7522
7527
|
{
|
|
7523
7528
|
"path": "tests/scrubber_corpus.mjs",
|
|
7524
7529
|
"mode": "0000644",
|
|
@@ -423,21 +423,40 @@ async function upsertActivity({ clientId, githubId, credits = 0, tasksShipped =
|
|
|
423
423
|
// The scouting rollup: one row PER BUILDER, aggregated across every project they
|
|
424
424
|
// build on. Joined to the account for display. Ranked by total credits then ships —
|
|
425
425
|
// a talent signal, NOT an authority. Powers the Archon-only scouting view (#1002114).
|
|
426
|
+
//
|
|
427
|
+
// The scalar aggregates MUST see exactly one row per (builder, project): joining
|
|
428
|
+
// unnest(disciplines) before GROUP BY multiplied every activity row by the number
|
|
429
|
+
// of disciplines on it, inflating project_count and the credits/ships/karma sums
|
|
430
|
+
// (task 1002911). So scalars aggregate in `agg` over the un-fanned table, and the
|
|
431
|
+
// disciplines union is computed separately in `disc`. Proven against real SQL by
|
|
432
|
+
// tests/scouting_rollup_fanout.mjs.
|
|
426
433
|
async function listScoutingRollup({ limit = 100, pool = defaultPool } = {}) {
|
|
427
434
|
const { rows } = await pool.query(
|
|
428
|
-
`
|
|
435
|
+
`WITH agg AS (
|
|
436
|
+
SELECT a.github_id,
|
|
437
|
+
count(*)::int AS project_count,
|
|
438
|
+
sum(a.credits)::int AS total_credits,
|
|
439
|
+
sum(a.tasks_shipped)::int AS total_tasks_shipped,
|
|
440
|
+
sum(a.karma)::int AS total_karma,
|
|
441
|
+
max(a.last_active) AS last_active
|
|
442
|
+
FROM platform_identity_builder_project_activity a
|
|
443
|
+
GROUP BY a.github_id
|
|
444
|
+
), disc AS (
|
|
445
|
+
SELECT x.github_id, array_agg(DISTINCT x.d) AS disciplines
|
|
446
|
+
FROM (SELECT a.github_id, unnest(a.disciplines) AS d
|
|
447
|
+
FROM platform_identity_builder_project_activity a) x
|
|
448
|
+
WHERE x.d IS NOT NULL
|
|
449
|
+
GROUP BY x.github_id
|
|
450
|
+
)
|
|
451
|
+
SELECT agg.github_id,
|
|
429
452
|
acc.github_login, acc.display_name, acc.avatar_url,
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
LEFT JOIN platform_identity_accounts acc ON acc.github_id = a.github_id
|
|
438
|
-
LEFT JOIN LATERAL unnest(a.disciplines) AS d ON true
|
|
439
|
-
GROUP BY a.github_id, acc.github_login, acc.display_name, acc.avatar_url
|
|
440
|
-
ORDER BY total_credits DESC, total_tasks_shipped DESC
|
|
453
|
+
agg.project_count, agg.total_credits, agg.total_tasks_shipped,
|
|
454
|
+
agg.total_karma, agg.last_active,
|
|
455
|
+
disc.disciplines
|
|
456
|
+
FROM agg
|
|
457
|
+
LEFT JOIN platform_identity_accounts acc ON acc.github_id = agg.github_id
|
|
458
|
+
LEFT JOIN disc ON disc.github_id = agg.github_id
|
|
459
|
+
ORDER BY agg.total_credits DESC, agg.total_tasks_shipped DESC
|
|
441
460
|
LIMIT $1`,
|
|
442
461
|
[limit],
|
|
443
462
|
);
|
|
@@ -342,10 +342,27 @@ await ta('listScoutingRollup aggregates per builder across projects, ranked, joi
|
|
|
342
342
|
assert.match(c.text, /FROM platform_identity_builder_project_activity a/);
|
|
343
343
|
assert.match(c.text, /GROUP BY a\.github_id/);
|
|
344
344
|
assert.match(c.text, /sum\(a\.credits\)/);
|
|
345
|
-
assert.match(c.text, /ORDER BY total_credits DESC/);
|
|
345
|
+
assert.match(c.text, /ORDER BY agg\.total_credits DESC, agg\.total_tasks_shipped DESC/);
|
|
346
346
|
assert.deepEqual(c.params, [50]);
|
|
347
347
|
});
|
|
348
348
|
|
|
349
|
+
await ta('listScoutingRollup scalar aggregation is fan-out-free (task 1002911)', async () => {
|
|
350
|
+
// The old query LEFT JOIN LATERAL unnest(a.disciplines)'d BEFORE GROUP BY, so
|
|
351
|
+
// sums/counts multiplied by the discipline count. Structural guard: the agg CTE
|
|
352
|
+
// (the scalar sums) must contain no unnest/LATERAL; unnest may appear only in
|
|
353
|
+
// the disc CTE. Real-SQL proof of the semantics: tests/scouting_rollup_fanout.mjs.
|
|
354
|
+
const pool = mockPool([{ rows: [] }]);
|
|
355
|
+
await P.listScoutingRollup({ pool, limit: 10 });
|
|
356
|
+
const sql = pool.calls[0].text;
|
|
357
|
+
assert.doesNotMatch(sql, /LATERAL/i, 'no lateral join anywhere in the rollup');
|
|
358
|
+
const aggCte = sql.slice(sql.indexOf('WITH agg AS'), sql.indexOf('disc AS'));
|
|
359
|
+
assert.match(aggCte, /sum\(a\.credits\)/, 'scalar sums live in the agg CTE');
|
|
360
|
+
assert.doesNotMatch(aggCte, /unnest/i, 'the scalar-aggregation CTE must not fan rows out');
|
|
361
|
+
const discCte = sql.slice(sql.indexOf('disc AS'), sql.indexOf('SELECT agg.github_id'));
|
|
362
|
+
assert.match(discCte, /unnest\(a\.disciplines\)/, 'disciplines union is computed separately');
|
|
363
|
+
assert.match(discCte, /array_agg\(DISTINCT x\.d\)/, 'disciplines are deduped across projects');
|
|
364
|
+
});
|
|
365
|
+
|
|
349
366
|
console.log('\nauthorization codes:');
|
|
350
367
|
|
|
351
368
|
await ta('mintAuthCode stores the HASH of the code (never the plaintext) + binds it', async () => {
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.158",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.158",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.158",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
|
@@ -76,7 +76,7 @@ const INTEGRATION = new Set([
|
|
|
76
76
|
'claim_gate_open_blocker', 'claim_gate_rebase', 'clawback_confirmed_backlog', 'discord_autojoin', 'discord_oauth',
|
|
77
77
|
'governance_fold_bench', 'governance_live_proof', 'linkify_refs', 'memory_isolation',
|
|
78
78
|
'memory_pull', 'needs_rebase', 'parallel-load', 'reward_mode_cost_plus_only',
|
|
79
|
-
'session_records', 'setup_smoke',
|
|
79
|
+
'scouting_rollup_fanout', 'session_records', 'setup_smoke',
|
|
80
80
|
'status-probe',
|
|
81
81
|
]);
|
|
82
82
|
|
package/src/module-api.js
CHANGED
|
@@ -741,8 +741,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
741
741
|
// landed since 1.19.155 with no explicit bump. run 31957976979. (task 1002620)
|
|
742
742
|
// 1.19.157 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
743
743
|
// landed since 1.19.156 with no explicit bump. run 31958139486. (task 1002620)
|
|
744
|
+
// 1.19.158 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
745
|
+
// landed since 1.19.157 with no explicit bump. run 31958753412. (task 1002620)
|
|
744
746
|
// ---------------------------------------------------------------------------
|
|
745
|
-
const CORE_VERSION = '1.19.
|
|
747
|
+
const CORE_VERSION = '1.19.158'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
746
748
|
|
|
747
749
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
748
750
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// tests/scouting_rollup_fanout.mjs — task 1002911 (real DB).
|
|
2
|
+
//
|
|
3
|
+
// listScoutingRollup used to `LEFT JOIN LATERAL unnest(a.disciplines)` BEFORE
|
|
4
|
+
// GROUP BY, multiplying every activity row by max(1, cardinality(disciplines)) —
|
|
5
|
+
// so project_count and the credits/ships/karma sums were inflated for any builder
|
|
6
|
+
// with 2+ disciplines on a project row. The module's own test suite is DB-free
|
|
7
|
+
// and can only regex-match the SQL text; THIS test proves the aggregation
|
|
8
|
+
// semantics against real SQL: it replays the module's actual migrations, seeds a
|
|
9
|
+
// multi-discipline builder across two projects, and asserts exact totals.
|
|
10
|
+
//
|
|
11
|
+
// Real-DB integration test: self-skips without a reachable Postgres; also in the
|
|
12
|
+
// INTEGRATION set so the default DB-free lane never runs it.
|
|
13
|
+
//
|
|
14
|
+
// Run: DATABASE_URL=... node tests/scouting_rollup_fanout.mjs
|
|
15
|
+
|
|
16
|
+
import { createRequire } from 'node:module';
|
|
17
|
+
import { readFileSync } from 'node:fs';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { dirname, join } from 'node:path';
|
|
20
|
+
import { strict as assert } from 'node:assert';
|
|
21
|
+
const require = createRequire(import.meta.url);
|
|
22
|
+
const { Pool } = require('pg');
|
|
23
|
+
const pi = require('../modules/platform-identity/platform-identity.js');
|
|
24
|
+
|
|
25
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const MIG_DIR = join(here, '..', 'modules', 'platform-identity', 'migrations');
|
|
27
|
+
const migrationSql = (f) => readFileSync(join(MIG_DIR, f), 'utf8');
|
|
28
|
+
|
|
29
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL || undefined });
|
|
30
|
+
try {
|
|
31
|
+
await pool.query('SELECT 1');
|
|
32
|
+
} catch (e) {
|
|
33
|
+
console.log(`scouting_rollup_fanout.mjs SKIPPED — no reachable Postgres (${e.code || e.message}).`);
|
|
34
|
+
await pool.end().catch(() => {});
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// High-numbered fixture ids so a shared DB can never collide with real data.
|
|
39
|
+
const MULTI = 90002911, SOLO = 90002912, GHOST = 90002913;
|
|
40
|
+
const ALL_IDS = [MULTI, SOLO, GHOST];
|
|
41
|
+
const CLIENTS = ['t1002911-p1', 't1002911-p2'];
|
|
42
|
+
const q = (sql, params) => pool.query(sql, params);
|
|
43
|
+
|
|
44
|
+
async function cleanup() {
|
|
45
|
+
await q(`DELETE FROM platform_identity_builder_project_activity WHERE github_id = ANY($1::bigint[])`, [ALL_IDS]).catch(() => {});
|
|
46
|
+
await q(`DELETE FROM platform_identity_accounts WHERE github_id = ANY($1::bigint[])`, [ALL_IDS]).catch(() => {});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let pass = 0;
|
|
50
|
+
const ok = (cond, msg) => { assert.ok(cond, msg); pass++; console.log(` ok - ${msg}`); };
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
// The module's actual migrations (idempotent CREATE TABLE IF NOT EXISTS).
|
|
54
|
+
await q(migrationSql('platform_identity_001_tables.sql'));
|
|
55
|
+
await q(migrationSql('platform_identity_005_builder_activity.sql'));
|
|
56
|
+
await cleanup();
|
|
57
|
+
|
|
58
|
+
await q(
|
|
59
|
+
`INSERT INTO platform_identity_accounts (github_id, github_login, display_name, avatar_url)
|
|
60
|
+
VALUES ($1, 'multi', 'Multi', 'a'), ($2, 'solo', 'Solo', 'b'), ($3, 'ghost', 'Ghost', 'c')`,
|
|
61
|
+
[MULTI, SOLO, GHOST],
|
|
62
|
+
);
|
|
63
|
+
// MULTI: two projects, 2 and 3 disciplines — the exact fan-out shape the old
|
|
64
|
+
// query inflated (credits would have summed 100*2 + 50*3 = 350, count 5).
|
|
65
|
+
// SOLO: one project, one discipline (was always correct — must stay correct).
|
|
66
|
+
// GHOST: one project, NO disciplines (old query kept it via LEFT JOIN + FILTER).
|
|
67
|
+
await q(
|
|
68
|
+
`INSERT INTO platform_identity_builder_project_activity
|
|
69
|
+
(client_id, github_id, credits, tasks_shipped, karma, disciplines, last_active)
|
|
70
|
+
VALUES ($1, $3, 100, 5, 3, '{engineer,ui}', '2026-01-01T00:00:00Z'),
|
|
71
|
+
($2, $3, 50, 2, 1, '{artist,engineer,ui}', '2026-02-01T00:00:00Z'),
|
|
72
|
+
($1, $4, 60, 4, 2, '{engineer}', '2026-03-01T00:00:00Z'),
|
|
73
|
+
($2, $5, 10, 1, 0, '{}', '2026-04-01T00:00:00Z')`,
|
|
74
|
+
[...CLIENTS, MULTI, SOLO, GHOST],
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const rows = (await pi.listScoutingRollup({ limit: 500, pool })).filter((r) => ALL_IDS.includes(Number(r.github_id)));
|
|
78
|
+
const by = Object.fromEntries(rows.map((r) => [Number(r.github_id), r]));
|
|
79
|
+
|
|
80
|
+
const m = by[MULTI];
|
|
81
|
+
ok(m, 'multi-discipline builder appears in the rollup');
|
|
82
|
+
ok(m.project_count === 2, `project_count is 2, not fanned (got ${m.project_count})`);
|
|
83
|
+
ok(m.total_credits === 150, `total_credits is 100+50=150, not inflated (got ${m.total_credits})`);
|
|
84
|
+
ok(m.total_tasks_shipped === 7, `total_tasks_shipped is 5+2=7 (got ${m.total_tasks_shipped})`);
|
|
85
|
+
ok(m.total_karma === 4, `total_karma is 3+1=4 (got ${m.total_karma})`);
|
|
86
|
+
ok(new Date(m.last_active).toISOString() === '2026-02-01T00:00:00.000Z', 'last_active is the max across projects');
|
|
87
|
+
assert.deepEqual([...m.disciplines].sort(), ['artist', 'engineer', 'ui']);
|
|
88
|
+
pass++; console.log(' ok - disciplines are the deduped union across projects');
|
|
89
|
+
ok(m.github_login === 'multi', 'account display fields join through');
|
|
90
|
+
|
|
91
|
+
const s = by[SOLO];
|
|
92
|
+
ok(s.project_count === 1 && s.total_credits === 60 && s.total_tasks_shipped === 4 && s.total_karma === 2,
|
|
93
|
+
'single-discipline builder totals are unchanged');
|
|
94
|
+
assert.deepEqual(s.disciplines, ['engineer']);
|
|
95
|
+
pass++; console.log(' ok - single discipline survives as a one-element array');
|
|
96
|
+
|
|
97
|
+
const g = by[GHOST];
|
|
98
|
+
ok(g, 'a builder with an empty disciplines array still appears');
|
|
99
|
+
ok(g.disciplines === null, 'empty disciplines roll up as NULL (matches the old FILTER behavior)');
|
|
100
|
+
ok(g.project_count === 1 && g.total_credits === 10, 'empty-disciplines totals are correct');
|
|
101
|
+
|
|
102
|
+
const fixtureOrder = rows.map((r) => Number(r.github_id));
|
|
103
|
+
assert.deepEqual(fixtureOrder, [MULTI, SOLO, GHOST]);
|
|
104
|
+
pass++; console.log(' ok - ranked by total_credits DESC');
|
|
105
|
+
|
|
106
|
+
const limited = await pi.listScoutingRollup({ limit: 1, pool });
|
|
107
|
+
ok(limited.length === 1, 'LIMIT is respected');
|
|
108
|
+
|
|
109
|
+
console.log(`\nscouting_rollup_fanout.mjs: ${pass} passed`);
|
|
110
|
+
} finally {
|
|
111
|
+
await cleanup();
|
|
112
|
+
await pool.end().catch(() => {});
|
|
113
|
+
}
|