@bongos/core 1.19.185 → 1.19.186

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.
@@ -751,7 +751,7 @@ function mountInternalSurfaces(app) {
751
751
  // The /watch monitoring page and the /harbor dev-box management page (idea 257)
752
752
  // and the /gate page (task 1179) all sit behind `page.view.government` — Metic
753
753
  // and up since ADR 0157. Same server-side page gate, same posture: a builder
754
- // without the permission never receives the shell (requireGovernancePage — named
754
+ // without the permission never receives the shell (requireGovernmentPage — named
755
755
  // requireArchonPage before ADR 0157 — redirects them to the hall, and an
756
756
  // unauthenticated visitor into sign-in). The subdomain shim above has already
757
757
  // rewritten the bare /watch + /harbor + /gate onto /builders/*. (Defense in
@@ -766,13 +766,13 @@ function mountInternalSurfaces(app) {
766
766
  // data never leaked (the route gate held), but the page was the one item in the
767
767
  // group without a wall. Gated here so the group is uniform (task 1002710).
768
768
  const SESSIONS_PAGE_RE = /^\/builders\/sessions(?:\.html)?\/?$/;
769
- // The /governance roles-and-permissions tab (BV1.R113) sits one floor higher:
769
+ // The /government ranks-and-permissions tab (BV1.R113) sits one floor higher:
770
770
  // the `government.manage` atom itself (archon), the same key its data routes
771
- // check — requireGovernanceManagePage, not the metic-floored gate above.
772
- const GOVERNANCE_PAGE_RE = /^\/builders\/governance(?:\.html)?\/?$/;
771
+ // check — requireGovernmentManagePage, not the metic-floored gate above.
772
+ const GOVERNMENT_PAGE_RE = /^\/builders\/government(?:\.html)?\/?$/;
773
773
  // The /people builder directory (task 1002577): gated on `project.curate`, the
774
774
  // exact permission its data route (GET /scouting) checks — same posture as the
775
- // /governance tab above (the page can never drift wider than the API it fronts).
775
+ // /government tab above (the page can never drift wider than the API it fronts).
776
776
  const PEOPLE_PAGE_RE = /^\/builders\/people(?:\.html)?\/?$/;
777
777
  // /scouting is the LEGACY twin of /people — the same /api/gds/scouting directory,
778
778
  // left behind when task 1002577 graduated it into the hall. It has no nav entry
@@ -787,10 +787,10 @@ function mountInternalSurfaces(app) {
787
787
  const norm = path.posix.normalize(req.path);
788
788
  if (WATCH_PAGE_RE.test(norm) || HARBOR_PAGE_RE.test(norm) || GATE_PAGE_RE.test(norm)
789
789
  || SESSIONS_PAGE_RE.test(norm)) {
790
- return gdsAuth.requireGovernancePage(req, res, next);
790
+ return gdsAuth.requireGovernmentPage(req, res, next);
791
791
  }
792
- if (GOVERNANCE_PAGE_RE.test(norm)) {
793
- return gdsAuth.requireGovernanceManagePage(req, res, next);
792
+ if (GOVERNMENT_PAGE_RE.test(norm)) {
793
+ return gdsAuth.requireGovernmentManagePage(req, res, next);
794
794
  }
795
795
  if (PEOPLE_PAGE_RE.test(norm) || SCOUTING_PAGE_RE.test(norm)) {
796
796
  return gdsAuth.requireProjectCuratePage(req, res, next);
package/src/module-api.js CHANGED
@@ -797,8 +797,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
797
797
  // landed since 1.19.183 with no explicit bump. run 31974847472. (task 1002620)
798
798
  // 1.19.185 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
799
799
  // landed since 1.19.184 with no explicit bump. run 31976564067. (task 1002620)
800
+ // 1.19.186 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
801
+ // landed since 1.19.185 with no explicit bump. run 31977207459. (task 1002620)
800
802
  // ---------------------------------------------------------------------------
801
- const CORE_VERSION = '1.19.185'; // CI auto-patch carrier (ADR 0161); see changelog above
803
+ const CORE_VERSION = '1.19.186'; // CI auto-patch carrier (ADR 0161); see changelog above
802
804
 
803
805
  // A namespaced logger so a module's log lines are attributable + consistent.
804
806
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -93,17 +93,17 @@ test('AUDIT: every async middleware mounted directly on a router owns its reject
93
93
  // rot: a NEW bare `async function require*` added later fails this test.
94
94
  // requireBuilder — the API gate. Guarded here (BV1.R109-follow-up).
95
95
  // requireBuilderPage \
96
- // requireGovernancePage \
97
- // requireGovernanceManagePage > all five delegate to runPageGate, guarded by BV1.R107.
96
+ // requireGovernmentPage \
97
+ // requireGovernmentManagePage > all five delegate to runPageGate, guarded by BV1.R107.
98
98
  // requireNonXenosPage / (the R113 roles-tab gate; the /people curate gate)
99
99
  // requireProjectCuratePage /
100
100
  // Anything else async and router-mounted must be added with its own try/catch.
101
101
  const src = fs.readFileSync(path.join(ROOT, 'src', 'bongos', 'auth.js'), 'utf8');
102
102
  const asyncMw = [...src.matchAll(/^async function (require\w+|allow\w+)\s*\(req, res, next\)/gm)].map((m) => m[1]);
103
- assert.deepEqual(asyncMw.sort(), ['requireBuilder', 'requireBuilderPage', 'requireGovernanceManagePage', 'requireGovernancePage', 'requireNonXenosPage', 'requireProjectCuratePage'],
103
+ assert.deepEqual(asyncMw.sort(), ['requireBuilder', 'requireBuilderPage', 'requireGovernmentManagePage', 'requireGovernmentPage', 'requireNonXenosPage', 'requireProjectCuratePage'],
104
104
  'a new async router middleware appeared — give it a try/catch and add it here');
105
105
  // The page gates must actually delegate (that is WHY they are safe).
106
- for (const name of ['requireBuilderPage', 'requireGovernancePage', 'requireGovernanceManagePage', 'requireNonXenosPage', 'requireProjectCuratePage']) {
106
+ for (const name of ['requireBuilderPage', 'requireGovernmentPage', 'requireGovernmentManagePage', 'requireNonXenosPage', 'requireProjectCuratePage']) {
107
107
  const fn = src.slice(src.indexOf(`async function ${name}`));
108
108
  assert.match(sliceFn(fn), /runPageGate\(/, `${name} must delegate to the guarded runPageGate`);
109
109
  }
@@ -264,9 +264,9 @@ test('V4: mass-assignment on role create is refused outright (strict schema)', a
264
264
  test('V4: a clean role create is never system and carries no grants', async () => {
265
265
  const r = await call('POST', '/government/ranks', { rank_key: 'sneaky-role', label: 'Sneaky' });
266
266
  assert.equal(r.status, 201);
267
- assert.equal(r.json.role.is_system, false, 'a created role is never system');
268
- assert.equal(r.json.role.seeded_rank, null);
269
- assert.deepEqual(r.json.role.permissions, [], 'creation carries no grants');
267
+ assert.equal(r.json.rank.is_system, false, 'a created role is never system');
268
+ assert.equal(r.json.rank.seeded_rank, null);
269
+ assert.deepEqual(r.json.rank.permissions, [], 'creation carries no grants');
270
270
  const c = calls.find(([k]) => k === 'createRole');
271
271
  assert.deepEqual(Object.keys(c[1]).sort(), ['createdBy', 'description', 'label', 'rankKey'],
272
272
  'nothing beyond the four whitelisted fields reaches the db');
@@ -50,13 +50,13 @@ const resolver = require('../modules/government/resolver.js');
50
50
  const wandering = require('../modules/builder-settings/wandering-prefs.js');
51
51
  const { attachFail } = require('../src/bongos/middleware/error-envelope.js');
52
52
 
53
- const GATES = ['requireBuilderPage', 'requireNonXenosPage', 'requireGovernancePage'];
53
+ const GATES = ['requireBuilderPage', 'requireNonXenosPage', 'requireGovernmentPage'];
54
54
  const LIVE_RANKS = ['xenos', 'thetes', 'metic', 'archon'];
55
55
 
56
56
  // ═══ the ORACLE — hand-written, from the rank tests R107 deleted ═════════════
57
57
  // requireBuilderPage admitted any session at all → every live rank
58
58
  // requireNonXenosPage admitted rank !== 'xenos' → thetes and up
59
- // requireGovernancePage admitted rank === 'archon' → archon only …until ADR 0157
59
+ // requireGovernmentPage admitted rank === 'archon' → archon only …until ADR 0157
60
60
  //
61
61
  // ADR 0157 (owner decision, 2026-08-05) lowered `page.view.government` archon→metic, so
62
62
  // the monitoring pages (/watch, /harbor, /gate) now open to the working rank. That gate
@@ -71,7 +71,7 @@ const LIVE_RANKS = ['xenos', 'thetes', 'metic', 'archon'];
71
71
  const PRE_R107_AUDIENCE = {
72
72
  requireBuilderPage: { xenos: true, thetes: true, metic: true, archon: true },
73
73
  requireNonXenosPage: { xenos: false, thetes: true, metic: true, archon: true },
74
- requireGovernancePage: { xenos: false, thetes: false, metic: true, archon: true },
74
+ requireGovernmentPage: { xenos: false, thetes: false, metic: true, archon: true },
75
75
  };
76
76
  // Where a SIGNED-IN builder who fails the gate is sent. Never the sign-in flow —
77
77
  // they already have a session, so that would loop. Since task 1002613 (map N-1)
@@ -81,12 +81,12 @@ const PRE_R107_AUDIENCE = {
81
81
  const DENIED_TO = {
82
82
  requireBuilderPage: '/builders',
83
83
  requireNonXenosPage: '/builders/not-ready',
84
- requireGovernancePage: '/builders',
84
+ requireGovernmentPage: '/builders',
85
85
  };
86
86
  const GATE_PERMISSION = {
87
87
  requireBuilderPage: 'page.view.public',
88
88
  requireNonXenosPage: 'page.view.builder',
89
- requireGovernancePage: 'page.view.government',
89
+ requireGovernmentPage: 'page.view.government',
90
90
  };
91
91
  const deniedLoc = (gate, { unavailable = false } = {}) =>
92
92
  `${DENIED_TO[gate]}?denied=${GATE_PERMISSION[gate]}${unavailable ? '&reason=unavailable' : ''}`;
@@ -95,7 +95,7 @@ const GATE_FLOOR = {
95
95
  requireBuilderPage: 'xenos',
96
96
  requireNonXenosPage: 'thetes',
97
97
  // ADR 0157: `page.view.government` floor archon→metic.
98
- requireGovernancePage: 'metic',
98
+ requireGovernmentPage: 'metic',
99
99
  };
100
100
  const SIGN_IN = '/api/gds/auth/web/start';
101
101
 
@@ -249,16 +249,16 @@ test('B4: the page gates resolve per request — no memoised authority (ADR 0016
249
249
  });
250
250
  const req = { headers: { cookie: 'gds_session=validtoken' } };
251
251
  let admitted = false;
252
- await auth.requireGovernancePage(req, fakeRes(), () => { admitted = true; });
252
+ await auth.requireGovernmentPage(req, fakeRes(), () => { admitted = true; });
253
253
  assert.equal(admitted, true, 'an Archon should reach the page');
254
254
  keys = []; // the role is revoked between requests
255
255
  const second = fakeRes();
256
256
  let admittedAgain = false;
257
- await auth.requireGovernancePage(req, second, () => { admittedAgain = true; });
257
+ await auth.requireGovernmentPage(req, second, () => { admittedAgain = true; });
258
258
  seams._reset();
259
259
  assert.equal(calls, 2, 'the resolver must be consulted on every request');
260
260
  assert.equal(admittedAgain, false, 'a revoked role must deny on the very next request');
261
- assert.equal(second._redirect.loc, deniedLoc('requireGovernancePage'));
261
+ assert.equal(second._redirect.loc, deniedLoc('requireGovernmentPage'));
262
262
  });
263
263
 
264
264
  test('B5: every permission a page gate names is a real catalog key', async () => {
@@ -189,8 +189,8 @@ test('without government.manage EVERY route 403s (reads included)', async () =>
189
189
  test('GET /government/ranks returns the role list with grants', async () => {
190
190
  const r = await call('GET', '/government/ranks');
191
191
  assert.equal(r.status, 200);
192
- assert.equal(r.json.roles[0].rank_key, 'metic');
193
- assert.deepEqual(r.json.roles[0].permissions, ['task.create']);
192
+ assert.equal(r.json.ranks[0].rank_key, 'metic');
193
+ assert.deepEqual(r.json.ranks[0].permissions, ['task.create']);
194
194
  });
195
195
 
196
196
  test('GET /government/permissions renders the catalog: grantable flags + principals, no DB call', async () => {
@@ -221,8 +221,8 @@ test('GET /government/assignments returns builder↔role rows', async () => {
221
221
  test('POST /government/ranks creates a custom role, created_by = the session builder', async () => {
222
222
  const r = await call('POST', '/government/ranks', { rank_key: 'triage-helper', label: 'Triage helper' });
223
223
  assert.equal(r.status, 201);
224
- assert.equal(r.json.role.rank_key, 'triage-helper');
225
- assert.equal(r.json.role.is_system, false);
224
+ assert.equal(r.json.rank.rank_key, 'triage-helper');
225
+ assert.equal(r.json.rank.is_system, false);
226
226
  const c = calls.find(([k]) => k === 'createRole');
227
227
  assert.equal(c[1].createdBy, 42, 'created_by must be the session builder, never input');
228
228
  });
@@ -127,21 +127,22 @@ function hrefResolves(item) {
127
127
 
128
128
  test('sidebar groups read by audience, in order', () => {
129
129
  const nav = parseNav(boot().sidebarHtml);
130
- // 'Archon' → 'Governance' in ADR 0157: the group's pages opened to Metic, so a header
130
+ // 'Archon' → 'Governance' in ADR 0157 (the group's pages opened to Metic, so a header
131
+ // naming the top rank was wrong), then → 'Government' by ADR 0174: a header
131
132
  // reading "Archon" over items a Metic can open would misdescribe the ladder.
132
- assert.deepEqual(nav.map((g) => g.label), ['', 'You', 'Community', 'Docs', 'Governance']);
133
+ assert.deepEqual(nav.map((g) => g.label), ['', 'You', 'Community', 'Docs', 'Government']);
133
134
  assert.deepEqual(nav[0].items.map((i) => i.id), ['home', 'roadmap', 'goals', 'work'],
134
135
  'the unlabeled group is the project itself (task 1002425 order)');
135
136
  assert.deepEqual(nav[1].items.map((i) => i.id), ['standing', 'profile'], 'You = your own numbers');
136
137
  // People (task 1002577): the /scouting directory graduated into the hall.
137
138
  // Gated cosmetically to match its data endpoint — `project.curate`, floor METIC,
138
- // so the nav entry reads 'governance' since task 1002710 (it said 'archon', which
139
+ // so the nav entry reads 'government' since ADR 0174 (it said 'archon', then
139
140
  // was stricter than the wall). requireProjectCuratePage is the wall server-side.
140
141
  assert.deepEqual(nav[2].items.map((i) => i.id), ['leaderboard', 'people'], 'Community = everyone else');
141
142
  assert.deepEqual(nav[3].items.map((i) => i.id),
142
143
  ['primer', 'ranks', 'economy', 'diagrams', 'atlas', 'modules', 'apidocs'], 'Docs = reference material');
143
- // 'governance' (the Roles tab, BV1.R113) is the group's one archon-gated item.
144
- assert.deepEqual(nav[4].items.map((i) => i.id), ['watch', 'harbor', 'gate', 'sessions', 'governance']);
144
+ // 'government' (the Permissions tab, BV1.R113) is the group's one archon-gated item.
145
+ assert.deepEqual(nav[4].items.map((i) => i.id), ['watch', 'harbor', 'gate', 'sessions', 'government']);
145
146
  });
146
147
 
147
148
  test('Economy lives in Docs and points at the reward-schedule page', () => {
@@ -190,11 +191,11 @@ test('Achievements has no nav entry, but #achievements still routes to Standing'
190
191
 
191
192
  test('collapsible groups carry a stable storage key (collapse persistence)', () => {
192
193
  const html = boot().sidebarHtml;
193
- // 'archon' → 'governance' follows the ADR 0157 relabel. The slug is the
194
+ // 'archon' → 'governance' → 'government' follows ADR 0157 then ADR 0174. The slug is the
194
195
  // hall-nav-collapsed storage key, so any builder who had collapsed the old "archon"
195
196
  // group gets the new one expanded once — cosmetic, and preferable to keeping a stale
196
197
  // slug that no longer matches the visible label.
197
- for (const g of ['you', 'community', 'docs', 'governance']) {
198
+ for (const g of ['you', 'community', 'docs', 'government']) {
198
199
  assert.match(html, new RegExp(`data-group="${g}"`), `${g} is collapsible (hall-nav-collapsed key)`);
199
200
  }
200
201
  assert.ok(!/nav-group--collapsible[^>]*>(?!<button)/.test(html.split('<div class="nav-group')[1] || ''),
@@ -35,9 +35,9 @@ const SRC = fs.readFileSync(path.join(ROOT, 'src/bongos/serve-internal.js'), 'ut
35
35
  // Audiences (the floors live in modules/government/catalog.js; the gate
36
36
  // functions in src/bongos/auth.js):
37
37
  // requireBuilderPage page.view.public any signed-in builder (xenos+)
38
- // requireGovernancePage page.view.government metic+ (ADR 0157)
38
+ // requireGovernmentPage page.view.government metic+ (ADR 0157)
39
39
  // requireProjectCuratePage project.curate metic+
40
- // requireGovernanceManagePage government.manage archon
40
+ // requireGovernmentManagePage government.manage archon
41
41
  const ORACLE = {
42
42
  // Any signed-in builder — no rank floor.
43
43
  '/builders/work': 'requireBuilderPage',
@@ -57,10 +57,10 @@ const ORACLE = {
57
57
  '/builders/diagrams/02-b.png': 'requireBuilderPage',
58
58
 
59
59
  // Metic+ operator surfaces (the Governance nav group).
60
- '/builders/watch': 'requireGovernancePage',
61
- '/builders/harbor': 'requireGovernancePage',
62
- '/builders/gate': 'requireGovernancePage',
63
- '/builders/sessions': 'requireGovernancePage',
60
+ '/builders/watch': 'requireGovernmentPage',
61
+ '/builders/harbor': 'requireGovernmentPage',
62
+ '/builders/gate': 'requireGovernmentPage',
63
+ '/builders/sessions': 'requireGovernmentPage',
64
64
 
65
65
  // Metic+ via the `project.curate` atom — the exact permission GET /scouting
66
66
  // checks, so neither page can drift wider than the API it fronts.
@@ -68,7 +68,7 @@ const ORACLE = {
68
68
  '/builders/scouting': 'requireProjectCuratePage',
69
69
 
70
70
  // Archon.
71
- '/builders/governance': 'requireGovernanceManagePage',
71
+ '/builders/government': 'requireGovernmentManagePage',
72
72
 
73
73
  // Deliberately UNGATED — the landing card, the rank ladder, the reward
74
74
  // schedule and a builder's public portfolio all render for a stranger.
@@ -124,7 +124,7 @@ test('the static parse actually found the gate wiring', () => {
124
124
  // If this file is ever restructured past the parser, these two fail LOUDLY
125
125
  // rather than letting every row below pass vacuously against an empty map.
126
126
  assert.ok(DISPATCH.some((d) => d.gate === 'requireBuilderPage'));
127
- assert.ok(DISPATCH.some((d) => d.gate === 'requireGovernancePage'));
127
+ assert.ok(DISPATCH.some((d) => d.gate === 'requireGovernmentPage'));
128
128
  });
129
129
 
130
130
  test('every hall URL routes to the page gate the oracle names', () => {