@bongos/core 1.19.828 → 1.19.829

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.828",
6
- "core_contract": "1.19.828",
7
- "source_commit": "1468eede4d69ff51f17aca93df0be9ce3e7c61d4",
5
+ "core_version": "1.19.829",
6
+ "core_contract": "1.19.829",
7
+ "source_commit": "5e752a914c7b9d8e6a0a2d433c4489eae62a201f",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-17T14:06:18.434Z",
9
+ "built_at": "2026-09-17T14:33:33.476Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 491,
13
13
  "agent_docs_stubbed": 26,
14
- "functional_verbatim": 2241,
14
+ "functional_verbatim": 2242,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 2758,
20
- "tree_sha256": "bf97a7d239bc1b9ea00eef896ec4b06c6c598d7d4db056e25bb24066862bd6a3",
19
+ "file_count": 2759,
20
+ "tree_sha256": "d2c620df0cd6ca7d45a6da686f8faae022490ec620e93678796d350e2a8f78d3",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -2552,7 +2552,7 @@
2552
2552
  {
2553
2553
  "path": "docs/module-api-changelog.md",
2554
2554
  "mode": "0000644",
2555
- "sha256": "1b25950c5cbc4a0495bc7ae5aa052980032732201e87fd4db19c2d00a0497659"
2555
+ "sha256": "031987fb991bfd88d12ce55f8408abd54fa7c0ae14aeb06773590ce347765fe9"
2556
2556
  },
2557
2557
  {
2558
2558
  "path": "docs/modules-contract.md",
@@ -2817,7 +2817,7 @@
2817
2817
  {
2818
2818
  "path": "docs/recipes/windows-builders.md",
2819
2819
  "mode": "0000644",
2820
- "sha256": "eef830271b0e77a161c61759970346b63730d3e6049b4944d24586abadb843de"
2820
+ "sha256": "85823a0b17b646da4b17596c78819da8e7342f24395dcc2e3ea82b5e07c25cba"
2821
2821
  },
2822
2822
  {
2823
2823
  "path": "docs/recipes/work-on-a-task-from-your-dev-box.md",
@@ -7837,12 +7837,12 @@
7837
7837
  {
7838
7838
  "path": "package-lock.json",
7839
7839
  "mode": "0000644",
7840
- "sha256": "233324094e9598241150aa565dbbe7ea26d977714a940b5c4e0d595ab42428da"
7840
+ "sha256": "8af9d6b81006a300da6bb7df13c0114e0a94882a25715a314c31daa8039943aa"
7841
7841
  },
7842
7842
  {
7843
7843
  "path": "package.json",
7844
7844
  "mode": "0000644",
7845
- "sha256": "6e817e4918c137b1d910514c7155c0a22d7c5558b9ba9934892916df07e90b78"
7845
+ "sha256": "facb74c30d620f2cecc5b3476752741626fc11fb5f3a19f0736eee180404e301"
7846
7846
  },
7847
7847
  {
7848
7848
  "path": "public-docs/index.html",
@@ -8887,7 +8887,7 @@
8887
8887
  {
8888
8888
  "path": "scripts/gds/run-unit-tests.js",
8889
8889
  "mode": "0000644",
8890
- "sha256": "be80f6ea38abdbc71f5f718b51db9a54b931745859e885ea1a97ff9465a81596"
8890
+ "sha256": "1bbffb825eae2d4253a0f2562609fc158c0307e6120bc83fd659890cdbcff2d6"
8891
8891
  },
8892
8892
  {
8893
8893
  "path": "scripts/gds/sandbox-stage.js",
@@ -9762,7 +9762,7 @@
9762
9762
  {
9763
9763
  "path": "src/module-api.js",
9764
9764
  "mode": "0000644",
9765
- "sha256": "32383a56130d07528e7c21e2867c7285a18e4bf7d4dc1d120e510b0f76e4db7c"
9765
+ "sha256": "9d2cc3cc105693debf99590994668c2060c0d39ecca4fa14b217c87eb1fb2ca4"
9766
9766
  },
9767
9767
  {
9768
9768
  "path": "src/module-loader/catalog.js",
@@ -13639,6 +13639,11 @@
13639
13639
  "mode": "0000644",
13640
13640
  "sha256": "024c65f27b684bc3a00181cd72df7e7152af3372c9338d9a38f0a1200c25b678"
13641
13641
  },
13642
+ {
13643
+ "path": "tests/unit_runner_teardown_abort.mjs",
13644
+ "mode": "0000644",
13645
+ "sha256": "b10a938bb1b229ba03e9e8ba80b04e6d8f8686d25a2b71e2cafa50eba63a2b21"
13646
+ },
13642
13647
  {
13643
13648
  "path": "tests/update_channel.mjs",
13644
13649
  "mode": "0000644",
@@ -2130,5 +2130,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
2130
2130
  landed since 1.19.826 with no explicit bump. run 35230339528. (task 1002620)
2131
2131
  1.19.828 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2132
2132
  landed since 1.19.827 with no explicit bump. run 35231300810. (task 1002620)
2133
+ 1.19.829 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2134
+ landed since 1.19.828 with no explicit bump. run 35234300466. (task 1002620)
2133
2135
  ---------------------------------------------------------------------------
2134
2136
  ```
@@ -119,34 +119,26 @@ listed as expected-red for a matter of hours: the list was written the same day
119
119
  deps in the worktree" family and task 1003616 fixed it on 2026-09-07 (see the walk-up section
120
120
  below).
121
121
 
122
- **THREE suites are INTERMITTENT on a Windows box, so neither a green run nor a red one settles
123
- anything on its own.** Two of the three are on the baseline above; the third is NOT, and keeping
124
- that distinction straight is the whole point of this paragraph — "intermittent" is a property of a
125
- suite, not a licence to add it to the expected-red list.
126
-
127
- *Intermittent AND on the list:* `session_caps` and `search_isolation` exit `3221226505`
128
- (`0xC0000409`, STATUS_STACK_BUFFER_OVERRUN) — the process dies during teardown *after* its
129
- assertions have all passed, so whether the suite counts them as failures depends on timing. Both
130
- directions are now on record. Task 1003529: `session_caps` passed inside a full
131
- `run-unit-tests.js` sweep and failed twice in a row when run on its own, minutes apart. Task
132
- 1003860, two sweeps minutes apart on one unchanged tree: `session_caps` was RED in the first and
133
- GREEN in the second, plus green twice standalone — so the same tree answers 9 failures or 8
134
- depending on nothing you did, and `search_isolation` was red in both. Task 1003827 is still open
135
- and nothing on main has touched either. This is the `cliExit` class documented in
136
- [scripts/gds/CLAUDE.md](../../scripts/gds/CLAUDE.md) — a hard exit while handles are still closing.
137
- Do not mark either fixed on the strength of one green sweep, and do not let their noise cover a
138
- real regression: diff by name AND re-run the individual suite.
139
-
140
- *Intermittent but NOT on the list:* `<redacted>` (task 1003860). It failed inside one
141
- full sweep on 2026-09-12 and has not reproduced since — green standalone 7-of-7 then, and green in
142
- both full sweeps and three more standalone runs when the baseline was re-measured. It binds an
143
- ephemeral socket (`app.listen(0)`) and calls `server.close()` without awaiting it, so it is the
144
- same teardown-timing class as the two above. Know the name, so a red here does not send you
145
- hunting your own diff — but it stays OFF the expected-red list, because it is green on
146
- `origin/main`. **A suite that reds once and then will not reproduce is not a baseline entry.**
147
- Promoting a one-off red to a known name buys nothing and gives a genuine regression somewhere to
148
- hide; `git_merge_regen` below is the case that cost a whole session. Re-run this one alone before
149
- you touch anything.
122
+ **The teardown-abort class no longer reads red (task 1003845).** On Windows + Node 24 a suite that
123
+ hard-exits after serving HTTP can print a clean verdict and then die natively —
124
+ `Assertion failed: !(handle->flags & UV_HANDLE_CLOSING)`, exit `127` or `3221226505`
125
+ (`0xC0000409`). This is the `cliExit` class documented in
126
+ [scripts/gds/CLAUDE.md](../../scripts/gds/CLAUDE.md). Timing decides it:
127
+ `provisioning_github_app_routes` aborted 5 runs of 6, and in task 1003860 one unchanged tree
128
+ answered 9 failures in one sweep and 8 in the next. A new failure could take a vanished one's
129
+ place and the count would not move. `run-unit-tests.js` now recognises the abort by that stderr
130
+ signature **plus** the suite's own verdict: its last line must read `N passed, 0 failed`, and no
131
+ line may start with `FAIL`. Such a suite prints
132
+ `ok <name> (clean verdict, then libuv teardown abort, exit …)` and is named on its own summary
133
+ line, never in `FAILED`. That stops `search_isolation` (on the list above) flapping. It does the same
134
+ for `session_caps`, which task 1003827 also fixed outright, and for `<redacted>`,
135
+ `provisioning_github_app_routes` and `task_visual_route_gate`, which all aborted in the
136
+ 2026-09-17 sweep. The match is on the signature, not on a list of names. An abort with no clean
137
+ verdict (a crash mid-run, or a summary that is not the last thing printed) still reads `FAIL`.
138
+
139
+ **A suite that reds once and then will not reproduce is not a baseline entry.** Promoting a one-off
140
+ red to a known name buys nothing and gives a genuine regression somewhere to hide; `git_merge_regen`
141
+ below is the case that cost a whole session. Re-run the suite alone before you touch anything.
150
142
 
151
143
  Two warnings about that list, both paid for:
152
144
 
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.828",
3
+ "version": "1.19.829",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.828",
9
+ "version": "1.19.829",
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.828",
3
+ "version": "1.19.829",
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",
@@ -191,6 +191,24 @@ const KNOWN_FAILING = new Set([
191
191
  'served_paths_shipped',
192
192
  ]);
193
193
 
194
+ // Task 1003845: on Windows + Node 24 a suite that hard-exits after serving HTTP can
195
+ // print a clean verdict and THEN abort natively (the cliExit class in
196
+ // scripts/gds/CLAUDE.md), exiting 127 or 0xC0000409 depending on timing. Counted as
197
+ // FAILED, these swap in and out of a Windows baseline, so a new failure can hide
198
+ // behind a vanished one. Recognised by signature plus the suite's own verdict —
199
+ // never by a list of names, which docs/recipes/windows-builders.md shows goes stale
200
+ // in hours. Only the `N passed, M failed` summary counts as a verdict: node:test
201
+ // suites set exitCode rather than hard-exiting, so they do not hit this.
202
+ const LIBUV_TEARDOWN_ABORT = /Assertion failed: !\(handle->flags & UV_HANDLE_CLOSING\)/;
203
+
204
+ function isCleanTeardownAbort(stdout, stderr) {
205
+ if (!LIBUV_TEARDOWN_ABORT.test(stderr)) return false;
206
+ // Line-leading only: a passing case titled "... is FAIL-CLOSED" is not a failure.
207
+ if (/^\s*FAIL\b/m.test(stdout)) return false;
208
+ const lastLine = stdout.trim().split('\n').pop();
209
+ return /\b[1-9]\d* passed, 0 failed\s*$/.test(lastLine);
210
+ }
211
+
194
212
  // Discover modules/<key>/tests/*.mjs. Pure-ish (reads disk; `isEnabled` is
195
213
  // injected so this is unit-testable with zero real modules/ or DB config —
196
214
  // mirrors the injected-dependency pattern the module loader itself uses).
@@ -266,6 +284,7 @@ function main() {
266
284
  const total = files.length + moduleTests.length;
267
285
 
268
286
  const failures = [];
287
+ const teardownAborts = [];
269
288
  const runOne = (displayName, filePath) => {
270
289
  try {
271
290
  execFileSync(process.execPath, [filePath], {
@@ -283,8 +302,13 @@ function main() {
283
302
  console.log(` ok ${displayName}`);
284
303
  } catch (err) {
285
304
  const why = err.killed ? 'TIMEOUT' : `exit ${err.status}`;
286
- console.log(` FAIL ${displayName} (${why})`);
287
305
  const stdoutText = (err.stdout ? err.stdout.toString() : '').trim();
306
+ if (!err.killed && isCleanTeardownAbort(stdoutText, err.stderr ? err.stderr.toString() : '')) {
307
+ console.log(` ok ${displayName} (clean verdict, then libuv teardown abort, ${why})`);
308
+ teardownAborts.push(displayName);
309
+ return;
310
+ }
311
+ console.log(` FAIL ${displayName} (${why})`);
288
312
  // The actual failures first: the file's own FAIL lines, then its stdout
289
313
  // tail (the last failure's assertion detail + summary), both bounded.
290
314
  const failLines = stdoutText ? stdoutText.split('\n').filter((l) => /\bFAIL\b/.test(l)).slice(0, 5) : [];
@@ -323,6 +347,9 @@ function main() {
323
347
 
324
348
  const label = integrationMode ? 'run-unit-tests --integration' : 'run-unit-tests';
325
349
  console.log('');
350
+ if (teardownAborts.length) {
351
+ console.log(`${label}: ${teardownAborts.length} passed but aborted in libuv teardown (not failures; task 1003845) — ${teardownAborts.join(', ')}`);
352
+ }
326
353
  if (failures.length === 0) {
327
354
  console.log(`${label}: all ${total} passed.`);
328
355
  process.exit(0);
@@ -333,4 +360,4 @@ function main() {
333
360
 
334
361
  if (require.main === module) main();
335
362
 
336
- module.exports = { discoverModuleTests };
363
+ module.exports = { discoverModuleTests, isCleanTeardownAbort };
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.828'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
74
+ const CORE_VERSION = '1.19.829'; // 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,67 @@
1
+ // tests/unit_runner_teardown_abort.mjs
2
+ //
3
+ // The unit runner tells a libuv TEARDOWN abort apart from a failing suite (task 1003845).
4
+ //
5
+ // On Windows + Node 24 a suite that hard-exits after serving HTTP can print a clean
6
+ // verdict and THEN die natively:
7
+ //
8
+ // Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\win\async.c, line 76
9
+ //
10
+ // exit 127 or 0xC0000409, nondeterministically. tests/provisioning_github_app_routes.mjs
11
+ // did it 5 runs of 6 on origin/main, and 51 suites share that shape. Counted as FAILED,
12
+ // these swap in and out of a Windows baseline with timing, so a genuinely new failure
13
+ // can take a vanished one's place without the count moving.
14
+ //
15
+ // The runner recognises the class by its stderr signature AND the suite's own clean
16
+ // verdict, never by a curated list of names. The cases below are the ways that pairing
17
+ // must refuse: a real failure is never allowed to read as teardown noise.
18
+ //
19
+ // Run: node --test tests/unit_runner_teardown_abort.mjs
20
+
21
+ import assert from 'node:assert/strict';
22
+ import { test } from 'node:test';
23
+ import { isCleanTeardownAbort } from '../scripts/gds/run-unit-tests.js';
24
+
25
+ // Captured from a real aborted run of tests/provisioning_github_app_routes.mjs.
26
+ const ABORT_76 = 'Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\\win\\async.c, line 76\r\n';
27
+ const ABORT_94 = 'Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\\win\\async.c, line 94\r\n';
28
+ const CLEAN = ' PASS non-numeric :id → 400 bad_id (never a 500)\n\nprovisioning github-app routes: 23 passed, 0 failed\n';
29
+
30
+ test('a clean verdict followed by the libuv abort is teardown noise', () => {
31
+ assert.equal(isCleanTeardownAbort(CLEAN, ABORT_76), true);
32
+ assert.equal(isCleanTeardownAbort(' PASS a\n\n9 passed, 0 failed\n', ABORT_94), true);
33
+ });
34
+
35
+ test('the abort does not excuse a verdict that reports failures', () => {
36
+ assert.equal(isCleanTeardownAbort(' PASS a\n FAIL b\n\n22 passed, 1 failed\n', ABORT_76), false);
37
+ });
38
+
39
+ test('a FAIL line outranks a summary that claims zero failures', () => {
40
+ assert.equal(isCleanTeardownAbort(' FAIL b\n\n23 passed, 0 failed\n', ABORT_76), false);
41
+ });
42
+
43
+ test('FAIL inside a passing case title is not a FAIL line', () => {
44
+ // tests/search_isolation.mjs prints exactly this and passes.
45
+ const stdout = ' ok rank helper is FAIL-CLOSED: unknown/missing rank → least privilege only\n\n5 passed, 0 failed\n';
46
+ assert.equal(isCleanTeardownAbort(stdout, ABORT_76), true);
47
+ });
48
+
49
+ test('an ordinary non-zero exit with no abort signature stays a failure', () => {
50
+ assert.equal(isCleanTeardownAbort(CLEAN, ''), false);
51
+ assert.equal(isCleanTeardownAbort(CLEAN, 'Error: listen EADDRINUSE\n'), false);
52
+ });
53
+
54
+ test('an abort with no verdict at all is a crash, not noise', () => {
55
+ assert.equal(isCleanTeardownAbort(' PASS a\n', ABORT_76), false);
56
+ assert.equal(isCleanTeardownAbort('', ABORT_76), false);
57
+ });
58
+
59
+ test('the verdict must be the LAST thing the suite printed', () => {
60
+ // A mid-file section summary does not vouch for whatever ran after it.
61
+ const midFile = 'section one: 4 passed, 0 failed\n PASS c\nnext section starting\n';
62
+ assert.equal(isCleanTeardownAbort(midFile, ABORT_76), false);
63
+ });
64
+
65
+ test('a suite that asserted nothing is not vouched for', () => {
66
+ assert.equal(isCleanTeardownAbort('\n0 passed, 0 failed\n', ABORT_76), false);
67
+ });