@bongos/core 1.19.289 → 1.19.291

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/package-lock.json CHANGED
@@ -1,18 +1,24 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.289",
3
+ "version": "1.19.291",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.289",
9
+ "version": "1.19.291",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
13
13
  "pg": "^8.20.0"
14
14
  }
15
15
  },
16
+ "node_modules/@pinojs/redact": {
17
+ "version": "0.4.0",
18
+ "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz",
19
+ "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==",
20
+ "license": "MIT"
21
+ },
16
22
  "node_modules/accepts": {
17
23
  "version": "1.3.8",
18
24
  "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@@ -32,6 +38,15 @@
32
38
  "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
33
39
  "license": "MIT"
34
40
  },
41
+ "node_modules/atomic-sleep": {
42
+ "version": "1.0.0",
43
+ "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
44
+ "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
45
+ "license": "MIT",
46
+ "engines": {
47
+ "node": ">=8.0.0"
48
+ }
49
+ },
35
50
  "node_modules/body-parser": {
36
51
  "version": "1.20.5",
37
52
  "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz",
@@ -539,6 +554,15 @@
539
554
  "url": "https://github.com/sponsors/ljharb"
540
555
  }
541
556
  },
557
+ "node_modules/on-exit-leak-free": {
558
+ "version": "2.1.2",
559
+ "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
560
+ "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==",
561
+ "license": "MIT",
562
+ "engines": {
563
+ "node": ">=14.0.0"
564
+ }
565
+ },
542
566
  "node_modules/on-finished": {
543
567
  "version": "2.4.1",
544
568
  "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
@@ -655,6 +679,43 @@
655
679
  "split2": "^4.1.0"
656
680
  }
657
681
  },
682
+ "node_modules/pino": {
683
+ "version": "9.14.0",
684
+ "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz",
685
+ "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==",
686
+ "license": "MIT",
687
+ "dependencies": {
688
+ "@pinojs/redact": "^0.4.0",
689
+ "atomic-sleep": "^1.0.0",
690
+ "on-exit-leak-free": "^2.1.0",
691
+ "pino-abstract-transport": "^2.0.0",
692
+ "pino-std-serializers": "^7.0.0",
693
+ "process-warning": "^5.0.0",
694
+ "quick-format-unescaped": "^4.0.3",
695
+ "real-require": "^0.2.0",
696
+ "safe-stable-stringify": "^2.3.1",
697
+ "sonic-boom": "^4.0.1",
698
+ "thread-stream": "^3.0.0"
699
+ },
700
+ "bin": {
701
+ "pino": "bin.js"
702
+ }
703
+ },
704
+ "node_modules/pino-abstract-transport": {
705
+ "version": "2.0.0",
706
+ "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz",
707
+ "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==",
708
+ "license": "MIT",
709
+ "dependencies": {
710
+ "split2": "^4.0.0"
711
+ }
712
+ },
713
+ "node_modules/pino-std-serializers": {
714
+ "version": "7.1.0",
715
+ "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz",
716
+ "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==",
717
+ "license": "MIT"
718
+ },
658
719
  "node_modules/postgres-array": {
659
720
  "version": "2.0.0",
660
721
  "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
@@ -694,6 +755,22 @@
694
755
  "node": ">=0.10.0"
695
756
  }
696
757
  },
758
+ "node_modules/process-warning": {
759
+ "version": "5.1.0",
760
+ "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.1.0.tgz",
761
+ "integrity": "sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw==",
762
+ "funding": [
763
+ {
764
+ "type": "github",
765
+ "url": "https://github.com/sponsors/fastify"
766
+ },
767
+ {
768
+ "type": "opencollective",
769
+ "url": "https://opencollective.com/fastify"
770
+ }
771
+ ],
772
+ "license": "MIT"
773
+ },
697
774
  "node_modules/proxy-addr": {
698
775
  "version": "2.0.7",
699
776
  "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -722,6 +799,12 @@
722
799
  "url": "https://github.com/sponsors/ljharb"
723
800
  }
724
801
  },
802
+ "node_modules/quick-format-unescaped": {
803
+ "version": "4.0.4",
804
+ "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
805
+ "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==",
806
+ "license": "MIT"
807
+ },
725
808
  "node_modules/range-parser": {
726
809
  "version": "1.2.1",
727
810
  "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -746,6 +829,15 @@
746
829
  "node": ">= 0.8"
747
830
  }
748
831
  },
832
+ "node_modules/real-require": {
833
+ "version": "0.2.0",
834
+ "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
835
+ "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==",
836
+ "license": "MIT",
837
+ "engines": {
838
+ "node": ">= 12.13.0"
839
+ }
840
+ },
749
841
  "node_modules/safe-buffer": {
750
842
  "version": "5.2.1",
751
843
  "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -766,6 +858,15 @@
766
858
  ],
767
859
  "license": "MIT"
768
860
  },
861
+ "node_modules/safe-stable-stringify": {
862
+ "version": "2.5.0",
863
+ "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
864
+ "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
865
+ "license": "MIT",
866
+ "engines": {
867
+ "node": ">=10"
868
+ }
869
+ },
769
870
  "node_modules/safer-buffer": {
770
871
  "version": "2.1.2",
771
872
  "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -895,6 +996,15 @@
895
996
  "url": "https://github.com/sponsors/ljharb"
896
997
  }
897
998
  },
999
+ "node_modules/sonic-boom": {
1000
+ "version": "4.2.1",
1001
+ "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz",
1002
+ "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==",
1003
+ "license": "MIT",
1004
+ "dependencies": {
1005
+ "atomic-sleep": "^1.0.0"
1006
+ }
1007
+ },
898
1008
  "node_modules/split2": {
899
1009
  "version": "4.2.0",
900
1010
  "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
@@ -913,6 +1023,15 @@
913
1023
  "node": ">= 0.8"
914
1024
  }
915
1025
  },
1026
+ "node_modules/thread-stream": {
1027
+ "version": "3.2.0",
1028
+ "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.2.0.tgz",
1029
+ "integrity": "sha512-zLBvqpwr4Esa0kRjcrzGU6zL25lePWaCLMx0RQFrmteozIfeNdaMLpG5U7PeHzvlFkAWaRKA9/KVW4F60iB+qw==",
1030
+ "license": "MIT",
1031
+ "dependencies": {
1032
+ "real-require": "^0.2.0"
1033
+ }
1034
+ },
916
1035
  "node_modules/toidentifier": {
917
1036
  "version": "1.0.1",
918
1037
  "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.289",
3
+ "version": "1.19.291",
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",
@@ -25,7 +25,8 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "express": "^4.21.2",
28
- "pg": "^8.20.0"
28
+ "pg": "^8.20.0",
29
+ "pino": "^9.14.0"
29
30
  },
30
31
  "overrides": {
31
32
  "undici": "^6.27.0"
@@ -252,10 +252,13 @@ function isNpmStripped(pathRel) {
252
252
  // git) and staged in their place. Everything else still ships through the
253
253
  // normal isPublishable()/processCoreFile path.
254
254
  //
255
- // Core deps are express + pg (+ an undici pin) — ADR 0108 §Context. discord.js
256
- // (+ its undici requirement) is an OPTIONAL MODULE dependency, not core; a
257
- // consumer that enables the discord module installs it itself.
258
- const CORE_DEP_NAMES = ['express', 'pg'];
255
+ // Core deps are express + pg + pino (+ an undici pin) — ADR 0108 §Context.
256
+ // pino joined the core boot path with the structured logger (task 1003130:
257
+ // platform-server → serve-internal → routes → middleware/audit → logger); a
258
+ // published core without it crashes MODULE_NOT_FOUND at startup (task 1003160).
259
+ // discord.js (+ its undici requirement) is an OPTIONAL MODULE dependency, not
260
+ // core; a consumer that enables the discord module installs it itself.
261
+ const CORE_DEP_NAMES = ['express', 'pg', 'pino'];
259
262
 
260
263
  // Pure — takes the source package.json's `dependencies`/`overrides` as plain
261
264
  // objects so this is unit-testable with no disk I/O. Throws if a required core
@@ -151,11 +151,80 @@ async function patchIdea(id, patch) {
151
151
  return r.data?.idea;
152
152
  }
153
153
 
154
- async function createTask({ versionId, title, description, priority, sourceRef }) {
155
- // NOTE: POST /api/gds/tasks doesn't currently accept 'kind' — tasks default
156
- // to 'unclassified' and the server-side classifier (B1) runs separately.
157
- // The idea's auto-classified kind is therefore informational only at this
158
- // step; the task description preserves the body for re-classification.
154
+ // resolveGoalDefault — what goal should a triaged promotion land in? (task
155
+ // 1003071 / R13.)
156
+ //
157
+ // PRECEDENCE IS DELIBERATE:
158
+ // 1. `idea.suggested_goal_id` — the STORED hint. inbox.captureIdea writes it
159
+ // even when the matrix declined to route, precisely so "/idea-triage
160
+ // inherits the guess instead of re-deriving it" (core_222, its own comment).
161
+ // A human or a filing surface already named this goal; a heuristic must not
162
+ // overrule that.
163
+ // 2. the R07 suggester (GET /goals/suggestions) — only when there is no hint.
164
+ // Best-scoring candidate, and its score/matched_terms are shown so the
165
+ // operator can see WHY before accepting.
166
+ // Returns { goalId, label, source } — goalId null when neither yields anything.
167
+ // The caller prints `source` as provenance, because "the suggester guessed this"
168
+ // and "the filing already named this" deserve different trust. Never throws: a
169
+ // suggester outage degrades to "no default", it does not block a triage verdict.
170
+ //
171
+ // `client` is injectable so the precedence can be driven end-to-end in a test
172
+ // rather than pattern-matched in the source. (Task 1003102 shipped a handler
173
+ // whose collaborators were not injectable; its tests read the source, passed,
174
+ // and the route 500'd on every call. Not repeating that.)
175
+ async function resolveGoalDefault(idea, versionId, { client = cliClient } = {}) {
176
+ // Guarded: a junk stored value must not become goalId NaN and then a goal_id
177
+ // the server rejects with 400 on every retry.
178
+ const hint = Number(idea.suggested_goal_id);
179
+ if (Number.isInteger(hint) && hint > 0) {
180
+ return { goalId: hint, label: `goal ${hint}`, source: 'hint' };
181
+ }
182
+ try {
183
+ const api = await client();
184
+ // The GENERATED typed method (regenerated when R07 landed), not the generic
185
+ // request() escape hatch — so a path or param rename reds the client build.
186
+ const r = await api.goals.getGoalsSuggestions({
187
+ query: { version: versionId, title: idea.title || '', body: (idea.body_md || '').slice(0, 2000), limit: 3 },
188
+ });
189
+ const top = r.ok && r.data && Array.isArray(r.data.suggestions) ? r.data.suggestions[0] : null;
190
+ if (!top) return { goalId: null, label: null, source: 'none' };
191
+ const why = (top.matched_terms || []).join('/');
192
+ return { goalId: Number(top.id), label: `${top.id} ${trim(top.title, 44)} (score ${top.score}${why ? ' · ' + why : ''})`, source: 'suggester' };
193
+ } catch (_) {
194
+ return { goalId: null, label: null, source: 'unavailable' };
195
+ }
196
+ }
197
+
198
+ // What the operator's answer to the goal prompt means. Blank ACCEPTS the shown
199
+ // default — declining has to be said ('-' or 'none'), because silently dropping a
200
+ // real default on a bare Enter is how work ends up in the general bucket by
201
+ // accident. Declining is legal: the server then applies the catch-all itself.
202
+ function applyGoalAnswer(defaultGoalId, rawAnswer) {
203
+ const answer = String(rawAnswer == null ? '' : rawAnswer).trim();
204
+ if (answer === '') return { goalId: defaultGoalId, warn: null };
205
+ if (answer === '-' || answer.toLowerCase() === 'none') return { goalId: null, warn: null };
206
+ const parsed = Number(answer);
207
+ if (!Number.isInteger(parsed) || parsed <= 0) {
208
+ return { goalId: null, warn: "not a goal id — falling back to the version's general bucket" };
209
+ }
210
+ return { goalId: parsed, warn: null };
211
+ }
212
+
213
+ async function createTask({ versionId, title, description, priority, sourceRef, goalId = null }) {
214
+ // (The old note here said POST /api/gds/tasks does not accept 'kind'. That is
215
+ // no longer true — routes/tasks.js takes it, and the hall promote path already
216
+ // sends it. Left un-sent from THIS path deliberately: changing what kind a
217
+ // triaged promotion lands with is a behaviour change task R13 was not asked to
218
+ // make, and the server classifier still runs. Corrected rather than deleted so
219
+ // the next reader does not re-derive it.)
220
+ //
221
+ // task 1003071 (R13): goal_id is the point of this task. Omitting it does NOT
222
+ // leave the task goal-less — db.createTask (modules/lifecycle/db.js) defaults a
223
+ // goal-less create into the version's catch-all "<version> — general" goal. That
224
+ // bucket is the actual defect: it carries no done-when criteria, so since ADR
225
+ // 0183 made criteria close themselves off LINKED tasks, work parked there can
226
+ // never advance anything, and it does not read as real goal work on the board.
227
+ // Naming the goal here is what stops a triaged promotion falling into it.
159
228
  const body = {
160
229
  version_id: versionId,
161
230
  title,
@@ -165,6 +234,7 @@ async function createTask({ versionId, title, description, priority, sourceRef }
165
234
  source: 'idea_inbox',
166
235
  source_ref: sourceRef ?? null,
167
236
  };
237
+ if (goalId != null) body.goal_id = Number(goalId);
168
238
  const api = await cliClient();
169
239
  const r = await api.tasks.postTasks({ body });
170
240
  if (!r.ok) {
@@ -296,7 +366,7 @@ async function main() {
296
366
  }
297
367
 
298
368
  if (ans === 'p' || ans === 'promote') {
299
- // Promote: ask for version + priority (defaulted from idea).
369
+ // Promote: version, priority, title, goal — each defaulted from the idea.
300
370
  const defaultVersion = idea.suggested_version || 'PMS-V2';
301
371
  const versionAns = (await prompt(rl, ` version [${defaultVersion}]: `)).trim();
302
372
  const versionId = versionAns || defaultVersion;
@@ -308,6 +378,23 @@ async function main() {
308
378
  const titleAns = (await prompt(rl, ` title [${trim(idea.title, 60)}]: `)).trim();
309
379
  const title = titleAns || idea.title;
310
380
 
381
+ // task 1003071 (R13): a triaged promotion NAMES its goal. The default
382
+ // comes from the stored hint, else the R07 suggester; '-' opts out
383
+ // explicitly, which falls through to the version's catch-all "general"
384
+ // bucket — now a choice someone typed rather than the silent default.
385
+ // The EFFECTIVE title, not idea.title: the suggester matches on title
386
+ // terms, and the operator may just have retitled this.
387
+ const gd = await resolveGoalDefault({ ...idea, title }, versionId);
388
+ if (gd.source === 'suggester') console.log(` suggested goal: ${gd.label}`);
389
+ if (gd.source === 'unavailable') console.log(' (goal suggester unreachable — enter a goal id, or "-" to accept the general bucket)');
390
+ const goalPrompt = gd.goalId != null
391
+ ? ` goal [${gd.goalId}] ("-" for the version's general bucket): `
392
+ : ' goal (blank/"-" for the version\'s general bucket): ';
393
+ const goalAns = (await prompt(rl, goalPrompt)).trim();
394
+ const picked = applyGoalAnswer(gd.goalId, goalAns);
395
+ if (picked.warn) console.log(` ! ${picked.warn}`);
396
+ const goalId = picked.goalId;
397
+
311
398
  try {
312
399
  const task = await createTask({
313
400
  versionId,
@@ -315,6 +402,7 @@ async function main() {
315
402
  description: idea.body_md || '',
316
403
  priority: Number.isFinite(priority) ? priority : null,
317
404
  sourceRef: `idea:${idea.id}`,
405
+ goalId,
318
406
  });
319
407
  await patchIdea(idea.id, {
320
408
  status: 'promoted',
@@ -323,7 +411,7 @@ async function main() {
323
411
  });
324
412
  summary.promoted++;
325
413
  promotedIds.push({ ideaId: idea.id, taskId: task.id });
326
- console.log(` ✓ promoted #${idea.id} → task #${task.id} (${versionId}, P${priority})`);
414
+ console.log(` ✓ promoted #${idea.id} → task #${task.id} (${versionId}, P${priority}${goalId != null ? `, goal ${goalId}` : ', → general bucket'})`);
327
415
  } catch (err) {
328
416
  console.error(` ! ${err.message}`);
329
417
  }
@@ -360,6 +448,9 @@ if (require.main === module) {
360
448
  }
361
449
 
362
450
  module.exports = {
451
+ resolveGoalDefault,
452
+ applyGoalAnswer,
453
+ createTask,
363
454
  resolveDevelopmentPath,
364
455
  parseDevelopment,
365
456
  loadDevelopment,
package/src/module-api.js CHANGED
@@ -1005,8 +1005,12 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
1005
1005
  // landed since 1.19.287 with no explicit bump. run 32546561232. (task 1002620)
1006
1006
  // 1.19.289 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1007
1007
  // landed since 1.19.288 with no explicit bump. run 32549912116. (task 1002620)
1008
+ // 1.19.290 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1009
+ // landed since 1.19.289 with no explicit bump. run 32552538814. (task 1002620)
1010
+ // 1.19.291 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1011
+ // landed since 1.19.290 with no explicit bump. run 32580353000. (task 1002620)
1008
1012
  // ---------------------------------------------------------------------------
1009
- const CORE_VERSION = '1.19.289'; // CI auto-patch carrier (ADR 0161); see changelog above
1013
+ const CORE_VERSION = '1.19.291'; // CI auto-patch carrier (ADR 0161); see changelog above
1010
1014
 
1011
1015
  // A namespaced logger so a module's log lines are attributable + consistent.
1012
1016
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -174,7 +174,7 @@ t('planCodemod rewrites host files, skips core files, reports unmapped', () => {
174
174
 
175
175
  t('every CORE_INTERNAL_MAP target is exported by the core package (a rewrite never outruns an export)', () => {
176
176
  const { buildPackageJson } = require('../scripts/gds/package-core.js');
177
- const pkg = buildPackageJson({ version: '0.0.0-test', sourceDeps: { express: '^4.0.0', pg: '^8.0.0' } });
177
+ const pkg = buildPackageJson({ version: '0.0.0-test', sourceDeps: { express: '^4.0.0', pg: '^8.0.0', pino: '^9.0.0' } });
178
178
  for (const mapped of Object.values(CORE_INTERNAL_MAP)) {
179
179
  assert.ok(pkg.exports[`./${mapped}`], `@bongos/core must export ./${mapped} (targeted by the codemod)`);
180
180
  }
@@ -360,15 +360,15 @@ t('TAR_ROOT is the npm-required "package/" (not the ARTIFACT_NAME dir)', () => {
360
360
  assert.equal(TAR_ROOT, 'package/');
361
361
  });
362
362
 
363
- t('buildPackageJson: prunes to express+pg, keeps the source ranges + the undici override', () => {
363
+ t('buildPackageJson: prunes to express+pg+pino, keeps the source ranges + the undici override', () => {
364
364
  const pkg = buildPackageJson({
365
365
  version: '1.14.0',
366
- sourceDeps: { express: '^4.21.2', pg: '^8.20.0', phaser: '^3.87.0', 'discord.js': '^14.26.4' },
366
+ sourceDeps: { express: '^4.21.2', pg: '^8.20.0', pino: '^9.14.0', phaser: '^3.87.0', 'discord.js': '^14.26.4' },
367
367
  sourceOverrides: { undici: '^6.27.0' },
368
368
  });
369
369
  assert.equal(pkg.name, '@bongos/core');
370
370
  assert.equal(pkg.version, '1.14.0');
371
- assert.deepEqual(pkg.dependencies, { express: '^4.21.2', pg: '^8.20.0' });
371
+ assert.deepEqual(pkg.dependencies, { express: '^4.21.2', pg: '^8.20.0', pino: '^9.14.0' });
372
372
  assert.equal(pkg.dependencies.phaser, undefined);
373
373
  assert.equal(pkg.dependencies['discord.js'], undefined);
374
374
  assert.deepEqual(pkg.overrides, { undici: '^6.27.0' });
@@ -400,7 +400,7 @@ t('publishArgs: private (restricted) publish, dry-run unless live (task 2089 / A
400
400
  });
401
401
 
402
402
  t('buildPackageJson: omits `overrides` when the source has none', () => {
403
- const pkg = buildPackageJson({ version: '1.0.0', sourceDeps: { express: '^4.0.0', pg: '^8.0.0' } });
403
+ const pkg = buildPackageJson({ version: '1.0.0', sourceDeps: { express: '^4.0.0', pg: '^8.0.0', pino: '^9.0.0' } });
404
404
  assert.equal(pkg.overrides, undefined);
405
405
  });
406
406
 
@@ -408,8 +408,8 @@ t('buildPackageJson: throws when a required core dep is missing from the source'
408
408
  assert.throws(() => buildPackageJson({ version: '1.0.0', sourceDeps: { express: '^4.0.0' } }), /missing required core dependency "pg"/);
409
409
  });
410
410
 
411
- t('CORE_DEP_NAMES is exactly [express, pg]', () => {
412
- assert.deepEqual(CORE_DEP_NAMES, ['express', 'pg']);
411
+ t('CORE_DEP_NAMES is exactly [express, pg, pino]', () => {
412
+ assert.deepEqual(CORE_DEP_NAMES, ['express', 'pg', 'pino']);
413
413
  });
414
414
 
415
415
  // A tiny synthetic lockfile fixture with a THREE-level chain (root -> a -> b) and