@bongos/core 1.19.336 → 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 +30 -30
- 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 +29 -18
- 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 +44 -6
- package/tests/logger.mjs +67 -0
- package/tests/provisioning_github_app_routes.mjs +40 -1
|
@@ -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
|
|
@@ -144,7 +146,9 @@ code{font-family:"Fira Code",ui-monospace,SFMono-Regular,monospace;background:rg
|
|
|
144
146
|
// land. Deliberately contains NO backticks and NO ${...} so it survives the outer
|
|
145
147
|
// template literal verbatim; the only server data it reads is the JSON config block
|
|
146
148
|
// written just above it. Fails soft — a poll hiccup just retries; a long wait reveals a
|
|
147
|
-
// manual "open" button rather than ever falsely erroring.
|
|
149
|
+
// manual "open" button rather than ever falsely erroring. The auto-open countdown runs
|
|
150
|
+
// ONLY when this page is the wizard's pop-up; a same-tab return (pop-up blocked) keeps
|
|
151
|
+
// the founder here with the door and a way back to the checklist (task 1003236).
|
|
148
152
|
const CALLBACK_SCRIPT = `
|
|
149
153
|
(function(){
|
|
150
154
|
var cfg=window.__CB_SETUP__||{};
|
|
@@ -153,12 +157,18 @@ const CALLBACK_SCRIPT = `
|
|
|
153
157
|
var target='https://'+domain;
|
|
154
158
|
var pollUrl=location.pathname.replace('/callback','/status')+'?state='+encodeURIComponent(state);
|
|
155
159
|
var steps=document.getElementById('steps'),statusEl=document.getElementById('status');
|
|
156
|
-
var cta=document.getElementById('cta'),go=document.getElementById('go'),foot=document.getElementById('foot');
|
|
160
|
+
var cta=document.getElementById('cta'),go=document.getElementById('go'),foot=document.getElementById('foot'),back=document.getElementById('back');
|
|
161
|
+
/* Opened from the wizard as a pop-up (window.opener alive, or the tab still carries the name the wizard gave it):
|
|
162
|
+
the checklist is in the other tab, so this one may auto-open the instance. Same-tab fallback (pop-up blocked,
|
|
163
|
+
form target=_self): this IS the founder's checklist tab — never navigate it away; offer both doors (task 1003236). */
|
|
164
|
+
var popup=!!window.opener||window.name==='ghAppSetup';
|
|
157
165
|
var finished=false,tries=0,manual=false;
|
|
158
166
|
function dot(n){return steps.querySelector('[data-step="'+n+'"]');}
|
|
159
167
|
function setActive(n){for(var i=1;i<=3;i++){var el=dot(i);if(!el)continue;el.classList.remove('active','done');if(i<n){el.classList.add('done');el.querySelector('.dot').textContent='\\u2713';}else if(i===n){el.classList.add('active');}}}
|
|
160
168
|
function allDone(){for(var i=1;i<=3;i++){var el=dot(i);if(!el)continue;el.classList.remove('active');el.classList.add('done');el.querySelector('.dot').textContent='\\u2713';}}
|
|
161
|
-
function ready(){if(finished)return;finished=true;allDone();statusEl.textContent='Your instance is ready \\u2014 sign in with GitHub to claim it as owner.';cta.classList.remove('hidden');
|
|
169
|
+
function ready(){if(finished)return;finished=true;allDone();statusEl.textContent='Your instance is ready \\u2014 sign in with GitHub to claim it as owner.';cta.classList.remove('hidden');
|
|
170
|
+
if(!popup){go.textContent='Open '+domain+' \\u2192';if(back)back.classList.remove('hidden');if(foot)foot.textContent='Your setup checklist is waiting where you left it \\u2014 finish it first, or open your instance now.';return;}
|
|
171
|
+
var n=4;(function tick(){if(n<=0){location.href=target;return;}go.textContent='Opening '+domain+' in '+n+'\\u2026';n--;setTimeout(tick,1000);})();}
|
|
162
172
|
function failed(){if(finished)return;finished=true;statusEl.textContent='Setup didn\\u2019t finish. Open your instance page and start the GitHub sign-in setup again.';cta.classList.remove('hidden');go.textContent='Open '+domain+' \\u2192';}
|
|
163
173
|
function revealManual(){if(manual||finished)return;manual=true;cta.classList.remove('hidden');go.textContent='Open '+domain+' \\u2192';if(foot)foot.textContent='This is taking a little longer than usual \\u2014 you can open your instance now, or keep waiting.';}
|
|
164
174
|
function schedule(){if(!finished)setTimeout(poll,3000);}
|
|
@@ -195,6 +205,7 @@ function callbackPage({ ok, title, body, state, domain }) {
|
|
|
195
205
|
</div>
|
|
196
206
|
<p class="status-line" id="status">Hang tight — keep this tab open while we finish.</p>
|
|
197
207
|
<div class="cta hidden" id="cta"><a class="btn" id="go" href="https://${esc(domain)}">Open ${esc(domain)} →</a></div>
|
|
208
|
+
<p class="muted hidden" id="back"><a href="/projects?view=new">Finish your remaining setup steps →</a></p>
|
|
198
209
|
<p class="muted" id="foot">This page opens <code>${esc(domain)}</code> for you the moment it’s ready.</p>
|
|
199
210
|
<noscript><p class="muted">When setup finishes, open <a href="https://${esc(domain)}">${esc(domain)}</a> and sign in with GitHub — that first sign-in makes you the owner.</p></noscript>
|
|
200
211
|
</main>
|
|
@@ -270,7 +281,7 @@ module.exports = function provisioningRoutes() {
|
|
|
270
281
|
const rows = await provisioning.listAllInstances(pool, { status });
|
|
271
282
|
res.json({ instances: rows.map(fleetInstance) });
|
|
272
283
|
} catch (err) {
|
|
273
|
-
|
|
284
|
+
log.error('[provisioning] GET /provisioning/fleet', err);
|
|
274
285
|
res.fail('fleet_failed', { status: 500, message: 'internal error' });
|
|
275
286
|
}
|
|
276
287
|
});
|
|
@@ -285,7 +296,7 @@ module.exports = function provisioningRoutes() {
|
|
|
285
296
|
const out = await provisioning.costLedger(pool, { limit });
|
|
286
297
|
res.json(out);
|
|
287
298
|
} catch (err) {
|
|
288
|
-
|
|
299
|
+
log.error('[provisioning] GET /provisioning/fleet/cost-ledger', err);
|
|
289
300
|
res.fail('cost_ledger_failed', { status: 500, message: 'internal error' });
|
|
290
301
|
}
|
|
291
302
|
});
|
|
@@ -338,7 +349,7 @@ module.exports = function provisioningRoutes() {
|
|
|
338
349
|
res.json({ ok: true, queued: r.created, action: 'teardown', status: inst.status, superseded: r.superseded,
|
|
339
350
|
message: r.created ? 'The instance will be torn down shortly.' : 'A teardown is already queued for that instance.' });
|
|
340
351
|
} catch (err) {
|
|
341
|
-
|
|
352
|
+
log.error('[provisioning] POST /provisioning/instances/:id/force-teardown', err);
|
|
342
353
|
res.fail('teardown_failed', { status: 500, message: 'internal error' });
|
|
343
354
|
}
|
|
344
355
|
});
|
|
@@ -513,7 +524,7 @@ module.exports = function provisioningRoutes() {
|
|
|
513
524
|
...(typeIgnored ? { type_ignored: typeIgnored } : {}),
|
|
514
525
|
});
|
|
515
526
|
} catch (err) {
|
|
516
|
-
|
|
527
|
+
log.error('[provisioning] POST /provisioning/instances', err);
|
|
517
528
|
res.fail('request_failed', { status: 500, message: 'internal error' });
|
|
518
529
|
}
|
|
519
530
|
});
|
|
@@ -561,7 +572,7 @@ module.exports = function provisioningRoutes() {
|
|
|
561
572
|
// disclosure this route has no reason to make.
|
|
562
573
|
res.json({ slug, available: reason === null, reason, address: address || null });
|
|
563
574
|
} catch (err) {
|
|
564
|
-
|
|
575
|
+
log.error('[provisioning] GET /provisioning/slug-available', err);
|
|
565
576
|
res.fail('check_failed', { status: 500, message: 'internal error' });
|
|
566
577
|
}
|
|
567
578
|
});
|
|
@@ -581,7 +592,7 @@ module.exports = function provisioningRoutes() {
|
|
|
581
592
|
}
|
|
582
593
|
res.json({ instances });
|
|
583
594
|
} catch (err) {
|
|
584
|
-
|
|
595
|
+
log.error('[provisioning] GET /provisioning/instances', err);
|
|
585
596
|
res.fail('list_failed', { status: 500, message: 'internal error' });
|
|
586
597
|
}
|
|
587
598
|
});
|
|
@@ -613,7 +624,7 @@ module.exports = function provisioningRoutes() {
|
|
|
613
624
|
open_intent: open ? { action: open.action, state: open.state } : null,
|
|
614
625
|
});
|
|
615
626
|
} catch (err) {
|
|
616
|
-
|
|
627
|
+
log.error('[provisioning] GET /provisioning/instances/:id', err);
|
|
617
628
|
res.fail('read_failed', { status: 500, message: 'internal error' });
|
|
618
629
|
}
|
|
619
630
|
});
|
|
@@ -635,7 +646,7 @@ module.exports = function provisioningRoutes() {
|
|
|
635
646
|
if (instanceAccessDenied(inst, req.builder)) return res.fail('instance_not_found', 404);
|
|
636
647
|
res.json({ ok: true, settings: provisioning.effectiveSettings(inst), options: provisioning.SETTINGS_VOCAB });
|
|
637
648
|
} catch (err) {
|
|
638
|
-
|
|
649
|
+
log.error('[provisioning] GET /provisioning/instances/:id/settings', err);
|
|
639
650
|
res.fail('settings_read_failed', { status: 500, message: 'internal error' });
|
|
640
651
|
}
|
|
641
652
|
});
|
|
@@ -670,7 +681,7 @@ module.exports = function provisioningRoutes() {
|
|
|
670
681
|
}).catch(() => {});
|
|
671
682
|
res.json({ ok: true, settings: provisioning.effectiveSettings(updated) });
|
|
672
683
|
} catch (err) {
|
|
673
|
-
|
|
684
|
+
log.error('[provisioning] PATCH /provisioning/instances/:id/settings', err);
|
|
674
685
|
res.fail('settings_update_failed', { status: 500, message: 'internal error' });
|
|
675
686
|
}
|
|
676
687
|
});
|
|
@@ -707,7 +718,7 @@ module.exports = function provisioningRoutes() {
|
|
|
707
718
|
res.json({ ok: true, queued: r.created, action: 'teardown', status: inst.status, superseded: r.superseded,
|
|
708
719
|
message: r.created ? 'Your instance will be torn down shortly.' : 'A teardown is already queued for that instance.' });
|
|
709
720
|
} catch (err) {
|
|
710
|
-
|
|
721
|
+
log.error('[provisioning] POST /provisioning/instances/:id/teardown', err);
|
|
711
722
|
res.fail('teardown_failed', { status: 500, message: 'internal error' });
|
|
712
723
|
}
|
|
713
724
|
});
|
|
@@ -832,7 +843,7 @@ module.exports = function provisioningRoutes() {
|
|
|
832
843
|
? 'Your instance is being wired to its new address — this card will show whether it answers.'
|
|
833
844
|
: 'Address recorded — it will be wired when the instance is provisioned.' });
|
|
834
845
|
} catch (err) {
|
|
835
|
-
|
|
846
|
+
log.error('[provisioning] POST /provisioning/instances/:id/domain', err);
|
|
836
847
|
res.fail('domain_attach_failed', { status: 500, message: 'internal error' });
|
|
837
848
|
}
|
|
838
849
|
});
|
|
@@ -890,7 +901,7 @@ module.exports = function provisioningRoutes() {
|
|
|
890
901
|
expires_at: expiresAt,
|
|
891
902
|
});
|
|
892
903
|
} catch (err) {
|
|
893
|
-
|
|
904
|
+
log.error('[provisioning] POST /provisioning/instances/:id/github-app', err);
|
|
894
905
|
res.fail('github_app_manifest_failed', { status: 500, message: 'internal error' });
|
|
895
906
|
}
|
|
896
907
|
});
|
|
@@ -946,7 +957,7 @@ module.exports = function provisioningRoutes() {
|
|
|
946
957
|
const ok = await provisioning.isProvisionedHostname(pool, req.query.domain);
|
|
947
958
|
return res.status(ok ? 200 : 404).json({ ok });
|
|
948
959
|
} catch (err) {
|
|
949
|
-
|
|
960
|
+
log.error('[provisioning] GET /provisioning/tls-check', err);
|
|
950
961
|
return res.status(503).json({ ok: false, error: 'check_failed' });
|
|
951
962
|
}
|
|
952
963
|
});
|
|
@@ -980,7 +991,7 @@ module.exports = function provisioningRoutes() {
|
|
|
980
991
|
app_slug: row.app_slug || null,
|
|
981
992
|
});
|
|
982
993
|
} catch (err) {
|
|
983
|
-
|
|
994
|
+
log.error('[provisioning] GET /provisioning/github-app/status', err);
|
|
984
995
|
return res.status(500).json({ ok: false, error: 'status_check_failed' });
|
|
985
996
|
}
|
|
986
997
|
});
|
|
@@ -1030,7 +1041,7 @@ module.exports = function provisioningRoutes() {
|
|
|
1030
1041
|
: callbackPage({ ok: true, title: 'GitHub app created',
|
|
1031
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>' }));
|
|
1032
1043
|
} catch (err) {
|
|
1033
|
-
|
|
1044
|
+
log.error('[provisioning] GET /provisioning/github-app/callback', err);
|
|
1034
1045
|
return sendPage(500, callbackPage({ ok: false, title: 'Something went wrong',
|
|
1035
1046
|
body: '<p>We couldn’t record GitHub’s response. Try starting the sign-in setup again from your instance’s page.</p>' }));
|
|
1036
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
|
|