@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 +18 -13
- package/docs/module-api-changelog.md +2 -0
- package/docs/recipes/windows-builders.md +20 -28
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/run-unit-tests.js +29 -2
- package/src/module-api.js +1 -1
- package/tests/unit_runner_teardown_abort.mjs +67 -0
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.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
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:
|
|
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":
|
|
14
|
+
"functional_verbatim": 2242,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
7840
|
+
"sha256": "8af9d6b81006a300da6bb7df13c0114e0a94882a25715a314c31daa8039943aa"
|
|
7841
7841
|
},
|
|
7842
7842
|
{
|
|
7843
7843
|
"path": "package.json",
|
|
7844
7844
|
"mode": "0000644",
|
|
7845
|
-
"sha256": "
|
|
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": "
|
|
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": "
|
|
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
|
-
**
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
and
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
+
});
|