@bongos/core 1.19.320 → 1.19.321

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,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.320",
6
- "core_contract": "1.19.320",
7
- "source_commit": "add665ea5d3565d67ff3ef951b724cae1b7e2ca7",
5
+ "core_version": "1.19.321",
6
+ "core_contract": "1.19.321",
7
+ "source_commit": "242735e2b39608c5500b8c5bd231832a90c5fae7",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-24T00:55:40.560Z",
9
+ "built_at": "2026-08-24T01:17:55.486Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 323,
@@ -17,7 +17,7 @@
17
17
  "gate": "passed"
18
18
  },
19
19
  "file_count": 1800,
20
- "tree_sha256": "e31137b82bd472bdedaa8baed29373670556670e80ed40ff0c4b841142b5259f",
20
+ "tree_sha256": "5d80f6db5c26850188eb0c3f5ace7c42ff85561d277dc525e24f46cdcf52dee8",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -2827,7 +2827,7 @@
2827
2827
  {
2828
2828
  "path": "modules/dev-box/box-access.js",
2829
2829
  "mode": "0000644",
2830
- "sha256": "870976966de7c48be4172390dc8e4f7d2eb6ac53e20dfb3fd5d40b08cfcd7850"
2830
+ "sha256": "a51e16aba67547c0692d9422587f83d8ce40f0e45830463b80bfd188401eb6c0"
2831
2831
  },
2832
2832
  {
2833
2833
  "path": "modules/dev-box/box-credential.js",
@@ -4642,12 +4642,12 @@
4642
4642
  {
4643
4643
  "path": "package-lock.json",
4644
4644
  "mode": "0000644",
4645
- "sha256": "cc6846ec9d50fa1d8e41c960ff14171e4033faf435fe54fdfe60b38ec69968a3"
4645
+ "sha256": "6f62185348f89cc7ae1c8998a25d4bdccb2e204ad81581555686f37057db1511"
4646
4646
  },
4647
4647
  {
4648
4648
  "path": "package.json",
4649
4649
  "mode": "0000644",
4650
- "sha256": "5b964977f97fcd0749db36916838c51f0ffbea4a6e2eba5af2538472b519758d"
4650
+ "sha256": "3ba87e851af1bf1ad2a08cb62d4c875be12d7ec6d86ffda9e5b26e24fee313ae"
4651
4651
  },
4652
4652
  {
4653
4653
  "path": "public-docs/index.html",
@@ -5292,7 +5292,7 @@
5292
5292
  {
5293
5293
  "path": "scripts/gds/fitness-ratchets.js",
5294
5294
  "mode": "0000644",
5295
- "sha256": "a27a3873f516abdc52f7f482603779010bbc1af4d6e73c0b86dd34292017bc9a"
5295
+ "sha256": "3f32080357abdecb0f0860c5c9313f12283f01774c8367ac22c1c12d0d176411"
5296
5296
  },
5297
5297
  {
5298
5298
  "path": "scripts/gds/fitness.js",
@@ -6417,7 +6417,7 @@
6417
6417
  {
6418
6418
  "path": "src/module-api.js",
6419
6419
  "mode": "0000644",
6420
- "sha256": "8cf8e0f172ad38bdf3871c06b99170adf366d2b2b5f1b0e993ecbc0877003fd7"
6420
+ "sha256": "31c5e453080859713ed00c327a6b1fb1a022d2509438523979bad2e0e8feb7bf"
6421
6421
  },
6422
6422
  {
6423
6423
  "path": "src/module-loader/catalog.js",
@@ -6732,7 +6732,7 @@
6732
6732
  {
6733
6733
  "path": "tests/box_access.mjs",
6734
6734
  "mode": "0000644",
6735
- "sha256": "1e4db680c5925e473fff9ad6247e797bd9089fdf3696c3ffb161903bda41dcda"
6735
+ "sha256": "fcad6c578c2d8698d6ad93fe04f500734d589013590f006910f1bc33fc908642"
6736
6736
  },
6737
6737
  {
6738
6738
  "path": "tests/box_auth_check.mjs",
@@ -7272,7 +7272,7 @@
7272
7272
  {
7273
7273
  "path": "tests/fitness_ratchets.mjs",
7274
7274
  "mode": "0000644",
7275
- "sha256": "781bdee0778645704b5179bf99507e839743158714f799e45d6cc64f8fc30477"
7275
+ "sha256": "e33edfa4925694d824c042f1fd06a977f1ac14b5c1cb24b5326014ca9f86ece0"
7276
7276
  },
7277
7277
  {
7278
7278
  "path": "tests/fitness_two_root.mjs",
@@ -158,6 +158,26 @@ const BASE_SPARSE_PATHS = [
158
158
  // and box-source-fetch re-applies this server-computed list every tick
159
159
  // (task 1002745). Generated ESM only, no product runtime.
160
160
  'clients',
161
+ // config/ — task 1003227. Without it a box CANNOT RUN ITS OWN QUALITY GATE:
162
+ // `scripts/gds/fitness.js` compares against config/fitness-baselines.json, and
163
+ // with the file absent the ratchet used to report PASS (it read the baselines
164
+ // through a try/catch that returned {}, so every metric had no floor to breach).
165
+ // A planted 5,000-line + 94-cycle regression sailed straight through. So a boxed
166
+ // builder shipped believing they were clean and learned otherwise from red CI,
167
+ // with nothing on the box to reproduce it. The fail-closed half is fixed in
168
+ // fitness-ratchets.js; this is the half that lets the gate actually run.
169
+ //
170
+ // Precedent, and why the "a module glob will pull it" argument does not hold:
171
+ // ADR 0174 put config/government.json in the `government` key's globs and the
172
+ // parent-directory emission below exists precisely because `config` was not in
173
+ // BASE — task 1003018 was RELEASED because its box could never see the file that
174
+ // was its entire deliverable. BASE is the right home, not a per-module accident.
175
+ //
176
+ // Secret posture unchanged: a cone-mode directory of JSON config. The NEUTRAL
177
+ // packs are already public, and an instance pack (config/branding.json,
178
+ // config/government.json) is host content the box's own builder authors — no
179
+ // credentials live here (ADR 0022); those are in /etc and the box env.
180
+ 'config',
161
181
  ];
162
182
 
163
183
  // sparsePathsForScopeKeys — PURE. Given the module KEYS a builder's active claims
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.320",
3
+ "version": "1.19.321",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.320",
9
+ "version": "1.19.321",
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.320",
3
+ "version": "1.19.321",
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",
@@ -215,12 +215,52 @@ function knipIssueCount() {
215
215
 
216
216
  // ---------- baseline + ratchet ----------
217
217
 
218
+ // readBaselines — task 1003227. Returns { metrics, error }: `error` is a reason
219
+ // string when the file cannot be trusted, null when it read cleanly.
220
+ //
221
+ // This used to `catch { return {}; }`, which made the gate FAIL OPEN. An empty
222
+ // baseline map leaves every `floor === undefined`, and compareToBaselines classifies
223
+ // an absent floor as a WARNING — so a checkout with no config/ reported the ratchet
224
+ // as PASSED. Measured: with the baselines gone, a planted 5,000-line and 94-cycle
225
+ // regression produced 0 violations. That is not a hypothetical checkout — `config/`
226
+ // is absent from every dev box's sparse slice, so the builders who most needed the
227
+ // gate were the ones silently exempt from it, learning from red CI instead.
228
+ //
229
+ // Distinguish the two absences, because they are not the same fact:
230
+ // - the FILE is missing/malformed -> cannot verify anything -> hard fail (here);
231
+ // - a KEY is missing for one metric -> a genuinely new metric -> stays a warning
232
+ // in compareToBaselines, so adding a metric does not break the build.
233
+ //
234
+ // The file's own header says raising a baseline to pass CI is out of policy;
235
+ // deleting the file must not be the loophole that raising it is not.
218
236
  function readBaselines() {
237
+ let raw;
219
238
  try {
220
- return JSON.parse(fs.readFileSync(BASELINE_PATH, 'utf8')).metrics || {};
221
- } catch {
222
- return {};
239
+ raw = fs.readFileSync(BASELINE_PATH, 'utf8');
240
+ } catch (e) {
241
+ const missing = e && e.code === 'ENOENT';
242
+ return {
243
+ metrics: {},
244
+ error: missing
245
+ ? `config/fitness-baselines.json is MISSING — the quality ratchet cannot verify anything, so it fails CLOSED rather than reporting a pass it did not check. `
246
+ + `If this is a dev box, config/ was historically outside the sparse checkout (task 1003227); re-sync the box (\`node scripts/gds/box-sync.js\`). `
247
+ + `To bootstrap a fresh baseline deliberately: node scripts/gds/fitness-ratchets.js --update-baselines`
248
+ : `config/fitness-baselines.json is unreadable (${e && e.message}) — failing CLOSED.`,
249
+ };
250
+ }
251
+ let parsed;
252
+ try {
253
+ parsed = JSON.parse(raw);
254
+ } catch (e) {
255
+ return { metrics: {}, error: `config/fitness-baselines.json is not valid JSON (${e && e.message}) — failing CLOSED.` };
256
+ }
257
+ const metrics = parsed && typeof parsed.metrics === 'object' && parsed.metrics !== null
258
+ ? parsed.metrics
259
+ : null;
260
+ if (!metrics) {
261
+ return { metrics: {}, error: 'config/fitness-baselines.json has no "metrics" object — failing CLOSED.' };
223
262
  }
263
+ return { metrics, error: null };
224
264
  }
225
265
 
226
266
  // Pure ratchet verdict — exported for the hermetic test.
@@ -256,7 +296,11 @@ function computeMetrics({ withDocs = true } = {}) {
256
296
  // The fitness.js check (in-suite, hermetic — everything except knip).
257
297
  function checkQualityRatchets() {
258
298
  const metrics = computeMetrics();
259
- const { violations, warnings } = compareToBaselines(metrics, readBaselines());
299
+ const { metrics: baselines, error: baselineError } = readBaselines();
300
+ const { violations, warnings } = compareToBaselines(metrics, baselines);
301
+ // task 1003227: an unreadable baseline file is a hard failure, not silence. Put it
302
+ // FIRST so it is the headline rather than buried under per-metric noise.
303
+ if (baselineError) violations.unshift(baselineError);
260
304
  return {
261
305
  name: 'quality ratchets — size, duplication, cycles, console, doc entropy only tighten (task 1003129)',
262
306
  hardFail: violations.length > 0,
@@ -274,7 +318,9 @@ function main() {
274
318
  const metrics = computeMetrics();
275
319
  const knip = knipIssueCount();
276
320
  if (knip !== null) metrics.knip_issue_count = knip;
277
- const existing = readBaselines();
321
+ // The bootstrap path deliberately tolerates an absent file — writing it is the
322
+ // whole point — so it reads the metrics and ignores any error (task 1003227).
323
+ const { metrics: existing } = readBaselines();
278
324
  if (knip === null && existing.knip_issue_count !== undefined) metrics.knip_issue_count = existing.knip_issue_count;
279
325
  fs.writeFileSync(
280
326
  BASELINE_PATH,
@@ -290,7 +336,9 @@ function main() {
290
336
  console.warn('fitness-ratchets: WARN — knip not runnable here (no node_modules / npx failure); failing OPEN.');
291
337
  process.exit(0);
292
338
  }
293
- const { violations, warnings } = compareToBaselines({ knip_issue_count: value }, readBaselines());
339
+ const { metrics: knipBaselines, error: knipBaselineError } = readBaselines();
340
+ const { violations, warnings } = compareToBaselines({ knip_issue_count: value }, knipBaselines);
341
+ if (knipBaselineError) violations.unshift(knipBaselineError);
294
342
  for (const w of warnings) console.warn(`fitness-ratchets: ${w}`);
295
343
  if (violations.length) {
296
344
  for (const v of violations) console.error(`fitness-ratchets: FAIL — ${v}`);
package/src/module-api.js CHANGED
@@ -1067,8 +1067,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
1067
1067
  // landed since 1.19.318 with no explicit bump. run 32670195088. (task 1002620)
1068
1068
  // 1.19.320 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1069
1069
  // landed since 1.19.319 with no explicit bump. run 32678147546. (task 1002620)
1070
+ // 1.19.321 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1071
+ // landed since 1.19.320 with no explicit bump. run 32679324013. (task 1002620)
1070
1072
  // ---------------------------------------------------------------------------
1071
- const CORE_VERSION = '1.19.320'; // CI auto-patch carrier (ADR 0161); see changelog above
1073
+ const CORE_VERSION = '1.19.321'; // CI auto-patch carrier (ADR 0161); see changelog above
1072
1074
 
1073
1075
  // A namespaced logger so a module's log lines are attributable + consistent.
1074
1076
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -368,5 +368,51 @@ t('a DENIED decision projects to null (the route returns 403, never a scope body
368
368
  assert.equal(access.scopeMetadata(), null, 'no args → null');
369
369
  });
370
370
 
371
+ // ---------- task 1003227: config/ must be in the box's BASE slice ----------
372
+ //
373
+ // Without it a box cannot run its own quality gate: fitness.js compares against
374
+ // config/fitness-baselines.json, and with the file absent the ratchet used to report
375
+ // PASS (a planted 5,000-line + 94-cycle regression sailed through). So a boxed builder
376
+ // shipped believing they were clean and learned otherwise from red CI, with nothing on
377
+ // the box to reproduce it. Precedent for the same class: ADR 0174 put
378
+ // config/government.json in the `government` globs, config/ was not in BASE, and task
379
+ // 1003018 was RELEASED because its box could never see its own deliverable.
380
+
381
+ t('1003227 BASE includes config/ so a box can read its own fitness baselines', () => {
382
+ const spec = access.decideSourceAccess({
383
+ rank: 'archon', status: 'active', scopeKeys: [], hasActiveClaims: false, moduleScopeMap: {},
384
+ });
385
+ assert.equal(spec.scope, 'base');
386
+ assert.ok(spec.spec.sparsePaths.includes('config'),
387
+ `base scope must carry config/; got ${spec.spec.sparsePaths.join(',')}`);
388
+ });
389
+
390
+ t('1003227 a TASK-scoped box carries config/ too (the common case — a claim is held)', () => {
391
+ // The REAL scope map, not a hand-rolled object — the shape matters here.
392
+ const scopeMap = require('../src/bongos/module-scope-map.js');
393
+ const spec = access.decideSourceAccess({
394
+ rank: 'xenos', status: 'active', scopeKeys: ['economy'], hasActiveClaims: true,
395
+ moduleScopeMap: scopeMap,
396
+ });
397
+ assert.equal(spec.scope, 'task');
398
+ assert.ok(spec.spec.sparsePaths.includes('config'), 'task scope must carry config/ as well');
399
+ assert.ok(spec.spec.sparsePaths.includes('modules/economy'),
400
+ `and still layer the claimed module on top; got ${spec.spec.sparsePaths.join(',')}`);
401
+ });
402
+
403
+ t('1003227 config/ is a cone-mode DIRECTORY entry (a file path aborts the whole set)', () => {
404
+ // ADR 0148: `git sparse-checkout set` rejects a file path, which kills the entire
405
+ // checkout — so every BASE entry must be a bare directory, no trailing slash.
406
+ for (const p of access.BASE_SPARSE_PATHS) {
407
+ assert.ok(!p.endsWith('/'), `${p} must not carry a trailing slash`);
408
+ // A dotted DIRECTORY (.claude, .devcontainer) is fine; what cone mode rejects is
409
+ // a file, so only test for an extension on the FINAL segment of a non-dotfile.
410
+ const last = p.split('/').pop();
411
+ if (!last.startsWith('.')) {
412
+ assert.ok(!/\.[a-z0-9]+$/i.test(last), `${p} looks like a FILE — cone mode would abort the set`);
413
+ }
414
+ }
415
+ });
416
+
371
417
  console.log(`\nbox_access.mjs: ${passed} passed, ${failed} failed`);
372
418
  process.exit(failed === 0 ? 0 : 1);
@@ -89,3 +89,85 @@ test('the committed baseline file parses and covers every hermetic metric', () =
89
89
  assert.ok(Number.isInteger(b.metrics[key]), `baseline missing ${key}`);
90
90
  }
91
91
  });
92
+
93
+ // ---------- task 1003227: the gate must FAIL CLOSED on an unusable baseline ----------
94
+ //
95
+ // readBaselines used to `catch { return {} }`. An empty baseline map leaves every
96
+ // `floor === undefined`, which compareToBaselines classifies as a WARNING — so a
97
+ // checkout with no config/ reported the ratchet PASSED. Measured before the fix: a
98
+ // planted 5,000-line + 94-cycle regression produced ZERO violations. That was not a
99
+ // hypothetical checkout — config/ is absent from every dev box's sparse slice, so the
100
+ // builders who most needed the gate were the ones silently exempt from it.
101
+ //
102
+ // checkQualityRatchets() reads the real repo, so these tests drive compareToBaselines
103
+ // (pure) for the metric-level rules and exercise the file-level rule by pointing at a
104
+ // baseline that does not exist.
105
+
106
+ test('1003227 an ABSENT baseline file makes the gate hard-fail, not pass', async () => {
107
+ const fs = require('node:fs');
108
+ const real = rat.BASELINE_PATH;
109
+ const backup = `${real}.1003227-test-backup`;
110
+ const had = fs.existsSync(real);
111
+ if (had) fs.renameSync(real, backup);
112
+ try {
113
+ const r = rat.checkQualityRatchets();
114
+ assert.equal(r.hardFail, true, 'a gate that cannot read its floors must not report a pass');
115
+ assert.ok(r.violations.length > 0, 'and it must say so as a VIOLATION, not a warning');
116
+ assert.match(r.violations[0], /MISSING/, 'the first violation names the real cause');
117
+ assert.match(r.violations[0], /fails CLOSED/i, 'and states the posture explicitly');
118
+ } finally {
119
+ if (had) fs.renameSync(backup, real);
120
+ }
121
+ });
122
+
123
+ test('1003227 a MALFORMED baseline file also hard-fails', async () => {
124
+ const fs = require('node:fs');
125
+ const real = rat.BASELINE_PATH;
126
+ const backup = `${real}.1003227-test-backup2`;
127
+ const had = fs.existsSync(real);
128
+ if (had) fs.copyFileSync(real, backup);
129
+ try {
130
+ fs.writeFileSync(real, '{ this is not json');
131
+ const r = rat.checkQualityRatchets();
132
+ assert.equal(r.hardFail, true, 'unparseable is no more verifiable than absent');
133
+ assert.match(r.violations[0], /not valid JSON/);
134
+ } finally {
135
+ if (had) { fs.copyFileSync(backup, real); fs.unlinkSync(backup); }
136
+ }
137
+ });
138
+
139
+ test('1003227 a baseline file with no "metrics" object hard-fails', async () => {
140
+ const fs = require('node:fs');
141
+ const real = rat.BASELINE_PATH;
142
+ const backup = `${real}.1003227-test-backup3`;
143
+ const had = fs.existsSync(real);
144
+ if (had) fs.copyFileSync(real, backup);
145
+ try {
146
+ fs.writeFileSync(real, JSON.stringify({ _comment: 'no metrics key' }));
147
+ const r = rat.checkQualityRatchets();
148
+ assert.equal(r.hardFail, true);
149
+ assert.match(r.violations[0], /no "metrics" object/);
150
+ } finally {
151
+ if (had) { fs.copyFileSync(backup, real); fs.unlinkSync(backup); }
152
+ }
153
+ });
154
+
155
+ test('1003227 a missing KEY for a NEW metric stays a warning (adding a metric must not brick CI)', () => {
156
+ // The two absences are different facts and must not collapse: no FILE = cannot
157
+ // verify = fail; no KEY = a genuinely new metric = warn.
158
+ const { violations, warnings } = rat.compareToBaselines(
159
+ { brand_new_metric: 7 },
160
+ { excess_lines_total: 100 },
161
+ );
162
+ assert.deepEqual(violations, [], 'a new metric with no floor yet must not fail the build');
163
+ assert.match(warnings.join(' '), /no baseline for "brand_new_metric"/);
164
+ });
165
+
166
+ test('1003227 a real regression is still caught when the baselines ARE readable', () => {
167
+ const { violations } = rat.compareToBaselines(
168
+ { excess_lines_total: 5100, require_cycle_count: 99 },
169
+ { excess_lines_total: 100, require_cycle_count: 5 },
170
+ );
171
+ assert.equal(violations.length, 2, 'both regressions reported');
172
+ assert.match(violations.join(' '), /never raise the baseline to pass/);
173
+ });