@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 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.247",
6
- "core_contract": "1.19.247",
7
- "source_commit": "34586475492a431129c3612b15bfc96828177bf6",
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:31:27.304Z",
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": 1329,
14
+ "functional_verbatim": 1330,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 1662,
20
- "tree_sha256": "8eef4ef670a75bd7d852d9e5d23425374d6bcd2513a0196f502761410001bc64",
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": "a83fced3f633cdf5b808849c457288968d47be1b7c5298004dc350f75b3ddeba"
4350
+ "sha256": "72b91522dadad774aa718d0b832f77d942d427175b52285eb15416e4f95a1337"
4351
4351
  },
4352
4352
  {
4353
4353
  "path": "package.json",
4354
4354
  "mode": "0000644",
4355
- "sha256": "d90aea68a4820402ff80446a1b18960f18b833e91162e17c50f2c780d6c4fb26"
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": "83d27d14600c4c8ec8912c7ad8ce771e0e34903ccc9b567dcc27cb95aeb3c37d"
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": "ecb7f326b9ef642e330b3f1d5dd68cf40ee76e451552291804b4a71a28cfab40"
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": "277097c686b695688dee35d158c4bb8384b4a5a89c61f30c7bda7d9f4b477bee"
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": "d810e3307f867bb1495e40227c233cf57f3ee82cd9ee7e052eae43ac4e4eecff"
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": "9eb05d38e5138e0da16d57d1d4c8768528dbc052acbc1a9d5d5e7cef7d38f06c"
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.247",
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.247",
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.247",
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",
@@ -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 fetch(`${base}${urlPath}`, {
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 fetch(url, init);
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,
@@ -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.247'; // CI auto-patch carrier (ADR 0161); see changelog above
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
+ });