@bongos/core 1.19.648 → 1.19.649
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 +13 -13
- package/docs/module-api-changelog.md +2 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/module-api.js +1 -1
- package/src/module-loader/loader.js +47 -2
- package/src/module-seams.js +8 -4
- package/tests/module_loader.mjs +68 -0
- package/tests/platform_boot.mjs +53 -0
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.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.649",
|
|
6
|
+
"core_contract": "1.19.649",
|
|
7
|
+
"source_commit": "10ae5631783d03386a93457958ac7846e3ad4b83",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-10T19:
|
|
9
|
+
"built_at": "2026-09-10T19:54:28.577Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 470,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
19
|
"file_count": 2603,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "05f4d97454a0e6f6d8eadf6c13d88c68d8619b32269b20409e5a79505dad4596",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/backlog-review/SKILL.md",
|
|
@@ -2772,7 +2772,7 @@
|
|
|
2772
2772
|
{
|
|
2773
2773
|
"path": "docs/module-api-changelog.md",
|
|
2774
2774
|
"mode": "0000644",
|
|
2775
|
-
"sha256": "
|
|
2775
|
+
"sha256": "454e5258c7dccd1013445f290acf0cfbd0cf396c9cb5bbcf8a08b911770bfa07"
|
|
2776
2776
|
},
|
|
2777
2777
|
{
|
|
2778
2778
|
"path": "docs/modules-contract.md",
|
|
@@ -7727,12 +7727,12 @@
|
|
|
7727
7727
|
{
|
|
7728
7728
|
"path": "package-lock.json",
|
|
7729
7729
|
"mode": "0000644",
|
|
7730
|
-
"sha256": "
|
|
7730
|
+
"sha256": "356482b30a64a35a5ae0a670d0b26e0b10dcdd71ae106e8b22fb8f5843282c05"
|
|
7731
7731
|
},
|
|
7732
7732
|
{
|
|
7733
7733
|
"path": "package.json",
|
|
7734
7734
|
"mode": "0000644",
|
|
7735
|
-
"sha256": "
|
|
7735
|
+
"sha256": "30e3bed231acb41d36332cae243423aee74a09604e87c966908543fefde74b65"
|
|
7736
7736
|
},
|
|
7737
7737
|
{
|
|
7738
7738
|
"path": "public-docs/index.html",
|
|
@@ -9487,7 +9487,7 @@
|
|
|
9487
9487
|
{
|
|
9488
9488
|
"path": "src/module-api.js",
|
|
9489
9489
|
"mode": "0000644",
|
|
9490
|
-
"sha256": "
|
|
9490
|
+
"sha256": "eaa8edfbeec057365723ebfc9d148061b96be50cc37a1ad51b7d050f6e2906c4"
|
|
9491
9491
|
},
|
|
9492
9492
|
{
|
|
9493
9493
|
"path": "src/module-loader/catalog.js",
|
|
@@ -9497,7 +9497,7 @@
|
|
|
9497
9497
|
{
|
|
9498
9498
|
"path": "src/module-loader/loader.js",
|
|
9499
9499
|
"mode": "0000644",
|
|
9500
|
-
"sha256": "
|
|
9500
|
+
"sha256": "5e417760b49f19d48af3dbd72a90edb53b6a9710ce54ca33052f4fbe86cb9229"
|
|
9501
9501
|
},
|
|
9502
9502
|
{
|
|
9503
9503
|
"path": "src/module-loader/manifest-schema.js",
|
|
@@ -9517,7 +9517,7 @@
|
|
|
9517
9517
|
{
|
|
9518
9518
|
"path": "src/module-seams.js",
|
|
9519
9519
|
"mode": "0000644",
|
|
9520
|
-
"sha256": "
|
|
9520
|
+
"sha256": "34378efdff13b7e44e38f248026aa927073e0c0c15db670cabaea7aa939c4959"
|
|
9521
9521
|
},
|
|
9522
9522
|
{
|
|
9523
9523
|
"path": "src/modules.js",
|
|
@@ -11632,7 +11632,7 @@
|
|
|
11632
11632
|
{
|
|
11633
11633
|
"path": "tests/module_loader.mjs",
|
|
11634
11634
|
"mode": "0000644",
|
|
11635
|
-
"sha256": "
|
|
11635
|
+
"sha256": "5d09d5ccedf777c55509dc98365b46c443848c854158ace66fa17c99ba538737"
|
|
11636
11636
|
},
|
|
11637
11637
|
{
|
|
11638
11638
|
"path": "tests/module_manifest.mjs",
|
|
@@ -11827,7 +11827,7 @@
|
|
|
11827
11827
|
{
|
|
11828
11828
|
"path": "tests/platform_boot.mjs",
|
|
11829
11829
|
"mode": "0000644",
|
|
11830
|
-
"sha256": "
|
|
11830
|
+
"sha256": "02a3d4540ddecfe530022df0c6cab4f48258b78efd44224c097622e45b8cc0d5"
|
|
11831
11831
|
},
|
|
11832
11832
|
{
|
|
11833
11833
|
"path": "tests/platform_visibility_gate.mjs",
|
|
@@ -1745,5 +1745,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
1745
1745
|
landed since 1.19.646 with no explicit bump. run 34515733973. (task 1002620)
|
|
1746
1746
|
1.19.648 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1747
1747
|
landed since 1.19.647 with no explicit bump. run 34518387614. (task 1002620)
|
|
1748
|
+
1.19.649 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1749
|
+
landed since 1.19.648 with no explicit bump. run 34523111338. (task 1002620)
|
|
1748
1750
|
---------------------------------------------------------------------------
|
|
1749
1751
|
```
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.649",
|
|
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.649",
|
|
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.649",
|
|
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",
|
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.649'; // 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');
|
|
@@ -20,6 +20,10 @@ const path = require('node:path');
|
|
|
20
20
|
const { validateManifest } = require('./manifest-schema');
|
|
21
21
|
const { satisfies } = require('./semver');
|
|
22
22
|
const { resolveCoreRoot, resolveInstanceRoot } = require('../instance-config');
|
|
23
|
+
// Required DIRECTLY, never through src/module-api.js: the doorway pulls in auth
|
|
24
|
+
// and would re-form the eval-time cycle coreVersion() exists to avoid.
|
|
25
|
+
// src/module-seams.js is pure (it requires nothing), so this is cycle-safe.
|
|
26
|
+
const { verifyPortsSatisfied } = require('../module-seams');
|
|
23
27
|
|
|
24
28
|
// Collapse roots that resolve to the SAME absolute path, preserving order.
|
|
25
29
|
// Normalizes via path.resolve so a cwd that arrives with a trailing '.'/'//'
|
|
@@ -129,6 +133,39 @@ function discoveredRegistry(opts) {
|
|
|
129
133
|
return out;
|
|
130
134
|
}
|
|
131
135
|
|
|
136
|
+
// The ENABLED slice of the discovered set, in discovery order. The one place the
|
|
137
|
+
// `isEnabled` gate is spelled out, so verification and mounting can never disagree
|
|
138
|
+
// about which modules are in play.
|
|
139
|
+
function enabledModules({ isEnabled } = {}, opts) {
|
|
140
|
+
return discovered(opts).loaded.filter(({ key }) => typeof isEnabled !== 'function' || isEnabled(key));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// BOOT-TIME seam verification (BV1.R41), run over the ENABLED manifests before a
|
|
144
|
+
// single module router is mounted. THROWS on an unsatisfiable set — a module that
|
|
145
|
+
// consumes a port no enabled module provides, or a port claimed by two of them.
|
|
146
|
+
//
|
|
147
|
+
// Why it throws rather than warns: without it the failure surfaces as resolve()'s
|
|
148
|
+
// runtime throw the first time the seam is used — an arbitrarily long time after
|
|
149
|
+
// boot, on a request path, in a process that reported healthy. ADR 0150's "fail
|
|
150
|
+
// loud, never boot half-wired" is the same argument: refuse at boot, name the
|
|
151
|
+
// module and the port, and let the operator fix the module set.
|
|
152
|
+
//
|
|
153
|
+
// Only the ENABLED set is checked, deliberately. A disabled module's `consumes` is
|
|
154
|
+
// not a wiring error — it is a module that isn't there.
|
|
155
|
+
function verifyEnabledPorts(enabled) {
|
|
156
|
+
const { ok, errors } = verifyPortsSatisfied(enabled.map(({ key, manifest }) => ({
|
|
157
|
+
key,
|
|
158
|
+
provides: manifest.provides || [],
|
|
159
|
+
consumes: manifest.consumes || [],
|
|
160
|
+
})));
|
|
161
|
+
if (ok) return;
|
|
162
|
+
throw new Error([
|
|
163
|
+
'[module-loader] unsatisfiable module wiring — refusing to boot half-wired:',
|
|
164
|
+
...errors.map((e) => ` - ${e}`),
|
|
165
|
+
'Fix the enabled module set (config/modules.json, or a <PREFIX>_MODULE_<KEY> env override) so every consumed port has exactly one provider.',
|
|
166
|
+
].join('\n'));
|
|
167
|
+
}
|
|
168
|
+
|
|
132
169
|
// Mount each discovered + ENABLED module's route factories onto `router`. A
|
|
133
170
|
// module declares contributes.routes:["box"] → factory at modules/<key>/routes/box.js
|
|
134
171
|
// exporting a function returning an express Router. Auth model: audit is composed
|
|
@@ -136,10 +173,18 @@ function discoveredRegistry(opts) {
|
|
|
136
173
|
// its required rank via api.requireBuilder / api.requireRank (public read routes
|
|
137
174
|
// need no gate). The BV1.R44 fitness check enforces that every write route is
|
|
138
175
|
// rank-gated. Returns the count mounted. No-op when nothing is discovered/enabled.
|
|
176
|
+
//
|
|
177
|
+
// Verifies the seam wiring FIRST (verifyEnabledPorts, above). This is the boot hook
|
|
178
|
+
// the check belongs on because it is the one hook every server that mounts modules
|
|
179
|
+
// runs — the live API, platform-server, and the test harness alike — so the
|
|
180
|
+
// verification cannot be skipped by adding an entrypoint, the way a separately
|
|
181
|
+
// called check could be. It runs before the first `require` of a module's route
|
|
182
|
+
// factory, so a half-wired instance never gets as far as loading module code.
|
|
139
183
|
function mountModuleRoutes(router, { isEnabled } = {}, opts) {
|
|
184
|
+
const enabled = enabledModules({ isEnabled }, opts);
|
|
185
|
+
verifyEnabledPorts(enabled);
|
|
140
186
|
let mounted = 0;
|
|
141
|
-
for (const {
|
|
142
|
-
if (typeof isEnabled === 'function' && !isEnabled(key)) continue;
|
|
187
|
+
for (const { dir, manifest } of enabled) {
|
|
143
188
|
const routeKeys = (manifest.contributes && manifest.contributes.routes) || [];
|
|
144
189
|
for (const rk of routeKeys) {
|
|
145
190
|
const factory = require(path.join(dir, 'routes', `${rk}.js`));
|
package/src/module-seams.js
CHANGED
|
@@ -75,10 +75,14 @@ function listPorts() {
|
|
|
75
75
|
|
|
76
76
|
// BOOT-TIME (manifest-level) verification, distinct from resolve()'s RUNTIME
|
|
77
77
|
// throw: given the ENABLED modules' manifests, confirm every consumed port is
|
|
78
|
-
// provided by some enabled module, and no port has two providers. The loader
|
|
79
|
-
// (BV1.R41) runs this before mounting
|
|
80
|
-
//
|
|
81
|
-
//
|
|
78
|
+
// provided by some enabled module, and no port has two providers. The loader's
|
|
79
|
+
// mountModuleRoutes (BV1.R41) runs this via verifyEnabledPorts before mounting any
|
|
80
|
+
// module router, and THROWS on a non-ok result, so a required capability with no
|
|
81
|
+
// provider is caught at boot with a clear message — not as a surprise throw the
|
|
82
|
+
// first time the seam is used. This function itself only REPORTS: it is pure (no
|
|
83
|
+
// registry mutation, no throw), so it is safe to run anytime, and the loader owns
|
|
84
|
+
// the decision to refuse the boot. Wired + regression-pinned by task 1003820,
|
|
85
|
+
// which found it defined, exported, tested, and called by nothing.
|
|
82
86
|
// manifests: [{ key, provides: [...], consumes: [...] }] → { ok, errors: [...] }.
|
|
83
87
|
function verifyPortsSatisfied(manifests = []) {
|
|
84
88
|
const provided = new Map(); // port -> [providerKey,...]
|
package/tests/module_loader.mjs
CHANGED
|
@@ -155,6 +155,71 @@ test('mountModuleRoutes: mounts an enabled module router, skips a disabled one',
|
|
|
155
155
|
loader._reset();
|
|
156
156
|
});
|
|
157
157
|
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
// task 1003820 — the mount hook must VERIFY the seam wiring before it mounts.
|
|
160
|
+
//
|
|
161
|
+
// verifyPortsSatisfied was defined, exported through the doorway, and unit-tested
|
|
162
|
+
// in isolation, but no production code called it: a module consuming a port no
|
|
163
|
+
// enabled module provided booted clean and threw later, on a request path, out of
|
|
164
|
+
// resolve(). These pin the CALL, not the function — see also the spawn-boot half
|
|
165
|
+
// in tests/platform_boot.mjs, which is what actually turns red if the call site is
|
|
166
|
+
// deleted (learning 1000154).
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
|
|
169
|
+
test('task 1003820: mountModuleRoutes refuses an enabled module consuming an unprovided port', () => {
|
|
170
|
+
loader._reset();
|
|
171
|
+
const factory = "module.exports = () => function moduleRoute(req, res, next) { next(); };";
|
|
172
|
+
const root = fixtureRoot({
|
|
173
|
+
consumer: {
|
|
174
|
+
manifest: validManifest('consumer', { consumes: ['reward'], contributes: { routes: ['main'] } }),
|
|
175
|
+
files: { 'routes/main.js': factory },
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
const used = [];
|
|
179
|
+
const fakeRouter = { use: (fn) => used.push(fn) };
|
|
180
|
+
assert.throws(
|
|
181
|
+
() => loader.mountModuleRoutes(fakeRouter, { isEnabled: () => true }, { roots: [root], core: '1.0.0' }),
|
|
182
|
+
(e) => /unsatisfiable module wiring/.test(e.message)
|
|
183
|
+
&& /"consumer" consumes port "reward"/.test(e.message),
|
|
184
|
+
'the refusal names the module and the port it cannot satisfy'
|
|
185
|
+
);
|
|
186
|
+
assert.equal(used.length, 0, 'it refuses BEFORE requiring any module route factory');
|
|
187
|
+
loader._reset();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('task 1003820: a DISABLED module’s unsatisfied consumes is not a wiring error', () => {
|
|
191
|
+
loader._reset();
|
|
192
|
+
const factory = "module.exports = () => function moduleRoute(req, res, next) { next(); };";
|
|
193
|
+
const root = fixtureRoot({
|
|
194
|
+
consumer: { manifest: validManifest('consumer', { consumes: ['reward'] }) },
|
|
195
|
+
plain: {
|
|
196
|
+
manifest: validManifest('plain', { contributes: { routes: ['main'] } }),
|
|
197
|
+
files: { 'routes/main.js': factory },
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
const used = [];
|
|
201
|
+
const fakeRouter = { use: (fn) => used.push(fn) };
|
|
202
|
+
// Only the ENABLED set is verified: a module that is off is a module that is
|
|
203
|
+
// not there, so its consumes names nothing that has to be provided.
|
|
204
|
+
const mounted = loader.mountModuleRoutes(fakeRouter, { isEnabled: (k) => k === 'plain' }, { roots: [root], core: '1.0.0' });
|
|
205
|
+
assert.equal(mounted, 1);
|
|
206
|
+
loader._reset();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test('task 1003820: mountModuleRoutes refuses two enabled providers of one port', () => {
|
|
210
|
+
loader._reset();
|
|
211
|
+
const root = fixtureRoot({
|
|
212
|
+
a: { manifest: validManifest('a', { provides: ['reward'] }) },
|
|
213
|
+
b: { manifest: validManifest('b', { provides: ['reward'] }) },
|
|
214
|
+
});
|
|
215
|
+
assert.throws(
|
|
216
|
+
() => loader.mountModuleRoutes({ use: () => {} }, { isEnabled: () => true }, { roots: [root], core: '1.0.0' }),
|
|
217
|
+
(e) => /provided by more than one enabled module/.test(e.message),
|
|
218
|
+
'the single-provider invariant is enforced at boot, not at first resolve()'
|
|
219
|
+
);
|
|
220
|
+
loader._reset();
|
|
221
|
+
});
|
|
222
|
+
|
|
158
223
|
test('the live tree discovers the extracted modules cleanly (BV1.R43/R48/R47/R49/R72)', () => {
|
|
159
224
|
// dev-box graduated first (modules/dev-box/, BV1.R43), art-pipeline next
|
|
160
225
|
// (BV1.R48 / task 1417), discord next (BV1.R47 / task 1416), game next
|
|
@@ -352,6 +417,9 @@ test('the demo proof module mounts + serves through the loader (BV1.R52 / task 1
|
|
|
352
417
|
|
|
353
418
|
const used = [];
|
|
354
419
|
const fakeRouter = { use: (fn) => used.push(fn) };
|
|
420
|
+
// Since task 1003820 this also proves the SHIPPED enabled set is satisfiable:
|
|
421
|
+
// mountModuleRoutes verifies the seam wiring first and throws on a half-wired
|
|
422
|
+
// set, so a manifest change that strands a consumed port reds this test.
|
|
355
423
|
const mounted = loader.mountModuleRoutes(fakeRouter, { isEnabled: isModuleEnabled });
|
|
356
424
|
assert.ok(mounted >= 1, 'at least the demo router mounted');
|
|
357
425
|
|
package/tests/platform_boot.mjs
CHANGED
|
@@ -284,3 +284,56 @@ test('task 1002419: a real `node src/platform-server.js` boot announces the reco
|
|
|
284
284
|
child.kill('SIGKILL');
|
|
285
285
|
}
|
|
286
286
|
});
|
|
287
|
+
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
// task 1003820 — a half-wired module set must REFUSE the boot.
|
|
290
|
+
//
|
|
291
|
+
// The mutation-proof half of the wiring guard. tests/module_loader.mjs calls
|
|
292
|
+
// mountModuleRoutes directly, so it stays green if the verifyEnabledPorts call
|
|
293
|
+
// site is deleted; this spawns the real entrypoint the way docker-entrypoint.sh
|
|
294
|
+
// does and asserts the process dies with the wiring error, so removing the call
|
|
295
|
+
// turns it red (learning 1000154 — the same shape as task 1002419 above).
|
|
296
|
+
//
|
|
297
|
+
// The unsatisfiable set is built from REAL modules via a documented env knob, not
|
|
298
|
+
// a fixture: economy provides the `reward` port and ideas consumes it, both
|
|
299
|
+
// default:true, so turning economy off strands ideas. DB-free — the refusal
|
|
300
|
+
// happens while the router is being built, long before anything opens a socket.
|
|
301
|
+
// ---------------------------------------------------------------------------
|
|
302
|
+
|
|
303
|
+
test('task 1003820: platform-server refuses to boot when an enabled module consumes an unprovided port', async () => {
|
|
304
|
+
const port = await freePort();
|
|
305
|
+
const child = spawn(process.execPath, [path.join(REPO_ROOT, 'src', 'platform-server.js')], {
|
|
306
|
+
cwd: REPO_ROOT,
|
|
307
|
+
env: {
|
|
308
|
+
...process.env,
|
|
309
|
+
PORT: String(port),
|
|
310
|
+
HOST: '127.0.0.1',
|
|
311
|
+
// economy provides "reward"; ideas consumes it and stays on.
|
|
312
|
+
CLOUDBONGOS_MODULE_ECONOMY: '0',
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
let out = '';
|
|
317
|
+
child.stdout.on('data', (d) => { out += d; });
|
|
318
|
+
child.stderr.on('data', (d) => { out += d; });
|
|
319
|
+
|
|
320
|
+
// SIGKILL in a finally: if the guard regresses, the child boots CLEANLY and never
|
|
321
|
+
// exits, so without this the suite would hang on a live server instead of failing.
|
|
322
|
+
try {
|
|
323
|
+
const code = await new Promise((resolve, reject) => {
|
|
324
|
+
const timer = setTimeout(
|
|
325
|
+
() => reject(new Error(`platform-server stayed up; it must refuse a half-wired boot. Log:${out}`)),
|
|
326
|
+
20_000
|
|
327
|
+
);
|
|
328
|
+
child.on('error', (e) => { clearTimeout(timer); reject(e); });
|
|
329
|
+
child.on('exit', (c) => { clearTimeout(timer); resolve(c); });
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
assert.notEqual(code, 0, `the boot must FAIL, not come up half-wired (exit ${code}). Log:${out}`);
|
|
333
|
+
assert.match(out, /unsatisfiable module wiring/, 'the refusal says what is wrong');
|
|
334
|
+
assert.match(out, /"ideas" consumes port "reward"/, 'it names the module and the port');
|
|
335
|
+
assert.doesNotMatch(out, /listening/, 'it never reaches the listen announcement');
|
|
336
|
+
} finally {
|
|
337
|
+
child.kill('SIGKILL');
|
|
338
|
+
}
|
|
339
|
+
});
|