@bongos/core 1.19.189 → 1.19.190
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 +11 -11
- package/modules/government/routes/government.js +34 -6
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/fitness.js +6 -4
- package/src/module-api.js +3 -1
- package/tests/government_routes.mjs +69 -2
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.190",
|
|
6
|
+
"core_contract": "1.19.190",
|
|
7
|
+
"source_commit": "99bc31cf263be3e12b7260e97be6d86b95d7d210",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-17T00:
|
|
9
|
+
"built_at": "2026-08-17T00:54:57.379Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 309,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
19
|
"file_count": 1612,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "2c7de5d40b0d85e5c39e5f55c8c0f340cdf45df52687019751d2ec3088194d14",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -2977,7 +2977,7 @@
|
|
|
2977
2977
|
{
|
|
2978
2978
|
"path": "modules/government/routes/government.js",
|
|
2979
2979
|
"mode": "0000644",
|
|
2980
|
-
"sha256": "
|
|
2980
|
+
"sha256": "c229aec4f02fe970f4a3f798d370eef9b080cd0837f906b574362abda57107ad"
|
|
2981
2981
|
},
|
|
2982
2982
|
{
|
|
2983
2983
|
"path": "modules/government/routes/rank-sync.js",
|
|
@@ -4262,12 +4262,12 @@
|
|
|
4262
4262
|
{
|
|
4263
4263
|
"path": "package-lock.json",
|
|
4264
4264
|
"mode": "0000644",
|
|
4265
|
-
"sha256": "
|
|
4265
|
+
"sha256": "b63d0bd77af72944106d5b482777b258187917342097cae72ad09078747b411c"
|
|
4266
4266
|
},
|
|
4267
4267
|
{
|
|
4268
4268
|
"path": "package.json",
|
|
4269
4269
|
"mode": "0000644",
|
|
4270
|
-
"sha256": "
|
|
4270
|
+
"sha256": "b5675fea41d22048c57fe51577a36330597e91b1864051a1537aa37f18e1bc16"
|
|
4271
4271
|
},
|
|
4272
4272
|
{
|
|
4273
4273
|
"path": "public-docs/index.html",
|
|
@@ -4892,7 +4892,7 @@
|
|
|
4892
4892
|
{
|
|
4893
4893
|
"path": "scripts/gds/fitness.js",
|
|
4894
4894
|
"mode": "0000644",
|
|
4895
|
-
"sha256": "
|
|
4895
|
+
"sha256": "5cc94859033c924884efa60f50f8a1f7810a85382c42d785d62b804181a195f5"
|
|
4896
4896
|
},
|
|
4897
4897
|
{
|
|
4898
4898
|
"path": "scripts/gds/gate-review.js",
|
|
@@ -5872,7 +5872,7 @@
|
|
|
5872
5872
|
{
|
|
5873
5873
|
"path": "src/module-api.js",
|
|
5874
5874
|
"mode": "0000644",
|
|
5875
|
-
"sha256": "
|
|
5875
|
+
"sha256": "3b8d188039825cd1f1a64e7d33246152e293e452df2f2bee8a586acfc22ccfc4"
|
|
5876
5876
|
},
|
|
5877
5877
|
{
|
|
5878
5878
|
"path": "src/module-loader/catalog.js",
|
|
@@ -6852,7 +6852,7 @@
|
|
|
6852
6852
|
{
|
|
6853
6853
|
"path": "tests/government_routes.mjs",
|
|
6854
6854
|
"mode": "0000644",
|
|
6855
|
-
"sha256": "
|
|
6855
|
+
"sha256": "3b2e0ca134737992590132f089d5f93b479ad6cec83a5d2a932412b3edaf4547"
|
|
6856
6856
|
},
|
|
6857
6857
|
{
|
|
6858
6858
|
"path": "tests/government_seed.mjs",
|
|
@@ -185,11 +185,11 @@ module.exports = function buildGovernmentRouter() {
|
|
|
185
185
|
const ARCHON_REASON = 'the Archon rank-rank holds every permission and is immutable — it is the rank that can never be locked out of this tab (ADR 0172)';
|
|
186
186
|
const ASSIGN_REASON = 'a seeded rank-rank is assigned by rank (R95b sync); change the builder\'s rank instead';
|
|
187
187
|
|
|
188
|
-
// ── one-release
|
|
188
|
+
// ── the one-release LEGACY path aliases: /governance/* -> /government/* ────
|
|
189
189
|
// ADR 0174 committed to keeping the pre-rename paths answering for exactly one
|
|
190
190
|
// release. The case this actually protects is narrow but real: a browser that
|
|
191
191
|
// still has the OLD hall page open when the instance upgrades will keep calling
|
|
192
|
-
// /governance/* against the new server. Everything else that
|
|
192
|
+
// the legacy /governance/* paths against the new server. Everything else that
|
|
193
193
|
// paths (the hall bundle, the generated client) ships INSIDE the same core
|
|
194
194
|
// package and moves in lockstep.
|
|
195
195
|
//
|
|
@@ -201,11 +201,39 @@ module.exports = function buildGovernmentRouter() {
|
|
|
201
201
|
// REMOVE THIS BLOCK one release after the rename ships (goal 1000068 follow-up).
|
|
202
202
|
// The `alias` counter in the log line is how we tell whether anything still
|
|
203
203
|
// calls them before deleting.
|
|
204
|
+
//
|
|
205
|
+
// EXPRESS 4 WILDCARD SYNTAX. This is express 4.22 — the wildcard is a bare `*`
|
|
206
|
+
// and the captured tail is req.params[0]. Express 5's named form (`/*rest`) is
|
|
207
|
+
// NOT a wildcard here, it is a literal path segment, so it silently matches
|
|
208
|
+
// nothing and every legacy path 404s. It shipped that way once (task 1003006)
|
|
209
|
+
// and was only caught by curling the live site after the cutover — the module
|
|
210
|
+
// parsed fine, so nothing local failed.
|
|
204
211
|
const LEGACY_PATH_PREFIX = '/governance';
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
212
|
+
// The legacy noun -> the current one, as a NAMED DATA PAIR rather than an
|
|
213
|
+
// inline regex literal. An inline pattern here was silently rewritten by a
|
|
214
|
+
// repo-wide sweep so that both sides matched, turning the translation into an
|
|
215
|
+
// identity function that redirected to a path which does not exist. Naming the
|
|
216
|
+
// pair and asserting they differ makes that class of damage loud.
|
|
217
|
+
//
|
|
218
|
+
// Every line in this block that spells the legacy vocabulary carries the word
|
|
219
|
+
// LEGACY on purpose: that is what the ADR 0174 fitness allowlist keys on, so
|
|
220
|
+
// the shim can name what it is compatible with without excusing the whole file.
|
|
221
|
+
const LEGACY_SEGMENT = 'roles'; // legacy
|
|
222
|
+
const CURRENT_SEGMENT = 'ranks';
|
|
223
|
+
if (LEGACY_SEGMENT === CURRENT_SEGMENT) {
|
|
224
|
+
throw new Error('government legacy alias: the rename pair collapsed to an identity — a sweep has eaten it');
|
|
225
|
+
}
|
|
226
|
+
router.all(`${LEGACY_PATH_PREFIX}/*`, (req, res, next) => {
|
|
227
|
+
const tail = req.params[0] || ''; // express 4: bare * captures here
|
|
228
|
+
if (!tail) return next();
|
|
229
|
+
// Translate the leading segment, keep everything after it, keep the query.
|
|
230
|
+
const translated = tail === LEGACY_SEGMENT || tail.startsWith(`${LEGACY_SEGMENT}/`)
|
|
231
|
+
? CURRENT_SEGMENT + tail.slice(LEGACY_SEGMENT.length)
|
|
232
|
+
: tail;
|
|
233
|
+
const qs = req.originalUrl.includes('?') ? req.originalUrl.slice(req.originalUrl.indexOf('?')) : '';
|
|
234
|
+
// req.baseUrl is the router's MOUNT point (/api/gds). Redirecting without it
|
|
235
|
+
// sends the caller to /government/... at the site root, which 404s.
|
|
236
|
+
const target = `${req.baseUrl}/government/${translated}${qs}`;
|
|
209
237
|
console.warn(`[government] legacy path hit: ${req.method} ${req.originalUrl} -> ${target} (ADR 0174 alias; remove one release after the rename)`);
|
|
210
238
|
return res.redirect(308, target);
|
|
211
239
|
});
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.190",
|
|
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.190",
|
|
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.190",
|
|
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/fitness.js
CHANGED
|
@@ -1690,11 +1690,13 @@ const VOCAB_ALLOW = [
|
|
|
1690
1690
|
},
|
|
1691
1691
|
{
|
|
1692
1692
|
file: 'modules/government/routes/government.js',
|
|
1693
|
-
match: '
|
|
1693
|
+
match: 'legacy',
|
|
1694
1694
|
why: 'the one-release 308 alias from the pre-rename route paths (ADR 0174). A '
|
|
1695
|
-
+ 'compatibility shim has to name the thing it is compatible with.
|
|
1696
|
-
+ '
|
|
1697
|
-
+ '
|
|
1695
|
+
+ 'compatibility shim has to name the thing it is compatible with. The match is '
|
|
1696
|
+
+ 'the word LEGACY, and the shim block is written so that every line spelling the '
|
|
1697
|
+
+ 'old vocabulary carries it — so the exemption is scoped to the shim rather than '
|
|
1698
|
+
+ 'to the whole file, and a stray "role" elsewhere in this file still reds the '
|
|
1699
|
+
+ 'build. Delete with the alias block one release after the rename ships.',
|
|
1698
1700
|
},
|
|
1699
1701
|
{
|
|
1700
1702
|
file: 'modules/government/module.json',
|
package/src/module-api.js
CHANGED
|
@@ -805,8 +805,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
805
805
|
// landed since 1.19.187 with no explicit bump. run 31978931152. (task 1002620)
|
|
806
806
|
// 1.19.189 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
807
807
|
// landed since 1.19.188 with no explicit bump. run 31983108640. (task 1002620)
|
|
808
|
+
// 1.19.190 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
809
|
+
// landed since 1.19.189 with no explicit bump. run 31983360426. (task 1002620)
|
|
808
810
|
// ---------------------------------------------------------------------------
|
|
809
|
-
const CORE_VERSION = '1.19.
|
|
811
|
+
const CORE_VERSION = '1.19.190'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
810
812
|
|
|
811
813
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
812
814
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -151,16 +151,19 @@ after(() => {
|
|
|
151
151
|
seams._reset();
|
|
152
152
|
});
|
|
153
153
|
|
|
154
|
-
async function call(method, p, body) {
|
|
154
|
+
async function call(method, p, body, opts = {}) {
|
|
155
155
|
calls.length = 0;
|
|
156
156
|
const res = await fetch(`${BASE}${p}`, {
|
|
157
157
|
method,
|
|
158
158
|
headers: body === undefined ? {} : { 'content-type': 'application/json' },
|
|
159
159
|
body: body === undefined ? undefined : JSON.stringify(body),
|
|
160
|
+
// `redirect: 'manual'` lets a caller assert the 308 itself rather than
|
|
161
|
+
// silently following it — required by the legacy-alias tests at the bottom.
|
|
162
|
+
...(opts.redirect ? { redirect: opts.redirect } : {}),
|
|
160
163
|
});
|
|
161
164
|
let json = null;
|
|
162
165
|
try { json = await res.json(); } catch { /* no body */ }
|
|
163
|
-
return { status: res.status, json };
|
|
166
|
+
return { status: res.status, json, headers: res.headers };
|
|
164
167
|
}
|
|
165
168
|
|
|
166
169
|
// --- the deny wall -----------------------------------------------------------
|
|
@@ -392,3 +395,67 @@ test('DELETE /governance/assignments validates the builder id param', async () =
|
|
|
392
395
|
const r = await call('DELETE', '/government/assignments/not-a-number/triage-helper');
|
|
393
396
|
assert.equal(r.status, 400);
|
|
394
397
|
});
|
|
398
|
+
|
|
399
|
+
// ── the one-release /governance/* compatibility alias (ADR 0174) ──────────────
|
|
400
|
+
//
|
|
401
|
+
// This block exists because the alias shipped BROKEN and nothing caught it.
|
|
402
|
+
// Three independent defects, none visible to a parse check or a unit test that
|
|
403
|
+
// only imports the module:
|
|
404
|
+
// 1. it was written with express 5's named-wildcard syntax (`/*rest`) on an
|
|
405
|
+
// express 4.22 router, where that is a literal segment — so it matched
|
|
406
|
+
// nothing and every legacy path 404'd;
|
|
407
|
+
// 2. the redirect omitted req.baseUrl, sending callers to /government/... at
|
|
408
|
+
// the site root instead of under the /api/gds mount;
|
|
409
|
+
// 3. a repo-wide roles->ranks sweep rewrote the SOURCE side of the
|
|
410
|
+
// /^\/roles/ -> '/ranks' translation, turning it into an identity that
|
|
411
|
+
// redirected to a path which does not exist.
|
|
412
|
+
//
|
|
413
|
+
// All three only surfaced when the running site was curled after the cutover.
|
|
414
|
+
// These tests drive a real router so they cannot repeat that.
|
|
415
|
+
|
|
416
|
+
// A SECOND server, mounted at /api/gds exactly as production does. The main
|
|
417
|
+
// harness above mounts at the root, so it cannot see a missing req.baseUrl —
|
|
418
|
+
// which was one of the three real defects. Mounting like production is the
|
|
419
|
+
// whole point of these three tests.
|
|
420
|
+
const aliasApp = express();
|
|
421
|
+
aliasApp.use(express.json());
|
|
422
|
+
aliasApp.use(attachFail);
|
|
423
|
+
aliasApp.use('/api/gds', buildGovernmentRouter());
|
|
424
|
+
const aliasServer = aliasApp.listen(0);
|
|
425
|
+
await new Promise((r) => aliasServer.once('listening', r));
|
|
426
|
+
const ALIAS_BASE = `http://127.0.0.1:${aliasServer.address().port}`;
|
|
427
|
+
after(() => aliasServer.close());
|
|
428
|
+
|
|
429
|
+
const hop = (method, p) =>
|
|
430
|
+
fetch(`${ALIAS_BASE}${p}`, { method, redirect: 'manual' });
|
|
431
|
+
|
|
432
|
+
test('the legacy /governance/* alias redirects, under the mount, with the noun translated', async () => {
|
|
433
|
+
const cases = [
|
|
434
|
+
['GET', '/api/gds/governance/roles', '/api/gds/government/ranks'],
|
|
435
|
+
['GET', '/api/gds/governance/permissions', '/api/gds/government/permissions'],
|
|
436
|
+
['GET', '/api/gds/governance/assignments', '/api/gds/government/assignments'],
|
|
437
|
+
['PUT', '/api/gds/governance/roles/metic/permissions/idea.triage',
|
|
438
|
+
'/api/gds/government/ranks/metic/permissions/idea.triage'],
|
|
439
|
+
['POST', '/api/gds/governance/roles/metic/reset', '/api/gds/government/ranks/metic/reset'],
|
|
440
|
+
];
|
|
441
|
+
for (const [method, from, expected] of cases) {
|
|
442
|
+
const r = await hop(method, from);
|
|
443
|
+
assert.equal(r.status, 308, `${method} ${from} must 308 — a 404 means the wildcard is not matching`);
|
|
444
|
+
assert.equal(r.headers.get('location'), expected,
|
|
445
|
+
`${method} ${from} must keep the /api/gds mount AND translate roles→ranks`);
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
test('the alias preserves the query string', async () => {
|
|
450
|
+
const r = await hop('GET', '/api/gds/governance/roles?x=1&y=2');
|
|
451
|
+
assert.equal(r.headers.get('location'), '/api/gds/government/ranks?x=1&y=2');
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
test('the alias carries no gate of its own — the followed request re-runs the real one', async () => {
|
|
455
|
+
// A 308 preserves method and body, so the wall is enforced on the FOLLOWED
|
|
456
|
+
// request by the real route. The alias must never become a way around it.
|
|
457
|
+
const src = fs.readFileSync(path.join(ROOT, 'modules', 'government', 'routes', 'government.js'), 'utf8');
|
|
458
|
+
const block = src.slice(src.indexOf('LEGACY_PATH_PREFIX'), src.indexOf('res.redirect(308'));
|
|
459
|
+
assert.ok(!/requirePermission|requireBuilder/.test(block),
|
|
460
|
+
'the alias must not carry its own gate — it redirects, and the real route gates the followed request');
|
|
461
|
+
});
|