@bongos/core 1.19.800 → 1.19.801

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 CHANGED
@@ -2,11 +2,11 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.19.800",
6
- "core_contract": "1.19.800",
7
- "source_commit": "61c8980e6b9f6fb0cbd611f41520cbcae4780f11",
5
+ "core_version": "1.19.801",
6
+ "core_contract": "1.19.801",
7
+ "source_commit": "9cb40ed355c3f8bb4d4b67fbc6e6ba939793827a",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-17T02:01:42.605Z",
9
+ "built_at": "2026-09-17T02:11:49.566Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 487,
@@ -17,7 +17,7 @@
17
17
  "gate": "passed"
18
18
  },
19
19
  "file_count": 2699,
20
- "tree_sha256": "0d54a704f2ba5c26c517372981337570ff4a70da884edee06ef52c30e836fda3",
20
+ "tree_sha256": "2587f0458e02facb77f933eab7710e8b69181347c171c7cc65600840dc02b6ce",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -2532,7 +2532,7 @@
2532
2532
  {
2533
2533
  "path": "docs/module-api-changelog.md",
2534
2534
  "mode": "0000644",
2535
- "sha256": "07a1c706c1ddc1d0c51da0737e861e8af59c0a50caa9119cfca43f1ad3fde6ba"
2535
+ "sha256": "3b499ff68308c4d7cab67302718a4c853209e02543f016b4ff710c94436e98a2"
2536
2536
  },
2537
2537
  {
2538
2538
  "path": "docs/modules-contract.md",
@@ -7712,12 +7712,12 @@
7712
7712
  {
7713
7713
  "path": "package-lock.json",
7714
7714
  "mode": "0000644",
7715
- "sha256": "2fa689bfe81f60fd4b8ca8e0304b2eac417c172af397d23c1de3427e0a6544a5"
7715
+ "sha256": "c276ff38b81a024e50fce58a6f52594a7927a2dfddd50665cd6dd75df09ad73c"
7716
7716
  },
7717
7717
  {
7718
7718
  "path": "package.json",
7719
7719
  "mode": "0000644",
7720
- "sha256": "2e40419061babf39395a90a2b45c7c7409e1acce44e86984a3913dd6010a485a"
7720
+ "sha256": "e2e2a9e07886e3e8a7c77787d04ed7e8160fd682b1d4aace12914f93a93b358f"
7721
7721
  },
7722
7722
  {
7723
7723
  "path": "public-docs/index.html",
@@ -8977,7 +8977,7 @@
8977
8977
  {
8978
8978
  "path": "scripts/gds/ship-honesty.js",
8979
8979
  "mode": "0000644",
8980
- "sha256": "3ab4816fdd43d589cda73698a514f8e23103be166c1650fd24beeee9f529bb5b"
8980
+ "sha256": "f9cafe9726d6cedc398dc9898e33a9d2d5eca32354661b58d7f820e195d59137"
8981
8981
  },
8982
8982
  {
8983
8983
  "path": "scripts/gds/ship-io.js",
@@ -8997,7 +8997,7 @@
8997
8997
  {
8998
8998
  "path": "scripts/gds/ship-merge.js",
8999
8999
  "mode": "0000644",
9000
- "sha256": "b602b4cf8f0e66610991237e1cb7ade9cd5c14659b155ce3512268d40ae670eb"
9000
+ "sha256": "99401464922cc545681786e59ebdc5ad35ec5578f7562c7986e458af797e85ab"
9001
9001
  },
9002
9002
  {
9003
9003
  "path": "scripts/gds/ship-preflight-steps.js",
@@ -9602,7 +9602,7 @@
9602
9602
  {
9603
9603
  "path": "src/module-api.js",
9604
9604
  "mode": "0000644",
9605
- "sha256": "0a1e5d3b5db82b4a63d3936fd70ff4e079a1eef0b81b8d7041fcb3f1aaffc109"
9605
+ "sha256": "de45933f1b58f0a41a36b2de49fc55f758c34e42b48c2599d2dd4d1afad9a88d"
9606
9606
  },
9607
9607
  {
9608
9608
  "path": "src/module-loader/catalog.js",
@@ -12927,7 +12927,7 @@
12927
12927
  {
12928
12928
  "path": "tests/ship_cannot_lie.mjs",
12929
12929
  "mode": "0000644",
12930
- "sha256": "55e94ed59bc1bd19e4a3dd0e9a9a415dde659c5d71af0dd827d1b288e66de3c7"
12930
+ "sha256": "55adaf198cd21d4f267f1f50c8e31e176d4b2f037a87abc5a7d10e5df0d123d2"
12931
12931
  },
12932
12932
  {
12933
12933
  "path": "tests/ship_card.mjs",
@@ -2074,5 +2074,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
2074
2074
  landed since 1.19.798 with no explicit bump. run 35170980369. (task 1002620)
2075
2075
  1.19.800 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2076
2076
  landed since 1.19.799 with no explicit bump. run 35172784518. (task 1002620)
2077
+ 1.19.801 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2078
+ landed since 1.19.800 with no explicit bump. run 35173442594. (task 1002620)
2077
2079
  ---------------------------------------------------------------------------
2078
2080
  ```
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.800",
3
+ "version": "1.19.801",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.800",
9
+ "version": "1.19.801",
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.800",
3
+ "version": "1.19.801",
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",
@@ -249,9 +249,66 @@ function staleSummaryNotice(taskId, storedSummary) {
249
249
  ];
250
250
  }
251
251
 
252
+ // ---- a --db-only that cannot apply must SAY so (task 1003959) ---------------
253
+ //
254
+ // THE FIFTH SILENCE, and the only one that is not about prose: a flag the
255
+ // operator typed, dropped without a word. zeroCommitVerdict(commitsAhead, dbOnly)
256
+ // consults dbOnly ONLY at zero commits ahead — with commits ahead the verdict is
257
+ // 'has-commits' and the run falls through to the ordinary merge/PR path, which
258
+ // never receives dbOnly at all.
259
+ //
260
+ // The BEHAVIOUR is right and stays: a branch carrying real commits must be
261
+ // merged, not waved through as an empty ship. What was missing is the sentence
262
+ // saying so, and its absence is expensive out of proportion to its size — task
263
+ // 1003229 was filed against --db-only and survived for days on the strength of
264
+ // it. The reporter's flag was dropped here, their PR then failed to land for an
265
+ // unrelated reason (the reconciler, task 1002723), and with nothing in the
266
+ // output distinguishing an honoured flag from an ignored one, every visible sign
267
+ // pointed at --db-only.
268
+ //
269
+ // So: a notice, not a refusal and not a behaviour change — the same posture as
270
+ // staleSummaryNotice above, and testable for the same reason. It names the path
271
+ // actually being taken, because "your flag was ignored" without "and here is
272
+ // what is happening instead" only moves the confusion.
273
+ //
274
+ // Returns [] when the flag was HONOURED (zero commits ahead) or never passed —
275
+ // a notice that fires on the happy path is noise.
276
+ function dbOnlyIgnoredNotice(taskId, { dbOnly = false, commitsAhead = '0', branch = '', deployMode = 'laptop' } = {}) {
277
+ if (!dbOnly) return [];
278
+ // The one case the flag really covers. gitOk returns null when the count could
279
+ // not be read, which zeroCommitVerdict already treats as 'has-commits' — keep
280
+ // that reading rather than inventing a second opinion about the same branch.
281
+ if (String(commitsAhead) === '0') return [];
282
+
283
+ // Branch names and a git count both reach a TERMINAL here. git forbids control
284
+ // characters in a refname, so this strips nothing in practice — but the notice
285
+ // exists to be believed, and one that could be repainted from below by its own
286
+ // subject would be the wrong thing to have built.
287
+ const clean = (s) => String(s == null ? '' : s).replace(/[\u0000-\u001f\u007f-\u009f]/g, ' ').replace(/\s+/g, ' ').trim();
288
+ const head = clean(branch) || 'this branch';
289
+ const n = Number(clean(commitsAhead));
290
+ const ahead = Number.isInteger(n) && n > 0
291
+ ? `${n} commit${n === 1 ? '' : 's'} ahead of origin/main`
292
+ : 'ahead of origin/main';
293
+ // Name the real path, not a generic one — 'ci' lands by PR and never touches
294
+ // the operator's main worktree; 'laptop' merges and deploys from this machine.
295
+ const instead = deployMode === 'ci'
296
+ ? `pushing ${head} and landing it by PR, with CI deploying`
297
+ : `pushing ${head}, merging it into main here and deploying`;
298
+
299
+ return [
300
+ `⚠ --db-only was passed, but ${head} is ${ahead} — the flag does not apply and is being IGNORED.`,
301
+ ` Shipping the normal way instead: ${instead}. Nothing is skipped and nothing is stranded.`,
302
+ ' --db-only only covers a ship with NOTHING to merge — work written straight to the prod DB.',
303
+ ' If you expected a zero-commit ship, this is the branch disagreeing; see what is on it:',
304
+ ' git log --oneline origin/main..HEAD',
305
+ ` Then re-ship when the branch is what you meant: node scripts/gds/ship.js ${taskId}`,
306
+ ];
307
+ }
308
+
252
309
  module.exports = {
253
310
  resolveProse, markShipProgress, fatalRecoveryLines, smokeGateMessages,
254
- proseFlagPresent, regradeNotesRefusal, staleSummaryNotice,
311
+ proseFlagPresent, regradeNotesRefusal, staleSummaryNotice, dbOnlyIgnoredNotice,
255
312
  _resetForTest: () => { shipProgress = { stage: 'preflight', taskId: null }; },
256
313
  _peekForTest: () => shipProgress,
257
314
  };
@@ -26,9 +26,15 @@ const {
26
26
  landed,
27
27
  } = require('./ship-land.js');
28
28
  const { runSmoke } = require('./ship-grade.js');
29
+ const { dbOnlyIgnoredNotice } = require('./ship-honesty.js');
29
30
  const { currentBranch, mainWorktreeBusy, regenerateApiClient, regenerateApiDocs, regenerateDiagrams, regenerateFileMap, regenerateRepoMap, regenerateSessionIndex } = require('./ship-regen.js');
30
31
  const { REPO_ROOT } = require('./ship-state.js');
31
32
 
33
+ // zeroCommitVerdict — dbOnly is deliberately consulted ONLY at zero commits
34
+ // ahead: a branch carrying real commits must be merged, whatever flag was typed.
35
+ // That asymmetry is correct and is not a bug — but it means an explicitly-passed
36
+ // --db-only can be DROPPED, so mergeLocally pairs this with dbOnlyIgnoredNotice
37
+ // (task 1003959) rather than letting the drop happen in silence.
32
38
  function zeroCommitVerdict(commitsAhead, dbOnly) {
33
39
  if (commitsAhead !== '0') return 'has-commits';
34
40
  return dbOnly ? 'db-only' : 'refuse';
@@ -141,6 +147,11 @@ async function mergeLocally(taskId, valueSummary, { dbOnly = false } = {}) {
141
147
  }
142
148
  const commitsAhead = gitOk('git rev-list origin/main..HEAD --count');
143
149
  const verdict = zeroCommitVerdict(commitsAhead, dbOnly);
150
+ // task 1003959: the verdict above drops an explicitly-passed --db-only when the
151
+ // branch has commits. Say so HERE — before the push, the merge and the deploy —
152
+ // so the operator learns their flag did not apply while the run can still be
153
+ // stopped, not from a PR that landed differently than they expected.
154
+ for (const line of dbOnlyIgnoredNotice(taskId, { dbOnly, commitsAhead, branch, deployMode })) console.log(line);
144
155
  if (verdict === 'refuse') {
145
156
  // task 391 (idea #37): the old code short-circuited ANY zero-commit branch
146
157
  // straight to shipped — silently advancing a claim with empty/wrong touches[]
package/src/module-api.js CHANGED
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
71
71
  // there. scripts/gds/bump-version.js still rewrites the literal below; it appends
72
72
  // the entry to that file. Look for a version's history there, not here.
73
73
  // ---------------------------------------------------------------------------
74
- const CORE_VERSION = '1.19.800'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
74
+ const CORE_VERSION = '1.19.801'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
75
75
 
76
76
  // A namespaced logger so a module's log lines are attributable + consistent.
77
77
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -21,6 +21,10 @@
21
21
  // (4) --regrade must not silently REUSE the failed attempt's summary. The same
22
22
  // silence as (3) from the other side: prose the builder never handed over,
23
23
  // published as though they had (task 1003693).
24
+ //
25
+ // (5) An explicitly-passed --db-only that cannot apply must say so. The same
26
+ // silence again, and the first one that is not about prose: a flag the
27
+ // operator typed, dropped by the merge leg without a word (task 1003959).
24
28
  import { strict as assert } from 'node:assert';
25
29
  import { test } from 'node:test';
26
30
  import { createRequire } from 'node:module';
@@ -30,7 +34,7 @@ const require = createRequire(import.meta.url);
30
34
  const { argText } = require('../scripts/gds/cli-lib.js');
31
35
  const {
32
36
  fatalRecoveryLines, markShipProgress, _peekForTest,
33
- proseFlagPresent, regradeNotesRefusal, staleSummaryNotice,
37
+ proseFlagPresent, regradeNotesRefusal, staleSummaryNotice, dbOnlyIgnoredNotice,
34
38
  } = require('../scripts/gds/ship-honesty.js');
35
39
 
36
40
  // Injected IO so no case touches the filesystem.
@@ -371,3 +375,88 @@ test('the notice is wired into the regrade path, before any grade spend', () =>
371
375
  const preflight = code.indexOf('preflight(claimLike');
372
376
  assert.ok(preflight !== -1 && call < preflight, 'and prints before the preflight and the panel');
373
377
  });
378
+ // ── (5) a --db-only that cannot apply says so (task 1003959) ─────────────────
379
+ //
380
+ // The same silence as (3) and (4) from a third side, and the only one that is
381
+ // not about prose: a flag the operator TYPED, dropped without a word.
382
+ // zeroCommitVerdict consults dbOnly only at zero commits ahead, so with commits
383
+ // ahead the ordinary merge/PR path runs and never sees the flag. That path is
384
+ // correct; the silence is what let task 1003229 sit open against --db-only while
385
+ // the real stall was elsewhere (task 1002723).
386
+
387
+ test('the dropped --db-only notice names the flag, the branch and the path taken instead', () => {
388
+ const lines = dbOnlyIgnoredNotice(1003959, {
389
+ dbOnly: true, commitsAhead: '3', branch: 'task-1003959', deployMode: 'ci',
390
+ });
391
+ const text = lines.join('\n');
392
+ // The whole deliverable: the operator must be able to tell an honoured flag
393
+ // from an ignored one by reading the output.
394
+ assert.match(text, /--db-only/, 'names the flag that was dropped');
395
+ assert.match(text, /IGNORED/, 'and says plainly that it was');
396
+ assert.match(text, /3 commits ahead of origin\/main/, 'names why it does not apply');
397
+ assert.match(text, /task-1003959/, 'and which branch disagreed');
398
+ // "your flag was ignored" without "and here is what happens instead" only
399
+ // moves the confusion one step.
400
+ assert.match(text, /landing it by PR/, 'names the path actually being taken');
401
+ assert.match(text, /nothing is stranded/i, 'and says the ship is not in danger');
402
+ // A notice that names no next step is noise with a warning sign on it.
403
+ assert.match(text, /git log --oneline origin\/main\.\.HEAD/, 'names a runnable way to check the branch');
404
+ assert.match(text, /ship\.js 1003959/, 'and the re-ship with the real task id');
405
+ });
406
+
407
+ test('the notice names the LAPTOP path when that is the one being taken', () => {
408
+ // Naming a PR land on a box that is about to merge and deploy locally would be
409
+ // a second untruth told while correcting the first.
410
+ const laptop = dbOnlyIgnoredNotice(7, { dbOnly: true, commitsAhead: '1', branch: 'task-7', deployMode: 'laptop' }).join('\n');
411
+ assert.match(laptop, /merging it into main here and deploying/, 'the local merge chain');
412
+ assert.ok(!/by PR/.test(laptop), 'and not the PR path it is not taking');
413
+ assert.match(laptop, /1 commit ahead/, 'singular reads as English');
414
+ });
415
+
416
+ test('the notice is silent when the flag was honoured, or never passed', () => {
417
+ // Zero commits ahead IS the case --db-only covers: the flag applied, nothing
418
+ // was dropped, and warning there would train the builder to skip the notice.
419
+ assert.deepEqual(dbOnlyIgnoredNotice(1, { dbOnly: true, commitsAhead: '0', branch: 'task-1' }), []);
420
+ for (const ahead of ['0', '4', null, undefined]) {
421
+ assert.deepEqual(dbOnlyIgnoredNotice(1, { dbOnly: false, commitsAhead: ahead }), [], 'no flag, no notice');
422
+ }
423
+ assert.deepEqual(dbOnlyIgnoredNotice(1), [], 'and a bare call cannot fire it');
424
+ });
425
+
426
+ test('an unreadable commit count still produces a truthful notice', () => {
427
+ // gitOk returns null when the count cannot be read, and zeroCommitVerdict
428
+ // already reads that as 'has-commits'. The notice must not invent "null
429
+ // commits ahead" — it drops the number and keeps the claim.
430
+ const lines = dbOnlyIgnoredNotice(9, { dbOnly: true, commitsAhead: null, branch: null });
431
+ const text = lines.join('\n');
432
+ assert.ok(lines.length > 0, 'an unreadable count is still an ignored flag');
433
+ assert.ok(!/null|NaN|undefined/.test(text), 'and never says so out loud');
434
+ assert.match(text, /this branch is ahead of origin\/main/, 'falls back to the claim without a count');
435
+
436
+ // The one place a branch name reaches a TERMINAL. git forbids control
437
+ // characters in a refname, so this strips nothing in practice — but a notice
438
+ // that could be repainted by its own subject is the wrong thing to have built.
439
+ const esc = dbOnlyIgnoredNotice(11, { dbOnly: true, commitsAhead: '2', branch: 'task\u001b[31m-11\u0007' }).join(' ');
440
+ assert.ok(!/[\u0000-\u001f\u007f-\u009f]/.test(esc), 'terminal control characters are stripped before display');
441
+ });
442
+
443
+ test('the notice is wired into the merge leg, before the push', () => {
444
+ // The (1) lesson again: a helper nothing calls is not a fix. And the ORDER is
445
+ // load-bearing for the same reason the --notes refusal's is — a warning read
446
+ // after the branch has already been pushed and merged cannot stop that run.
447
+ const src = readFileSync(new URL('../scripts/gds/ship-merge.js', import.meta.url), 'utf8');
448
+ assert.match(src, /require\('\.\/ship-honesty\.js'\)/, 'ship-merge imports the notice');
449
+ const merge = src.slice(src.indexOf('async function mergeLocally'));
450
+ const code = merge.replace(/\/\/[^\n]*/g, ''); // comments describe, they do not print
451
+
452
+ const call = code.indexOf('dbOnlyIgnoredNotice(taskId,');
453
+ assert.ok(call !== -1, 'mergeLocally emits the notice');
454
+ // It must be fed the real values, not a re-derived opinion about the branch.
455
+ assert.match(code.slice(call, call + 200), /dbOnly, commitsAhead, branch, deployMode/,
456
+ 'from the same values the verdict was computed from');
457
+ // Every path out of the verdict is downstream of it: the refusal, the empty
458
+ // ship, the ci land and the laptop push.
459
+ for (const after of ["=== 'refuse'", "=== 'ci'", "gitArgvOk(['push'"]) {
460
+ assert.ok(call < code.indexOf(after), `prints before ${after}`);
461
+ }
462
+ });