@bongos/core 1.19.655 → 1.19.657

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.
@@ -37,6 +37,13 @@ const LIFECYCLE = path.join(ROOT, 'modules', 'lifecycle');
37
37
  const eligibility = require(path.join(LIFECYCLE, 'claim-eligibility.js'));
38
38
  const db = require(path.join(LIFECYCLE, 'db.js'));
39
39
  const catalog = require(path.join(ROOT, 'modules', 'government', 'catalog.js'));
40
+ // The doorway, so section D can stand a stub `government` port up in front of the
41
+ // REAL claimPermissionsFor instead of re-implementing its branches in the test.
42
+ const moduleApi = require(path.join(ROOT, 'src', 'module-api.js'));
43
+ // claimPermissionsFor is NOT on the db.js facade — db-claims.js imports it from
44
+ // the authz module directly, so the test reads it from the same place the real
45
+ // caller does rather than from a re-export that does not exist.
46
+ const rankAuthz = require(path.join(LIFECYCLE, 'db-rank-authz.js'));
40
47
  import { lifecycleDbSource } from './helpers.mjs';
41
48
 
42
49
  const { CLAIM_ANY, CLAIM_NEWCOMER, permissionAllowsClaim, claimEligibilityAllows } = eligibility;
@@ -262,3 +269,78 @@ test('the abuse case stays blocked: a xenos cannot reach the general queue by an
262
269
  assert.equal(permissionAllowsClaim(held, false), false);
263
270
  assert.equal(permissionAllowsClaim(held, true), true);
264
271
  });
272
+
273
+ // --- D. claimPermissionsFor's three-way answer (task 1003816) ---------------
274
+ //
275
+ // The function this section covers had NO test of its own: every case above
276
+ // drives the pure consumer (claimEligibilityAllows) with a hand-made
277
+ // heldPermissions value, so nothing proved which value the REAL reader produces.
278
+ // That is exactly where the bug lived — an empty permission set was folded in
279
+ // with "could not establish authority" and silently became a rank-rule fallback.
280
+ //
281
+ // The distinction is the whole point, so it is asserted as a distinction: null
282
+ // (fall back) for the two unestablished cases, a Set (a real verdict, denied when
283
+ // empty) whenever the resolver actually answered.
284
+ const realResolveOptional = moduleApi.resolveOptional;
285
+ async function withGovernance(stub, fn) {
286
+ moduleApi.resolveOptional = (name) => (name === 'government' ? stub : realResolveOptional.call(moduleApi, name));
287
+ try { return await fn(); } finally { moduleApi.resolveOptional = realResolveOptional; }
288
+ }
289
+
290
+ test('no governance port — authority cannot be established, so the rank rule decides', async () => {
291
+ await withGovernance(undefined, async () => {
292
+ assert.equal(await rankAuthz.claimPermissionsFor('42'), null);
293
+ });
294
+ // A port object that does not implement the resolver is the same situation.
295
+ await withGovernance({}, async () => {
296
+ assert.equal(await rankAuthz.claimPermissionsFor('42'), null);
297
+ });
298
+ });
299
+
300
+ test('the resolver throwing is UNKNOWN, not empty — still the rank rule', async () => {
301
+ await withGovernance({ resolveBuilderPermissions: async () => { throw new Error('synthetic resolver outage'); } }, async () => {
302
+ assert.equal(await rankAuthz.claimPermissionsFor('42'), null);
303
+ });
304
+ });
305
+
306
+ test('a broken port contract (non-array) is unknown, never a deny', async () => {
307
+ for (const bad of [null, undefined, 'task.claim.any', 42, {}]) {
308
+ await withGovernance({ resolveBuilderPermissions: async () => bad }, async () => {
309
+ assert.equal(await rankAuthz.claimPermissionsFor('42'), null, `a ${typeof bad} return must not read as "denied"`);
310
+ });
311
+ }
312
+ });
313
+
314
+ test('ZERO permissions is a real verdict now, not a fallback (the 1003816 fix)', async () => {
315
+ await withGovernance({ resolveBuilderPermissions: async () => [] }, async () => {
316
+ const held = await rankAuthz.claimPermissionsFor('42');
317
+ assert.ok(held instanceof Set, 'an answered resolve returns a Set, so the rank rule is NOT consulted');
318
+ assert.equal(held.size, 0);
319
+ // And the consumer turns that into a deny for every queue, however permissive
320
+ // the incumbent rank rule would have been.
321
+ for (const nf of NF_VALUES) {
322
+ assert.equal(
323
+ claimEligibilityAllows({ heldPermissions: held, newcomerFriendly: nf, rankRuleAllows: true }),
324
+ false,
325
+ 'a builder with no role row is denied even where the rank rule would allow',
326
+ );
327
+ }
328
+ });
329
+ });
330
+
331
+ test('a real permission set is passed through as the verdict', async () => {
332
+ await withGovernance({ resolveBuilderPermissions: async () => [CLAIM_ANY] }, async () => {
333
+ const held = await rankAuthz.claimPermissionsFor('42');
334
+ assert.ok(held instanceof Set);
335
+ assert.equal(held.has(CLAIM_ANY), true);
336
+ assert.equal(claimEligibilityAllows({ heldPermissions: held, newcomerFriendly: false, rankRuleAllows: false }), true);
337
+ });
338
+ });
339
+
340
+ test('the doc comment no longer argues for the fallback it describes retiring', () => {
341
+ const src = fs.readFileSync(path.join(LIFECYCLE, 'db-rank-authz.js'), 'utf8');
342
+ assert.ok(
343
+ !/nothing yet re-assigns a rank-role/.test(src),
344
+ 'the stale "nothing yet re-assigns a rank-role" claim is false since BV1.R95b + task 1003193 — it must not survive the fix',
345
+ );
346
+ });
@@ -95,12 +95,17 @@ test('the read rides the vote atom, and the client renders the SERVER value (sou
95
95
  // of a grammar is exactly the drift that produced the bug this task fixes, so
96
96
  // they are held equal here over every form.
97
97
  //
98
+ // The mirror lives in modules/hall-ui/public/board-lib.js since task 1003761.
99
+ // It was in government.js, and ADR 0266's carve left a SECOND copy in
100
+ // board-room.js — so this test lifted one of two implementations and proved
101
+ // nothing about the other. One copy, one lift.
102
+ //
98
103
  // This matters more than a normal mirror: the Constitution panel is where an
99
104
  // owner READS who governs before deciding whether to change it. A panel that
100
105
  // misdescribes the franchise is the same failure as a resolver that mis-seats
101
106
  // it, one layer up.
102
107
  test('the hall renders every predicate form the server actually resolves', () => {
103
- const src = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/government.js'), 'utf8');
108
+ const src = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/board-lib.js'), 'utf8');
104
109
 
105
110
  // Lift the browser mirror out of the IIFE and run it.
106
111
  const ladder = /const STANDARD_LADDER = (\[[^\]]*\]);/.exec(src);
@@ -131,7 +136,7 @@ test('the hall renders every predicate form the server actually resolves', () =>
131
136
  });
132
137
 
133
138
  test('the ladder the hall expands against IS the government ladder', () => {
134
- const src = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/government.js'), 'utf8');
139
+ const src = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/board-lib.js'), 'utf8');
135
140
  const { RANK_ORDER } = require(path.join(ROOT, 'modules/government/catalog.js'));
136
141
  const m = /const STANDARD_LADDER = \[([^\]]*)\]/.exec(src);
137
142
  assert.ok(m);
@@ -107,23 +107,35 @@ const STATIC_LADDER_FILES = [
107
107
  'modules/hall-ui/public/ranks.js',
108
108
  'modules/hall-ui/public/profile.js',
109
109
  'modules/hall-ui/public/watch.js',
110
- // task 1003094: the Constitution panel expands the `rank:<key>+` membership
111
- // form ("that rank and above") to render who governs in plain language, so it
112
- // needs the ladder to expand against. Browser code cannot require the server
113
- // module the established mirror pattern hereand listing it in this array
114
- // is what makes the copy provable rather than merely allowed. A second,
115
- // government-specific pin lives in tests/government_constitution_view.mjs,
116
- // which holds it against catalog.RANK_ORDER and holds the whole browser parser
117
- // equal to the server one.
110
+ // task 1003094: the Constitution panel and the Board Room's sitting card both
111
+ // expand the `rank:<key>+` membership form ("that rank and above") to render
112
+ // who governs in plain language, so both need the ladder to expand against.
113
+ // Browser code cannot require the server modulethe established mirror
114
+ // pattern here — and listing it in this array is what makes the copy provable
115
+ // rather than merely allowed.
116
+ //
117
+ // ONE ROW, not two, since task 1003761. ADR 0266 carved the Board Room onto
118
+ // its own page and the carve COPIED the ladder and the parser into both page
119
+ // files, so this array had to allowlist BOTH — which permitted a drift between
120
+ // them by construction: the exact-literal pin below proved each matched the
121
+ // canonical ladder, but nothing held the two parsers equal to EACH OTHER, and
122
+ // government_constitution_view.mjs lifted only one of them. board-lib.js is
123
+ // the single copy now. A second, government-specific pin lives in
124
+ // tests/government_constitution_view.mjs, which holds it against
125
+ // catalog.RANK_ORDER and runs the whole browser parser against the server one.
126
+ //
127
+ // This row used to claim tests/government_board_vote_ui.mjs pinned the
128
+ // board-room copy. It does not — that file asserts the sitting card, the
129
+ // ballot and the vote form, and has no parser assertion at all, so the second
130
+ // copy was guarded only by the exact-literal ladder match.
131
+ 'modules/hall-ui/public/board-lib.js',
132
+ // government.js keeps a ladder literal of its own — the charter library's
133
+ // `seatsRanks` worked example for `rank:xenos+` (the full ladder, spelled out
134
+ // so a reader sees what the predicate resolves to). That is documentation of
135
+ // the grammar rather than a second implementation of it, and it must still
136
+ // match the canonical order or the example teaches a ladder that does not
137
+ // exist.
118
138
  'modules/hall-ui/public/government.js',
119
- // ADR 0266: the Board Room moved off the government page onto its own, and the
120
- // sitting card's `decidedByHtml` expands the same `rank:<key>+` membership form
121
- // to say who may vote on THAT sitting — so the carve took a copy of the ladder
122
- // with it. Same reason as the row above (browser code cannot require the server
123
- // module), and the same pinning: `tests/government_board_vote_ui.mjs` reads
124
- // board-room.js and holds its predicate parser equal to the server's, and the
125
- // pin below in this file holds the array itself against LIVE_RANK_LADDER.
126
- 'modules/hall-ui/public/board-room.js',
127
139
  // task 1002487: the pure agent-definition validator applies the ADR 0043
128
140
  // sub-Metic floor to an agent's declared scope, so it needs the ladder to
129
141
  // compare against. It is one of the "dependency-free" cases this array exists