@bongos/core 1.19.337 → 1.19.338
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 +29 -29
- package/modules/builder-settings/routes/settings.js +18 -16
- package/modules/dev-box/routes/box.js +25 -23
- package/modules/discord/discord-bot.js +37 -35
- package/modules/discord/discord-role-sync.js +14 -12
- package/modules/government/routes/government.js +15 -13
- package/modules/ideas/routes/inbox.js +16 -14
- package/modules/lifecycle/routes/claims.js +11 -9
- package/modules/lifecycle/routes/goals.js +25 -23
- package/modules/lifecycle/routes/task-write-routes.js +17 -15
- package/modules/lifecycle/routes/tasks.js +21 -19
- package/modules/memory/routes/memory.js +18 -16
- package/modules/platform-identity/routes/sso.js +13 -11
- package/modules/provisioning/routes/provisioning.js +17 -15
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/fitness.js +6 -0
- package/src/bongos/auth-admission.js +9 -6
- package/src/bongos/routes/auth.js +20 -17
- package/src/bongos/routes/backup.js +7 -4
- package/src/bongos/routes/builders.js +17 -14
- package/src/bongos/routes/public.js +9 -6
- package/src/bongos/uptime-poller.js +8 -5
- package/src/module-api.js +42 -6
- package/tests/logger.mjs +67 -0
|
@@ -61,6 +61,8 @@ const express = require('express');
|
|
|
61
61
|
const api = require('../../../src/module-api');
|
|
62
62
|
const auth = api;
|
|
63
63
|
const memory = require('../memory');
|
|
64
|
+
// task 1003208: structured logging (pino via the doorway) — was console.*.
|
|
65
|
+
const log = api.logger('memory');
|
|
64
66
|
const db = api;
|
|
65
67
|
|
|
66
68
|
// Per-builder rate limit for POST /memory/sync, layered ON TOP of the global
|
|
@@ -357,7 +359,7 @@ module.exports = function buildMemoryRouter() {
|
|
|
357
359
|
} catch (e) {
|
|
358
360
|
// A throw here means the transaction itself failed (BEGIN/COMMIT/connect),
|
|
359
361
|
// not a per-file rejection — those come back inside the results array.
|
|
360
|
-
|
|
362
|
+
log.error('[gds] POST /memory/sync batch transaction error', { builderId, err: e });
|
|
361
363
|
return res.fail('store_failed', { status: 500, message: 'memory sync failed; nothing was stored' });
|
|
362
364
|
}
|
|
363
365
|
|
|
@@ -397,9 +399,9 @@ module.exports = function buildMemoryRouter() {
|
|
|
397
399
|
hint = 'Prune old memory files (the limit is per-builder, summed across all files).';
|
|
398
400
|
} else if (code !== 'BAD_PATH' && code !== 'STORE_FAILED') {
|
|
399
401
|
// Unknown helper error code — log for forensics, still report per-file.
|
|
400
|
-
|
|
402
|
+
log.error('[gds] POST /memory/sync store error', { builderId, path, code, err: e });
|
|
401
403
|
} else if (code === 'STORE_FAILED') {
|
|
402
|
-
|
|
404
|
+
log.error('[gds] POST /memory/sync unexpected error', { builderId, path, err: e });
|
|
403
405
|
}
|
|
404
406
|
const err = { path, code, message, ...(hint ? { hint } : {}) };
|
|
405
407
|
errors.push(err);
|
|
@@ -452,7 +454,7 @@ module.exports = function buildMemoryRouter() {
|
|
|
452
454
|
})),
|
|
453
455
|
});
|
|
454
456
|
} catch (err) {
|
|
455
|
-
|
|
457
|
+
log.error('[gds] GET /memory/files', { builderId, err });
|
|
456
458
|
return res.fail('list_failed', { status: 500, message: 'could not list memory files' });
|
|
457
459
|
}
|
|
458
460
|
});
|
|
@@ -504,7 +506,7 @@ module.exports = function buildMemoryRouter() {
|
|
|
504
506
|
created_at: row.created_at,
|
|
505
507
|
});
|
|
506
508
|
} catch (err) {
|
|
507
|
-
|
|
509
|
+
log.error('[gds] GET /memory/file', { builderId, path, err });
|
|
508
510
|
return res.fail('fetch_failed', { status: 500, message: 'could not fetch memory file' });
|
|
509
511
|
}
|
|
510
512
|
});
|
|
@@ -537,7 +539,7 @@ module.exports = function buildMemoryRouter() {
|
|
|
537
539
|
if (n === 0) return res.fail('not_found', 404, { path });
|
|
538
540
|
return res.json({ deleted: true, builder_id: builderId, path });
|
|
539
541
|
} catch (err) {
|
|
540
|
-
|
|
542
|
+
log.error('[gds] DELETE /memory/file', { builderId, path, err });
|
|
541
543
|
return res.fail('delete_failed', { status: 500, message: 'could not delete memory file' });
|
|
542
544
|
}
|
|
543
545
|
});
|
|
@@ -568,7 +570,7 @@ module.exports = function buildMemoryRouter() {
|
|
|
568
570
|
try {
|
|
569
571
|
rows = await memory.listMemoryFiles(targetId);
|
|
570
572
|
} catch (err) {
|
|
571
|
-
|
|
573
|
+
log.error('[gds] GET /memory/builders/:builderId/files', { targetId, err });
|
|
572
574
|
return res.fail('list_failed', { status: 500, message: 'could not list memory files' });
|
|
573
575
|
}
|
|
574
576
|
// FAIL CLOSED: the audit row must land durably BEFORE we return any of the
|
|
@@ -579,7 +581,7 @@ module.exports = function buildMemoryRouter() {
|
|
|
579
581
|
try {
|
|
580
582
|
await recordForensicRead(req, { route, targetBuilderId: targetId, target: 'list', responseStatus: 200 });
|
|
581
583
|
} catch (auditErr) {
|
|
582
|
-
|
|
584
|
+
log.error('[gds] forensic-read audit insert failed (read withheld)', {
|
|
583
585
|
route, targetId, err: auditErr && auditErr.message ? auditErr.message : String(auditErr),
|
|
584
586
|
});
|
|
585
587
|
return res.fail('audit_failed', { status: 500, message: 'forensic read not served: audit could not be recorded' });
|
|
@@ -628,7 +630,7 @@ module.exports = function buildMemoryRouter() {
|
|
|
628
630
|
try {
|
|
629
631
|
row = await memory.fetchMemoryFile(targetId, path);
|
|
630
632
|
} catch (err) {
|
|
631
|
-
|
|
633
|
+
log.error('[gds] GET /memory/builders/:builderId/file', { targetId, path, err });
|
|
632
634
|
return res.fail('fetch_failed', { status: 500, message: 'could not fetch memory file' });
|
|
633
635
|
}
|
|
634
636
|
// FAIL CLOSED: audit the look — logged even if the path is a 404, because
|
|
@@ -640,7 +642,7 @@ module.exports = function buildMemoryRouter() {
|
|
|
640
642
|
try {
|
|
641
643
|
await recordForensicRead(req, { route, targetBuilderId: targetId, target: path, responseStatus: outcomeStatus });
|
|
642
644
|
} catch (auditErr) {
|
|
643
|
-
|
|
645
|
+
log.error('[gds] forensic-read audit insert failed (read withheld)', {
|
|
644
646
|
route, targetId, path, err: auditErr && auditErr.message ? auditErr.message : String(auditErr),
|
|
645
647
|
});
|
|
646
648
|
return res.fail('audit_failed', { status: 500, message: 'forensic read not served: audit could not be recorded' });
|
|
@@ -687,7 +689,7 @@ module.exports = function buildMemoryRouter() {
|
|
|
687
689
|
try {
|
|
688
690
|
rows = await memory.listMemoryFiles(targetId);
|
|
689
691
|
} catch (err) {
|
|
690
|
-
|
|
692
|
+
log.error('[gds] GET /memory/bfg/builders/:builderId/files', { targetId, err });
|
|
691
693
|
return res.fail('list_failed', { status: 500, message: 'could not list memory files' });
|
|
692
694
|
}
|
|
693
695
|
// FAIL CLOSED: the audit row must land before any of the target's data is
|
|
@@ -695,7 +697,7 @@ module.exports = function buildMemoryRouter() {
|
|
|
695
697
|
try {
|
|
696
698
|
await recordForensicRead(req, { route, targetBuilderId: targetId, target: 'list', responseStatus: 200, action: 'bfg_memory_read' });
|
|
697
699
|
} catch (auditErr) {
|
|
698
|
-
|
|
700
|
+
log.error('[gds] bfg-read audit insert failed (read withheld)', { route, targetId, err: auditErr && auditErr.message ? auditErr.message : String(auditErr) });
|
|
699
701
|
return res.fail('audit_failed', { status: 500, message: 'read not served: audit could not be recorded' });
|
|
700
702
|
}
|
|
701
703
|
res.set('Cache-Control', 'no-store');
|
|
@@ -731,14 +733,14 @@ module.exports = function buildMemoryRouter() {
|
|
|
731
733
|
try {
|
|
732
734
|
row = await memory.fetchMemoryFile(targetId, path);
|
|
733
735
|
} catch (err) {
|
|
734
|
-
|
|
736
|
+
log.error('[gds] GET /memory/bfg/builders/:builderId/file', { targetId, path, err });
|
|
735
737
|
return res.fail('fetch_failed', { status: 500, message: 'could not fetch memory file' });
|
|
736
738
|
}
|
|
737
739
|
const outcomeStatus = row ? 200 : 404;
|
|
738
740
|
try {
|
|
739
741
|
await recordForensicRead(req, { route, targetBuilderId: targetId, target: path, responseStatus: outcomeStatus, action: 'bfg_memory_read' });
|
|
740
742
|
} catch (auditErr) {
|
|
741
|
-
|
|
743
|
+
log.error('[gds] bfg-read audit insert failed (read withheld)', { route, targetId, path, err: auditErr && auditErr.message ? auditErr.message : String(auditErr) });
|
|
742
744
|
return res.fail('audit_failed', { status: 500, message: 'read not served: audit could not be recorded' });
|
|
743
745
|
}
|
|
744
746
|
if (!row) return res.fail('not_found', 404, { builder_id: targetId, path });
|
|
@@ -830,7 +832,7 @@ module.exports = function buildMemoryRouter() {
|
|
|
830
832
|
try {
|
|
831
833
|
target = await db.getBuilderById(targetId);
|
|
832
834
|
} catch (err) {
|
|
833
|
-
|
|
835
|
+
log.error('[gds] bfg-write target lookup', { targetId, err });
|
|
834
836
|
return res.fail('lookup_failed', 500);
|
|
835
837
|
}
|
|
836
838
|
if (!target) return res.fail('target_not_found', 404);
|
|
@@ -893,7 +895,7 @@ module.exports = function buildMemoryRouter() {
|
|
|
893
895
|
// own assertBfgPath guard mapped to a clean 400 for any direct caller.
|
|
894
896
|
return res.fail('bad_path', 400, { code });
|
|
895
897
|
}
|
|
896
|
-
|
|
898
|
+
log.error('[gds] POST /memory/builders/:builderId/bfg-write', e);
|
|
897
899
|
return res.fail('bfg_write_failed', 500);
|
|
898
900
|
}
|
|
899
901
|
}
|
|
@@ -19,6 +19,8 @@ const api = require('../../../src/module-api');
|
|
|
19
19
|
const pi = require('../platform-identity');
|
|
20
20
|
const hubKeys = require('../hub-keys');
|
|
21
21
|
const hubDevice = require('../hub-device');
|
|
22
|
+
// task 1003208: structured logging (pino via the doorway) — was console.*.
|
|
23
|
+
const log = api.logger('platform-identity');
|
|
22
24
|
|
|
23
25
|
const { pool, validateOrRespond, resolveSession } = api;
|
|
24
26
|
|
|
@@ -65,12 +67,12 @@ async function notifyInstanceLogout(membership, githubId) {
|
|
|
65
67
|
body: JSON.stringify({ logout_token: token }),
|
|
66
68
|
signal: ac.signal,
|
|
67
69
|
});
|
|
68
|
-
if (!resp.ok)
|
|
70
|
+
if (!resp.ok) log.warn(`[platform-identity] SLO: ${origin} returned ${resp.status}`);
|
|
69
71
|
} finally {
|
|
70
72
|
clearTimeout(timer);
|
|
71
73
|
}
|
|
72
74
|
} catch (err) {
|
|
73
|
-
|
|
75
|
+
log.warn(`[platform-identity] SLO: notifying ${origin} failed (non-blocking):`, err && err.message ? err.message : err);
|
|
74
76
|
}
|
|
75
77
|
}
|
|
76
78
|
|
|
@@ -90,7 +92,7 @@ async function fanOutBackchannelLogout(githubId) {
|
|
|
90
92
|
const targets = (memberships || []).filter((m) => m.project_origin && m.client_id);
|
|
91
93
|
await Promise.allSettled(targets.map((m) => notifyInstanceLogout(m, githubId)));
|
|
92
94
|
} catch (err) {
|
|
93
|
-
|
|
95
|
+
log.error('[platform-identity] SLO fan-out failed (non-blocking):', err && err.message ? err.message : err);
|
|
94
96
|
}
|
|
95
97
|
}
|
|
96
98
|
|
|
@@ -149,7 +151,7 @@ module.exports = function ssoRoutes() {
|
|
|
149
151
|
try {
|
|
150
152
|
client = await pi.getClient(clientId, { pool });
|
|
151
153
|
} catch (err) {
|
|
152
|
-
|
|
154
|
+
log.error('[platform-identity] /sso/authorize getClient', err);
|
|
153
155
|
return res.status(500).type('text/html').send(errorPage('Internal error resolving the project.'));
|
|
154
156
|
}
|
|
155
157
|
if (!client || client.status !== 'active') {
|
|
@@ -171,7 +173,7 @@ module.exports = function ssoRoutes() {
|
|
|
171
173
|
try {
|
|
172
174
|
session = await resolveSession(req);
|
|
173
175
|
} catch (err) {
|
|
174
|
-
|
|
176
|
+
log.error('[platform-identity] /sso/authorize resolveSession', err);
|
|
175
177
|
return res.status(500).type('text/html').send(errorPage('Internal error checking your hub session.'));
|
|
176
178
|
}
|
|
177
179
|
const usable = pi.sessionUsableForFederation(session);
|
|
@@ -213,7 +215,7 @@ module.exports = function ssoRoutes() {
|
|
|
213
215
|
if (state) u.searchParams.set('state', state);
|
|
214
216
|
return res.redirect(302, u.toString());
|
|
215
217
|
} catch (err) {
|
|
216
|
-
|
|
218
|
+
log.error('[platform-identity] /sso/authorize mint', err);
|
|
217
219
|
return res.status(500).type('text/html').send(errorPage('Could not complete the sign-in handoff.'));
|
|
218
220
|
}
|
|
219
221
|
});
|
|
@@ -261,7 +263,7 @@ module.exports = function ssoRoutes() {
|
|
|
261
263
|
avatar_url: identity.avatar_url,
|
|
262
264
|
});
|
|
263
265
|
} catch (err) {
|
|
264
|
-
|
|
266
|
+
log.error('[platform-identity] POST /sso/token', err);
|
|
265
267
|
return res.fail('token_exchange_failed', { status: 500, message: 'internal error' });
|
|
266
268
|
}
|
|
267
269
|
});
|
|
@@ -302,7 +304,7 @@ module.exports = function ssoRoutes() {
|
|
|
302
304
|
const out = await hubDevice.startHubGithubDevice(client.client_id);
|
|
303
305
|
res.json(out);
|
|
304
306
|
} catch (err) {
|
|
305
|
-
|
|
307
|
+
log.error('[platform-identity] /sso/device/start', err);
|
|
306
308
|
// 424, not 502: Cloudflare replaces origin 5xx bodies with its own page, so a
|
|
307
309
|
// proxied upstream refusal must ride a 4xx to keep its JSON diagnosis readable
|
|
308
310
|
// externally (finding W2, task 1003052 — the core device/start twin).
|
|
@@ -345,7 +347,7 @@ module.exports = function ssoRoutes() {
|
|
|
345
347
|
});
|
|
346
348
|
res.json({ state: 'ok', assertion });
|
|
347
349
|
} catch (err) {
|
|
348
|
-
|
|
350
|
+
log.error('[platform-identity] /sso/device/poll', err);
|
|
349
351
|
// 424, matching the core device/poll (finding W2, task 1003052): this catch
|
|
350
352
|
// proxies the hub's GitHub poll refusals, and a 5xx body never survives
|
|
351
353
|
// Cloudflare. (The !out.client_id branch above keeps its 500: an internal
|
|
@@ -399,7 +401,7 @@ module.exports = function ssoRoutes() {
|
|
|
399
401
|
await pi.upsertMembership({ clientId: client_id, githubId: github_id, membershipKind }, { pool });
|
|
400
402
|
res.json({ ok: true });
|
|
401
403
|
} catch (err) {
|
|
402
|
-
|
|
404
|
+
log.error('[platform-identity] /sso/membership/check-in', err);
|
|
403
405
|
res.fail('checkin_failed', { status: 500, message: 'internal error' });
|
|
404
406
|
}
|
|
405
407
|
});
|
|
@@ -453,7 +455,7 @@ module.exports = function ssoRoutes() {
|
|
|
453
455
|
}, { pool });
|
|
454
456
|
res.json({ ok: true });
|
|
455
457
|
} catch (err) {
|
|
456
|
-
|
|
458
|
+
log.error('[platform-identity] /sso/activity/rollup', err);
|
|
457
459
|
res.fail('rollup_failed', { status: 500, message: 'internal error' });
|
|
458
460
|
}
|
|
459
461
|
});
|
|
@@ -34,6 +34,8 @@ const { pool, validateOrRespond, parseId } = api;
|
|
|
34
34
|
const provisioning = require('../provisioning');
|
|
35
35
|
const { onboardPlan, stepsForShape } = require('../onboard-plan');
|
|
36
36
|
const { makeAskRateLimiter, clientIp } = require('../rate-limit');
|
|
37
|
+
// task 1003208: structured logging (pino via the doorway) — was console.*.
|
|
38
|
+
const log = api.logger('provisioning');
|
|
37
39
|
|
|
38
40
|
// How long a GitHub-App-Manifest `state` token is valid — the owner must click the
|
|
39
41
|
// pre-filled link + approve on GitHub within this window (task 2080). Comfortably
|
|
@@ -279,7 +281,7 @@ module.exports = function provisioningRoutes() {
|
|
|
279
281
|
const rows = await provisioning.listAllInstances(pool, { status });
|
|
280
282
|
res.json({ instances: rows.map(fleetInstance) });
|
|
281
283
|
} catch (err) {
|
|
282
|
-
|
|
284
|
+
log.error('[provisioning] GET /provisioning/fleet', err);
|
|
283
285
|
res.fail('fleet_failed', { status: 500, message: 'internal error' });
|
|
284
286
|
}
|
|
285
287
|
});
|
|
@@ -294,7 +296,7 @@ module.exports = function provisioningRoutes() {
|
|
|
294
296
|
const out = await provisioning.costLedger(pool, { limit });
|
|
295
297
|
res.json(out);
|
|
296
298
|
} catch (err) {
|
|
297
|
-
|
|
299
|
+
log.error('[provisioning] GET /provisioning/fleet/cost-ledger', err);
|
|
298
300
|
res.fail('cost_ledger_failed', { status: 500, message: 'internal error' });
|
|
299
301
|
}
|
|
300
302
|
});
|
|
@@ -347,7 +349,7 @@ module.exports = function provisioningRoutes() {
|
|
|
347
349
|
res.json({ ok: true, queued: r.created, action: 'teardown', status: inst.status, superseded: r.superseded,
|
|
348
350
|
message: r.created ? 'The instance will be torn down shortly.' : 'A teardown is already queued for that instance.' });
|
|
349
351
|
} catch (err) {
|
|
350
|
-
|
|
352
|
+
log.error('[provisioning] POST /provisioning/instances/:id/force-teardown', err);
|
|
351
353
|
res.fail('teardown_failed', { status: 500, message: 'internal error' });
|
|
352
354
|
}
|
|
353
355
|
});
|
|
@@ -522,7 +524,7 @@ module.exports = function provisioningRoutes() {
|
|
|
522
524
|
...(typeIgnored ? { type_ignored: typeIgnored } : {}),
|
|
523
525
|
});
|
|
524
526
|
} catch (err) {
|
|
525
|
-
|
|
527
|
+
log.error('[provisioning] POST /provisioning/instances', err);
|
|
526
528
|
res.fail('request_failed', { status: 500, message: 'internal error' });
|
|
527
529
|
}
|
|
528
530
|
});
|
|
@@ -570,7 +572,7 @@ module.exports = function provisioningRoutes() {
|
|
|
570
572
|
// disclosure this route has no reason to make.
|
|
571
573
|
res.json({ slug, available: reason === null, reason, address: address || null });
|
|
572
574
|
} catch (err) {
|
|
573
|
-
|
|
575
|
+
log.error('[provisioning] GET /provisioning/slug-available', err);
|
|
574
576
|
res.fail('check_failed', { status: 500, message: 'internal error' });
|
|
575
577
|
}
|
|
576
578
|
});
|
|
@@ -590,7 +592,7 @@ module.exports = function provisioningRoutes() {
|
|
|
590
592
|
}
|
|
591
593
|
res.json({ instances });
|
|
592
594
|
} catch (err) {
|
|
593
|
-
|
|
595
|
+
log.error('[provisioning] GET /provisioning/instances', err);
|
|
594
596
|
res.fail('list_failed', { status: 500, message: 'internal error' });
|
|
595
597
|
}
|
|
596
598
|
});
|
|
@@ -622,7 +624,7 @@ module.exports = function provisioningRoutes() {
|
|
|
622
624
|
open_intent: open ? { action: open.action, state: open.state } : null,
|
|
623
625
|
});
|
|
624
626
|
} catch (err) {
|
|
625
|
-
|
|
627
|
+
log.error('[provisioning] GET /provisioning/instances/:id', err);
|
|
626
628
|
res.fail('read_failed', { status: 500, message: 'internal error' });
|
|
627
629
|
}
|
|
628
630
|
});
|
|
@@ -644,7 +646,7 @@ module.exports = function provisioningRoutes() {
|
|
|
644
646
|
if (instanceAccessDenied(inst, req.builder)) return res.fail('instance_not_found', 404);
|
|
645
647
|
res.json({ ok: true, settings: provisioning.effectiveSettings(inst), options: provisioning.SETTINGS_VOCAB });
|
|
646
648
|
} catch (err) {
|
|
647
|
-
|
|
649
|
+
log.error('[provisioning] GET /provisioning/instances/:id/settings', err);
|
|
648
650
|
res.fail('settings_read_failed', { status: 500, message: 'internal error' });
|
|
649
651
|
}
|
|
650
652
|
});
|
|
@@ -679,7 +681,7 @@ module.exports = function provisioningRoutes() {
|
|
|
679
681
|
}).catch(() => {});
|
|
680
682
|
res.json({ ok: true, settings: provisioning.effectiveSettings(updated) });
|
|
681
683
|
} catch (err) {
|
|
682
|
-
|
|
684
|
+
log.error('[provisioning] PATCH /provisioning/instances/:id/settings', err);
|
|
683
685
|
res.fail('settings_update_failed', { status: 500, message: 'internal error' });
|
|
684
686
|
}
|
|
685
687
|
});
|
|
@@ -716,7 +718,7 @@ module.exports = function provisioningRoutes() {
|
|
|
716
718
|
res.json({ ok: true, queued: r.created, action: 'teardown', status: inst.status, superseded: r.superseded,
|
|
717
719
|
message: r.created ? 'Your instance will be torn down shortly.' : 'A teardown is already queued for that instance.' });
|
|
718
720
|
} catch (err) {
|
|
719
|
-
|
|
721
|
+
log.error('[provisioning] POST /provisioning/instances/:id/teardown', err);
|
|
720
722
|
res.fail('teardown_failed', { status: 500, message: 'internal error' });
|
|
721
723
|
}
|
|
722
724
|
});
|
|
@@ -841,7 +843,7 @@ module.exports = function provisioningRoutes() {
|
|
|
841
843
|
? 'Your instance is being wired to its new address — this card will show whether it answers.'
|
|
842
844
|
: 'Address recorded — it will be wired when the instance is provisioned.' });
|
|
843
845
|
} catch (err) {
|
|
844
|
-
|
|
846
|
+
log.error('[provisioning] POST /provisioning/instances/:id/domain', err);
|
|
845
847
|
res.fail('domain_attach_failed', { status: 500, message: 'internal error' });
|
|
846
848
|
}
|
|
847
849
|
});
|
|
@@ -899,7 +901,7 @@ module.exports = function provisioningRoutes() {
|
|
|
899
901
|
expires_at: expiresAt,
|
|
900
902
|
});
|
|
901
903
|
} catch (err) {
|
|
902
|
-
|
|
904
|
+
log.error('[provisioning] POST /provisioning/instances/:id/github-app', err);
|
|
903
905
|
res.fail('github_app_manifest_failed', { status: 500, message: 'internal error' });
|
|
904
906
|
}
|
|
905
907
|
});
|
|
@@ -955,7 +957,7 @@ module.exports = function provisioningRoutes() {
|
|
|
955
957
|
const ok = await provisioning.isProvisionedHostname(pool, req.query.domain);
|
|
956
958
|
return res.status(ok ? 200 : 404).json({ ok });
|
|
957
959
|
} catch (err) {
|
|
958
|
-
|
|
960
|
+
log.error('[provisioning] GET /provisioning/tls-check', err);
|
|
959
961
|
return res.status(503).json({ ok: false, error: 'check_failed' });
|
|
960
962
|
}
|
|
961
963
|
});
|
|
@@ -989,7 +991,7 @@ module.exports = function provisioningRoutes() {
|
|
|
989
991
|
app_slug: row.app_slug || null,
|
|
990
992
|
});
|
|
991
993
|
} catch (err) {
|
|
992
|
-
|
|
994
|
+
log.error('[provisioning] GET /provisioning/github-app/status', err);
|
|
993
995
|
return res.status(500).json({ ok: false, error: 'status_check_failed' });
|
|
994
996
|
}
|
|
995
997
|
});
|
|
@@ -1039,7 +1041,7 @@ module.exports = function provisioningRoutes() {
|
|
|
1039
1041
|
: callbackPage({ ok: true, title: 'GitHub app created',
|
|
1040
1042
|
body: '<p>Nice — GitHub created the app. We’re placing its credentials on your instance now; this finishes on its own within a few minutes.</p><p class="muted">You can close this tab.</p>' }));
|
|
1041
1043
|
} catch (err) {
|
|
1042
|
-
|
|
1044
|
+
log.error('[provisioning] GET /provisioning/github-app/callback', err);
|
|
1043
1045
|
return sendPage(500, callbackPage({ ok: false, title: 'Something went wrong',
|
|
1044
1046
|
body: '<p>We couldn’t record GitHub’s response. Try starting the sign-in setup again from your instance’s page.</p>' }));
|
|
1045
1047
|
}
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.338",
|
|
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.338",
|
|
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.338",
|
|
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",
|
package/scripts/gds/fitness.js
CHANGED
|
@@ -488,6 +488,12 @@ const KERNEL_FILES = [
|
|
|
488
488
|
'src/bongos/goal-scope-check.js', // goal create-gate scope wall (composes the 3 matchers above; ADR 0086 §3, BV1.R60)
|
|
489
489
|
'src/bongos/secret-box.js', // AES-256-GCM per-builder secret box
|
|
490
490
|
'src/bongos/pool.js', // the singleton Postgres pool accessor
|
|
491
|
+
// task 1003208: the structured logger is kernel infrastructure in the same sense
|
|
492
|
+
// the pool is — src/module-api.js hands every module a child of it, so it sits
|
|
493
|
+
// BELOW the module boundary, not beside a domain. It imports only node built-ins
|
|
494
|
+
// and pino, so joining the roster costs nothing and buys the import-direction
|
|
495
|
+
// guarantee on the file every module now logs through.
|
|
496
|
+
'src/bongos/logger.js', // pino + request correlation + ADR 0022 redaction
|
|
491
497
|
'src/bongos/db-kernel.js', // the kernel db slice — identity/audit/rank/withTx (BV1.R69)
|
|
492
498
|
'src/bongos/routes/_helpers.js', // shared request validators the doorway re-exports
|
|
493
499
|
// config / identity (host context a module reads, never hardcodes)
|
|
@@ -29,6 +29,9 @@ const seams = require('../module-seams');
|
|
|
29
29
|
const ic = require('../instance-config');
|
|
30
30
|
const { branding, userAgent } = require('../branding');
|
|
31
31
|
const { loadIdpConfig } = require('./auth-config.js');
|
|
32
|
+
// task 1003208: structured logging — was console.*. A core file logs through
|
|
33
|
+
// the kernel logger directly; only MODULES go via api.logger (the doorway).
|
|
34
|
+
const log = require('./logger').logger.child({ src: 'auth-admission' });
|
|
32
35
|
|
|
33
36
|
function membershipKind({ githubLogin } = {}) {
|
|
34
37
|
const fa = firstAdminLogin();
|
|
@@ -70,7 +73,7 @@ async function reportMembershipCheckIn({ githubId, githubLogin, displayName, ava
|
|
|
70
73
|
}),
|
|
71
74
|
});
|
|
72
75
|
} catch (err) {
|
|
73
|
-
|
|
76
|
+
log.info('[auth] hub membership check-in failed (non-blocking):', err.message);
|
|
74
77
|
}
|
|
75
78
|
}
|
|
76
79
|
|
|
@@ -107,7 +110,7 @@ async function reportActivityRollup({ builderId, githubId }) {
|
|
|
107
110
|
}),
|
|
108
111
|
});
|
|
109
112
|
} catch (err) {
|
|
110
|
-
|
|
113
|
+
log.info('[auth] hub activity rollup failed (non-blocking):', err.message);
|
|
111
114
|
}
|
|
112
115
|
}
|
|
113
116
|
|
|
@@ -180,7 +183,7 @@ async function webAdmissionStatus(login) {
|
|
|
180
183
|
// A DB hiccup must not turn the waiting page into a false "you're in".
|
|
181
184
|
// Fail closed on `admitted`; still let a firstAdmin keep polling, since
|
|
182
185
|
// that is the bootstrap window this page exists for.
|
|
183
|
-
|
|
186
|
+
log.error('[auth] webAdmissionStatus', err);
|
|
184
187
|
return { admitted: false, expectedSoon: isFirstAdmin };
|
|
185
188
|
}
|
|
186
189
|
|
|
@@ -213,11 +216,11 @@ async function maybeSeatFirstAdmin(builder) {
|
|
|
213
216
|
try {
|
|
214
217
|
const seated = await seatFirstAdminIfUnclaimed(builder.id, builder.github_login);
|
|
215
218
|
if (seated) {
|
|
216
|
-
|
|
219
|
+
log.info(`[auth] first-admin bootstrap: seated ${builder.github_login} as Archon (configured firstAdmin, no prior Archon).`);
|
|
217
220
|
return { ...builder, rank: 'archon' };
|
|
218
221
|
}
|
|
219
222
|
} catch (err) {
|
|
220
|
-
|
|
223
|
+
log.error('[auth] first-admin bootstrap failed (non-fatal):', err.message);
|
|
221
224
|
}
|
|
222
225
|
return builder;
|
|
223
226
|
}
|
|
@@ -246,7 +249,7 @@ async function safeSyncRankRole(builder) {
|
|
|
246
249
|
await seams.resolveOptional('government.rankSync')
|
|
247
250
|
?.syncRank(null, { builderId: builder.id, rank: builder.rank });
|
|
248
251
|
} catch (err) {
|
|
249
|
-
|
|
252
|
+
log.error('[auth] rank-role sync failed (non-fatal):', err.message);
|
|
250
253
|
}
|
|
251
254
|
}
|
|
252
255
|
|
|
@@ -7,6 +7,9 @@ const { pairStore } = require('../app-pair');
|
|
|
7
7
|
const { publicOrigin, parseCookie, validateOrRespond } = require('./_helpers');
|
|
8
8
|
const { branding } = require('../../branding');
|
|
9
9
|
const seams = require('../../module-seams');
|
|
10
|
+
// task 1003208: structured logging — was console.*. A core file logs through
|
|
11
|
+
// the kernel logger directly; only MODULES go via api.logger (the doorway).
|
|
12
|
+
const log = require('../logger').logger.child({ src: 'auth' });
|
|
10
13
|
|
|
11
14
|
function escapeHtml(s) {
|
|
12
15
|
return String(s ?? '').replace(/[&<>"']/g, (c) => ({
|
|
@@ -358,7 +361,7 @@ module.exports = function buildAuthRouter() {
|
|
|
358
361
|
const out = await auth.startDeviceFlow();
|
|
359
362
|
res.json(out);
|
|
360
363
|
} catch (err) {
|
|
361
|
-
|
|
364
|
+
log.error('[gds] device/start', err);
|
|
362
365
|
// Surface the underlying reason (task 2126). A thrown device-flow start is almost
|
|
363
366
|
// always a GitHub OAuth-App misconfig (device_flow_disabled / bad client) — safe,
|
|
364
367
|
// non-secret, and actionable — so return it instead of an opaque "internal error".
|
|
@@ -394,7 +397,7 @@ module.exports = function buildAuthRouter() {
|
|
|
394
397
|
if (err && err.code === 'NOT_APPROVED') {
|
|
395
398
|
return res.status(403).json({ state: 'not_approved', error: 'not_approved' });
|
|
396
399
|
}
|
|
397
|
-
|
|
400
|
+
log.error('[gds] device/poll', err);
|
|
398
401
|
// 424 for the same reason as device/start above (finding W2, task 1003052):
|
|
399
402
|
// this catch proxies GitHub's poll refusals too, and a 5xx body never
|
|
400
403
|
// survives Cloudflare — the JSON envelope (its code at minimum) must.
|
|
@@ -432,7 +435,7 @@ module.exports = function buildAuthRouter() {
|
|
|
432
435
|
}
|
|
433
436
|
// A bad / expired / forged / wrong-audience assertion is an auth failure (401),
|
|
434
437
|
// not a server error — never mint a session for it.
|
|
435
|
-
|
|
438
|
+
log.error('[gds] /auth/device/exchange', err);
|
|
436
439
|
res.fail('invalid_assertion', { status: 401, message: String(err.message || 'invalid assertion').slice(0, 160) });
|
|
437
440
|
}
|
|
438
441
|
});
|
|
@@ -536,7 +539,7 @@ module.exports = function buildAuthRouter() {
|
|
|
536
539
|
return res.json({ admitted: !!admitted });
|
|
537
540
|
} catch (err) {
|
|
538
541
|
// Fail closed and quietly — the page just keeps polling.
|
|
539
|
-
|
|
542
|
+
log.error('[gds] auth/web/admission-status', err);
|
|
540
543
|
return res.json({ admitted: false });
|
|
541
544
|
}
|
|
542
545
|
});
|
|
@@ -594,7 +597,7 @@ module.exports = function buildAuthRouter() {
|
|
|
594
597
|
try {
|
|
595
598
|
await db.upsertGithubToken({ builderId: builder.id, token: githubToken, scope: githubScope });
|
|
596
599
|
} catch (e) {
|
|
597
|
-
|
|
600
|
+
log.error('[gds] web/callback: repo-token capture failed (non-blocking):', e.message);
|
|
598
601
|
}
|
|
599
602
|
}
|
|
600
603
|
// The state cookie is apex-scoped now (#734) — clear it with the same
|
|
@@ -636,7 +639,7 @@ module.exports = function buildAuthRouter() {
|
|
|
636
639
|
} catch (probeErr) {
|
|
637
640
|
// Never let the classifier turn a clean 403 into a 500 — fall through
|
|
638
641
|
// to the existing page, which is the safe answer for a stranger.
|
|
639
|
-
|
|
642
|
+
log.error('[gds] web/callback admission classify', probeErr);
|
|
640
643
|
}
|
|
641
644
|
if (expectedSoon) {
|
|
642
645
|
return res.status(202).type('text/html').send(renderAccessPendingPage(err.login, recoveryStartHref(req)));
|
|
@@ -657,7 +660,7 @@ module.exports = function buildAuthRouter() {
|
|
|
657
660
|
},
|
|
658
661
|
));
|
|
659
662
|
}
|
|
660
|
-
|
|
663
|
+
log.error('[gds] web/callback', err);
|
|
661
664
|
res.status(500).type('text/html').send(renderAuthErrorPage(
|
|
662
665
|
'The handshake threw an error',
|
|
663
666
|
`The server tried to exchange the auth code for a session and something went wrong: ${err.message}. Try again; if it persists, ask the ${admitterLabel()} to investigate the server logs.`,
|
|
@@ -778,7 +781,7 @@ module.exports = function buildAuthRouter() {
|
|
|
778
781
|
});
|
|
779
782
|
joined = true;
|
|
780
783
|
} catch (e) {
|
|
781
|
-
|
|
784
|
+
log.info('[gds] discord auto-join failed (non-blocking):', e.message);
|
|
782
785
|
}
|
|
783
786
|
}
|
|
784
787
|
// F5 role-sync belt-and-braces (ADR 0033 §4): set/repair the rank-role even
|
|
@@ -790,7 +793,7 @@ module.exports = function buildAuthRouter() {
|
|
|
790
793
|
// callback returns there (settings.js surfaces the ?discord= toast).
|
|
791
794
|
res.redirect(joined ? '/builders/settings?discord=joined' : '/builders/settings?discord=linked');
|
|
792
795
|
} catch (err) {
|
|
793
|
-
|
|
796
|
+
log.error('[gds] discord/callback', err);
|
|
794
797
|
res.status(500).type('text/html').send(renderAuthErrorPage(
|
|
795
798
|
'The Discord link threw an error',
|
|
796
799
|
`Linking failed unexpectedly. Try again; if it persists, ask the ${admitterLabel()} to check the server logs.`,
|
|
@@ -814,13 +817,13 @@ module.exports = function buildAuthRouter() {
|
|
|
814
817
|
)(null);
|
|
815
818
|
if (guildId) {
|
|
816
819
|
auth.removeDiscordGuildMember({ guildId, discordUserId: link.discord_user_id })
|
|
817
|
-
.then((r) =>
|
|
818
|
-
.catch((e) =>
|
|
820
|
+
.then((r) => log.info(`[gds] discord unlink kick: ${r.removed ? 'removed' : r.reason}`))
|
|
821
|
+
.catch((e) => log.info('[gds] discord unlink kick failed (non-blocking):', e.message));
|
|
819
822
|
}
|
|
820
823
|
}
|
|
821
824
|
res.json({ ok: true, unlinked: removed });
|
|
822
825
|
} catch (err) {
|
|
823
|
-
|
|
826
|
+
log.error('[gds] discord/unlink', err);
|
|
824
827
|
res.fail('unlink_failed', 500);
|
|
825
828
|
}
|
|
826
829
|
});
|
|
@@ -897,7 +900,7 @@ module.exports = function buildAuthRouter() {
|
|
|
897
900
|
if (builder) await db.revokeAllSessionsForBuilder(builder.id);
|
|
898
901
|
res.json({ ok: true });
|
|
899
902
|
} catch (err) {
|
|
900
|
-
|
|
903
|
+
log.error('[gds] /auth/backchannel-logout revoke', err);
|
|
901
904
|
res.fail('server_error', { status: 500, message: 'logout failed' });
|
|
902
905
|
}
|
|
903
906
|
});
|
|
@@ -941,7 +944,7 @@ module.exports = function buildAuthRouter() {
|
|
|
941
944
|
paste_command: `node scripts/gds/paste-token.js ${token}`,
|
|
942
945
|
});
|
|
943
946
|
} catch (err) {
|
|
944
|
-
|
|
947
|
+
log.error('[gds] POST /auth/cli-token/issue', err);
|
|
945
948
|
res.fail('issue_failed', { status: 500, message: 'internal error' });
|
|
946
949
|
}
|
|
947
950
|
});
|
|
@@ -968,7 +971,7 @@ module.exports = function buildAuthRouter() {
|
|
|
968
971
|
const revoked = await db.revokeSessionById(id);
|
|
969
972
|
res.json({ ok: true, revoked });
|
|
970
973
|
} catch (err) {
|
|
971
|
-
|
|
974
|
+
log.error('[gds] POST /auth/revoke', err);
|
|
972
975
|
res.fail('revoke_failed', { status: 500, message: 'internal error' });
|
|
973
976
|
}
|
|
974
977
|
});
|
|
@@ -995,7 +998,7 @@ module.exports = function buildAuthRouter() {
|
|
|
995
998
|
const revoked = await db.revokeAllSessionsForBuilder(id);
|
|
996
999
|
res.json({ ok: true, revoked });
|
|
997
1000
|
} catch (err) {
|
|
998
|
-
|
|
1001
|
+
log.error('[gds] POST /auth/revoke-all', err);
|
|
999
1002
|
res.fail('revoke_all_failed', { status: 500, message: 'internal error' });
|
|
1000
1003
|
}
|
|
1001
1004
|
});
|
|
@@ -1092,7 +1095,7 @@ module.exports = function buildAuthRouter() {
|
|
|
1092
1095
|
}
|
|
1093
1096
|
res.json({ ok: true });
|
|
1094
1097
|
} catch (err) {
|
|
1095
|
-
|
|
1098
|
+
log.error('[gds] POST /auth/app-pair/approve', err);
|
|
1096
1099
|
res.fail('approve_failed', { status: 500, message: 'internal error' });
|
|
1097
1100
|
}
|
|
1098
1101
|
});
|