@bongos/core 1.21.100 → 1.21.102

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.21.100",
6
- "core_contract": "1.21.100",
7
- "source_commit": "8305d0876dae80fcb40694e6ec47fd7a58c39758",
5
+ "core_version": "1.21.102",
6
+ "core_contract": "1.21.102",
7
+ "source_commit": "f552db78c82ef1ca4418a9de313e150bb29ce4b1",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-10-09T12:41:06.123Z",
9
+ "built_at": "2026-10-09T13:15:03.097Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 592,
@@ -17,7 +17,7 @@
17
17
  "gate": "passed"
18
18
  },
19
19
  "file_count": 3640,
20
- "tree_sha256": "3ffdbef90f1b79435d7c0d64b7ab8ae554d1d7fc99b18c7126a37465763e5a17",
20
+ "tree_sha256": "834d88c63001764c8ed72f393a6c49d3d123469f268cf129a13cab0b342e5ebe",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -2872,7 +2872,7 @@
2872
2872
  {
2873
2873
  "path": "docs/module-api-changelog.md",
2874
2874
  "mode": "0000644",
2875
- "sha256": "f69d36a5d32aab16dd006bc16c8117d481064c56528a989997dcb8f516c37daa"
2875
+ "sha256": "b44e9230778cf7b2aafd8d7971fb862c00ab3c8c667da525dc83ceb6238a0908"
2876
2876
  },
2877
2877
  {
2878
2878
  "path": "docs/modules-contract.md",
@@ -10132,12 +10132,12 @@
10132
10132
  {
10133
10133
  "path": "package-lock.json",
10134
10134
  "mode": "0000644",
10135
- "sha256": "989f100357f592179e3bace36ba999890b2f872b511d894606d6815c3c9fffe5"
10135
+ "sha256": "4abc8512ca8578d3a43a7d915574dc473061e2e77cd20e3902c5478af77a671f"
10136
10136
  },
10137
10137
  {
10138
10138
  "path": "package.json",
10139
10139
  "mode": "0000644",
10140
- "sha256": "028cf735e44c50e2e49fae8b3820d9163570c8e56fc324d429b22c8a01236c56"
10140
+ "sha256": "7b6b52ae08358107e824752af2427b6efaa33d7ea5bcf9a0b464584eef6708a5"
10141
10141
  },
10142
10142
  {
10143
10143
  "path": "public-docs/index.html",
@@ -10157,7 +10157,7 @@
10157
10157
  {
10158
10158
  "path": "release-notes.json",
10159
10159
  "mode": "0000644",
10160
- "sha256": "0bd4355a927a990dc4cf27f748792e9c02f5feeb38bb55848aec90258ec95176"
10160
+ "sha256": "947800c2b145b25a07f38bdfc1274ba9316de0b1bccc35e43de3ea2ddc44f7a3"
10161
10161
  },
10162
10162
  {
10163
10163
  "path": "scripts/cron/style_guide_audit.sh",
@@ -11932,7 +11932,7 @@
11932
11932
  {
11933
11933
  "path": "scripts/gds/worktree.js",
11934
11934
  "mode": "0000644",
11935
- "sha256": "69f167d079c1900568f740d8d4a1a2288f5ff47146634209f8a1a9350a3aa5e2"
11935
+ "sha256": "40aa204dd081022580bbd016b1f2741ebfc4eb9324d4f60a2ac7da9636d813f1"
11936
11936
  },
11937
11937
  {
11938
11938
  "path": "scripts/hall-preview/README.md",
@@ -12432,7 +12432,7 @@
12432
12432
  {
12433
12433
  "path": "src/module-api.js",
12434
12434
  "mode": "0000644",
12435
- "sha256": "b56a3d392b5d31ada22d6c5cf12bb9c39514c4b707d5cb63239a4209b256c648"
12435
+ "sha256": "090a1a041a5e03ddfd2bb4c93ff9e28407a1cb662da2492e3741450900282f44"
12436
12436
  },
12437
12437
  {
12438
12438
  "path": "src/module-loader/catalog.js",
@@ -14752,7 +14752,7 @@
14752
14752
  {
14753
14753
  "path": "tests/guilds_db.mjs",
14754
14754
  "mode": "0000644",
14755
- "sha256": "8d8847a10fb1930a1c64acd66efb372acef0e742b0b5d804a994d003759f5248"
14755
+ "sha256": "e964c8a42a1703eb4b9edb45ad16d99340fcb08c5d28b0cbc24f5e94313f5b61"
14756
14756
  },
14757
14757
  {
14758
14758
  "path": "tests/hall_approval_queue.mjs",
@@ -18207,7 +18207,7 @@
18207
18207
  {
18208
18208
  "path": "tests/worktree_helper.mjs",
18209
18209
  "mode": "0000644",
18210
- "sha256": "cac88e36d1e2ef2cbe2799275186f0787431b5b636fc52d1325b5dbbfcd56368"
18210
+ "sha256": "0dea42cbad0d17eb63e516a6812050b239c51b0f2f2d13c3a949eff27ec44657"
18211
18211
  },
18212
18212
  {
18213
18213
  "path": "tests/world_tokens.mjs",
@@ -3037,5 +3037,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
3037
3037
  landed since 1.21.98 with no explicit bump. run 37927704012. (task 1002620)
3038
3038
  1.21.100 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3039
3039
  landed since 1.21.99 with no explicit bump. run 37931583687. (task 1002620)
3040
+ 1.21.101 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3041
+ landed since 1.21.100 with no explicit bump. run 37933684016. (task 1002620)
3042
+ 1.21.102 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3043
+ landed since 1.21.101 with no explicit bump. run 37935415352. (task 1002620)
3040
3044
  ---------------------------------------------------------------------------
3041
3045
  ```
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.21.100",
3
+ "version": "1.21.102",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.21.100",
9
+ "version": "1.21.102",
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.21.100",
3
+ "version": "1.21.102",
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",
@@ -9438,5 +9438,17 @@
9438
9438
  "id": "1004659",
9439
9439
  "text": "There is now a plain-language guide to how a project's government works: GOVERNMENT.md, linked from the README and the hall primer. It explains the four forms you can pick today, who votes and how many must agree, who decides"
9440
9440
  }
9441
+ ],
9442
+ "1.21.101": [
9443
+ {
9444
+ "id": "1004582",
9445
+ "text": "Cleaning up a finished worktree no longer crashes on Linux and Mac machines. It used to stop halfway and leave the old workspace registered; now it removes the shortcut to the shared packages safely and carries on."
9446
+ }
9447
+ ],
9448
+ "1.21.102": [
9449
+ {
9450
+ "id": "1004588",
9451
+ "text": "The guilds test now sets up everything it needs on a brand-new database, so it runs all the way through instead of stopping early. It also checks, without needing a database, that its setup keeps up with the tables the guild c"
9452
+ }
9441
9453
  ]
9442
9454
  }
@@ -173,6 +173,24 @@ function nodeModulesState(wtPath) {
173
173
  return st.isSymbolicLink() ? 'junction' : 'real-dir';
174
174
  }
175
175
 
176
+ // Remove a node_modules LINK without ever following it (task 1004582). rmdir is
177
+ // only right for a Windows junction: on Linux/macOS (cloud containers) the link
178
+ // is a plain symlink and rmdir throws ENOTDIR, leaving the worktree registered.
179
+ // unlink removes the link itself on every platform — libuv unlinks a junction
180
+ // too — and refuses a real directory outright, so it can never delete contents.
181
+ // The win32 rmdir fallback covers a reparse point unlink will not take.
182
+ function unlinkNodeModulesLink(linkPath, { platform = process.platform, fsImpl = fs } = {}) {
183
+ if (!fsImpl.lstatSync(linkPath).isSymbolicLink()) {
184
+ throw new Error(`${linkPath} is not a link — refusing to remove a real directory`);
185
+ }
186
+ try {
187
+ fsImpl.unlinkSync(linkPath);
188
+ } catch (e) {
189
+ if (platform !== 'win32' || (e.code !== 'EPERM' && e.code !== 'EISDIR')) throw e;
190
+ fsImpl.rmdirSync(linkPath); // on a junction, rmdir removes the LINK only
191
+ }
192
+ }
193
+
176
194
  function resolveWorktreePath(root, nameOrPath) {
177
195
  const all = listWorktrees(root);
178
196
  const norm = (p) => path.resolve(p).replace(/\\/g, '/').toLowerCase();
@@ -296,8 +314,8 @@ function cmdRemove(root, argv) {
296
314
  const state = nodeModulesState(wtPath);
297
315
  for (const step of removalPlan({ nodeModulesState: state })) {
298
316
  if (step === 'unlink-junction') {
299
- // rmdir on a junction removes the LINK only, never the target's contents.
300
- fs.rmdirSync(path.join(wtPath, 'node_modules'));
317
+ // Removes the LINK only, never the target's contents — junction or symlink.
318
+ unlinkNodeModulesLink(path.join(wtPath, 'node_modules'));
301
319
  console.log(' removed node_modules junction (link only — the shared node_modules is untouched)');
302
320
  } else {
303
321
  const args = ['worktree', 'remove', ...(hasFlag('--force', argv) ? ['--force'] : []), wtPath];
@@ -427,4 +445,5 @@ if (require.main === module) main();
427
445
 
428
446
  module.exports = {
429
447
  worktreeNameFor, needsJunction, removalPlan, nodeModulesState, shadowingNodeModules, huskDisposition,
448
+ unlinkNodeModulesLink,
430
449
  };
package/src/module-api.js CHANGED
@@ -75,7 +75,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
75
75
  // MAJOR (see allowBoxScope below): passes the request through untouched.
76
76
  function deprecatedNoopMiddleware(_req, _res, next) { next(); }
77
77
 
78
- const CORE_VERSION = '1.21.100'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
78
+ const CORE_VERSION = '1.21.102'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
79
79
 
80
80
  // A namespaced logger so a module's log lines are attributable + consistent.
81
81
  // Usage: const log = api.logger('discord'); log.info('mounted');
@@ -28,6 +28,56 @@ const here = dirname(fileURLToPath(import.meta.url));
28
28
  const MIG_DIR = join(here, '..', 'modules', 'platform-identity', 'migrations');
29
29
  const migrationSql = (f) => readFileSync(join(MIG_DIR, f), 'utf8');
30
30
 
31
+ // The migrations this domain reads, all idempotent: accounts + consent,
32
+ // handles, privacy, terms, connections (addressing), guilds, and the stored
33
+ // guild totals getPublicGuild joins (027 — missing until task 1004588, so a
34
+ // fresh Postgres died on "relation platform_identity_guild_totals does not
35
+ // exist" before most assertions ran). 030 (engagements) is not read by guilds.js.
36
+ const SETUP_MIGRATIONS = [
37
+ 'platform_identity_001_tables.sql', 'platform_identity_007_profile_consent.sql',
38
+ 'platform_identity_008_handle.sql', 'platform_identity_012_account_privacy.sql',
39
+ 'platform_identity_015_connections.sql', 'platform_identity_019_terms_acceptance.sql',
40
+ 'platform_identity_025_guilds.sql', 'platform_identity_027_guild_totals.sql',
41
+ ];
42
+
43
+ // DB-FREE GUARD, run before the Postgres probe so it holds even where this file
44
+ // skips (task 1004588). Every platform_identity_ table guilds.js reads or
45
+ // writes — itself, plus the bodies of the functions it imports from ./guild-*
46
+ // siblings — must be CREATEd by a setup migration; otherwise a fresh DB fails
47
+ // mid-run, and a shared dev DB that already has the table hides it. Scoped to
48
+ // the imported FUNCTIONS on purpose: guild-totals.js also holds the poller,
49
+ // whose tables this test never exercises. A scan that finds nothing fails.
50
+ {
51
+ const MOD_DIR = join(here, '..', 'modules', 'platform-identity');
52
+ const guildsSrc = readFileSync(join(MOD_DIR, 'guilds.js'), 'utf8');
53
+ const sources = [['guilds.js', guildsSrc]];
54
+ const reqRe = /const\s*\{([^}]+)\}\s*=\s*require\(\s*'\.\/(guild-[a-z-]+)(?:\.js)?'\s*\)/g;
55
+ for (const [, names, file] of guildsSrc.matchAll(reqRe)) {
56
+ const src = readFileSync(join(MOD_DIR, `${file}.js`), 'utf8');
57
+ for (const name of names.split(',').map((n) => n.trim()).filter(Boolean)) {
58
+ const start = src.search(new RegExp(`\\n(?:async\\s+)?function\\s+${name}\\b`));
59
+ assert.ok(start >= 0, `guard scan found function ${name} in ${file}.js`);
60
+ const rest = src.slice(start + 1);
61
+ const end = rest.search(/\n(?:async\s+)?function\s|\nmodule\.exports/);
62
+ sources.push([`${file}.js:${name}`, end < 0 ? rest : rest.slice(0, end)]);
63
+ }
64
+ }
65
+ const used = new Set();
66
+ const useRe = /\b(?:FROM|JOIN|INTO|UPDATE)\s+(platform_identity_[a-z0-9_]+)/gi;
67
+ for (const [, src] of sources) for (const m of src.matchAll(useRe)) used.add(m[1]);
68
+ const created = new Set();
69
+ const createRe = /CREATE TABLE IF NOT EXISTS\s+(platform_identity_[a-z0-9_]+)/gi;
70
+ for (const f of SETUP_MIGRATIONS) for (const m of migrationSql(f).matchAll(createRe)) created.add(m[1]);
71
+ const scanned = sources.map(([n]) => n);
72
+ assert.ok(scanned.includes('guild-totals.js:readGuildTotals'), `guard scan followed guilds.js's imports: ${scanned.join(', ')}`);
73
+ for (const t of ['platform_identity_guilds', 'platform_identity_guild_totals']) {
74
+ assert.ok(used.has(t), `guard scan saw ${t} (found: ${[...used].join(', ')})`);
75
+ }
76
+ const missing = [...used].filter((t) => !created.has(t));
77
+ assert.deepEqual(missing, [], `SETUP_MIGRATIONS creates no table for: ${missing.join(', ')}`);
78
+ console.log(` ok setup migrations create every table guilds.js uses (${used.size} tables; ${scanned.join(' + ')})`);
79
+ }
80
+
31
81
  const pool = new Pool({ connectionString: process.env.DATABASE_URL || undefined });
32
82
  try {
33
83
  await pool.query('SELECT 1');
@@ -74,14 +124,8 @@ const openRows = async (slug, gid) => (await q(
74
124
  const pendingId = async (slug, gid) => (await openRows(slug, gid)).find((r) => r.status === 'pending')?.id;
75
125
 
76
126
  try {
77
- // The migrations this domain reads, all idempotent: accounts + consent,
78
- // handles, privacy, terms, connections (addressing), and guilds.
79
- for (const f of [
80
- 'platform_identity_001_tables.sql', 'platform_identity_007_profile_consent.sql',
81
- 'platform_identity_008_handle.sql', 'platform_identity_012_account_privacy.sql',
82
- 'platform_identity_015_connections.sql', 'platform_identity_019_terms_acceptance.sql',
83
- 'platform_identity_025_guilds.sql',
84
- ]) await q(migrationSql(f));
127
+ // The migrations this domain reads — see SETUP_MIGRATIONS above.
128
+ for (const f of SETUP_MIGRATIONS) await q(migrationSql(f));
85
129
  await q(migrationSql('platform_identity_025_guilds.sql')); // re-runnable
86
130
  await cleanup();
87
131
 
@@ -27,7 +27,7 @@ import os from 'node:os';
27
27
  import path from 'node:path';
28
28
 
29
29
  const require = createRequire(import.meta.url);
30
- const { worktreeNameFor, needsJunction, removalPlan, nodeModulesState, shadowingNodeModules, huskDisposition } = require('../scripts/gds/worktree.js');
30
+ const { worktreeNameFor, needsJunction, removalPlan, nodeModulesState, shadowingNodeModules, huskDisposition, unlinkNodeModulesLink } = require('../scripts/gds/worktree.js');
31
31
 
32
32
  const HOUR = 60 * 60 * 1000;
33
33
 
@@ -118,16 +118,75 @@ test('nodeModulesState: absent / real-dir / junction classified from disk', () =
118
118
  fs.symlinkSync(target, link, 'junction');
119
119
  eq(nodeModulesState(wtJct), 'junction');
120
120
 
121
- // Removing the link (the helper's unlink step) must not touch the target.
122
- // win32 junctions need rmdir; a posix dir-symlink needs unlink — cover both
123
- // so this file passes on Linux CI and the real Windows box alike.
124
- try { fs.rmdirSync(link); } catch (_) { fs.unlinkSync(link); }
121
+ // Removing the link (the helper's own unlink step) must not touch the target.
122
+ // On Linux CI this is a plain dir-symlink — the case rmdir crashed on with
123
+ // ENOTDIR (task 1004582); on Windows it is a real junction.
124
+ unlinkNodeModulesLink(link);
125
+ eq(fs.existsSync(link), false, 'the link itself is gone');
125
126
  eq(fs.existsSync(path.join(target, 'canary.txt')), true, 'target survives link removal');
126
127
  } finally {
127
128
  fs.rmSync(base, { recursive: true, force: true });
128
129
  }
129
130
  });
130
131
 
132
+ // The win32 rmdir fallback, driven through a fake fs so every platform runs it.
133
+ function fakeFs(unlinkCode) {
134
+ const calls = [];
135
+ return {
136
+ calls,
137
+ lstatSync: () => ({ isSymbolicLink: () => true }),
138
+ unlinkSync: (p) => {
139
+ calls.push(['unlink', p]);
140
+ if (unlinkCode) { const e = new Error(unlinkCode); e.code = unlinkCode; throw e; }
141
+ },
142
+ rmdirSync: (p) => { calls.push(['rmdir', p]); },
143
+ };
144
+ }
145
+
146
+ test('unlinkNodeModulesLink: unlink succeeds → rmdir is never called', () => {
147
+ const f = fakeFs(null);
148
+ unlinkNodeModulesLink('L', { platform: 'linux', fsImpl: f });
149
+ eqArr(f.calls, [['unlink', 'L']]);
150
+ });
151
+
152
+ for (const code of ['EPERM', 'EISDIR']) {
153
+ test(`unlinkNodeModulesLink: win32 + ${code} falls back to rmdir`, () => {
154
+ const f = fakeFs(code);
155
+ unlinkNodeModulesLink('L', { platform: 'win32', fsImpl: f });
156
+ eqArr(f.calls, [['unlink', 'L'], ['rmdir', 'L']]);
157
+ });
158
+ test(`unlinkNodeModulesLink: non-win32 + ${code} rethrows, no rmdir`, () => {
159
+ const f = fakeFs(code);
160
+ let thrown = null;
161
+ try { unlinkNodeModulesLink('L', { platform: 'linux', fsImpl: f }); } catch (e) { thrown = e.code; }
162
+ eq(thrown, code);
163
+ eqArr(f.calls, [['unlink', 'L']]);
164
+ });
165
+ }
166
+
167
+ test('unlinkNodeModulesLink: win32 + any other code rethrows, no rmdir', () => {
168
+ const f = fakeFs('EBUSY');
169
+ let thrown = null;
170
+ try { unlinkNodeModulesLink('L', { platform: 'win32', fsImpl: f }); } catch (e) { thrown = e.code; }
171
+ eq(thrown, 'EBUSY');
172
+ eqArr(f.calls, [['unlink', 'L']]);
173
+ });
174
+
175
+ test('unlinkNodeModulesLink refuses a REAL directory and leaves it intact', () => {
176
+ const base = fs.mkdtempSync(path.join(os.tmpdir(), 'wt-helper-test-'));
177
+ try {
178
+ const real = path.join(base, 'node_modules');
179
+ fs.mkdirSync(real);
180
+ fs.writeFileSync(path.join(real, 'canary.txt'), 'x');
181
+ let threw = false;
182
+ try { unlinkNodeModulesLink(real); } catch (_) { threw = true; }
183
+ eq(threw, true, 'a real dir is refused');
184
+ eq(fs.existsSync(path.join(real, 'canary.txt')), true, 'its contents survive');
185
+ } finally {
186
+ fs.rmSync(base, { recursive: true, force: true });
187
+ }
188
+ });
189
+
131
190
  // ---- 3. removalPlan — junction-first ordering is the whole point ----
132
191
 
133
192
  test('junction node_modules: unlink FIRST, then git worktree remove', () => {