@bongos/core 1.19.875 → 1.19.876

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,22 +2,22 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.19.875",
6
- "core_contract": "1.19.875",
7
- "source_commit": "d1782be0c7671dd7dd82c4f354a77d231fb274be",
5
+ "core_version": "1.19.876",
6
+ "core_contract": "1.19.876",
7
+ "source_commit": "faaa7283a4bba70f85f9eb8659db39b2a673e099",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-19T04:13:30.837Z",
9
+ "built_at": "2026-09-19T04:18:45.980Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 500,
13
13
  "agent_docs_stubbed": 26,
14
- "functional_verbatim": 2305,
14
+ "functional_verbatim": 2306,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 2831,
20
- "tree_sha256": "1217f7ece32a73d51483fbdd8aabfd37cb8021c9506bca34d253378aedbdd9b3",
19
+ "file_count": 2832,
20
+ "tree_sha256": "e510e319efe7ab6f5d4b1964953c53929d02edc31236f7c777c472eac1ed7d80",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -2602,7 +2602,7 @@
2602
2602
  {
2603
2603
  "path": "docs/module-api-changelog.md",
2604
2604
  "mode": "0000644",
2605
- "sha256": "0d33c3fc850860d87fcb70790d850de2a2be38635fb152dd7d021c6176f67292"
2605
+ "sha256": "d83c5188b30ecc313961e181bd2ec86ea94ab94ef30e995469669b1d4ed889dc"
2606
2606
  },
2607
2607
  {
2608
2608
  "path": "docs/modules-contract.md",
@@ -7352,7 +7352,7 @@
7352
7352
  {
7353
7353
  "path": "modules/security/tests/artifact_scan.mjs",
7354
7354
  "mode": "0000644",
7355
- "sha256": "dbdc9f017a2a6dff3379c1a0acb08e023278cfb382d91b20a4ec0b5e5f8229b0"
7355
+ "sha256": "ee957538ecc1b0639d631d74bfee043a6c7b8f169bb90f807d99038e84943cb3"
7356
7356
  },
7357
7357
  {
7358
7358
  "path": "modules/security/tests/floor.mjs",
@@ -7362,12 +7362,12 @@
7362
7362
  {
7363
7363
  "path": "modules/security/tests/mirror.mjs",
7364
7364
  "mode": "0000644",
7365
- "sha256": "fbded0d3f137a62c9f6255297b2aa5752e0dc2a6175ce0e344d461cf59bc53b1"
7365
+ "sha256": "88415123848cc3f9a856104ca4a53db2fd73cdecaa98f8456e3e8369a050f194"
7366
7366
  },
7367
7367
  {
7368
7368
  "path": "modules/security/tests/sanitize_rebuild.mjs",
7369
7369
  "mode": "0000644",
7370
- "sha256": "4af88247bed3dcb3c12370e48380625871a9b50a0b430a6da34db0ce6d6c8aca"
7370
+ "sha256": "2cec1a9277878d2973838a4ae5dccbd3927a1bd8cfe227227aec01ea29b2ca5e"
7371
7371
  },
7372
7372
  {
7373
7373
  "path": "modules/security/tests/scan_route_schema.mjs",
@@ -7947,12 +7947,12 @@
7947
7947
  {
7948
7948
  "path": "package-lock.json",
7949
7949
  "mode": "0000644",
7950
- "sha256": "f5357bd73be9ea55bd37f72b3510637906159d36638a1ba23366fda56d93da6e"
7950
+ "sha256": "45ea30974ab43d90bf7b88b4afab20d9bd2c8d8ba80c891e464eab1c1a882aff"
7951
7951
  },
7952
7952
  {
7953
7953
  "path": "package.json",
7954
7954
  "mode": "0000644",
7955
- "sha256": "c4516d41236cc62820827c5fed694403aaf255e0e8ad566041ee6397e9eff429"
7955
+ "sha256": "e96760085687e6a0e6a1aa6caf9f011f54eddd43e6229f40140cee9c02332734"
7956
7956
  },
7957
7957
  {
7958
7958
  "path": "public-docs/index.html",
@@ -9037,7 +9037,7 @@
9037
9037
  {
9038
9038
  "path": "scripts/gds/run-unit-tests.js",
9039
9039
  "mode": "0000644",
9040
- "sha256": "f55da8bbc3fef912cfa99935d35e4c6da1c65a0db52aa81a64c77c520ab60e47"
9040
+ "sha256": "a6881d4f2edeeb200da7ad5953ed790dc24993a46e66cbfc5aa67d48a4e02395"
9041
9041
  },
9042
9042
  {
9043
9043
  "path": "scripts/gds/runner-drift.js",
@@ -9937,7 +9937,7 @@
9937
9937
  {
9938
9938
  "path": "src/module-api.js",
9939
9939
  "mode": "0000644",
9940
- "sha256": "30eab36a9a6432777dcf0df87512a6d06b40eb03ded24b3119a8ceb231e0cc52"
9940
+ "sha256": "106ce54796bb7d77e5438ef5848f8337927859114b48316ac151ca18f4f0c6be"
9941
9941
  },
9942
9942
  {
9943
9943
  "path": "src/module-loader/catalog.js",
@@ -13659,6 +13659,11 @@
13659
13659
  "mode": "0000644",
13660
13660
  "sha256": "b86d0d1e0ffeefa07ebda360b129389a02bbcf05296661d56a610c225098d451"
13661
13661
  },
13662
+ {
13663
+ "path": "tests/skip_is_not_pass.mjs",
13664
+ "mode": "0000644",
13665
+ "sha256": "6258b4a3ad76a1d9139b053535c173760c5b42aa82510541501b6fef5ddd1fe1"
13666
+ },
13662
13667
  {
13663
13668
  "path": "tests/smoke_preflight.mjs",
13664
13669
  "mode": "0000644",
@@ -13957,7 +13962,7 @@
13957
13962
  {
13958
13963
  "path": "tests/tool_scan_engine.mjs",
13959
13964
  "mode": "0000644",
13960
- "sha256": "10a6a41cafcba32406be1aa79e0be1ace1cde2c4163bf0b1a0efd1e5e8f8c403"
13965
+ "sha256": "a0d6e1921069e406071498949c792159cfaefabecddff11ea0eda5359cee549f"
13961
13966
  },
13962
13967
  {
13963
13968
  "path": "tests/touches_overlap.mjs",
@@ -14002,7 +14007,7 @@
14002
14007
  {
14003
14008
  "path": "tests/unit_runner_teardown_abort.mjs",
14004
14009
  "mode": "0000644",
14005
- "sha256": "b10a938bb1b229ba03e9e8ba80b04e6d8f8686d25a2b71e2cafa50eba63a2b21"
14010
+ "sha256": "d7e9a8e184cf17e889dac7a48f96b5e45f00ab787e79414ebf53bff731bd126d"
14006
14011
  },
14007
14012
  {
14008
14013
  "path": "tests/update_channel.mjs",
@@ -2224,5 +2224,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
2224
2224
  landed since 1.19.873 with no explicit bump. run 35419921833. (task 1002620)
2225
2225
  1.19.875 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2226
2226
  landed since 1.19.874 with no explicit bump. run 35420682750. (task 1002620)
2227
+ 1.19.876 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2228
+ landed since 1.19.875 with no explicit bump. run 35420916304. (task 1002620)
2227
2229
  ---------------------------------------------------------------------------
2228
2230
  ```
@@ -31,10 +31,35 @@ const { createQuarantine, siteRefusal, isSyncBacked, isInside, REPO_ROOT, CAPS }
31
31
  const { buildManifest, verifyUnchanged, CapExceeded, KIND } = manifest;
32
32
  const { validateTargetUrl, hardeningConfig, hardenedEnv, parseLsFiles, fetchArtifact, blockedAddress, assertHostResolvesPublic } = fetchMod;
33
33
 
34
+
35
+ // Test hook (task 1003993): forces the platform-cannot-symlink branch so the skip path is
36
+ // reachable on a box where symlinks work. Throws exactly where fs.symlinkSync would.
37
+ function symlinkOrRefuse(target, link) {
38
+ if (process.env.BONGOS_TEST_FORCE_NO_SYMLINK === '1') throw new Error('EPERM (forced by BONGOS_TEST_FORCE_NO_SYMLINK)');
39
+ fs.symlinkSync(target, link);
40
+ }
41
+
34
42
  let passed = 0;
35
43
  let failed = 0;
44
+ let skipped = 0;
45
+
46
+ // A case that did not run is NOT a case that passed (task 1003993). A bare `return` from a
47
+ // case that found its platform unable to do the thing left the harness seeing a callback that
48
+ // did not throw — so it printed `ok` and counted a pass for work never attempted. Returning
49
+ // skipCase(why) instead is the same one-line exit, and it tells the truth.
50
+ const SKIP = Symbol('skip');
51
+ const skipCase = (why) => ({ [SKIP]: why });
52
+ function noteSkip(name, why) {
53
+ skipped++;
54
+ console.log(` SKIP ${name}`);
55
+ console.log(` (not run: ${why})`);
56
+ }
36
57
  async function test(name, fn) {
37
- try { await fn(); passed++; console.log(` ok ${name}`); }
58
+ try {
59
+ const r = await fn();
60
+ if (r && r[SKIP]) { noteSkip(name, r[SKIP]); return; }
61
+ passed++; console.log(` ok ${name}`);
62
+ }
38
63
  catch (err) { failed++; console.log(` FAIL ${name}`); console.log(` ${err.message}`); }
39
64
  }
40
65
 
@@ -182,10 +207,9 @@ await test('a symlink is recorded by its link text and its target is NEVER opene
182
207
  fs.writeFileSync(secret, 'AKIA-SUPER-SECRET-TOKEN\n');
183
208
  fs.writeFileSync(path.join(root, 'README.md'), 'hi\n');
184
209
  try {
185
- fs.symlinkSync(secret, path.join(root, 'stolen'));
210
+ symlinkOrRefuse(secret, path.join(root, 'stolen'));
186
211
  } catch {
187
- console.log(' (skipped: this platform/account cannot create symlinks)');
188
- return;
212
+ return skipCase('this platform/account cannot create symlinks');
189
213
  }
190
214
  const m = await buildManifest(root, { maxFiles: 100, maxBytes: 1e6 });
191
215
  const link = m.entries.find((e) => e.path === 'stolen');
@@ -542,11 +566,10 @@ await test('symlinks count against maxFiles too — a cap on regular files only
542
566
  const root = mkTmp();
543
567
  fs.writeFileSync(path.join(root, 'a.txt'), 'a');
544
568
  try {
545
- fs.symlinkSync(path.join(root, 'a.txt'), path.join(root, 'l1'));
546
- fs.symlinkSync(path.join(root, 'a.txt'), path.join(root, 'l2'));
569
+ symlinkOrRefuse(path.join(root, 'a.txt'), path.join(root, 'l1'));
570
+ symlinkOrRefuse(path.join(root, 'a.txt'), path.join(root, 'l2'));
547
571
  } catch {
548
- console.log(' (skipped: this platform/account cannot create symlinks)');
549
- return;
572
+ return skipCase('this platform/account cannot create symlinks');
550
573
  }
551
574
  await assert.rejects(() => buildManifest(root, { maxFiles: 2, maxBytes: 1e6 }), /entry cap/);
552
575
  });
@@ -588,5 +611,5 @@ await test('CAPS are finite — no accidental unlimited default', async () => {
588
611
 
589
612
  for (const d of tmpDirs) { try { fs.rmSync(d, { recursive: true, force: true }); } catch { /* best effort */ } }
590
613
 
591
- console.log(`\nartifact_scan: ${passed} passed, ${failed} failed`);
614
+ console.log(`\nartifact_scan: ${passed} passed, ${failed} failed, ${skipped} skipped`);
592
615
  if (failed > 0) process.exit(1);
@@ -35,14 +35,35 @@ const { scrubUntrusted, newFenceDelim, FENCE_MARKER, DANGEROUS_CHARS } = fenceLo
35
35
 
36
36
  let passed = 0;
37
37
  let failed = 0;
38
+ let skipped = 0;
39
+
40
+ // A case that did not run is NOT a case that passed (task 1003993). A bare `return` from a
41
+ // case that found its platform unable to do the thing left the harness seeing a callback that
42
+ // did not throw — so it printed `ok` and counted a pass for work never attempted. Returning
43
+ // skipCase(why) instead is the same one-line exit, and it tells the truth.
44
+ const SKIP = Symbol('skip');
45
+ const skipCase = (why) => ({ [SKIP]: why });
46
+ function noteSkip(name, why) {
47
+ skipped++;
48
+ console.log(` SKIP ${name}`);
49
+ console.log(` (not run: ${why})`);
50
+ }
38
51
  async function test(name, fn) {
39
- try { await fn(); passed++; console.log(` ok ${name}`); }
52
+ try {
53
+ const r = await fn();
54
+ if (r && r[SKIP]) { noteSkip(name, r[SKIP]); return; }
55
+ passed++; console.log(` ok ${name}`);
56
+ }
40
57
  catch (err) { failed++; console.log(` FAIL ${name}`); console.log(` ${err.message}`); }
41
58
  }
42
59
 
43
60
  // Symlink creation needs privileges on some Windows accounts — same skip idiom
44
61
  // as artifact_scan.mjs so the suite stays green everywhere.
45
62
  function canSymlink(dir) {
63
+ // Test hook (task 1003993): the skip branch is unreachable on any box that CAN symlink,
64
+ // which is every CI runner — exactly why the old fake-pass went unnoticed for so long.
65
+ // tests/skip_is_not_pass.mjs sets this to exercise the skip path anywhere.
66
+ if (process.env.BONGOS_TEST_FORCE_NO_SYMLINK === '1') return false;
46
67
  const target = path.join(dir, 'symlink-probe-target');
47
68
  const link = path.join(dir, 'symlink-probe-link');
48
69
  try {
@@ -176,7 +197,7 @@ async function run() {
176
197
  const rel = `docs/${rlo}evil.md`;
177
198
  let wrote = true;
178
199
  try { writePristine(pen, rel, 'body'); } catch { wrote = false; }
179
- if (!wrote) { console.log(' (skipped: this filesystem refuses bidi filenames)'); return; }
200
+ if (!wrote) return skipCase('this filesystem refuses bidi filenames');
180
201
  const res = await buildMirror({
181
202
  pristineDir: pen.pristineDir, mirrorDir: pen.mirrorDir, quarantineRoot: pen.root,
182
203
  entries: [fileEntry(rel, 4)],
@@ -313,7 +334,8 @@ async function run() {
313
334
  } finally { pen.cleanup(); }
314
335
  });
315
336
  } else {
316
- console.log(' (skipped: this platform/account cannot create symlinks)');
337
+ noteSkip('a REAL on-disk symlink whose realpath escapes the pen is refused (defense in depth)',
338
+ 'this platform/account cannot create symlinks');
317
339
  }
318
340
 
319
341
  // ---------- the index: the record downstream stages reduce over ----------
@@ -352,5 +374,5 @@ function* walkFiles(dir) {
352
374
  }
353
375
 
354
376
  await run();
355
- console.log(`\nmirror: ${passed} passed, ${failed} failed`);
377
+ console.log(`\nmirror: ${passed} passed, ${failed} failed, ${skipped} skipped`);
356
378
  if (failed > 0) process.exit(1);
@@ -25,11 +25,23 @@ const { excerpt } = require('../artifact-scan/detectors/shared.js');
25
25
 
26
26
  let passed = 0;
27
27
  let failed = 0;
28
+ let skipped = 0;
28
29
  async function test(name, fn) {
29
30
  try { await fn(); passed++; console.log(` ok ${name}`); }
30
31
  catch (err) { failed++; console.log(` FAIL ${name}`); console.log(` ${err.stack || err.message}`); }
31
32
  }
32
33
 
34
+ // A case that did not run is NOT a case that passed (task 1003993). This prints SKIP rather
35
+ // than `ok` and counts separately, so the difference between "verified" and "not attempted"
36
+ // survives into the summary line and into run-unit-tests.js's roll-up. It takes the same
37
+ // `name` the real case would have used, because "which coverage did I lose?" is the only
38
+ // question worth answering here.
39
+ function skip(name, why) {
40
+ skipped++;
41
+ console.log(` SKIP ${name}`);
42
+ console.log(` (not run: ${why})`);
43
+ }
44
+
33
45
  const RAW_DANGEROUS_RE = new RegExp(
34
46
  '[\\u200b-\\u200f\\u2028\\u2029\\u202a-\\u202e\\u2060\\u2066-\\u2069\\ufeff]|[\\u{e0000}-\\u{e007f}]', 'u');
35
47
 
@@ -384,6 +396,11 @@ await test('a failed build leaves NO partial sanitized/ tree, and a retry is sti
384
396
  // Symlink creation needs privileges on some Windows accounts — same skip idiom
385
397
  // as mirror.mjs / artifact_scan.mjs so the suite stays green everywhere.
386
398
  function canSymlink(dir) {
399
+ // A test hook, because the branch below is unreachable on any box that CAN symlink — which
400
+ // is every CI runner and most dev machines, and is precisely why the old fake-pass survived
401
+ // so long unnoticed. tests/skip_is_not_pass.mjs sets this to exercise the skip path on a
402
+ // platform where the real probe succeeds.
403
+ if (process.env.BONGOS_TEST_FORCE_NO_SYMLINK === '1') return false;
387
404
  const target = path.join(dir, 'probe-target');
388
405
  const link = path.join(dir, 'probe-link');
389
406
  try {
@@ -434,9 +451,13 @@ function canSymlink(dir) {
434
451
  } finally { pen.cleanup(); fs.rmSync(farAway, { recursive: true, force: true }); }
435
452
  });
436
453
  } else {
437
- console.log(' ok physical-confinement symlink cases');
438
- console.log(' (skipped: this platform/account cannot create symlinks)');
439
- passed += 2;
454
+ // These two are the ONLY coverage of physical confinement, so losing them silently is
455
+ // how task 1003848 reached CI red while this box read 760/760: a change to isInside()
456
+ // collapsed the lexical/physical staging in sanitize.js, and the cases that would have
457
+ // caught it had been reporting `ok` without running for as long as anyone could remember.
458
+ const why = 'this platform/account cannot create symlinks';
459
+ skip('physical confinement: a real symlink the manifest calls a "file" is refused, and its target never reaches sanitized/', why);
460
+ skip('physical confinement: a symlink whose realpath escapes the QUARANTINE entirely is refused', why);
440
461
  }
441
462
  }
442
463
 
@@ -495,5 +516,5 @@ await test('an artifact with no docs still yields an honest spec — the no-docs
495
516
  });
496
517
 
497
518
  console.log('');
498
- console.log(`${passed} passed, ${failed} failed`);
519
+ console.log(`${passed} passed, ${failed} failed, ${skipped} skipped`);
499
520
  if (failed > 0) process.exit(1);
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.875",
3
+ "version": "1.19.876",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.875",
9
+ "version": "1.19.876",
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.875",
3
+ "version": "1.19.876",
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",
@@ -213,12 +213,27 @@ const KNOWN_FAILING = new Set([
213
213
  // suites set exitCode rather than hard-exiting, so they do not hit this.
214
214
  const LIBUV_TEARDOWN_ABORT = /Assertion failed: !\(handle->flags & UV_HANDLE_CLOSING\)/;
215
215
 
216
+ // A suite that reports honest skips ends `N passed, 0 failed, M skipped` (task 1003993), so
217
+ // the optional suffix is part of a clean verdict. Without it, the first suite to start telling
218
+ // the truth about unrun coverage would have its clean verdict read as unrecognised and its
219
+ // Windows teardown abort counted as a real failure — a fix for one honesty bug creating another.
220
+ const CLEAN_VERDICT_RE = /\b[1-9]\d* passed, 0 failed(?:, \d+ skipped)?\s*$/;
221
+
222
+ // ` SKIP <name>` from a suite's own stdout. Counted here rather than parsed out of the summary
223
+ // line so a suite that prints skips without a summary suffix is still surfaced.
224
+ const SKIP_LINE_RE = /^\s*SKIP\b/;
225
+
226
+ function countSkips(stdout) {
227
+ if (!stdout) return 0;
228
+ return stdout.split('\n').filter((l) => SKIP_LINE_RE.test(l)).length;
229
+ }
230
+
216
231
  function isCleanTeardownAbort(stdout, stderr) {
217
232
  if (!LIBUV_TEARDOWN_ABORT.test(stderr)) return false;
218
233
  // Line-leading only: a passing case titled "... is FAIL-CLOSED" is not a failure.
219
234
  if (/^\s*FAIL\b/m.test(stdout)) return false;
220
235
  const lastLine = stdout.trim().split('\n').pop();
221
- return /\b[1-9]\d* passed, 0 failed\s*$/.test(lastLine);
236
+ return CLEAN_VERDICT_RE.test(lastLine);
222
237
  }
223
238
 
224
239
  // The failing tests as ONE line, for the `unit-report` commit status the workflow
@@ -427,9 +442,13 @@ function main() {
427
442
 
428
443
  const failures = [];
429
444
  const teardownAborts = [];
445
+ // A GREEN suite that skipped cases is the shape this roll-up used to hide: the suite said so
446
+ // on its own stdout, which was discarded on success, so the only visible line was `ok` and a
447
+ // session reading the roll-up could not tell exercised coverage from absent coverage.
448
+ const skipped = [];
430
449
  const runOne = (displayName, filePath) => {
431
450
  try {
432
- execFileSync(process.execPath, [filePath], {
451
+ const out = execFileSync(process.execPath, [filePath], {
433
452
  // task 1002644: stdout used to be IGNORED, so the failing assertion (test
434
453
  // files print their FAIL lines to stdout) was discarded and the only
435
454
  // context shown was the last 3 STDERR lines — often stale diagnostic
@@ -441,7 +460,9 @@ function main() {
441
460
  timeout: perTestTimeout,
442
461
  env: { ...process.env, NODE_ENV: 'test' },
443
462
  });
444
- console.log(` ok ${displayName}`);
463
+ const n = countSkips(out ? out.toString() : '');
464
+ if (n) skipped.push({ name: displayName, n });
465
+ console.log(` ok ${displayName}${n ? ` (${n} SKIPPED — not run on this platform)` : ''}`);
445
466
  } catch (err) {
446
467
  const why = err.killed ? 'TIMEOUT' : `exit ${err.status}`;
447
468
  const stdoutText = (err.stdout ? err.stdout.toString() : '').trim();
@@ -492,6 +513,11 @@ function main() {
492
513
  if (teardownAborts.length) {
493
514
  console.log(`${label}: ${teardownAborts.length} passed but aborted in libuv teardown (not failures; task 1003845) — ${teardownAborts.join(', ')}`);
494
515
  }
516
+ if (skipped.length) {
517
+ const n = skipped.reduce((sum, s) => sum + s.n, 0);
518
+ console.log(`${label}: ${n} case(s) NOT RUN on this platform — ${skipped.map((s) => `${s.name} (${s.n})`).join(', ')}`);
519
+ console.log(`${label}: a skipped case is not a passing one. Green here does not mean that coverage was exercised.`);
520
+ }
495
521
  reportToWorkflow(failures);
496
522
  if (failures.length === 0) {
497
523
  console.log(`${label}: all ${total} passed.`);
@@ -503,4 +529,4 @@ function main() {
503
529
 
504
530
  if (require.main === module) main();
505
531
 
506
- module.exports = { discoverModuleTests, isCleanTeardownAbort, describeFailures, STATUS_DESCRIPTION_MAX, INTEGRATION, INTEGRATION_PARKED, KNOWN_FAILING };
532
+ module.exports = { discoverModuleTests, isCleanTeardownAbort, countSkips, describeFailures, STATUS_DESCRIPTION_MAX, INTEGRATION, INTEGRATION_PARKED, KNOWN_FAILING };
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.875'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
74
+ const CORE_VERSION = '1.19.876'; // 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');
@@ -0,0 +1,121 @@
1
+ // tests/skip_is_not_pass.mjs
2
+ //
3
+ // A case that did not run is not a case that passed (task 1003993).
4
+ //
5
+ // modules/security/tests/sanitize_rebuild.mjs gated its two physical-confinement symlink
6
+ // cases behind canSymlink() and, in the else-branch, printed `ok` and did `passed += 2`.
7
+ // So on any machine that cannot create symlinks — the Windows dev box, a container without
8
+ // CAP_SYMLINK, a non-admin account — the suite reported two passes for two cases that never
9
+ // ran, and those two are the ONLY coverage of physical confinement.
10
+ //
11
+ // It was not cosmetic. On task 1003848 a change to isInside() in quarantine.js made
12
+ // containment resolve symlinks, collapsing the deliberate lexical-then-physical staging in
13
+ // sanitize.js so the specific refusals became unreachable. The local suite read 760/760; CI
14
+ // went red on Linux, where those cases actually run. A whole session was spent re-deriving
15
+ // what the suite already knew and had reported as "ok".
16
+ //
17
+ // THE AUDIT THE TASK ASKED FOR FOUND FOUR MORE, in a second and more common shape: a bare
18
+ // `return` from inside the case's own callback. The harness sees a callback that did not
19
+ // throw, so it prints `ok` and counts a pass — the identical lie, written differently. Both
20
+ // shapes are fixed, and all four suites are covered below by one invariant.
21
+ //
22
+ // The branch under test is unreachable wherever symlinks work, which is every CI runner and
23
+ // most dev machines — which is exactly how these survived. BONGOS_TEST_FORCE_NO_SYMLINK
24
+ // exists so the skip paths can be exercised anywhere.
25
+ //
26
+ // Run: node --test tests/skip_is_not_pass.mjs
27
+
28
+ import assert from 'node:assert/strict';
29
+ import { test } from 'node:test';
30
+ import { execFileSync } from 'node:child_process';
31
+ import path from 'node:path';
32
+ import { fileURLToPath } from 'node:url';
33
+ import { countSkips } from '../scripts/gds/run-unit-tests.js';
34
+
35
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
36
+
37
+ // Every suite whose coverage can go missing on a platform that refuses symlinks, with how
38
+ // many cases each one loses. The counts are the point: they are what a fake pass hides.
39
+ const SUITES = [
40
+ { file: 'modules/security/tests/sanitize_rebuild.mjs', skips: 2 },
41
+ { file: 'modules/security/tests/artifact_scan.mjs', skips: 2 },
42
+ { file: 'tests/tool_scan_engine.mjs', skips: 2 },
43
+ { file: 'modules/security/tests/mirror.mjs', skips: 1 },
44
+ ];
45
+
46
+ function runSuite(file, { forceNoSymlink }) {
47
+ const env = { ...process.env, NODE_ENV: 'test' };
48
+ if (forceNoSymlink) env.BONGOS_TEST_FORCE_NO_SYMLINK = '1';
49
+ else delete env.BONGOS_TEST_FORCE_NO_SYMLINK;
50
+ // A skip must never be an error exit — execFileSync throws on non-zero, so reaching the
51
+ // parse below is itself the assertion that skipping did not fail the suite.
52
+ const stdout = execFileSync(process.execPath, [path.join(ROOT, file)], {
53
+ env, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024,
54
+ });
55
+ const summary = stdout.trim().split('\n').pop();
56
+ const m = summary.match(/(\d+) passed, (\d+) failed, (\d+) skipped\s*$/);
57
+ assert.ok(m, `${file}: summary must carry all three counts, got: ${JSON.stringify(summary)}`);
58
+ return { stdout, summary, passed: Number(m[1]), failed: Number(m[2]), skipped: Number(m[3]) };
59
+ }
60
+
61
+ for (const { file, skips } of SUITES) {
62
+ test(`${file}: unrun cases are reported as SKIPPED, not passed`, () => {
63
+ const r = runSuite(file, { forceNoSymlink: true });
64
+ assert.equal(r.skipped, skips, 'every unrun case must be counted as skipped');
65
+ assert.equal(r.failed, 0, 'a skip is not a failure');
66
+ });
67
+
68
+ test(`${file}: THE BUG — skipped cases no longer inflate the passed count`, () => {
69
+ // The heart of it. Under the old code BOTH runs printed the same pass count: the cases
70
+ // that did not run were indistinguishable from the ones that did. If these two ever
71
+ // agree again, the fake pass is back, whatever the summary line claims.
72
+ const ran = runSuite(file, { forceNoSymlink: false });
73
+ const skippedRun = runSuite(file, { forceNoSymlink: true });
74
+
75
+ assert.equal(ran.skipped, 0, 'where symlinks work, nothing is skipped');
76
+ assert.equal(skippedRun.passed, ran.passed - skips,
77
+ `skipping ${skips} case(s) must LOWER the pass count by exactly ${skips} `
78
+ + `(ran: ${ran.passed}, skipped-run: ${skippedRun.passed})`);
79
+ assert.equal(skippedRun.passed + skippedRun.skipped, ran.passed,
80
+ 'passed + skipped must account for every case either way');
81
+ });
82
+
83
+ test(`${file}: the skip names what was lost and never claims "ok"`, () => {
84
+ const r = runSuite(file, { forceNoSymlink: true });
85
+ const skipLines = r.stdout.split('\n').filter((l) => /^\s*SKIP\b/.test(l));
86
+ assert.equal(skipLines.length, skips, 'one SKIP line per unrun case');
87
+ for (const l of skipLines) {
88
+ assert.ok(l.trim().length > 'SKIP'.length + 8,
89
+ `a SKIP line must name the case, got: ${JSON.stringify(l)}`);
90
+ }
91
+ assert.ok(/not run:/.test(r.stdout), 'the reason must be stated, not merely implied');
92
+ assert.ok(
93
+ !/^\s*ok\s+physical-confinement/m.test(r.stdout),
94
+ 'the old fake-pass line ("ok physical-confinement symlink cases") must not come back',
95
+ );
96
+ });
97
+ }
98
+
99
+ test('sanitize_rebuild names both physical-confinement cases it lost', () => {
100
+ // The two that matter most: they are the ONLY coverage of physical confinement, and the
101
+ // pair whose silent absence let task 1003848 reach CI red.
102
+ const r = runSuite('modules/security/tests/sanitize_rebuild.mjs', { forceNoSymlink: true });
103
+ const skipLines = r.stdout.split('\n').filter((l) => /^\s*SKIP\b/.test(l));
104
+ assert.ok(
105
+ skipLines.some((l) => /manifest calls a "file"/.test(l)),
106
+ 'the manifest-lies-about-a-symlink case must be named',
107
+ );
108
+ assert.ok(
109
+ skipLines.some((l) => /realpath escapes the QUARANTINE/.test(l)),
110
+ 'the escaping-realpath case must be named',
111
+ );
112
+ });
113
+
114
+ test('the runner counts those SKIP lines, so a green roll-up still says coverage was not exercised', () => {
115
+ const r = runSuite('modules/security/tests/sanitize_rebuild.mjs', { forceNoSymlink: true });
116
+ assert.equal(countSkips(r.stdout), 2);
117
+ assert.equal(countSkips(' ok a\n ok b\n\n2 passed, 0 failed, 0 skipped\n'), 0);
118
+ // Line-leading only, the same discipline isCleanTeardownAbort uses for FAIL: a case whose
119
+ // NAME contains the word must not be miscounted.
120
+ assert.equal(countSkips(' ok refuses to SKIP a dangerous entry\n'), 0);
121
+ });
@@ -53,8 +53,25 @@ const { normalizeScanPayload } = require(path.join(ROOT, '..', 'modules', 'secur
53
53
 
54
54
  let passed = 0;
55
55
  let failed = 0;
56
+ let skipped = 0;
57
+
58
+ // A case that did not run is NOT a case that passed (task 1003993). A bare `return` from a
59
+ // case that found its platform unable to do the thing left the harness seeing a callback that
60
+ // did not throw — so it printed `ok` and counted a pass for work never attempted. Returning
61
+ // skipCase(why) instead is the same one-line exit, and it tells the truth.
62
+ const SKIP = Symbol('skip');
63
+ const skipCase = (why) => ({ [SKIP]: why });
64
+ function noteSkip(name, why) {
65
+ skipped++;
66
+ console.log(` SKIP ${name}`);
67
+ console.log(` (not run: ${why})`);
68
+ }
56
69
  async function test(name, fn) {
57
- try { await fn(); passed++; console.log(` ok ${name}`); }
70
+ try {
71
+ const r = await fn();
72
+ if (r && r[SKIP]) { noteSkip(name, r[SKIP]); return; }
73
+ passed++; console.log(` ok ${name}`);
74
+ }
58
75
  catch (err) { failed++; console.log(` FAIL ${name}`); console.log(` ${err.stack || err.message}`); }
59
76
  }
60
77
 
@@ -118,6 +135,10 @@ function materializeMalicious({ persuasion = true } = {}) {
118
135
  fs.writeFileSync(credTarget, `[default]\naws_secret_access_key = ${sentinel}\n`, 'utf8');
119
136
  let linked = false;
120
137
  try {
138
+ // Test hook (task 1003993): the two cases guarded by `linked` are the containment proof,
139
+ // and they used to report `ok` when they did not run. This forces that branch so the skip
140
+ // path is reachable on a box where symlinks work — see tests/skip_is_not_pass.mjs.
141
+ if (process.env.BONGOS_TEST_FORCE_NO_SYMLINK === '1') throw new Error('EPERM (forced)');
121
142
  fs.symlinkSync(credTarget, path.join(q.pristineDir, 'home-creds'));
122
143
  linked = true;
123
144
  } catch { /* platform/account cannot create symlinks — guarded at call sites */ }
@@ -216,7 +237,7 @@ await test('fixture A verdict is BYTE-IDENTICAL with the persuasion prose delete
216
237
 
217
238
  await test('fixture A: the credential sentinel reaches NO mirror file, report, manifest, or row', async () => {
218
239
  const { q, sentinel, linked } = materializeMalicious();
219
- if (!linked) { console.log(' (skipped: this platform/account cannot create symlinks)'); return; }
240
+ if (!linked) return skipCase('this platform/account cannot create symlinks');
220
241
 
221
242
  const lenses = healthyPanel();
222
243
  const { manifest, mirror, floorReport, reportPath, reduced } = await scan(q, { lenses });
@@ -257,7 +278,7 @@ await test('fixture A: the credential sentinel reaches NO mirror file, report, m
257
278
 
258
279
  await test('fixture A: the symlink is recorded but NEVER followed', async () => {
259
280
  const { q, credTarget, sentinel, linked } = materializeMalicious();
260
- if (!linked) { console.log(' (skipped: this platform/account cannot create symlinks)'); return; }
281
+ if (!linked) return skipCase('this platform/account cannot create symlinks');
261
282
 
262
283
  const { manifest, mirror } = await scan(q);
263
284
  const link = manifest.entries.find((e) => e.path === 'home-creds');
@@ -342,5 +363,5 @@ await test('teardown leaves none of this run\'s temp dirs behind', () => {
342
363
  });
343
364
 
344
365
  console.log('');
345
- console.log(`tool_scan_engine: ${passed} passed, ${failed} failed`);
366
+ console.log(`tool_scan_engine: ${passed} passed, ${failed} failed, ${skipped} skipped`);
346
367
  if (failed > 0) process.exit(1);
@@ -65,3 +65,40 @@ test('the verdict must be the LAST thing the suite printed', () => {
65
65
  test('a suite that asserted nothing is not vouched for', () => {
66
66
  assert.equal(isCleanTeardownAbort('\n0 passed, 0 failed\n', ABORT_76), false);
67
67
  });
68
+
69
+ // ---------------------------------------------------------------------------
70
+ // task 1003993 — a suite that reports honest skips still has a CLEAN verdict.
71
+ //
72
+ // The predicate above anchors on the summary line. When sanitize_rebuild.mjs stopped
73
+ // counting two unrun symlink cases as passes, its summary grew a `, 2 skipped` suffix —
74
+ // and an unchanged predicate would have stopped recognising it, turning that suite's
75
+ // Windows teardown abort into a reported failure. One honesty fix must not create the
76
+ // next false signal, so the suffix is pinned here.
77
+
78
+ test('a verdict carrying a skip count is still clean', () => {
79
+ assert.equal(
80
+ isCleanTeardownAbort(' ok a\n\n27 passed, 0 failed, 2 skipped\n', ABORT_76),
81
+ true,
82
+ );
83
+ });
84
+
85
+ test('a zero-skip suffix is clean too (the shape is printed unconditionally)', () => {
86
+ assert.equal(
87
+ isCleanTeardownAbort(' ok a\n\n29 passed, 0 failed, 0 skipped\n', ABORT_94),
88
+ true,
89
+ );
90
+ });
91
+
92
+ test('skips do not launder a FAILING verdict into teardown noise', () => {
93
+ assert.equal(
94
+ isCleanTeardownAbort(' ok a\n\n27 passed, 1 failed, 2 skipped\n', ABORT_76),
95
+ false,
96
+ );
97
+ });
98
+
99
+ test('a suite that ONLY skipped has no passes, so it is not a clean verdict', () => {
100
+ assert.equal(
101
+ isCleanTeardownAbort(' SKIP a\n\n0 passed, 0 failed, 2 skipped\n', ABORT_76),
102
+ false,
103
+ );
104
+ });