@bongos/core 1.19.757 → 1.19.758

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.757",
6
- "core_contract": "1.19.757",
7
- "source_commit": "cc213a1bab1fc07008953502c035d32a98ced469",
5
+ "core_version": "1.19.758",
6
+ "core_contract": "1.19.758",
7
+ "source_commit": "aa7bf913cb3802be5885fed7612f2c25cafc981a",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-16T02:31:57.888Z",
9
+ "built_at": "2026-09-16T02:42:51.087Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 483,
@@ -17,7 +17,7 @@
17
17
  "gate": "passed"
18
18
  },
19
19
  "file_count": 2623,
20
- "tree_sha256": "6dfa78abe36908cd3c7762f36e1145d5ca9a22362b4825d6eb8c240e05a01815",
20
+ "tree_sha256": "b18088e92ce27eda1c8bfe22e09c23c33beb4923285dc789628b56a2bfcb6d69",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -2512,7 +2512,7 @@
2512
2512
  {
2513
2513
  "path": "docs/module-api-changelog.md",
2514
2514
  "mode": "0000644",
2515
- "sha256": "c415a541beec31293c9fe17473fce5706b0c043818a3da9d2c092d6a36892a12"
2515
+ "sha256": "3b2e92dec077e82fd9d65127264fa203ed0eb8eb94e42c91559fa7eca4e85c90"
2516
2516
  },
2517
2517
  {
2518
2518
  "path": "docs/modules-contract.md",
@@ -7567,12 +7567,12 @@
7567
7567
  {
7568
7568
  "path": "package-lock.json",
7569
7569
  "mode": "0000644",
7570
- "sha256": "93d77133102c1137fe1e2772f30fabcd7e4630aece1c1ec238db81580f23f634"
7570
+ "sha256": "68833951b804e0bd61f70df18a54d9bb6b3c2d2115f6e58f19f2a113e0ab706a"
7571
7571
  },
7572
7572
  {
7573
7573
  "path": "package.json",
7574
7574
  "mode": "0000644",
7575
- "sha256": "eb18ccfda9df0b09d536acaff0fdb18df77860d53a35cfa65b41cc1aa70e361c"
7575
+ "sha256": "9e46060372527f559bca75e71439f4c40e326868bc51274a71a258c8c167e693"
7576
7576
  },
7577
7577
  {
7578
7578
  "path": "public-docs/index.html",
@@ -9382,7 +9382,7 @@
9382
9382
  {
9383
9383
  "path": "src/module-api.js",
9384
9384
  "mode": "0000644",
9385
- "sha256": "9798743d1dae3c952b257d48b1ee56341bf0a946504ebb6be66fd88f1a8cddcd"
9385
+ "sha256": "dda37ee6d5df39a520822ad072de3addc0a4cb335e48e82d9fc2f768ee68dfad"
9386
9386
  },
9387
9387
  {
9388
9388
  "path": "src/module-loader/catalog.js",
@@ -10882,7 +10882,7 @@
10882
10882
  {
10883
10883
  "path": "tests/government_parity.mjs",
10884
10884
  "mode": "0000644",
10885
- "sha256": "0c619508691d41f41b0fbbb1d6939ba9ac7fde2ef4ec08ae7622bfa70bfbf624"
10885
+ "sha256": "0d2df586d3ee58990b611799334e21f59ad69292285a787e776c16ef11c64625"
10886
10886
  },
10887
10887
  {
10888
10888
  "path": "tests/government_principals.mjs",
@@ -1988,5 +1988,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
1988
1988
  landed since 1.19.755 with no explicit bump. run 35047623582. (task 1002620)
1989
1989
  1.19.757 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1990
1990
  landed since 1.19.756 with no explicit bump. run 35048307666. (task 1002620)
1991
+ 1.19.758 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1992
+ landed since 1.19.757 with no explicit bump. run 35049006030. (task 1002620)
1991
1993
  ---------------------------------------------------------------------------
1992
1994
  ```
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.757",
3
+ "version": "1.19.758",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.757",
9
+ "version": "1.19.758",
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.757",
3
+ "version": "1.19.758",
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
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
71
71
  // there. scripts/gds/bump-version.js still rewrites the literal below; it appends
72
72
  // the entry to that file. Look for a version's history there, not here.
73
73
  // ---------------------------------------------------------------------------
74
- const CORE_VERSION = '1.19.757'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
74
+ const CORE_VERSION = '1.19.758'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
75
75
 
76
76
  // A namespaced logger so a module's log lines are attributable + consistent.
77
77
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -237,6 +237,166 @@ test('B0: every guards note that names a concrete route names one that EXISTS',
237
237
  `guards notes point at routes that do not exist — fix the note (or the route), never ignore it:\n ${dangling.join('\n ')}`);
238
238
  });
239
239
 
240
+ // ═══ B0b — a guards note naming a PAGE GATE names a live one, for its own atom ══
241
+ //
242
+ // task 1002878, from idea 1000625 (and independently from the BV1.R107 ship grade's
243
+ // hacker worker). B0 above pins a note that names a ROUTE. A note that names a page
244
+ // GATE was checked by nothing at all, and the `guards` field is the route-to-atom map
245
+ // every migration is instructed to read as authoritative.
246
+ //
247
+ // WHAT WENT WRONG ONCE. page.view.builder's note read 'authenticated hall pages
248
+ // (requireNonXenosPage / requireBuilderPage)'. Those two gates never admitted the same
249
+ // people: requireBuilderPage enforces page.view.public (floor xenos), requireNonXenosPage
250
+ // enforced page.view.builder (floor thetes). Migrating requireBuilderPage onto
251
+ // page.view.builder on the strength of that note would have locked every Xenos out of
252
+ // Work, Settings, pair, atlas, goals, roadmap and task-detail. R107 caught it by hand and
253
+ // used the right atom, so no lockout ever shipped — but the note stayed wrong, and the
254
+ // next reader had no reason to distrust it.
255
+ //
256
+ // This check would have failed on that note the day it was written: requireBuilderPage
257
+ // enforces page.view.public, not page.view.builder, so the pair disagrees mechanically.
258
+ //
259
+ // IT ALSO CATCHES THE STALE HALF. Task 1003450 has since deleted requireNonXenosPage
260
+ // along with /not-ready. A note still naming a gate that no longer exists reads as
261
+ // authoritative and resolves to nothing; an unknown gate name is therefore a failure
262
+ // here, not a skip — failing open is how the first version of this problem survived.
263
+ //
264
+ // PARSED FROM SOURCE, NOT IMPORTED. PAGE_GATES is internal to auth.js's page-gate
265
+ // dispatcher and deliberately unexported. Exporting it so a test could read it would
266
+ // widen the surface of a PROTECTED file (src/bongos/auth.js, group authz) for a test's
267
+ // convenience — the same trade tests/government_protected_surfaces.mjs declines for
268
+ // HARD_FLOOR_GLOBS, and for the same reason.
269
+ // PURE, and split out from the live assertion deliberately. Today's catalog is CORRECT
270
+ // (task 1003450 fixed the one bad note), so a check that only asserts "no problems
271
+ // against the real tree" passes without ever executing the branches that find one — a
272
+ // capture-group swap could make it vacuous forever and nothing would say so. The module's
273
+ // own precedent for a load-bearing invariant is government_ownership_scope.mjs F5–F8,
274
+ // which loads a MUTATED copy to prove each assert actually fires; these two functions are
275
+ // what let this do the same, in the negative tests below.
276
+ function parsePageGates(src) {
277
+ const block = src.match(/const PAGE_GATES = Object\.freeze\(\{([\s\S]*?)\n\}\);/);
278
+ if (!block) return null; // caller decides whether an absent table is fatal
279
+ const out = new Map();
280
+ // Each entry's own object literal is bounded by [^}]* so the scan cannot run past a
281
+ // gate that lacks a `permission` key and silently adopt the NEXT gate's. `permission`
282
+ // is then found ANYWHERE inside that literal rather than assumed to come first —
283
+ // requireProjectCuratePage already carries a second key, and key order in an object
284
+ // literal is not a contract anyone maintains.
285
+ for (const m of block[1].matchAll(/(require[A-Za-z]*Page):\s*Object\.freeze\(\{([^}]*)\}\)/g)) {
286
+ const perm = m[2].match(/permission:\s*'([^']+)'/);
287
+ if (perm) out.set(m[1], perm[1]);
288
+ }
289
+ return out;
290
+ }
291
+
292
+ // Any require*Page identifier mentioned anywhere in a guards note, prose included —
293
+ // which is the whole point, since B0's route regex only ever looked at the leading
294
+ // `METHOD /path` and prose went entirely unchecked.
295
+ const PAGE_GATE_RE = /\b(require[A-Za-z]*Page)\b/g;
296
+
297
+ // The comparison itself. Returns every disagreement plus how many notes participated,
298
+ // so the caller can tell "nothing is wrong" from "nothing was looked at".
299
+ function guardGateProblems(gates, permissions) {
300
+ const problems = [];
301
+ let named = 0;
302
+ for (const p of permissions) {
303
+ for (const [, gate] of String(p.guards || '').matchAll(PAGE_GATE_RE)) {
304
+ named += 1;
305
+ if (!gates.has(gate)) {
306
+ problems.push(`${p.key}: names '${gate}', which is not a gate in PAGE_GATES — `
307
+ + 'either the gate was deleted and the note was left behind, or the name is a typo.');
308
+ continue;
309
+ }
310
+ if (gates.get(gate) !== p.key) {
311
+ problems.push(`${p.key}: names '${gate}', but that gate enforces '${gates.get(gate)}'. `
312
+ + 'A guards note is the route-to-atom map a migration reads as authoritative, so a '
313
+ + 'gate listed against the wrong atom is an instruction to gate the wrong audience.');
314
+ }
315
+ }
316
+ }
317
+ return { problems, named };
318
+ }
319
+
320
+ test('B0b: a guards note naming a page gate names a live one, for its own atom', () => {
321
+ const gates = parsePageGates(fs.readFileSync(path.join(ROOT, 'src', 'bongos', 'auth.js'), 'utf8'));
322
+ assert.ok(gates, 'could not find the PAGE_GATES table in src/bongos/auth.js — if it '
323
+ + 'moved or changed shape, teach this test the new one rather than deleting it: it is '
324
+ + 'the only thing checking that a guards note names a gate that agrees with it.');
325
+ assert.ok(gates.size >= 4,
326
+ `fixture sanity: parsed ${gates.size} page gate(s) out of auth.js's PAGE_GATES, and the `
327
+ + 'table has held at least four since task 1003450 removed the two dead ones. A parse '
328
+ + 'returning few or none makes every assertion below vacuous.');
329
+
330
+ const { problems, named } = guardGateProblems(gates, FLOORED);
331
+ assert.ok(named >= 1,
332
+ 'fixture sanity: no guards note named a page gate at all. One does today '
333
+ + '(page.view.government → requireGovernmentPage), so either the note was removed — in '
334
+ + 'which case delete this floor deliberately — or PAGE_GATE_RE has stopped matching and '
335
+ + 'this test is now checking nothing.');
336
+ assert.deepEqual(problems, [],
337
+ `guards notes disagree with the page gates they name:\n ${problems.join('\n ')}`);
338
+ });
339
+
340
+ // ── B0b negative fixtures — proof the check above can actually FAIL ───────────────
341
+ // The real catalog is correct, so B0b passing proves only that it found no problem, not
342
+ // that it can find one. These replay the HISTORICAL defect verbatim against a synthetic
343
+ // pair, which is the repeatable form of the throwaway probe that was run by hand.
344
+
345
+ const GATES_FIXTURE = new Map([
346
+ ['requireBuilderPage', 'page.view.public'],
347
+ ['requireGovernmentPage', 'page.view.government'],
348
+ ]);
349
+
350
+ test('B0b catches the original defect: a gate listed against the WRONG atom', () => {
351
+ // page.view.builder's note as it actually stood — requireBuilderPage enforces
352
+ // page.view.public, so migrating on the strength of it would have locked every Xenos
353
+ // out of Work, Settings, pair, atlas, goals, roadmap and task-detail.
354
+ const { problems, named } = guardGateProblems(GATES_FIXTURE, [
355
+ { key: 'page.view.builder', guards: 'authenticated hall pages (requireBuilderPage)' },
356
+ ]);
357
+ assert.equal(named, 1);
358
+ assert.equal(problems.length, 1);
359
+ assert.match(problems[0], /names 'requireBuilderPage', but that gate enforces 'page\.view\.public'/);
360
+ });
361
+
362
+ test('B0b catches a note left pointing at a DELETED gate', () => {
363
+ // requireNonXenosPage went with /not-ready in task 1003450. A note outliving its gate
364
+ // reads as authoritative and resolves to nothing, so an unknown name must FAIL here
365
+ // rather than skip — failing open is how the original problem survived unnoticed.
366
+ const { problems } = guardGateProblems(GATES_FIXTURE, [
367
+ { key: 'page.view.builder', guards: 'authenticated hall pages (requireNonXenosPage)' },
368
+ ]);
369
+ assert.equal(problems.length, 1);
370
+ assert.match(problems[0], /names 'requireNonXenosPage', which is not a gate in PAGE_GATES/);
371
+ });
372
+
373
+ test('B0b stays silent on a note whose gate agrees with it, and on notes naming none', () => {
374
+ // The permissive half, asserted as deliberately as the strict ones: a correct note and
375
+ // a prose-only note must BOTH pass, or the check would just be noise someone disables.
376
+ const { problems, named } = guardGateProblems(GATES_FIXTURE, [
377
+ { key: 'page.view.government', guards: 'the government pages — /watch (requireGovernmentPage)' },
378
+ { key: 'page.view.builder', guards: 'nothing in the core today — seeded and attachable' },
379
+ ]);
380
+ assert.deepEqual(problems, []);
381
+ assert.equal(named, 1, 'only the note naming a gate participates');
382
+ });
383
+
384
+ test('parsePageGates reads permission wherever it sits, and never borrows a sibling gate\'s', () => {
385
+ // The nit the grade raised: the old inner regex assumed `permission` came first in a
386
+ // gate's literal. It does today; key order is not a contract. A gate with no permission
387
+ // key at all must be DROPPED, never silently given the next gate's.
388
+ const gates = parsePageGates([
389
+ 'const PAGE_GATES = Object.freeze({',
390
+ " requireSecondKeyPage: Object.freeze({ deniedTo: '/builders', permission: 'a.b' }),",
391
+ " requireNoPermissionPage: Object.freeze({ deniedTo: '/builders' }),",
392
+ " requireNormalPage: Object.freeze({ permission: 'c.d', deniedTo: '/builders' }),",
393
+ '});',
394
+ ].join('\n'));
395
+ assert.equal(gates.get('requireSecondKeyPage'), 'a.b', 'permission declared second still reads');
396
+ assert.equal(gates.has('requireNoPermissionPage'), false, 'a gate with no permission is dropped');
397
+ assert.equal(gates.get('requireNormalPage'), 'c.d', 'and it did not inherit a sibling\'s');
398
+ });
399
+
240
400
  // ONE-DIRECTIONAL, and deliberately so. A floor STRICTER than the route middleware
241
401
  // is legitimate and common: the wall lives in the handler (task.confirm.grade_bypass
242
402
  // on an any-builder POST /tasks/:id/confirm; goal.manage.own's owner|manager check;