@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.
@@ -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
- console.error('[gds] POST /memory/sync batch transaction error', { builderId, err: e });
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
- console.error('[gds] POST /memory/sync store error', { builderId, path, code, err: e });
402
+ log.error('[gds] POST /memory/sync store error', { builderId, path, code, err: e });
401
403
  } else if (code === 'STORE_FAILED') {
402
- console.error('[gds] POST /memory/sync unexpected error', { builderId, path, err: e });
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
- console.error('[gds] GET /memory/files', { builderId, err });
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
- console.error('[gds] GET /memory/file', { builderId, path, err });
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
- console.error('[gds] DELETE /memory/file', { builderId, path, err });
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
- console.error('[gds] GET /memory/builders/:builderId/files', { targetId, err });
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
- console.error('[gds] forensic-read audit insert failed (read withheld)', {
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
- console.error('[gds] GET /memory/builders/:builderId/file', { targetId, path, err });
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
- console.error('[gds] forensic-read audit insert failed (read withheld)', {
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
- console.error('[gds] GET /memory/bfg/builders/:builderId/files', { targetId, err });
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
- console.error('[gds] bfg-read audit insert failed (read withheld)', { route, targetId, err: auditErr && auditErr.message ? auditErr.message : String(auditErr) });
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
- console.error('[gds] GET /memory/bfg/builders/:builderId/file', { targetId, path, err });
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
- console.error('[gds] bfg-read audit insert failed (read withheld)', { route, targetId, path, err: auditErr && auditErr.message ? auditErr.message : String(auditErr) });
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
- console.error('[gds] bfg-write target lookup', { targetId, err });
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
- console.error('[gds] POST /memory/builders/:builderId/bfg-write', e);
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) console.warn(`[platform-identity] SLO: ${origin} returned ${resp.status}`);
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
- console.warn(`[platform-identity] SLO: notifying ${origin} failed (non-blocking):`, err && err.message ? err.message : err);
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
- console.error('[platform-identity] SLO fan-out failed (non-blocking):', err && err.message ? err.message : err);
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
- console.error('[platform-identity] /sso/authorize getClient', err);
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
- console.error('[platform-identity] /sso/authorize resolveSession', err);
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
- console.error('[platform-identity] /sso/authorize mint', err);
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
- console.error('[platform-identity] POST /sso/token', err);
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
- console.error('[platform-identity] /sso/device/start', err);
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
- console.error('[platform-identity] /sso/device/poll', err);
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
- console.error('[platform-identity] /sso/membership/check-in', err);
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
- console.error('[platform-identity] /sso/activity/rollup', err);
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');var n=4;(function tick(){if(n<=0){location.href=target;return;}go.textContent='Opening '+domain+' in '+n+'\\u2026';n--;setTimeout(tick,1000);})();}
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
- console.error('[provisioning] GET /provisioning/fleet', err);
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
- console.error('[provisioning] GET /provisioning/fleet/cost-ledger', err);
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
- console.error('[provisioning] POST /provisioning/instances/:id/force-teardown', err);
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
- console.error('[provisioning] POST /provisioning/instances', err);
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
- console.error('[provisioning] GET /provisioning/slug-available', err);
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
- console.error('[provisioning] GET /provisioning/instances', err);
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
- console.error('[provisioning] GET /provisioning/instances/:id', err);
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
- console.error('[provisioning] GET /provisioning/instances/:id/settings', err);
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
- console.error('[provisioning] PATCH /provisioning/instances/:id/settings', err);
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
- console.error('[provisioning] POST /provisioning/instances/:id/teardown', err);
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
- console.error('[provisioning] POST /provisioning/instances/:id/domain', err);
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
- console.error('[provisioning] POST /provisioning/instances/:id/github-app', err);
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
- console.error('[provisioning] GET /provisioning/tls-check', err);
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
- console.error('[provisioning] GET /provisioning/github-app/status', err);
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
- console.error('[provisioning] GET /provisioning/github-app/callback', err);
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.336",
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.336",
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.336",
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",
@@ -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
- console.log('[auth] hub membership check-in failed (non-blocking):', err.message);
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
- console.log('[auth] hub activity rollup failed (non-blocking):', err.message);
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
- console.error('[auth] webAdmissionStatus', err);
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
- console.log(`[auth] first-admin bootstrap: seated ${builder.github_login} as Archon (configured firstAdmin, no prior Archon).`);
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
- console.error('[auth] first-admin bootstrap failed (non-fatal):', err.message);
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
- console.error('[auth] rank-role sync failed (non-fatal):', err.message);
252
+ log.error('[auth] rank-role sync failed (non-fatal):', err.message);
250
253
  }
251
254
  }
252
255