@bongos/core 1.19.26 → 1.19.27

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.26",
6
- "core_contract": "1.19.26",
7
- "source_commit": "522fdcf67202b1961ce21cc5bb1e9e21f3455105",
5
+ "core_version": "1.19.27",
6
+ "core_contract": "1.19.27",
7
+ "source_commit": "a8f09266a4b9cdbdf70ea92de3c2a26553aacab2",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-11T13:25:13.567Z",
9
+ "built_at": "2026-08-11T13:38:48.421Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 285,
@@ -17,7 +17,7 @@
17
17
  "gate": "passed"
18
18
  },
19
19
  "file_count": 1511,
20
- "tree_sha256": "ecaa7b2c50ea700a1da02925b30bc638ccef3e362d27f47a50c59957afc232be",
20
+ "tree_sha256": "b85699092f300b5bab1ab32e95763e4f032d2ee8f688466f4c4dc939048174bd",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -2882,7 +2882,7 @@
2882
2882
  {
2883
2883
  "path": "modules/grading/grader.js",
2884
2884
  "mode": "0000644",
2885
- "sha256": "a08e9c017a07bffa311a9de6df007c30d9f3766c86d0ce2ec88660fed60d37aa"
2885
+ "sha256": "bc2b580d2be98f5b2acb9a0dd9416dd3c2cad387bbe61a6823e37102d3468675"
2886
2886
  },
2887
2887
  {
2888
2888
  "path": "modules/grading/module.json",
@@ -3457,7 +3457,7 @@
3457
3457
  {
3458
3458
  "path": "modules/lifecycle/routes/tasks.js",
3459
3459
  "mode": "0000644",
3460
- "sha256": "f5041a43ea0a74bc6b16f8ceb6490c70090e7959e66f137c92040ee842db1ec0"
3460
+ "sha256": "aa179ab79508a002e8f580fb839767d8aaa8b6f02a46d021bb61633dd9f8f9a4"
3461
3461
  },
3462
3462
  {
3463
3463
  "path": "modules/lifecycle/routes/versions.js",
@@ -4002,12 +4002,12 @@
4002
4002
  {
4003
4003
  "path": "package-lock.json",
4004
4004
  "mode": "0000644",
4005
- "sha256": "08c0827545637895b730387d92d4a0ce7fef7b055a966cb88ab70e6be901880c"
4005
+ "sha256": "a32cc5b3e33b39013ebe6508049b2ce0dac4c6ac8edce2adf396af265a0c4df8"
4006
4006
  },
4007
4007
  {
4008
4008
  "path": "package.json",
4009
4009
  "mode": "0000644",
4010
- "sha256": "1ac1c894e7f11d5502855775e3f2ca37031927a68ec7e8dca447661b872467e5"
4010
+ "sha256": "aea4f2f3266f8107e00346fb410bb214997bc0cda76e7ecba36b7f2210b9b661"
4011
4011
  },
4012
4012
  {
4013
4013
  "path": "public-docs/index.html",
@@ -5627,7 +5627,7 @@
5627
5627
  {
5628
5628
  "path": "src/module-api.js",
5629
5629
  "mode": "0000644",
5630
- "sha256": "39b05d86d4fbb1035769ff0c30a97b21c75eea8ee145a25784789d17fb20aa38"
5630
+ "sha256": "f75a298d5c776e9bb7ba33c75061ca193c73b0004eea7c0f6706f02b8daad0f0"
5631
5631
  },
5632
5632
  {
5633
5633
  "path": "src/module-loader/loader.js",
@@ -6587,7 +6587,7 @@
6587
6587
  {
6588
6588
  "path": "tests/grade_server_authoritative.mjs",
6589
6589
  "mode": "0000644",
6590
- "sha256": "c650bd3c79d469f3ffb1cdada6f23521dd09c97844affd5b9e7f340d1796daa6"
6590
+ "sha256": "b0af74bc3b3e203716b13fbf3fc174a90f51daf659d5c5e5400725e85a0da4fc"
6591
6591
  },
6592
6592
  {
6593
6593
  "path": "tests/grade_unavailable_display.mjs",
@@ -645,6 +645,37 @@ function serverSideGuards(gradeResult, { committedFiles, builderRank, diffStats,
645
645
  if (clientPassed) downgradeTo = 'fail';
646
646
  }
647
647
 
648
+ // --- Guard 1b: vacuous permission-path input (task 1002646 / report 1000021 G-1) ---
649
+ // The pre-pass above pairs the SERVER-known rank with CLIENT-supplied
650
+ // committed_files, so omitting the list (or sending []) made the sub-floor
651
+ // protected-path check pass VACUOUSLY — zero files, zero hits, hardFail:false —
652
+ // a trusted-rank check applied to an untrusted, omittable diff. The server
653
+ // still cannot re-derive the feature-branch diff at grade time (boundary note
654
+ // above; the task-1002645 published tip is recorded at ci-land, AFTER the
655
+ // grade), but it can refuse to treat "no input" as "no hits": a caller below
656
+ // the registry's lowest floor submitting NO file list makes the check
657
+ // UNPROVABLE → fail closed. Honest ships are unaffected (ship.js always sends
658
+ // the real committed list — task 878 — and a floor-clearing rank never enters
659
+ // this branch). The dishonest-LIST case (a sub-floor caller sending an
660
+ // innocuous list while touching protected paths) stays owned by gate-review CI
661
+ // + the post-push main audit, which read the REAL diff. Unknown rank keeps
662
+ // failing open at this layer (isBelowFloor → false) — the main audit reads the
663
+ // real author rank from the DB and owns that case, exactly as for the pre-pass.
664
+ const submittedFileCount = Array.isArray(committedFiles)
665
+ ? committedFiles.filter((f) => typeof f === 'string' && f.trim()).length
666
+ : 0;
667
+ if (permissionPathCheck.isBelowFloor(builderRank) && submittedFileCount === 0) {
668
+ // Resolve the floor label from the registry at runtime (the D-16/N-3
669
+ // stale-string rule) — never a hardcoded rank name.
670
+ const floorRank = Object.keys(permissionPathCheck.RANK_TIER)
671
+ .find((r) => permissionPathCheck.RANK_TIER[r] === permissionPathCheck.MIN_TIER) || 'the floor';
672
+ guardsTripped.push({ guard: 'permission_path_unprovable', severity: 'blocker',
673
+ summary: `rank '${builderRank}' is below the protected-path floor ('${floorRank}') and the grade submitted no committed_files — the permission-path check cannot run on an empty list, so a client PASS cannot stand (fail closed; task 1002646 / SR-9). Remedy: re-run the ship — ship.js submits the real committed file list; a genuinely file-less (DB-only) change needs a ${floorRank}+ builder to land it via /tasks/:id/confirm.` });
674
+ extraIssues.push({ severity: 'blocker', file: '(grade)', summary:
675
+ `[server permission-path] no committed_files submitted by a rank below the '${floorRank}' floor — the protected-path check is unprovable and fails closed (task 1002646)`, worker: 'server-permission-path' });
676
+ if (clientPassed) downgradeTo = 'fail';
677
+ }
678
+
648
679
  // --- Guard 2: implausible client verdict (SR-9) ---
649
680
  // A client PASS with an empty issues[] on a non-trivial diff is not a credible
650
681
  // default-fail panel verdict. Downgrade to manual-confirm (a human lands it via
@@ -697,7 +728,7 @@ function serverSideGuards(gradeResult, { committedFiles, builderRank, diffStats,
697
728
  ...base,
698
729
  signals: {
699
730
  ...(base.signals || {}),
700
- server_guard: { applied: ['route_rank', 'permission_path', 'implausible_pass', 'forged_net_negative'], ok: true, tripped: [] },
731
+ server_guard: { applied: ['route_rank', 'permission_path', 'permission_path_unprovable', 'implausible_pass', 'forged_net_negative'], ok: true, tripped: [] },
701
732
  },
702
733
  };
703
734
  }
@@ -729,7 +760,7 @@ function serverSideGuards(gradeResult, { committedFiles, builderRank, diffStats,
729
760
  // body already ran; this is the authoritative post-hoc adjustment).
730
761
  confidence: forwardedConfidence,
731
762
  server_guard: {
732
- applied: ['route_rank', 'permission_path', 'implausible_pass', 'forged_net_negative'],
763
+ applied: ['route_rank', 'permission_path', 'permission_path_unprovable', 'implausible_pass', 'forged_net_negative'],
733
764
  ok: guardsTripped.length === 0,
734
765
  downgraded_to: downgradeTo,
735
766
  client_passed: clientPassed,
@@ -739,7 +770,9 @@ function serverSideGuards(gradeResult, { committedFiles, builderRank, diffStats,
739
770
  // the code. Named here so clients render it as a wall, not a quality
740
771
  // FAIL the builder should fix-and-retry (the envelope used to teach
741
772
  // exactly that wrong lesson). route_rank stays a code finding.
742
- authorization_refusal: guardsTripped.some((g) => g.guard === 'permission_path'),
773
+ // task 1002646: permission_path_unprovable is the same wall — the rank
774
+ // floor refusing an unverifiable diff — so it renders the same way.
775
+ authorization_refusal: guardsTripped.some((g) => g.guard === 'permission_path' || g.guard === 'permission_path_unprovable'),
743
776
  },
744
777
  },
745
778
  notes_md: `${base.notes_md || ''}${guardNote}`,
@@ -1431,8 +1431,10 @@ module.exports = function buildTasksRouter() {
1431
1431
  // forged {ff:10,cq:10,issues:[]} to self-pass. serverSideGuards re-runs the
1432
1432
  // two DETERMINISTIC pre-passes here (route-rank against the server's own
1433
1433
  // route files; permission-path against the caller's LIVE, authoritative
1434
- // rank — req.builder.rank, read fresh from the DB per request), rejects an
1435
- // implausible empty-issues PASS on a non-trivial diff, and neutralizes an
1434
+ // rank — req.builder.rank, read fresh from the DB per request), fails a
1435
+ // sub-floor PASS that submitted NO committed_files closed (the check is
1436
+ // unprovable on an empty list — task 1002646 / report 1000021 G-1), rejects
1437
+ // an implausible empty-issues PASS on a non-trivial diff, and neutralizes an
1436
1438
  // obviously-forged net-negative credit signal (SR-18). It is DOWNGRADE-ONLY:
1437
1439
  // it can flip a client PASS to a hard FAIL or manual-confirm, but never
1438
1440
  // upgrades a fail and never relaxes the existing gate — so a legitimate
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.26",
3
+ "version": "1.19.27",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.26",
9
+ "version": "1.19.27",
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.26",
3
+ "version": "1.19.27",
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
@@ -479,8 +479,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
479
479
  // landed since 1.19.24 with no explicit bump. run 31495418623. (task 1002620)
480
480
  // 1.19.26 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
481
481
  // landed since 1.19.25 with no explicit bump. run 31495996665. (task 1002620)
482
+ // 1.19.27 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
483
+ // landed since 1.19.26 with no explicit bump. run 31497212656. (task 1002620)
482
484
  // ---------------------------------------------------------------------------
483
- const CORE_VERSION = '1.19.26'; // CI auto-patch carrier (ADR 0161); see changelog above
485
+ const CORE_VERSION = '1.19.27'; // CI auto-patch carrier (ADR 0161); see changelog above
484
486
 
485
487
  // A namespaced logger so a module's log lines are attributable + consistent.
486
488
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -141,6 +141,93 @@ await test('route-rank hard-fail (forged routes dir) flips a client PASS to FAIL
141
141
  }
142
142
  });
143
143
 
144
+ // ========================================================================
145
+ // Guard 1b — vacuous permission-path input fails CLOSED (task 1002646 /
146
+ // report 1000021 G-1). The pre-pass pairs the server-known rank with
147
+ // CLIENT-supplied committed_files, so omitting the list (or sending []) made
148
+ // the sub-floor protected-path check pass vacuously — the exact bypass the
149
+ // report demonstrated. A sub-floor caller submitting NO file list now fails
150
+ // closed; honest ships always send the real list (task 878).
151
+ // ========================================================================
152
+
153
+ const ppc = require('../src/bongos/permission-path-check.js');
154
+ const REGISTRY_FLOOR = Object.keys(ppc.RANK_TIER).find((r) => ppc.RANK_TIER[r] === ppc.MIN_TIER);
155
+
156
+ await test('1002646 sub-floor PASS with committed_files ABSENT → hard FAIL (omission no longer bypasses the floor)', () => {
157
+ const g = grader.serverSideGuards(
158
+ clientGrade({ issues: [{ severity: 'nit', file: 'a', summary: 'x' }] }),
159
+ { committedFiles: null, builderRank: 'thetes', diffStats: { files_changed: 1, lines_added: 3, lines_removed: 1 } }
160
+ );
161
+ assert.equal(g.passed, false, 'omitting committed_files must not make the protected-path floor vacuous');
162
+ assert.equal(g.signals.server_guard.downgraded_to, 'fail');
163
+ assert.equal(g.signals.confidence, 0, 'fail forwards confidence 0 so the server gate also fails');
164
+ assert.ok(g.signals.server_guard.tripped.some((t) => t.guard === 'permission_path_unprovable'));
165
+ assert.ok(g.issues.some((i) => i.worker === 'server-permission-path'));
166
+ assert.equal(g.signals.server_guard.authorization_refusal, true,
167
+ 'the unprovable trip is the same rank wall as permission_path — not a quality verdict (task 1002614 shape)');
168
+ });
169
+
170
+ await test('1002646 sub-floor PASS with committed_files [] → hard FAIL (empty list is the same omission)', () => {
171
+ const g = grader.serverSideGuards(
172
+ clientGrade({ issues: [{ severity: 'nit', file: 'a', summary: 'x' }] }),
173
+ { committedFiles: [], builderRank: 'xenos', diffStats: { files_changed: 1, lines_added: 3, lines_removed: 1 } }
174
+ );
175
+ assert.equal(g.passed, false);
176
+ assert.equal(g.signals.server_guard.downgraded_to, 'fail');
177
+ assert.ok(g.signals.server_guard.tripped.some((t) => t.guard === 'permission_path_unprovable'));
178
+ });
179
+
180
+ await test('1002646 whitespace-only entries do not count as a submitted list → hard FAIL', () => {
181
+ const g = grader.serverSideGuards(
182
+ clientGrade({ issues: [{ severity: 'nit', file: 'a', summary: 'x' }] }),
183
+ { committedFiles: [' ', ''], builderRank: 'thetes', diffStats: { files_changed: 1, lines_added: 3, lines_removed: 1 } }
184
+ );
185
+ assert.equal(g.passed, false, 'padding the list with blanks is still an empty list');
186
+ assert.ok(g.signals.server_guard.tripped.some((t) => t.guard === 'permission_path_unprovable'));
187
+ });
188
+
189
+ await test('1002646 the refusal names the registry-resolved floor + the remedy (wave-5 speech standard)', () => {
190
+ const g = grader.serverSideGuards(
191
+ clientGrade({ issues: [{ severity: 'nit', file: 'a', summary: 'x' }] }),
192
+ { committedFiles: null, builderRank: 'xenos', diffStats: { files_changed: 1, lines_added: 3, lines_removed: 1 } }
193
+ );
194
+ const trip = g.signals.server_guard.tripped.find((t) => t.guard === 'permission_path_unprovable');
195
+ assert.ok(trip, 'the guard must be recorded');
196
+ assert.ok(trip.summary.includes(`'${REGISTRY_FLOOR}'`),
197
+ 'the floor name comes from the registry at runtime, never a hardcoded rank string (D-16/N-3)');
198
+ assert.match(trip.summary, /re-run the ship/i, 'carries the remedy');
199
+ assert.match(trip.summary, /\/tasks\/:id\/confirm/, 'names the file-less-change exit too');
200
+ });
201
+
202
+ await test('1002646 a floor-clearing rank with an absent list is NOT blocked (guard scoped to the floor)', () => {
203
+ const g = grader.serverSideGuards(clientGrade(), {
204
+ committedFiles: null, builderRank: 'metic',
205
+ diffStats: { files_changed: 1, lines_added: 2, lines_removed: 1 },
206
+ });
207
+ assert.equal(g.passed, true, 'the floor does not apply to metic+ — no new refusal for them (ADR 0162)');
208
+ assert.equal(g.signals.server_guard.ok, true);
209
+ });
210
+
211
+ await test('1002646 an honest sub-floor ship with a real, unprotected list still passes (no new gate on honest work)', () => {
212
+ const g = grader.serverSideGuards(
213
+ clientGrade({ issues: [{ severity: 'nit', file: 'docs/notes.md', summary: 'x' }] }),
214
+ { committedFiles: ['docs/notes.md'], builderRank: 'thetes', diffStats: { files_changed: 1, lines_added: 5, lines_removed: 1 } }
215
+ );
216
+ assert.equal(g.passed, true,
217
+ 'a real submitted list keeps working; the dishonest-LIST residual is owned by gate-review CI + the post-push main audit');
218
+ });
219
+
220
+ await test('1002646 downgrade-only: a sub-floor client FAIL with an absent list stays FAIL (no behavior change)', () => {
221
+ const failGrade = grader.validateAndScore(
222
+ { functional_fidelity: 3, code_quality: 3, confidence: 1, issues: [{ severity: 'blocker', file: 'a', summary: 'broken' }] },
223
+ {}
224
+ );
225
+ const g = grader.serverSideGuards(failGrade, {
226
+ committedFiles: null, builderRank: 'xenos', diffStats: { files_changed: 1, lines_added: 1, lines_removed: 1 },
227
+ });
228
+ assert.equal(g.passed, false, 'still failed — the guard only ever downgrades');
229
+ });
230
+
144
231
  // ========================================================================
145
232
  // Guard 2 — implausible empty-issues PASS on a non-trivial diff → manual-confirm.
146
233
  // ========================================================================