@bongos/core 1.19.754 → 1.19.755

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,22 +2,22 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.19.754",
6
- "core_contract": "1.19.754",
7
- "source_commit": "8fd1dd9b3ddbc313d9e3bb09ba2a339cb9793e7f",
5
+ "core_version": "1.19.755",
6
+ "core_contract": "1.19.755",
7
+ "source_commit": "1cc8ad5ec0be2921b0e5cf12464689c86b5c36a6",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-16T00:24:38.617Z",
9
+ "built_at": "2026-09-16T02:11:51.276Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 483,
13
13
  "agent_docs_stubbed": 25,
14
- "functional_verbatim": 2111,
14
+ "functional_verbatim": 2112,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 2619,
20
- "tree_sha256": "a2f5f1ae36d83e33d7e61bab1264bf62287f10d175f2c9e1f3cabd695bc2b757",
19
+ "file_count": 2620,
20
+ "tree_sha256": "4960c3809b0d1bd4756c52793ef2167cfc1b70844cd2250f3e35723cd275bc50",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -2512,7 +2512,7 @@
2512
2512
  {
2513
2513
  "path": "docs/module-api-changelog.md",
2514
2514
  "mode": "0000644",
2515
- "sha256": "f367478f9031a67c77c1b43521aa215ea65068e9ad1b6b21540c81abd8a22dde"
2515
+ "sha256": "744adc56bff99972f3104823c66db347b9aaef9aee42d06f12bff8da8e559a7d"
2516
2516
  },
2517
2517
  {
2518
2518
  "path": "docs/modules-contract.md",
@@ -7567,12 +7567,12 @@
7567
7567
  {
7568
7568
  "path": "package-lock.json",
7569
7569
  "mode": "0000644",
7570
- "sha256": "0b25947dcf9f2906d39aec4e4ad10e63dc42831cf593c5404ba77dc51c2be946"
7570
+ "sha256": "d2721993cfbc4a656aab1c93434a55dd7a440a3bb63048893ed6b43c2b1bbeec"
7571
7571
  },
7572
7572
  {
7573
7573
  "path": "package.json",
7574
7574
  "mode": "0000644",
7575
- "sha256": "f340a8707e0abe69d0e7d1028a0a96675b66099ce87af7932004393e02dc2a19"
7575
+ "sha256": "23815bafaed5c20104d344967253d871d91b0df5524e5fba825119a316a0784a"
7576
7576
  },
7577
7577
  {
7578
7578
  "path": "public-docs/index.html",
@@ -9342,7 +9342,7 @@
9342
9342
  {
9343
9343
  "path": "src/bongos/serve-internal.js",
9344
9344
  "mode": "0000644",
9345
- "sha256": "61b13299ffdd7f036d5bc3d364135bd8ca4d3470223001b8ff6cc45d858d6107"
9345
+ "sha256": "9edf07966de8d137ba6d987557bb4a9fefeedd19e640f10302dbfd340e0343f9"
9346
9346
  },
9347
9347
  {
9348
9348
  "path": "src/bongos/uptime-poller.js",
@@ -9372,7 +9372,7 @@
9372
9372
  {
9373
9373
  "path": "src/module-api.js",
9374
9374
  "mode": "0000644",
9375
- "sha256": "df078d4bd7a6e0fda01ce242821141c66b6dc0f2d07ebfaf8de1a89ae279499e"
9375
+ "sha256": "690fb3d76f0d06fcc41490f58b4a273c42ecfd65c8e9a2acb7cff0e85885f5a4"
9376
9376
  },
9377
9377
  {
9378
9378
  "path": "src/module-loader/catalog.js",
@@ -11619,6 +11619,11 @@
11619
11619
  "mode": "0000644",
11620
11620
  "sha256": "40c396fda3a618bf9fa2d2ae3d8d30b50e62c4210199d70b5e29cb82a046d037"
11621
11621
  },
11622
+ {
11623
+ "path": "tests/module_asset_subdomain.mjs",
11624
+ "mode": "0000644",
11625
+ "sha256": "420e17095b054f87be386913077966d456ae687b030b476e1bc33b15ae35ed9f"
11626
+ },
11622
11627
  {
11623
11628
  "path": "tests/module_catalog.mjs",
11624
11629
  "mode": "0000644",
@@ -1982,5 +1982,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
1982
1982
  landed since 1.19.752 with no explicit bump. run 35036151976. (task 1002620)
1983
1983
  1.19.754 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1984
1984
  landed since 1.19.753 with no explicit bump. run 35039836268. (task 1002620)
1985
+ 1.19.755 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1986
+ landed since 1.19.754 with no explicit bump. run 35047010813. (task 1002620)
1985
1987
  ---------------------------------------------------------------------------
1986
1988
  ```
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.754",
3
+ "version": "1.19.755",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.754",
9
+ "version": "1.19.755",
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.754",
3
+ "version": "1.19.755",
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",
@@ -826,6 +826,20 @@ function mountInternalSurfaces(app) {
826
826
  app.use((req, res, next) => {
827
827
  if (!isBuildersHost(req)) return next();
828
828
  if (req.path.startsWith('/api/') || req.path === '/healthz') return next();
829
+ // Module browser assets are addressed at the ROOT on every host — the URL the
830
+ // loader's hallWidgetScripts projection emits is an absolute /modules/<key>/…
831
+ // and injectModuleHallWidgets puts that same tag in the hall index on BOTH
832
+ // hosts (task 1002848). Without this line the shim rewrites it to
833
+ // /builders/modules/… before the MODULE_ASSET_RE handler below can match, so
834
+ // the tag is injected on the subdomain and the asset 404s there — injection
835
+ // works on both hosts, serving works only on the apex. The handler keeps all
836
+ // its own defences (the isModuleEnabled gate, the traversal check); this only
837
+ // lets the request reach it.
838
+ //
839
+ // The general hazard: ANY apex-rooted route registered BELOW this shim is
840
+ // eaten the same way. Today that is this one and the /docs family (idea 529
841
+ // → task 1002861); /downloads/* dodges it by being registered ABOVE instead.
842
+ if (req.path.startsWith('/modules/')) return next();
829
843
  if (req.path === '/builders' || req.path.startsWith('/builders/')) return next();
830
844
  // Prefix with /builders so the handlers below match. req.url carries the
831
845
  // query string; preserve it. '/' → '/builders/', '/watch' → '/builders/watch'.
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.754'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
74
+ const CORE_VERSION = '1.19.755'; // 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');
@@ -0,0 +1,155 @@
1
+ // tests/module_asset_subdomain.mjs — module browser assets must route identically
2
+ // on the apex and on builders.<apex> (task 1002848).
3
+ //
4
+ // THE BUG. A module-contributed hall widget's <script src="/modules/<key>/public/
5
+ // hall-widget.js"> is injected into the hall index on BOTH hosts by
6
+ // injectModuleHallWidgets, but only SERVED on the apex. The builders-subdomain
7
+ // rewrite shim in serve-internal.js runs as app.use middleware, ahead of the
8
+ // MODULE_ASSET_RE route, and rewrites every path on that host onto the /builders/*
9
+ // tree — exempting only /api/, /healthz and paths already under /builders. So
10
+ // /modules/demo/public/hall-widget.js became /builders/modules/... , stopped
11
+ // matching ^/modules/, and fell through to a 404.
12
+ //
13
+ // WHY THIS TEST HAS TO EXIST. The seam is a live no-op today: no module ships a
14
+ // hall-widget.js, so nothing on either host currently requests one, and a
15
+ // regression here would be completely invisible until the first module that does
16
+ // ship one — at which point it would look like a bug in that module. The test
17
+ // therefore drives a module asset that DOES exist (hall-ui's own public/ dir) and
18
+ // asserts the two hosts agree, which is the property the fix is really about.
19
+ //
20
+ // serve-internal is DB-free (the pg pool connects lazily), so we mount it on a
21
+ // throwaway express app and hit it over loopback — same pattern as
22
+ // tests/docs_openapi_route.mjs.
23
+ //
24
+ // USE node:http, NOT fetch. `Host` is a forbidden header name in the fetch spec,
25
+ // so `fetch(url, { headers: { host } })` SILENTLY drops the override and sends the
26
+ // real one (127.0.0.1:<port>). A host-routing test written with fetch therefore
27
+ // compares the apex against itself, passes, and keeps passing with the fix
28
+ // reverted — which is exactly what the first draft of this file did until the
29
+ // mutation probe caught it. node:http honours an explicit Host in options.headers.
30
+ //
31
+ // Run: node --test tests/module_asset_subdomain.mjs
32
+
33
+ import { test } from 'node:test';
34
+ import assert from 'node:assert/strict';
35
+ import { createRequire } from 'node:module';
36
+ import http from 'node:http';
37
+
38
+ const require = createRequire(import.meta.url);
39
+
40
+ // Any host starting with "builders." is the subdomain; anything else is the apex.
41
+ const APEX = 'example.com';
42
+ const SUBDOMAIN = 'builders.example.com';
43
+
44
+ // A file the hall-ui module really ships, so a 200 proves the request reached the
45
+ // MODULE_ASSET_RE handler AND the handler found bytes — not merely that it matched.
46
+ const REAL_ASSET = '/modules/hall-ui/public/atlas.css';
47
+
48
+ async function withServer(fn) {
49
+ const express = require('express');
50
+ const { mountInternalSurfaces } = require('../src/bongos/serve-internal.js');
51
+ const app = express();
52
+ mountInternalSurfaces(app);
53
+ const server = await new Promise((resolve) => {
54
+ const s = app.listen(0, () => resolve(s));
55
+ });
56
+ try {
57
+ await fn(server.address().port);
58
+ } finally {
59
+ await new Promise((r) => server.close(r));
60
+ }
61
+ }
62
+
63
+ // One loopback request with a REAL Host header. Never follows redirects — a 301 is
64
+ // itself a routing decision we want to see.
65
+ function get(port, path, host) {
66
+ return new Promise((resolve, reject) => {
67
+ const req = http.request({ host: '127.0.0.1', port, path, headers: { Host: host } }, (res) => {
68
+ let body = '';
69
+ res.setEncoding('utf8');
70
+ res.on('data', (c) => { body += c; });
71
+ res.on('end', () => resolve({ status: res.statusCode, body, headers: res.headers }));
72
+ });
73
+ req.on('error', reject);
74
+ req.end();
75
+ });
76
+ }
77
+
78
+ // The harness is only meaningful if the Host header actually reaches the app, and
79
+ // the fetch trap above proves that is not self-evident. Assert it directly, so a
80
+ // future change that breaks host propagation fails HERE with a clear reason rather
81
+ // than turning every test below into a silent apex-vs-apex tautology.
82
+ test('the harness really varies the Host — without this the rest is vacuous', async () => {
83
+ await withServer(async (port) => {
84
+ const { isBuildersHost } = require('../src/bongos/serve-internal.js');
85
+ assert.equal(isBuildersHost({ headers: { host: SUBDOMAIN } }), true);
86
+ assert.equal(isBuildersHost({ headers: { host: APEX } }), false);
87
+ // and end-to-end: both hosts 301 /builders/, but to DIFFERENT places — the apex
88
+ // sends you to the subdomain, the subdomain strips the redundant prefix. Equal
89
+ // targets would mean the Host never reached the app.
90
+ const sub = await get(port, '/builders/', SUBDOMAIN);
91
+ const apex = await get(port, '/builders/', APEX);
92
+ assert.notEqual(sub.headers.location, apex.headers.location,
93
+ 'the two hosts must route differently, or the Host header is being dropped');
94
+ });
95
+ });
96
+
97
+ test('a real module asset serves on the apex AND on builders.<apex>', async () => {
98
+ await withServer(async (port) => {
99
+ const apex = await get(port, REAL_ASSET, APEX);
100
+ const sub = await get(port, REAL_ASSET, SUBDOMAIN);
101
+ assert.equal(apex.status, 200, 'precondition: the asset serves on the apex');
102
+ assert.equal(sub.status, 200,
103
+ 'the subdomain must serve it too — the rewrite shim used to turn this into /builders/modules/... and 404');
104
+ });
105
+ });
106
+
107
+ test('the two hosts return the same body, not just the same status', async () => {
108
+ await withServer(async (port) => {
109
+ const apex = await get(port, REAL_ASSET, APEX);
110
+ const sub = await get(port, REAL_ASSET, SUBDOMAIN);
111
+ assert.ok(apex.body.length > 0, 'precondition: the asset is not empty');
112
+ assert.equal(sub.body, apex.body, 'the subdomain must serve the same bytes, not a rewritten page');
113
+ });
114
+ });
115
+
116
+ test('an unknown module 404s on BOTH hosts — the exemption grants no new reach', async () => {
117
+ await withServer(async (port) => {
118
+ const p = '/modules/no-such-module/public/x.css';
119
+ assert.equal((await get(port, p, APEX)).status, 404);
120
+ assert.equal((await get(port, p, SUBDOMAIN)).status, 404,
121
+ 'the shim exemption must not turn an unknown module into anything but a 404');
122
+ });
123
+ });
124
+
125
+ test('a missing file inside a REAL module 404s on both hosts', async () => {
126
+ await withServer(async (port) => {
127
+ const p = '/modules/hall-ui/public/definitely-not-here.css';
128
+ assert.equal((await get(port, p, APEX)).status, 404);
129
+ assert.equal((await get(port, p, SUBDOMAIN)).status, 404);
130
+ });
131
+ });
132
+
133
+ test('path traversal out of the module dir is refused on the subdomain too', async () => {
134
+ await withServer(async (port) => {
135
+ // The handler normalizes and rejects a rel path escaping modules/<key>/public.
136
+ // Worth pinning on the SUBDOMAIN specifically: that request now reaches the
137
+ // handler where it previously never did, so this is newly-exercised ground.
138
+ for (const p of ['/modules/hall-ui/public/../../../package.json',
139
+ '/modules/hall-ui/public/..%2f..%2f..%2fpackage.json']) {
140
+ const r = await get(port, p, SUBDOMAIN);
141
+ assert.notEqual(r.status, 200, `traversal must not serve a file outside the module dir (${p})`);
142
+ assert.ok(!/"name":\s*"@bongos/.test(r.body), 'package.json bytes must never appear in the body');
143
+ }
144
+ });
145
+ });
146
+
147
+ test('the exemption is scoped to /modules/ — a lookalike path is still rewritten', async () => {
148
+ await withServer(async (port) => {
149
+ // If the exemption had been written as a bare `includes('modules')` or without
150
+ // the trailing slash, this path would escape the shim too.
151
+ const r = await get(port, '/moduleslike', SUBDOMAIN);
152
+ assert.notEqual(r.status, 200,
153
+ 'a path merely starting with "modules" but outside /modules/ is not a module asset');
154
+ });
155
+ });