@bongos/core 1.19.247 → 1.19.249
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 +19 -14
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/cli-lib.js +49 -2
- package/scripts/gds/start.js +5 -1
- package/src/bongos/module-scope-map.js +21 -0
- package/src/module-api.js +5 -1
- package/tests/cli_dispatcher.mjs +133 -0
- package/tests/module-scope-map.mjs +40 -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.249",
|
|
6
|
+
"core_contract": "1.19.249",
|
|
7
|
+
"source_commit": "992220580b9a449998b6c24f857cf0e770bb05cb",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-18T23:
|
|
9
|
+
"built_at": "2026-08-18T23:57:42.981Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 312,
|
|
13
13
|
"agent_docs_stubbed": 21,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 1330,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 1663,
|
|
20
|
+
"tree_sha256": "b7562afed829bda45658ec0e21c2c045fd7d4828b7074f2d4834937bb15d0121",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -4347,12 +4347,12 @@
|
|
|
4347
4347
|
{
|
|
4348
4348
|
"path": "package-lock.json",
|
|
4349
4349
|
"mode": "0000644",
|
|
4350
|
-
"sha256": "
|
|
4350
|
+
"sha256": "72b91522dadad774aa718d0b832f77d942d427175b52285eb15416e4f95a1337"
|
|
4351
4351
|
},
|
|
4352
4352
|
{
|
|
4353
4353
|
"path": "package.json",
|
|
4354
4354
|
"mode": "0000644",
|
|
4355
|
-
"sha256": "
|
|
4355
|
+
"sha256": "480de6ffb6a5f6da702e74843627d5083d9dec4a76e9efcb6a1e49f97ade8cbb"
|
|
4356
4356
|
},
|
|
4357
4357
|
{
|
|
4358
4358
|
"path": "public-docs/index.html",
|
|
@@ -4847,7 +4847,7 @@
|
|
|
4847
4847
|
{
|
|
4848
4848
|
"path": "scripts/gds/cli-lib.js",
|
|
4849
4849
|
"mode": "0000644",
|
|
4850
|
-
"sha256": "
|
|
4850
|
+
"sha256": "95574e210b1fd215b10c7664da109b0698b18aa7724daa736bade6d752318f63"
|
|
4851
4851
|
},
|
|
4852
4852
|
{
|
|
4853
4853
|
"path": "scripts/gds/code.js",
|
|
@@ -5572,7 +5572,7 @@
|
|
|
5572
5572
|
{
|
|
5573
5573
|
"path": "scripts/gds/start.js",
|
|
5574
5574
|
"mode": "0000644",
|
|
5575
|
-
"sha256": "
|
|
5575
|
+
"sha256": "cb37801c6ef35a64734af422c9651b287236c4d67f7ed72a63e1cd75a94d8b38"
|
|
5576
5576
|
},
|
|
5577
5577
|
{
|
|
5578
5578
|
"path": "scripts/gds/status.js",
|
|
@@ -5802,7 +5802,7 @@
|
|
|
5802
5802
|
{
|
|
5803
5803
|
"path": "src/bongos/module-scope-map.js",
|
|
5804
5804
|
"mode": "0000644",
|
|
5805
|
-
"sha256": "
|
|
5805
|
+
"sha256": "e0c20565bb59c93fa9fff0aafa5eb246a0b16564604d18b5ae9ca350dd9e1a44"
|
|
5806
5806
|
},
|
|
5807
5807
|
{
|
|
5808
5808
|
"path": "src/bongos/module-submissions.js",
|
|
@@ -5972,7 +5972,7 @@
|
|
|
5972
5972
|
{
|
|
5973
5973
|
"path": "src/module-api.js",
|
|
5974
5974
|
"mode": "0000644",
|
|
5975
|
-
"sha256": "
|
|
5975
|
+
"sha256": "79234cc30c410a215492f897139bf042c2ff58f778e490d18ec3f65a6fd860f8"
|
|
5976
5976
|
},
|
|
5977
5977
|
{
|
|
5978
5978
|
"path": "src/module-loader/catalog.js",
|
|
@@ -6439,6 +6439,11 @@
|
|
|
6439
6439
|
"mode": "0000644",
|
|
6440
6440
|
"sha256": "b9258f690d5dc7c87bcbb12d510b3071a38669777ae9a92a99e683e76ea15e83"
|
|
6441
6441
|
},
|
|
6442
|
+
{
|
|
6443
|
+
"path": "tests/cli_dispatcher.mjs",
|
|
6444
|
+
"mode": "0000644",
|
|
6445
|
+
"sha256": "3e3e17428a6c8f50ce0512cfacc4e18e0b6a2c506d185fa5f2adf21c9feffe9f"
|
|
6446
|
+
},
|
|
6442
6447
|
{
|
|
6443
6448
|
"path": "tests/cli_exit_no_abort.mjs",
|
|
6444
6449
|
"mode": "0000644",
|
|
@@ -7437,7 +7442,7 @@
|
|
|
7437
7442
|
{
|
|
7438
7443
|
"path": "tests/module-scope-map.mjs",
|
|
7439
7444
|
"mode": "0000644",
|
|
7440
|
-
"sha256": "
|
|
7445
|
+
"sha256": "2b7bc9594489401b3dd67070c3e549d0a81347abb625a04fe19a41a178af8857"
|
|
7441
7446
|
},
|
|
7442
7447
|
{
|
|
7443
7448
|
"path": "tests/module_api.mjs",
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.249",
|
|
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.249",
|
|
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.249",
|
|
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/scripts/gds/cli-lib.js
CHANGED
|
@@ -141,6 +141,49 @@ function noKeepAliveDispatcher() {
|
|
|
141
141
|
return _noKeepAliveDispatcher;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
// task 1002670 — the vendored Agent above is only valid as a `dispatcher` for
|
|
145
|
+
// Node's built-in fetch when the two undici copies speak the same internal
|
|
146
|
+
// handler protocol. Node 26 bundles undici 8 while package.json overrides pin
|
|
147
|
+
// the vendored copy at ^6, so built-in fetch rejects the v6 Agent with
|
|
148
|
+
// InvalidArgumentError('invalid onError method') / UND_ERR_INVALID_ARG *before
|
|
149
|
+
// any network I/O* — and every CLI then misreported a pure client-side bug as
|
|
150
|
+
// "this instance isn't reachable yet". The pairing can't be predicted from
|
|
151
|
+
// version numbers alone (a v6 Agent works fine under Node 24's undici 7), so
|
|
152
|
+
// the selection is empirical: try the vendored Agent, and on the first
|
|
153
|
+
// incompat rejection drop it for the rest of the process and retry the request
|
|
154
|
+
// once on the default dispatcher.
|
|
155
|
+
function isDispatcherIncompatError(err) {
|
|
156
|
+
const seen = new Set();
|
|
157
|
+
for (let e = err; e && typeof e === 'object' && !seen.has(e); e = e.cause) {
|
|
158
|
+
seen.add(e);
|
|
159
|
+
if (e.code === 'UND_ERR_INVALID_ARG' || e.name === 'InvalidArgumentError') return true;
|
|
160
|
+
}
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Test-only: reset the tried-once singleton so tests/cli_dispatcher.mjs can
|
|
165
|
+
// exercise the selection/fallback from a clean slate regardless of what earlier
|
|
166
|
+
// (real) requests did to it on this Node version.
|
|
167
|
+
function _noKeepAliveResetForTest() {
|
|
168
|
+
_noKeepAliveDispatcher = undefined;
|
|
169
|
+
_noKeepAliveTried = false;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function dispatchedFetch(url, init = {}, fetchImpl = fetch) {
|
|
173
|
+
const injected = init.dispatcher !== undefined && init.dispatcher === _noKeepAliveDispatcher;
|
|
174
|
+
try {
|
|
175
|
+
return await fetchImpl(url, init);
|
|
176
|
+
} catch (err) {
|
|
177
|
+
if (!injected || !isDispatcherIncompatError(err)) throw err;
|
|
178
|
+
// The vendored Agent is incompatible with this Node's built-in fetch.
|
|
179
|
+
// Stop offering it process-wide, then retry on the default dispatcher.
|
|
180
|
+
_noKeepAliveDispatcher = undefined;
|
|
181
|
+
const retry = { ...init };
|
|
182
|
+
delete retry.dispatcher;
|
|
183
|
+
return fetchImpl(url, retry);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
144
187
|
// cliExit(code) — the post-request replacement for process.exit(code). Records the
|
|
145
188
|
// exit status and lets node exit naturally once the loop drains, which is what
|
|
146
189
|
// keeps the UV_HANDLE_CLOSING abort above from firing. Callers must `return` after
|
|
@@ -463,7 +506,7 @@ async function apiCall(method, urlPath, body = null, opts = {}) {
|
|
|
463
506
|
// account billing, so identity is not an auth boundary. anthropic_user_id is
|
|
464
507
|
// still kept in the session as cosmetic attribution; it is simply not
|
|
465
508
|
// transmitted as a header.
|
|
466
|
-
const res = await
|
|
509
|
+
const res = await dispatchedFetch(`${base}${urlPath}`, {
|
|
467
510
|
method,
|
|
468
511
|
headers,
|
|
469
512
|
body: body ? JSON.stringify(body) : null,
|
|
@@ -531,7 +574,7 @@ async function cliClient(opts = {}) {
|
|
|
531
574
|
init = init || {};
|
|
532
575
|
init.headers = Object.assign({ 'User-Agent': CLI_USER_AGENT }, init.headers);
|
|
533
576
|
if (init.dispatcher === undefined) init.dispatcher = noKeepAliveDispatcher();
|
|
534
|
-
const res = await
|
|
577
|
+
const res = await dispatchedFetch(url, init);
|
|
535
578
|
if (res.status === 401 && !allowUnauthenticated && !_unauthExitInFlight) {
|
|
536
579
|
_unauthExitInFlight = true;
|
|
537
580
|
printUnauthGuidanceAndExit();
|
|
@@ -817,6 +860,10 @@ module.exports = {
|
|
|
817
860
|
readSessionToken,
|
|
818
861
|
apiCall,
|
|
819
862
|
cliExit,
|
|
863
|
+
isDispatcherIncompatError,
|
|
864
|
+
dispatchedFetch,
|
|
865
|
+
noKeepAliveDispatcher,
|
|
866
|
+
_noKeepAliveResetForTest,
|
|
820
867
|
cliClient,
|
|
821
868
|
requireSession,
|
|
822
869
|
printUnauthGuidanceAndExit,
|
package/scripts/gds/start.js
CHANGED
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
//
|
|
53
53
|
// Returns JSON if --json is passed (for the slash command to parse cleanly).
|
|
54
54
|
|
|
55
|
-
const { cliClient, requireSession, fmtUsd, printOnboardingNudge, printCostWarning, printNeedsNudge, hallBase, API_BASE, rewardPolicy, costPlusOnlyNote } = require('./cli-lib');
|
|
55
|
+
const { cliClient, requireSession, fmtUsd, printOnboardingNudge, printCostWarning, printNeedsNudge, hallBase, API_BASE, rewardPolicy, costPlusOnlyNote, isDispatcherIncompatError } = require('./cli-lib');
|
|
56
56
|
const { contextPack, formatContextPackCompact } = require('./context-pack');
|
|
57
57
|
const { depsFinding } = require('./preflight');
|
|
58
58
|
|
|
@@ -507,6 +507,10 @@ function rankCrossDisciplineRecommendations(claimable, preferredDisciplines, lim
|
|
|
507
507
|
// builder-start skill routes those + the pre-install "bongos not found" case.
|
|
508
508
|
function isConnectionError(err) {
|
|
509
509
|
if (!err) return false;
|
|
510
|
+
// task 1002670: a dispatcher/argument bug (e.g. a vendored undici Agent the
|
|
511
|
+
// built-in fetch rejects with UND_ERR_INVALID_ARG) throws before any network
|
|
512
|
+
// I/O. That is a CLIENT bug — never present it as "instance isn't reachable".
|
|
513
|
+
if (isDispatcherIncompatError(err)) return false;
|
|
510
514
|
const codes = ['ECONNREFUSED', 'ENOTFOUND', 'ECONNRESET', 'EAI_AGAIN', 'ETIMEDOUT', 'UND_ERR_CONNECT_TIMEOUT', 'UND_ERR_SOCKET'];
|
|
511
515
|
const code = err.code || (err.cause && err.cause.code) || '';
|
|
512
516
|
if (codes.includes(code)) return true;
|
|
@@ -274,6 +274,27 @@ const MODULE_GLOBS = {
|
|
|
274
274
|
'scripts/gds/box.js', 'scripts/gds/box-auth-check.js', 'scripts/gds/box-connect-lib.js',
|
|
275
275
|
'scripts/gds/smoke-box.sh',
|
|
276
276
|
],
|
|
277
|
+
// --- the dev-hygiene enforcement surfaces: CI workflows, the agent permission/
|
|
278
|
+
// hook files, git hooks, root manifests, and the fitness/entropy tooling
|
|
279
|
+
// (task 1003124, goal 1000076). Before this key, NO key resolved these paths,
|
|
280
|
+
// so the continuous-red-team goal could not wall its own work (the
|
|
281
|
+
// 1002690/1002736 precedent). Most of these paths were ALREADY in the
|
|
282
|
+
// protected-surfaces registry (ADR 0043) — what was missing was ownership —
|
|
283
|
+
// so `protected` DERIVES true via the .github/ / package.json / .husky/ /
|
|
284
|
+
// .claude-hooks overlap. Boundaries: '.claude/scheduled-tasks/' stays with
|
|
285
|
+
// autonomy and '.claude/agents/' with agents (their source surfaces);
|
|
286
|
+
// '.claude/skills/' stays with whichever module ships the skill.
|
|
287
|
+
// '.npmrc' and '.gitleaks.toml' are pre-carved territory for goal 1000076's
|
|
288
|
+
// R-tasks (the carve precedes the feature — ADR 0086 §4).
|
|
289
|
+
devsecops: [
|
|
290
|
+
'.github/',
|
|
291
|
+
'.claude/settings.json', '.claude/hooks/',
|
|
292
|
+
'.husky/',
|
|
293
|
+
'.npmrc', '.gitleaks.toml',
|
|
294
|
+
'package.json', 'package-lock.json',
|
|
295
|
+
'scripts/gds/fitness.js', 'scripts/gds/docs-entropy.js',
|
|
296
|
+
'scripts/gds/redteam-patrol.js',
|
|
297
|
+
],
|
|
277
298
|
};
|
|
278
299
|
|
|
279
300
|
// A glob set is protected iff it overlaps the imported PROTECTED_GLOBS — the one
|
package/src/module-api.js
CHANGED
|
@@ -921,8 +921,12 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
921
921
|
// landed since 1.19.245 with no explicit bump. run 32197129803. (task 1002620)
|
|
922
922
|
// 1.19.247 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
923
923
|
// landed since 1.19.246 with no explicit bump. run 32197488065. (task 1002620)
|
|
924
|
+
// 1.19.248 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
925
|
+
// landed since 1.19.247 with no explicit bump. run 32198208766. (task 1002620)
|
|
926
|
+
// 1.19.249 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
927
|
+
// landed since 1.19.248 with no explicit bump. run 32199293372. (task 1002620)
|
|
924
928
|
// ---------------------------------------------------------------------------
|
|
925
|
-
const CORE_VERSION = '1.19.
|
|
929
|
+
const CORE_VERSION = '1.19.249'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
926
930
|
|
|
927
931
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
928
932
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// tests/cli_dispatcher.mjs — dispatcher selection/fallback for the CLI fetch path
|
|
2
|
+
// (task 1002670).
|
|
3
|
+
//
|
|
4
|
+
// THE BUG. cli-lib's noKeepAliveDispatcher() builds an Agent from the VENDORED
|
|
5
|
+
// undici (package.json overrides pin ^6) and hands it to Node's BUILT-IN fetch as
|
|
6
|
+
// `dispatcher`. Whether that pairing works is an internal-protocol accident of the
|
|
7
|
+
// two undici versions: it works on Node 24 (built-in undici 7), and on Node 26
|
|
8
|
+
// (built-in undici 8) it throws InvalidArgumentError('invalid onError method') /
|
|
9
|
+
// UND_ERR_INVALID_ARG before any network I/O — and every CLI then misreported the
|
|
10
|
+
// client-side bug as "this instance isn't reachable yet".
|
|
11
|
+
//
|
|
12
|
+
// THE CONTRACT PINNED HERE:
|
|
13
|
+
// 1. isDispatcherIncompatError() recognizes the incompat shapes (top-level and
|
|
14
|
+
// wrapped in `cause`, per built-in fetch's TypeError('fetch failed')) and
|
|
15
|
+
// rejects genuine network-error shapes.
|
|
16
|
+
// 2. dispatchedFetch() retries exactly once on the default dispatcher when the
|
|
17
|
+
// injected vendored Agent is rejected — and does NOT mask other failures or
|
|
18
|
+
// caller-supplied dispatchers.
|
|
19
|
+
// 3. start.js's isConnectionError() returns false for the incompat shapes, so a
|
|
20
|
+
// dispatcher bug can never render the "instance isn't reachable" card.
|
|
21
|
+
// 4. End-to-end on THIS Node: the real built-in fetch + the real vendored Agent
|
|
22
|
+
// reach a live local server (on Node 26 this is the case that used to die).
|
|
23
|
+
//
|
|
24
|
+
// Run: node --test tests/cli_dispatcher.mjs
|
|
25
|
+
|
|
26
|
+
import assert from 'node:assert/strict';
|
|
27
|
+
import { test } from 'node:test';
|
|
28
|
+
import http from 'node:http';
|
|
29
|
+
import { createRequire } from 'node:module';
|
|
30
|
+
|
|
31
|
+
const require = createRequire(import.meta.url);
|
|
32
|
+
const cli = require('../scripts/gds/cli-lib.js');
|
|
33
|
+
const { isConnectionError } = require('../scripts/gds/start.js');
|
|
34
|
+
|
|
35
|
+
function incompatError() {
|
|
36
|
+
const cause = new TypeError('invalid onError method');
|
|
37
|
+
cause.name = 'InvalidArgumentError';
|
|
38
|
+
cause.code = 'UND_ERR_INVALID_ARG';
|
|
39
|
+
return new TypeError('fetch failed', { cause });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function connRefusedError() {
|
|
43
|
+
const cause = new Error('connect ECONNREFUSED 127.0.0.1:1');
|
|
44
|
+
cause.code = 'ECONNREFUSED';
|
|
45
|
+
return new TypeError('fetch failed', { cause });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
test('isDispatcherIncompatError: recognizes UND_ERR_INVALID_ARG wrapped in cause', () => {
|
|
49
|
+
assert.equal(cli.isDispatcherIncompatError(incompatError()), true);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('isDispatcherIncompatError: recognizes a bare InvalidArgumentError (no wrapper)', () => {
|
|
53
|
+
const err = new TypeError('invalid onError method');
|
|
54
|
+
err.name = 'InvalidArgumentError';
|
|
55
|
+
assert.equal(cli.isDispatcherIncompatError(err), true);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('isDispatcherIncompatError: rejects genuine network errors', () => {
|
|
59
|
+
assert.equal(cli.isDispatcherIncompatError(connRefusedError()), false);
|
|
60
|
+
assert.equal(cli.isDispatcherIncompatError(new Error('socket hang up')), false);
|
|
61
|
+
assert.equal(cli.isDispatcherIncompatError(null), false);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('isDispatcherIncompatError: survives a self-referential cause chain', () => {
|
|
65
|
+
const err = new Error('loop');
|
|
66
|
+
err.cause = err;
|
|
67
|
+
assert.equal(cli.isDispatcherIncompatError(err), false);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('end-to-end on this Node: real fetch + vendored Agent reach a live server', async () => {
|
|
71
|
+
const server = http.createServer((req, res) => {
|
|
72
|
+
res.setHeader('content-type', 'application/json');
|
|
73
|
+
res.end(JSON.stringify({ ok: true, path: req.url }));
|
|
74
|
+
});
|
|
75
|
+
await new Promise((r) => server.listen(0, '127.0.0.1', r));
|
|
76
|
+
const { port } = server.address();
|
|
77
|
+
try {
|
|
78
|
+
// Exactly what apiCall does: built-in fetch, vendored no-keep-alive Agent
|
|
79
|
+
// injected. On Node 26 without the fallback this rejected before any I/O.
|
|
80
|
+
const res = await cli.dispatchedFetch(`http://127.0.0.1:${port}/api/gds/me`, {
|
|
81
|
+
method: 'GET',
|
|
82
|
+
headers: { 'Content-Type': 'application/json' },
|
|
83
|
+
dispatcher: cli.noKeepAliveDispatcher(),
|
|
84
|
+
});
|
|
85
|
+
assert.equal(res.status, 200);
|
|
86
|
+
const data = await res.json();
|
|
87
|
+
assert.equal(data.ok, true);
|
|
88
|
+
} finally {
|
|
89
|
+
server.close();
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// NOTE: on Node 26 the end-to-end test above already fired the fallback and
|
|
94
|
+
// dropped the singleton, so each test below resets it to a clean slate.
|
|
95
|
+
|
|
96
|
+
test('dispatchedFetch: retries once without dispatcher when the vendored Agent is rejected', async () => {
|
|
97
|
+
cli._noKeepAliveResetForTest();
|
|
98
|
+
const vendored = cli.noKeepAliveDispatcher();
|
|
99
|
+
assert.ok(vendored !== undefined, 'vendored undici Agent should resolve in this repo');
|
|
100
|
+
|
|
101
|
+
const calls = [];
|
|
102
|
+
const fakeFetch = async (url, init) => {
|
|
103
|
+
calls.push(init.dispatcher);
|
|
104
|
+
if (init.dispatcher !== undefined) throw incompatError();
|
|
105
|
+
return { ok: true, status: 200 };
|
|
106
|
+
};
|
|
107
|
+
const res = await cli.dispatchedFetch('http://x/', { dispatcher: vendored }, fakeFetch);
|
|
108
|
+
assert.equal(res.status, 200);
|
|
109
|
+
assert.deepEqual(calls, [vendored, undefined]);
|
|
110
|
+
|
|
111
|
+
// After one incompat rejection the broken Agent is dropped process-wide:
|
|
112
|
+
// later calls get the default dispatcher instead of retrying the broken one.
|
|
113
|
+
assert.equal(cli.noKeepAliveDispatcher(), undefined);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('dispatchedFetch: does not retry a caller-supplied dispatcher, or non-incompat errors', async () => {
|
|
117
|
+
const foreign = { name: 'someone-elses-dispatcher' };
|
|
118
|
+
let calls = 0;
|
|
119
|
+
const rejectingFetch = async () => { calls++; throw incompatError(); };
|
|
120
|
+
await assert.rejects(() => cli.dispatchedFetch('http://x/', { dispatcher: foreign }, rejectingFetch));
|
|
121
|
+
assert.equal(calls, 1);
|
|
122
|
+
|
|
123
|
+
calls = 0;
|
|
124
|
+
const networkFail = async () => { calls++; throw connRefusedError(); };
|
|
125
|
+
await assert.rejects(() => cli.dispatchedFetch('http://x/', {}, networkFail), /fetch failed/);
|
|
126
|
+
assert.equal(calls, 1);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('start.js isConnectionError: dispatcher bug is NOT a connection error', () => {
|
|
130
|
+
assert.equal(isConnectionError(incompatError()), false);
|
|
131
|
+
assert.equal(isConnectionError(connRefusedError()), true);
|
|
132
|
+
assert.equal(isConnectionError(new Error('getaddrinfo ENOTFOUND nope.example')), true);
|
|
133
|
+
});
|
|
@@ -166,3 +166,43 @@ test('agents joined the roster (task 1002736) — the territory goals 1000038/39
|
|
|
166
166
|
assert.equal(covers('src/bongos/auth.js'), false, 'the wall must not cover the kernel auth core');
|
|
167
167
|
assert.equal(covers('.claude/scheduled-tasks/bfg/SKILL.md'), false, 'scheduled-tasks belongs to autonomy, not agents');
|
|
168
168
|
});
|
|
169
|
+
|
|
170
|
+
test('devsecops joined the roster (task 1003124) — the enforcement territory goal 1000076 walls', () => {
|
|
171
|
+
// Before this, .github/, .claude/settings.json + hooks, .husky/, the root
|
|
172
|
+
// manifests, and the fitness/entropy tooling were covered by NO key, so the
|
|
173
|
+
// continuous-red-team goal (1000076) could not wall its own work.
|
|
174
|
+
assert.ok(scope.isModuleKey('devsecops'), 'roster missing module "devsecops"');
|
|
175
|
+
const globs = scope.globsForModules(['devsecops']);
|
|
176
|
+
assert.ok(globs.includes('.github/'), 'devsecops owns the CI workflow surface');
|
|
177
|
+
assert.ok(globs.includes('.claude/hooks/'), 'devsecops owns the agent hook surface');
|
|
178
|
+
assert.ok(globs.includes('scripts/gds/fitness.js'), 'the fitness tool travels with the domain');
|
|
179
|
+
// Derived, not hand-set: .github/, package.json, .husky/, .claude/hooks/ and
|
|
180
|
+
// .claude/settings.json all sit in the ADR 0043 protected-surfaces registry, so
|
|
181
|
+
// this key MUST derive protected — creating/widening a goal into it is Archon-only.
|
|
182
|
+
assert.equal(scope.isProtectedModule('devsecops'), true, 'devsecops must derive protected (trust-core overlap)');
|
|
183
|
+
assert.equal(
|
|
184
|
+
scope.scopeForModule('devsecops').protected,
|
|
185
|
+
scope.isProtected(globs),
|
|
186
|
+
'protected must be the faithful derivation, never a literal',
|
|
187
|
+
);
|
|
188
|
+
// Asserted through the REAL consumer (directional containment), not re-implemented.
|
|
189
|
+
const covers = (touch) => gsc.scopeSubset(['devsecops'], [touch]).ok;
|
|
190
|
+
for (const touch of [
|
|
191
|
+
'.github/workflows/unit.yml', // the CI gate R03/R04 extend
|
|
192
|
+
'.claude/settings.json', // the deny rules R02 adds
|
|
193
|
+
'.claude/hooks/secret-gate.js', // the PreToolUse hook R02 adds
|
|
194
|
+
'.npmrc', // the install-script policy R04 lands
|
|
195
|
+
'package.json', // dependency manifest (R04/R07)
|
|
196
|
+
'.gitleaks.toml', // the scanner config R03 lands
|
|
197
|
+
'scripts/gds/fitness.js', // the ratchet pack R06 extends
|
|
198
|
+
'scripts/gds/docs-entropy.js', // the entropy scanner behind the ratchet
|
|
199
|
+
]) {
|
|
200
|
+
assert.equal(covers(touch), true, `wall does not cover ${touch}`);
|
|
201
|
+
}
|
|
202
|
+
// …and still BOUNDS: the trust kernel, the sibling .claude surfaces, and the
|
|
203
|
+
// lifecycle CLIs stay outside this wall.
|
|
204
|
+
assert.equal(covers('src/bongos/auth.js'), false, 'the wall must not cover the kernel auth core');
|
|
205
|
+
assert.equal(covers('.claude/scheduled-tasks/bfg/SKILL.md'), false, 'scheduled-tasks belongs to autonomy, not devsecops');
|
|
206
|
+
assert.equal(covers('.claude/skills/builder-ship/SKILL.md'), false, 'skills stay with the module that ships them');
|
|
207
|
+
assert.equal(covers('scripts/gds/ship.js'), false, 'the ship pipeline belongs to lifecycle, not devsecops');
|
|
208
|
+
});
|