@bongos/core 1.19.1015 → 1.19.1017

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.
Files changed (82) hide show
  1. package/.bongos-core.json +159 -94
  2. package/.claude/skills/planning-session/SKILL.md +1 -1
  3. package/clients/bongos-client/README.md +1 -1
  4. package/clients/bongos-client/bongos-client.global.js +4 -0
  5. package/clients/bongos-client/index.cjs +4 -0
  6. package/clients/bongos-client/index.d.ts +7 -2
  7. package/clients/bongos-client/index.mjs +4 -0
  8. package/docs/adr/0331-the-sky-is-the-ideators-home.md +12 -0
  9. package/docs/adr/0334-the-recruiting-directory-lists-a-hidden-builder-by-name.md +47 -0
  10. package/docs/adr/0335-an-invite-is-a-pre-approved-row-on-the-projects-own-instance.md +113 -0
  11. package/docs/adr/0336-a-guilds-record-is-the-sum-of-what-its-members-already-show.md +201 -0
  12. package/docs/adr/README.md +3 -0
  13. package/docs/api/openapi.json +60 -5
  14. package/docs/api-reference.md +8 -2
  15. package/docs/architecture.md +4 -1
  16. package/docs/copy-inventory.md +5 -3
  17. package/docs/copy-registry.json +22 -4
  18. package/docs/file-map.md +1 -2
  19. package/docs/module-api-changelog.md +5 -1
  20. package/migrations/core_253_idea_filed_via.sql +59 -0
  21. package/modules/agents/pollers/event-router.js +2 -0
  22. package/modules/discord/discord-bugs.js +2 -0
  23. package/modules/discord/discord-inbound.js +3 -0
  24. package/modules/hall-ui/public/people.html +11 -0
  25. package/modules/hall-ui/public/people.js +77 -11
  26. package/modules/hall-ui/public/people.states.json +1 -1
  27. package/modules/hall-ui/public/sky-data.js +22 -8
  28. package/modules/hall-ui/public/sky-draw.js +3 -2
  29. package/modules/hall-ui/public/sky-panel.js +1 -1
  30. package/modules/ideas/born-of.js +55 -0
  31. package/modules/ideas/inbox.js +23 -8
  32. package/modules/ideas/projection.js +48 -0
  33. package/modules/ideas/routes/inbox.js +20 -0
  34. package/modules/lifecycle/sky.js +25 -4
  35. package/modules/platform-identity/account-visibility.js +33 -2
  36. package/modules/platform-identity/activity-snapshots.js +47 -0
  37. package/modules/platform-identity/builder-search.js +153 -0
  38. package/modules/platform-identity/migrations/platform_identity_022_activity_snapshots.sql +46 -0
  39. package/modules/platform-identity/migrations/platform_identity_023_builder_search_trgm.sql +40 -0
  40. package/modules/platform-identity/module.json +1 -1
  41. package/modules/platform-identity/platform-identity.js +13 -0
  42. package/modules/platform-identity/routes/community-search.js +120 -0
  43. package/modules/platform-identity/routes/scouting.js +14 -7
  44. package/modules/platform-identity/scouting-name-only.js +47 -0
  45. package/modules/platform-identity/tests/activity-snapshots.mjs +68 -0
  46. package/modules/platform-identity/tests/platform-identity.mjs +21 -0
  47. package/modules/public-landing/public/projects-floor.states.json +9 -2
  48. package/modules/public-landing/public/projects.html +69 -4
  49. package/modules/ui-design/skills/impeccable/SKILL.md +1 -1
  50. package/modules/ui-design/styles/expedition/mock.html +16 -3
  51. package/package-lock.json +2 -2
  52. package/package.json +1 -1
  53. package/scripts/gds/capture.js +11 -0
  54. package/scripts/gds/run-unit-tests.js +4 -0
  55. package/src/module-api.js +1 -1
  56. package/tests/account_privacy_flags.mjs +4 -1
  57. package/tests/activity_snapshots_db.mjs +97 -0
  58. package/tests/card_apply_ui.mjs +297 -0
  59. package/tests/community_search_route.mjs +385 -0
  60. package/tests/discord_bugs.mjs +1 -0
  61. package/tests/discord_inbound.mjs +1 -0
  62. package/tests/effective_visibility_predicate.mjs +49 -1
  63. package/tests/goal_map_page.mjs +14 -0
  64. package/tests/guild_namespace_reserved.mjs +76 -0
  65. package/tests/hall_sky.mjs +33 -16
  66. package/tests/idea_filed_via.mjs +185 -0
  67. package/tests/landing_page.mjs +8 -0
  68. package/tests/people_name_only.mjs +262 -0
  69. package/tests/profile_rollup_consent.mjs +5 -1
  70. package/tests/profile_route.mjs +23 -5
  71. package/tests/project_admin_console_model.mjs +105 -0
  72. package/tests/recruiter_sliver_boundary.mjs +3 -0
  73. package/tests/scouting_owner_access_and_reach.mjs +112 -2
  74. package/tests/sky_read.mjs +19 -2
  75. package/tests/ui_design_skills.mjs +9 -0
  76. package/tests/ui_design_styles.mjs +13 -0
  77. package/modules/public-landing/public/world/assets/clouds/cloud-island-large.png +0 -0
  78. package/modules/public-landing/public/world/assets/clouds/cloud-puff.png +0 -0
  79. package/modules/public-landing/public/world/assets/creatures/creature-whale-cargo.png +0 -0
  80. package/modules/public-landing/public/world/assets/creatures/creature-whale-glide.png +0 -0
  81. package/modules/public-landing/public/world/assets/plates/plate-clouds-near.png +0 -0
  82. package/modules/public-landing/public/world/assets/plates/scene-establishing.png +0 -0
@@ -271,6 +271,9 @@ function accountFilter(where, params) {
271
271
  take(/\b(?:[a-z_]+\.)?terms_accepted_at IS NOT NULL/, () => (r) => r.terms_accepted_at != null);
272
272
  take(/\b(?:[a-z_]+\.)?account_visibility = 'public'/, () => (r) => r.account_visibility === 'public');
273
273
  take(/\bNOT (?:[a-z_]+\.)?hide_stats\b/, () => (r) => !r.hide_stats);
274
+ // The directory's name-only tier asks the hide flag POSITIVELY (task 1002291).
275
+ // After the NOT form, which consumes its own text first.
276
+ take(/\b(?:[a-z_]+\.)?hide_stats\b/, () => (r) => !!r.hide_stats);
274
277
 
275
278
  take(/\b(?:[a-z_]+\.)?github_id = \$(\d+)/, (m) => {
276
279
  consumed.add(m[1]);
@@ -70,7 +70,11 @@ import { fileURLToPath } from 'node:url';
70
70
  // importing them here safe.
71
71
  import {
72
72
  scoutingListableSql,
73
+ scoutingNameOnlySql,
73
74
  rollupVisibleSql,
75
+ accountVisibleSql,
76
+ accountActiveSql,
77
+ recruiterReachSql,
74
78
  isRecruiterDiscoverable,
75
79
  } from '../modules/platform-identity/account-visibility.js';
76
80
  import {
@@ -130,8 +134,21 @@ const ACCOUNTS = [
130
134
  // presence in the SQL provable here rather than merely asserted.
131
135
  { github_id: '3030', github_login: 'unaccepted', display_name: 'Unaccepted',
132
136
  profile_state: 'active', terms_accepted_at: null, account_visibility: 'public', hide_stats: false, recruiter_discoverable: null },
137
+ // Task 1002291's two controls for the NAME-ONLY tier, each differing from `shy`
138
+ // in the one axis that must keep them out of it: `shyout` opted out of
139
+ // recruiting, `shypriv` is private (and opted in, which never buys existence).
140
+ { github_id: '6161', github_login: 'shyout', display_name: 'Shy Out',
141
+ profile_state: 'active', terms_accepted_at: ACCEPTED_AT, account_visibility: 'public', hide_stats: true, recruiter_discoverable: false },
142
+ { github_id: '6262', github_login: 'shypriv', display_name: 'Shy Private',
143
+ profile_state: 'active', terms_accepted_at: ACCEPTED_AT, account_visibility: 'private', hide_stats: true, recruiter_discoverable: true },
133
144
  ];
134
145
 
146
+ // The record a hidden builder keeps private. Planted on every row the name-only
147
+ // read hands back — a real row grows columns — so a projection that spread the
148
+ // row instead of naming its keys would carry these into the body and fail below.
149
+ const HIDDEN_RECORD = { github_id_echo: '6060-ID-ECHO', total_credits: 773311, total_tasks_shipped: 773322,
150
+ total_karma: 773333, project_count: 773344, last_active: 'shy-last-active-773355', disciplines: ['shy-craft-773366'] };
151
+
135
152
  // Project memberships, as the hub holds them. `membership_kind` is what the
136
153
  // PROJECT reported at check-in; `signin_verified_at` is the hub's OWN witness
137
154
  // that the person really signed in (ADR 0205 D1/D2). Owner needs both.
@@ -233,6 +250,9 @@ function accountFilter(where, params) {
233
250
  take(/\b(?:[a-z_]+\.)?terms_accepted_at IS NOT NULL/, () => (r) => r.terms_accepted_at != null);
234
251
  take(/\b(?:[a-z_]+\.)?account_visibility = 'public'/, () => (r) => r.account_visibility === 'public');
235
252
  take(/\bNOT (?:[a-z_]+\.)?hide_stats\b/, () => (r) => !r.hide_stats);
253
+ // The name-only tier's term (task 1002291): the hide flag asked POSITIVELY.
254
+ // Tried after the NOT form, which consumes its own text first.
255
+ take(/\b(?:[a-z_]+\.)?hide_stats\b/, () => (r) => !!r.hide_stats);
236
256
 
237
257
  // The TERM guards run before the STRUCTURE guard, so an unreadable term is
238
258
  // reported as the unreadable term it is rather than as leftover text.
@@ -318,6 +338,22 @@ const pool = {
318
338
  return { rows, rowCount: rows.length };
319
339
  }
320
340
 
341
+ // The directory's NAME-ONLY tier (task 1002291): a plain read of the accounts
342
+ // table, judged by its own WHERE. Each row comes back WHOLE, plus the record
343
+ // the builder hid, so the route's projection is what has to drop them.
344
+ if (/^SELECT .+ FROM platform_identity_accounts WHERE /.test(sql)) {
345
+ assert.doesNotMatch(sql, /platform_identity_builder_project_activity/,
346
+ 'the name-only read must not consult the rollup — presence there would say who has built');
347
+ const where = sql.slice(sql.indexOf(' WHERE ') + 7).replace(/ ORDER BY .*| LIMIT .*/g, '');
348
+ const limit = Number(params[Number((/ LIMIT \$(\d+)/.exec(sql) || [, 0])[1]) - 1]);
349
+ assert.ok(limit > 0, 'the name-only read is bounded like the ranked one');
350
+ const rows = ACCOUNTS.filter(accountFilter(where, params))
351
+ .sort((a, b) => a.github_login.localeCompare(b.github_login))
352
+ .slice(0, limit)
353
+ .map((a) => ({ ...a, ...HIDDEN_RECORD }));
354
+ return { rows, rowCount: rows.length };
355
+ }
356
+
321
357
  throw new Error(`fake pool: unhandled statement — ${sql.slice(0, 120)}`);
322
358
  },
323
359
  };
@@ -391,6 +427,7 @@ function getScouting(caller, query = '') {
391
427
  });
392
428
  }
393
429
  const logins = (res) => JSON.parse(res.text).builders.map((b) => b.github_login).sort();
430
+ const namedLogins = (res) => (JSON.parse(res.text).name_only || []).map((b) => b.github_login).sort();
394
431
 
395
432
  // The cast. `rank` is the caller's rank on the HUB; `github_id` is the hub
396
433
  // account whose project memberships decide the ownership door.
@@ -521,8 +558,8 @@ await t('and the whole matrix lands exactly where the specs put it', async () =>
521
558
  assert.deepEqual(shown, ['octo', 'optin'],
522
559
  'in: reach-unset-and-public (octo) + explicitly-opted-in (optin). '
523
560
  + 'out: optout (opted out), scout (private, and reach never buys addressability), '
524
- + 'shy (hidden), newbie (provisional), unaccepted (never accepted the platform '
525
- + `terms — ADR 0213) — got ${JSON.stringify(shown)}`);
561
+ + 'shy (hidden — ranked out, listed by name in the second tier below), newbie (provisional), '
562
+ + `unaccepted (never accepted the platform terms — ADR 0213) — got ${JSON.stringify(shown)}`);
526
563
  });
527
564
 
528
565
  await t('opting back IN restores them — the toggle is a toggle, not a tombstone', async () => {
@@ -613,6 +650,77 @@ await t('/people is the SAME endpoint, so "absent from /people" is true by const
613
650
  'and listScoutingRollup — the one reader behind both surfaces — joins the composed predicate');
614
651
  });
615
652
 
653
+ // ===========================================================================
654
+ // 2b. HIDE CHANGES THE SHAPE, NOT THE PRESENCE (task 1002291, R15)
655
+ // ===========================================================================
656
+ //
657
+ // Privacy spec D2, the "active · public · hide_stats" row, recruiter column:
658
+ // "listed name-only (if not opted out)". Until R15 the directory read hide as a
659
+ // second opt-out and dropped the builder entirely, while the Settings row told
660
+ // that same builder recruiters would see their name and picture. The directory
661
+ // now has two tiers: the ranked record, and a name-only list.
662
+
663
+ console.log('\nhide — a hidden builder stays findable, by name alone:');
664
+
665
+ await t('a hidden, reachable builder is listed NAME-ONLY — and never in the ranked tier', async () => {
666
+ const res = await getScouting(OPERATOR);
667
+ assert.equal(res.status, 200);
668
+ assert.deepEqual(namedLogins(res), ['shy'],
669
+ 'in: shy (public, hidden, reach unset). out: shyout (hidden but opted out), shypriv (hidden and private — '
670
+ + `reach never buys existence), and every unhidden builder, who is ranked instead — got ${JSON.stringify(namedLogins(res))}`);
671
+ assert.ok(!logins(res).includes('shy'), 'a hidden builder is never RANKED — rank position is derived from the record');
672
+ const read = calls.find((c) => /^SELECT .+ FROM platform_identity_accounts WHERE /.test(c.sql));
673
+ assert.ok(read, 'the name-only tier is a read of its own');
674
+ assert.ok(read.sql.includes(scoutingNameOnlySql()),
675
+ 'and it joins the SHARED predicate, not a copy of it');
676
+ });
677
+
678
+ await t('the name-only row is identity and nothing else — the record the builder hid never reaches the body', async () => {
679
+ const res = await getScouting(OWNER);
680
+ const row = JSON.parse(res.text).name_only[0];
681
+ assert.deepEqual(Object.keys(row).sort(), ['avatar_url', 'display_name', 'github_login'],
682
+ 'a literal whitelist: what a hidden builder\'s own public page already shows');
683
+ for (const [key, value] of Object.entries(HIDDEN_RECORD)) {
684
+ for (const v of [].concat(value)) {
685
+ assert.ok(!res.text.includes(String(v)), `${key} (${v}) leaked into the directory — the read spread a row it should have named`);
686
+ }
687
+ }
688
+ assert.ok(!res.text.includes('Shy Out') && !res.text.includes('Shy Private'),
689
+ 'and the two hidden builders the tier refuses leave no trace either');
690
+ });
691
+
692
+ await t('un-hiding moves a builder between the tiers; opting out removes them from both', async () => {
693
+ const shy = ACCOUNTS.find((a) => a.github_login === 'shy');
694
+ try {
695
+ shy.hide_stats = false;
696
+ let res = await getScouting(OPERATOR);
697
+ assert.ok(logins(res).includes('shy') && !namedLogins(res).includes('shy'),
698
+ 'shown again, the builder is ranked on their record and gone from the name-only list — never in both');
699
+ shy.hide_stats = true;
700
+ shy.recruiter_discoverable = false;
701
+ res = await getScouting(OPERATOR);
702
+ assert.ok(!logins(res).includes('shy') && !namedLogins(res).includes('shy'),
703
+ 'hidden AND opted out: absent from the directory entirely — the opt-out is still the only thing that removes a builder');
704
+ } finally {
705
+ shy.hide_stats = true;
706
+ shy.recruiter_discoverable = null;
707
+ }
708
+ });
709
+
710
+ await t('MUTATION CHECK: drop the reach or the existence half and the wrong hidden builder walks in', async () => {
711
+ const admits = (clause) => ACCOUNTS.filter(accountFilter(clause, [])).map((a) => a.github_login).sort();
712
+ const tier = scoutingNameOnlySql();
713
+ assert.deepEqual(admits(tier), ['shy'], 'the real predicate');
714
+ const withoutReach = tier.replace(` AND ${recruiterReachSql()}`, '');
715
+ assert.notEqual(withoutReach, tier, 'fixture check: the reach term was really removed');
716
+ assert.deepEqual(admits(withoutReach), ['shy', 'shyout'],
717
+ 'WITHOUT reach, the builder who opted out is listed — so the opt-out assertion above is load-bearing');
718
+ const withoutExistence = tier.replace(accountVisibleSql(), accountActiveSql());
719
+ assert.notEqual(withoutExistence, tier, 'fixture check: the existence axis was really removed');
720
+ assert.deepEqual(admits(withoutExistence), ['shy', 'shypriv'],
721
+ 'WITHOUT the existence axis, a PRIVATE account is listed by name — the D3 sliver\'s subject, which the directory must never carry');
722
+ });
723
+
616
724
  // ===========================================================================
617
725
  // 3. THE TWO AXES NEVER TOUCH (ADR 0016)
618
726
  // ===========================================================================
@@ -631,6 +739,8 @@ await t('opting out of recruiting grants and denies NO permission', async () =>
631
739
  + 'so a reader can never infer a privacy setting from whose list they got');
632
740
  assert.deepEqual(shown, logins(await getScouting(OPERATOR)),
633
741
  'byte-for-byte the same population an operator sees');
742
+ assert.deepEqual(namedLogins(res), namedLogins(await getScouting(OPERATOR)),
743
+ 'and the same name-only list — neither tier varies by viewer');
634
744
  });
635
745
 
636
746
  await t('being scoutable buys nothing either — the response is the same descriptive whitelist', async () => {
@@ -47,7 +47,7 @@ await test('the shape: wells carry their criteria and their compact tasks; edges
47
47
  criteria: [cr(10, 1, 100, 'shipped'), cr(10, 1, 101, 'ready'), cr(11, 2, null)],
48
48
  tasks: [t(100, 1, { status: 'shipped', idea_id: '555' }), t(101, 1), t(200, 2)],
49
49
  });
50
- assert.deepEqual(Object.keys(out), ['goals', 'edges', 'counts']);
50
+ assert.deepEqual(Object.keys(out), ['goals', 'edges', 'born', 'counts']);
51
51
  assert.deepEqual(out.edges, [{ from: '1', to: '2', count: 3 }]);
52
52
  assert.deepEqual(out.counts, { goals: 2, edges: 1, criteria: 2, tasks: 3 });
53
53
  const w1 = out.goals[0];
@@ -108,7 +108,7 @@ await test('a PRIVATE goal the viewer cannot see stays a well with its counts, r
108
108
  });
109
109
 
110
110
  await test('an empty corpus is a valid, empty sky', () => {
111
- assert.deepEqual(shapeSky({}), { goals: [], edges: [], counts: { goals: 0, edges: 0, criteria: 0, tasks: 0 } });
111
+ assert.deepEqual(shapeSky({}), { goals: [], edges: [], born: { goals: {}, criteria: {} }, counts: { goals: 0, edges: 0, criteria: 0, tasks: 0 } });
112
112
  });
113
113
 
114
114
  // ---- the criterion→task link agrees with the progress rollup ------------------------
@@ -198,6 +198,23 @@ await test('the payload is the shaped sky, categories included, from one fake re
198
198
  assert.equal(out.goals[0].tasks[0].title, 'Task 100');
199
199
  });
200
200
 
201
+ // ---- born of a thought (task 1004233, sky part 5) ---------------------------------------
202
+
203
+ await test('born rides on the sky from the ideas port, normalised to string ids; an absent or failing port is an EMPTY map — never a guess', async () => {
204
+ const pool = fakePool({ goalRows: [{ goal_id: '1', title: 'A', status: 'open', version_id: 'V1', category_id: null, visible: true, task_total: 1, task_done: 0 }], extraRows: [x(1)] });
205
+ const out = await sky({ viewerId: 1 }, { pool, categoryIndex: async () => CATS, readBornOf: async () => ({ goals: { 1: 55 }, criteria: { 10: '56', 11: null } }) });
206
+ assert.deepEqual(out.born, { goals: { 1: '55' }, criteria: { 10: '56' } });
207
+ assert.equal(pool.calls.length, 5, 'the born read is the ideas module\'s own — not a sixth query here');
208
+ const none = await sky({ viewerId: 1 }, { pool: fakePool(), categoryIndex: async () => CATS, readBornOf: null });
209
+ assert.deepEqual(none.born, { goals: {}, criteria: {} });
210
+ const broken = await sky({ viewerId: 1 }, { pool: fakePool(), categoryIndex: async () => CATS, readBornOf: async () => { throw new Error('port down'); } });
211
+ assert.deepEqual(broken.born, { goals: {}, criteria: {} }, 'a failing port empties the map and the sky still draws');
212
+ assert.deepEqual(skyLib.normalizeBorn({ goals: 'nope' }), { goals: {}, criteria: {} });
213
+ const SKY_SRC = fs.readFileSync(path.join(ROOT, 'modules/lifecycle/sky.js'), 'utf8');
214
+ assert.match(SKY_SRC, /resolveOptional\('ideas\.bornOf'\)/, 'production resolves the port');
215
+ assert.equal(/ideas_goal_ratifications|criterion_proposal_audit/.test(SKY_SRC), false, 'lifecycle reads no ideas table itself');
216
+ });
217
+
201
218
  // ---- the cache and the handler -------------------------------------------------------
202
219
 
203
220
  function run(handler, req) {
@@ -145,6 +145,15 @@ test('impeccable is one in-house skill whose sixteen sub-commands each own exact
145
145
  }
146
146
  });
147
147
 
148
+ test('impeccable menu names the kit-report temp dir OS-generically, not Windows-only %TMP% (task 1003636)', () => {
149
+ const skill = read(path.join(IMPECCABLE, 'SKILL.md'));
150
+ const signalLine = skill.split('\n').find((l) => l.includes('The last kit report'));
151
+ assert.ok(signalLine, 'the kit-report menu signal line exists');
152
+ assert.doesNotMatch(signalLine, /`%TMP%\/ui-design-kit/, 'the signal does not lead with a bare Windows %TMP% path');
153
+ assert.match(signalLine, /os\.tmpdir\(\)/, 'points at os.tmpdir(), which resolves per-OS');
154
+ assert.match(signalLine, /\$TMPDIR/, 'names the mac/linux env var too, not just %TMP%');
155
+ });
156
+
148
157
  test('impeccable carries none of the behaviour that made its origin dangerous: no hooks, no bypass agent, no telemetry, no remote bundle, no live-server token', () => {
149
158
  const files = [path.join(IMPECCABLE, 'SKILL.md'), ...fs.readdirSync(path.join(IMPECCABLE, 'reference')).map((f) => path.join(IMPECCABLE, 'reference', f))];
150
159
  for (const f of files) {
@@ -120,6 +120,19 @@ test('every mock passes the platform rules under its own colours and its own DES
120
120
  }
121
121
  });
122
122
 
123
+ test("the expedition's halftone screen renders: it sits on a fixed body::before layer, never body's own background-image (task 1003633)", () => {
124
+ const html = read(path.join(STYLES_DIR, 'expedition', 'mock.html'));
125
+ const bodyRule = html.match(/\bbody\s*\{[^}]*\}/);
126
+ assert.ok(bodyRule, 'the mock declares a body rule');
127
+ assert.doesNotMatch(bodyRule[0], /background-image/, "body's own rule carries no background-image — the branding pack's backdrop emits body{background:...} after the page's sheet (themeMarkup, serve-internal.js) and that shorthand resets it to none");
128
+ const before = html.match(/body::before\s*\{[^}]*\}/);
129
+ assert.ok(before, 'the halftone moved onto a body::before layer');
130
+ assert.match(before[0], /position:\s*fixed/, 'the layer is fixed, not in flow');
131
+ assert.match(before[0], /pointer-events:\s*none/, 'the layer never intercepts input');
132
+ assert.match(before[0], /background-image:\s*radial-gradient\(var\(--dots\)/, 'the halftone dots ride the layer, keyed to the pack-derived --dots var');
133
+ assert.match(before[0], /background-size:\s*var\(--dot-pitch\)/, 'the dot pitch moved with it');
134
+ });
135
+
123
136
  test('the world, hub and hall sheets clear AA under every alternate in both modes: the three token suites run once per pack', () => {
124
137
  // relative, forward-slash paths: `node --test <path>` reads its positionals as globs, and a
125
138
  // Windows absolute path matches nothing (zero tests, exit 0 — a silent pass)