@bongos/core 1.19.667 → 1.19.668

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.667",
6
- "core_contract": "1.19.667",
7
- "source_commit": "fde10b200283c5047be40d5fc05a7489f3d5b73c",
5
+ "core_version": "1.19.668",
6
+ "core_contract": "1.19.668",
7
+ "source_commit": "7ef8e22537f40e46f6f068d2479fbbb853f91ac6",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-11T07:58:55.399Z",
9
+ "built_at": "2026-09-11T15:53:04.954Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 473,
@@ -17,7 +17,7 @@
17
17
  "gate": "passed"
18
18
  },
19
19
  "file_count": 2614,
20
- "tree_sha256": "cbb2d92ddbc241d615a5a7b105b021310b739c85b8709fd9479d256ceba573e5",
20
+ "tree_sha256": "f753736a9456c435a166c5d07d41ab343a26cf1d0ccbd1ddf50fe23325cb3bd3",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -2787,7 +2787,7 @@
2787
2787
  {
2788
2788
  "path": "docs/module-api-changelog.md",
2789
2789
  "mode": "0000644",
2790
- "sha256": "65505f4ffa0193c1827059d16ce364257fdfdc076f5585544e1d037f2bda40a8"
2790
+ "sha256": "6eba5a3ec525e945d04f4665e23d6b3731df16547f7b51593c589ff2569c682c"
2791
2791
  },
2792
2792
  {
2793
2793
  "path": "docs/modules-contract.md",
@@ -7757,12 +7757,12 @@
7757
7757
  {
7758
7758
  "path": "package-lock.json",
7759
7759
  "mode": "0000644",
7760
- "sha256": "e6a26c726108246ac30f4250a93e1fd7965dba6bd5dfcd950b849357634ddb02"
7760
+ "sha256": "2b109c02111e9aff72bc2a9b6a1a85805c7544cca1e68399ceaf5f8de8df0f47"
7761
7761
  },
7762
7762
  {
7763
7763
  "path": "package.json",
7764
7764
  "mode": "0000644",
7765
- "sha256": "30fa12bd80546328d075b3d2b251c528c59ec033b486a6084bcca3b3d5c6f703"
7765
+ "sha256": "41264c98f8188d3443d6195d223de91f9eeed86ce21ed880e0b0a138e72669ff"
7766
7766
  },
7767
7767
  {
7768
7768
  "path": "public-docs/index.html",
@@ -9357,7 +9357,7 @@
9357
9357
  {
9358
9358
  "path": "src/bongos/platform-visibility-gate.js",
9359
9359
  "mode": "0000644",
9360
- "sha256": "eea15ebe44e9450f2d62040c239828008e8953ea5ee465085d5f0e8e63fe6233"
9360
+ "sha256": "b09f1c0bcbb066ae80cbeebad730db8a8665dd077c3f2026c3b3d78298fdd866"
9361
9361
  },
9362
9362
  {
9363
9363
  "path": "src/bongos/pool.js",
@@ -9522,7 +9522,7 @@
9522
9522
  {
9523
9523
  "path": "src/module-api.js",
9524
9524
  "mode": "0000644",
9525
- "sha256": "c0e7feb5829d3c421b86a6a9a6465b05f495e66679c91688a41d0eb23d1c3207"
9525
+ "sha256": "43b74af9de322914ac0f43a8cd4adacc1de90fb874692744450ff18beeaa67e7"
9526
9526
  },
9527
9527
  {
9528
9528
  "path": "src/module-loader/catalog.js",
@@ -11867,7 +11867,7 @@
11867
11867
  {
11868
11868
  "path": "tests/platform_visibility_gate.mjs",
11869
11869
  "mode": "0000644",
11870
- "sha256": "2dc7a56caeababd5bf249e968e4353f8c738aef459947fde15c7c511640c13aa"
11870
+ "sha256": "7a4a3677e25bd55a6ebb64130f724f030f0b57d0ccc07172de3860cbeb4a66d7"
11871
11871
  },
11872
11872
  {
11873
11873
  "path": "tests/post_login_landing.mjs",
@@ -1793,5 +1793,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
1793
1793
  landed since 1.19.665 with no explicit bump. run 34567142386. (task 1002620)
1794
1794
  1.19.667 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1795
1795
  landed since 1.19.666 with no explicit bump. run 34576878372. (task 1002620)
1796
+ 1.19.668 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1797
+ landed since 1.19.667 with no explicit bump. run 34618700494. (task 1002620)
1796
1798
  ---------------------------------------------------------------------------
1797
1799
  ```
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.667",
3
+ "version": "1.19.668",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.667",
9
+ "version": "1.19.668",
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.667",
3
+ "version": "1.19.668",
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",
@@ -70,6 +70,18 @@ const API = `(?:${ALL_API_PREFIXES.map(escRe).join('|')})`;
70
70
  // /__prelaunch the pre-launch gate's own submit, when both gates are on.
71
71
  // /__stealth is its pre-rename alias (task 1003152) and is
72
72
  // exempt for as long as the gate still accepts it.
73
+ // <api>/access-requests POST ONLY — a stranger's application to a project that is
74
+ // BOTH gated and "apply to join" (task 1003624). The owner
75
+ // turned that door on deliberately; without this exemption the
76
+ // member door answers 401 first and the two settings compose
77
+ // into "nobody can apply", which nobody chose. The write is
78
+ // public on every other project and carries its own per-IP and
79
+ // per-login limits, and it GRANTS nothing — an application is a
80
+ // row in a queue the owner still reviews (ADR 0201). The bare
81
+ // GET on the same path is the owner's QUEUE (requireBuilder +
82
+ // access_request.review), so the exemption is scoped to the
83
+ // VERB and the queue keeps the member door ahead of its own
84
+ // rank check.
73
85
  const EXEMPT = [
74
86
  new RegExp(`^${API}/auth(/|$)`),
75
87
  new RegExp(`^${API}/instance$`),
@@ -81,6 +93,7 @@ const EXEMPT = [
81
93
  /^\/__prelaunch$/,
82
94
  /^\/__stealth$/,
83
95
  /^\/downloads\//,
96
+ { re: new RegExp(`^${API}/access-requests$`), methods: ['POST'] },
84
97
  ];
85
98
 
86
99
  // The policy, read through the memoized pack. A pack that cannot be read at all
@@ -106,9 +119,16 @@ function normalizePath(p) {
106
119
  return s;
107
120
  }
108
121
 
109
- function isExempt(path) {
122
+ // `method` is OPTIONAL, and each entry decides whether it matters: a bare RegExp
123
+ // exempts the path whatever the verb, while a method-scoped entry matches only the
124
+ // verbs it names. A caller that passes no method therefore gets the fail-CLOSED
125
+ // answer for the scoped entries (the static path checks in tests/version_literals.mjs
126
+ // and tests/prelaunch_gate.mjs call it that way) — never an accidental exemption of a
127
+ // sibling route that shares a path with an exempt one.
128
+ function isExempt(path, method) {
110
129
  const p = normalizePath(path);
111
- return EXEMPT.some((re) => re.test(p));
130
+ const m = String(method || '').toUpperCase();
131
+ return EXEMPT.some((e) => (e instanceof RegExp ? e.test(p) : e.re.test(p) && e.methods.includes(m)));
112
132
  }
113
133
 
114
134
  function isApiPath(path) { return /^\/api\//i.test(String(path || '')); }
@@ -233,7 +253,7 @@ function platformVisibilityGate(deps = {}) {
233
253
  return function platformVisibility(req, res, next) {
234
254
  if (!gated()) return next();
235
255
  if (req.method === 'OPTIONS') return next(); // a CORS preflight carries no identity
236
- if (isExempt(req.path)) return next();
256
+ if (isExempt(req.path, req.method)) return next();
237
257
  // Async middleware must own its rejection (Express 4 drops the promise) —
238
258
  // and it FAILS CLOSED: if the session cannot be resolved, the caller's
239
259
  // identity is unknown, and admitting is never an option (auth.js's rule).
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.667'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
74
+ const CORE_VERSION = '1.19.668'; // 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');
@@ -171,9 +171,38 @@ test('the exempt list: the door, the manifest, the probes, the downloads — on
171
171
  '/api/bongos/public/progress', '/api/gds/public/leaderboard', '/api/bongos/instances', '/api/gds/authors',
172
172
  '/api/bongos/v1/public/progress', '/api/gds/instance/anything', '/instance', '/healthzz', '/downloads',
173
173
  '/api/other/instance', '/api/bongos/provisioning/instances',
174
+ // the apply door is METHOD-scoped, so the bare path — no verb — is not exempt
175
+ '/api/gds/access-requests', '/api/gds/access-requests/status',
174
176
  ]) assert.equal(isExempt(p), false, `${p} must be gated`);
175
177
  });
176
178
 
179
+ // task 1003624. A project can be BOTH gated and "apply to join"; before this the
180
+ // member door answered 401 ahead of the apply route, so the two settings the owner
181
+ // set composed into "nobody can apply". The exemption is the narrowest thing that
182
+ // fixes it: one path, one verb.
183
+ test('the apply door: POST <api>/access-requests answers a stranger — and only that verb, only that path', () => {
184
+ for (const p of [
185
+ '/api/gds/access-requests', '/api/bongos/access-requests', '/api/bongos/v1/access-requests',
186
+ // the router's own tolerances, the same ones the rest of the list is held to
187
+ '/API/BONGOS/ACCESS-REQUESTS', '/api/gds/access-requests/',
188
+ ]) assert.equal(isExempt(p, 'POST'), true, `POST ${p} must reach the apply route`);
189
+
190
+ // The bare GET on that path is the OWNER'S QUEUE (requireBuilder +
191
+ // access_request.review). It has its own rank check, and the member door stays
192
+ // in front of it regardless — an exemption that ignored the verb would have
193
+ // silently removed a layer from the queue that lists would-be builders by name.
194
+ for (const m of ['GET', 'HEAD', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', '']) {
195
+ assert.equal(isExempt('/api/gds/access-requests', m), false, `${m || '(no verb)'} on the queue must stay gated`);
196
+ }
197
+ // and the exemption does not spread down the path
198
+ for (const p of ['/api/gds/access-requests/status', '/api/gds/access-requests/invite', '/api/gds/access-requests/1']) {
199
+ assert.equal(isExempt(p, 'POST'), false, `POST ${p} must stay gated`);
200
+ }
201
+ // a method-BLIND entry is unchanged by the new shape: it still exempts any verb
202
+ assert.equal(isExempt('/healthz', 'POST'), true, 'a bare-RegExp entry ignores the verb');
203
+ assert.equal(isExempt('/api/gds/instance', 'GET'), true);
204
+ });
205
+
177
206
  test('gated mode: an exempt path is never looked up, and a CORS preflight passes', async () => {
178
207
  let looked = 0;
179
208
  const mw = platformVisibilityGate({ isGated: () => true, resolveSession: async () => { looked += 1; return null; } });
@@ -272,6 +301,29 @@ test('booted gated: the door, the manifest, the probes and the downloads still a
272
301
  assert.doesNotMatch(dl.body, /Sign in to continue/);
273
302
  });
274
303
 
304
+ // task 1003624, end to end on the booted app. The pack at the top of this file is
305
+ // the composition that was broken: platformVisibility 'gated' AND joinability
306
+ // 'apply'. The stranger's application must now reach the apply route; the owner's
307
+ // queue on the same path must not.
308
+ test('booted gated + apply: the application reaches the door it was refused at, and the owner queue does not', async () => {
309
+ for (const p of ['/api/gds/access-requests', '/api/bongos/access-requests', '/api/bongos/v1/access-requests']) {
310
+ const res = await request(p, { method: 'POST' });
311
+ assert.doesNotMatch(res.body, /Sign in to continue/, `POST ${p}: never the HTML door`);
312
+ // The route is reached, so whatever it answers is ITS answer (a bodiless POST
313
+ // is refused by the join door or by validation — both ahead of any query).
314
+ // What must not come back is the member door's own refusal.
315
+ let j = null;
316
+ try { j = JSON.parse(res.body); } catch { /* not JSON — asserted below */ }
317
+ assert.ok(j && j.error, `POST ${p}: the API envelope, from the route`);
318
+ assert.notEqual(j.error.code, 'unauthenticated', `POST ${p}: the member door must not answer first`);
319
+
320
+ // the same path, the owner's verb: still the member door
321
+ const q = await request(p, { method: 'GET' });
322
+ assert.equal(q.status, 401, `GET ${p}: the owner queue stays behind the member door`);
323
+ assert.equal(JSON.parse(q.body).error.code, 'unauthenticated');
324
+ }
325
+ });
326
+
275
327
  test('booted gated: a bogus session cookie is not a member', async () => {
276
328
  // resolves to no session (or fails closed if there is no database here) —
277
329
  // either way the stranger stays outside