@bongos/core 1.19.98 → 1.19.99
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 +10 -10
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/module-api.js +3 -1
- package/src/platform-server.js +47 -1
- package/tests/public_landing_projects.mjs +59 -0
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.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.99",
|
|
6
|
+
"core_contract": "1.19.99",
|
|
7
|
+
"source_commit": "7ab95a1bcf1751dbc3f5890c41461dcad6826506",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-13T22:
|
|
9
|
+
"built_at": "2026-08-13T22:41:45.519Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 298,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
19
|
"file_count": 1549,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "37c2c9f1fb605b8e730ce3562abac5ed38699596c45f2113f0066464fb12b85f",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -4127,12 +4127,12 @@
|
|
|
4127
4127
|
{
|
|
4128
4128
|
"path": "package-lock.json",
|
|
4129
4129
|
"mode": "0000644",
|
|
4130
|
-
"sha256": "
|
|
4130
|
+
"sha256": "1df1aa5e45f918110c6adebcec118d46207c09bfadd5bf17ddc1b563cb67d8f7"
|
|
4131
4131
|
},
|
|
4132
4132
|
{
|
|
4133
4133
|
"path": "package.json",
|
|
4134
4134
|
"mode": "0000644",
|
|
4135
|
-
"sha256": "
|
|
4135
|
+
"sha256": "4cf100a9e30702e1e1bdcb42c3437e2ce4fe61795bca9c305ce08fb17c42438d"
|
|
4136
4136
|
},
|
|
4137
4137
|
{
|
|
4138
4138
|
"path": "public-docs/index.html",
|
|
@@ -5702,7 +5702,7 @@
|
|
|
5702
5702
|
{
|
|
5703
5703
|
"path": "src/module-api.js",
|
|
5704
5704
|
"mode": "0000644",
|
|
5705
|
-
"sha256": "
|
|
5705
|
+
"sha256": "896f7912e0981f27a671fb68d5cada8481f84e2cfc1025c3cb3c69eda95f7fe9"
|
|
5706
5706
|
},
|
|
5707
5707
|
{
|
|
5708
5708
|
"path": "src/module-loader/loader.js",
|
|
@@ -5737,7 +5737,7 @@
|
|
|
5737
5737
|
{
|
|
5738
5738
|
"path": "src/platform-server.js",
|
|
5739
5739
|
"mode": "0000644",
|
|
5740
|
-
"sha256": "
|
|
5740
|
+
"sha256": "6ffe23398bc452b4224e7c4aa49dbd3c4f61a61da0fd51d43bb6c5ce0bd51aa7"
|
|
5741
5741
|
},
|
|
5742
5742
|
{
|
|
5743
5743
|
"path": "src/ui/adapters/claude-design/index.js",
|
|
@@ -7247,7 +7247,7 @@
|
|
|
7247
7247
|
{
|
|
7248
7248
|
"path": "tests/public_landing_projects.mjs",
|
|
7249
7249
|
"mode": "0000644",
|
|
7250
|
-
"sha256": "
|
|
7250
|
+
"sha256": "e03bbf258cb4b8ffd3e242ab2ddd927d1f52706347d06b539a386ca502caf1e4"
|
|
7251
7251
|
},
|
|
7252
7252
|
{
|
|
7253
7253
|
"path": "tests/public_mirror_export.mjs",
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.99",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.99",
|
|
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.
|
|
3
|
+
"version": "1.19.99",
|
|
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",
|
package/src/module-api.js
CHANGED
|
@@ -623,8 +623,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
623
623
|
// landed since 1.19.96 with no explicit bump. run 31749223878. (task 1002620)
|
|
624
624
|
// 1.19.98 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
625
625
|
// landed since 1.19.97 with no explicit bump. run 31749841394. (task 1002620)
|
|
626
|
+
// 1.19.99 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
627
|
+
// landed since 1.19.98 with no explicit bump. run 31750941866. (task 1002620)
|
|
626
628
|
// ---------------------------------------------------------------------------
|
|
627
|
-
const CORE_VERSION = '1.19.
|
|
629
|
+
const CORE_VERSION = '1.19.99'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
628
630
|
|
|
629
631
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
630
632
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
package/src/platform-server.js
CHANGED
|
@@ -60,12 +60,58 @@ const HOST = process.env.HOST || '127.0.0.1';
|
|
|
60
60
|
// branding pack is injected once at boot (theme + window.__BRANDING__), exactly
|
|
61
61
|
// as the hall pages are, so the look + identity come from config, not hardcode.
|
|
62
62
|
const LANDING_DIR = path.join(__dirname, '..', 'public-landing');
|
|
63
|
+
// ── CONTENT-STAMPED STATIC ASSETS (task 1002766) ──
|
|
64
|
+
// The landing HTML is `cf-cache-status: DYNAMIC` and never cached, but the
|
|
65
|
+
// assets it references come back `Cache-Control: public, max-age=14400` — four
|
|
66
|
+
// hours, Cloudflare's default Browser Cache TTL, not a header this app sets. So
|
|
67
|
+
// after a deploy a RETURNING visitor gets brand-new markup styled by a
|
|
68
|
+
// stylesheet up to four hours old: markup whose components that stylesheet has
|
|
69
|
+
// never heard of. Measured live — a cached cosmos.css of 25,693 bytes with no
|
|
70
|
+
// `.shell{` against 42,610 fresh — and again when the project card shipped and
|
|
71
|
+
// rendered as an unstyled block at the foot of the page.
|
|
72
|
+
//
|
|
73
|
+
// A COLD visitor is unaffected, which is exactly what makes it easy to miss:
|
|
74
|
+
// whoever deploys usually reloads in the browser holding the stale copy.
|
|
75
|
+
//
|
|
76
|
+
// Fixed by changing the URL rather than by fighting the header: a content hash
|
|
77
|
+
// means a new core produces a new URL, so there is nothing to invalidate and no
|
|
78
|
+
// Cloudflare purge token to hold (task 1002766 weighed both). Same `?v=<sha8>`
|
|
79
|
+
// shape serve-internal.js already stamps onto the hall's own scripts.
|
|
80
|
+
//
|
|
81
|
+
// Stamped by RESOLUTION, not by a path allow-list: a URL is an asset if it
|
|
82
|
+
// actually names a file under public-landing/. Routes (`/projects`, `/create`)
|
|
83
|
+
// and API paths resolve to nothing and are left alone, so a new asset is
|
|
84
|
+
// stamped the day it is added and nobody has to remember to list it.
|
|
85
|
+
const _assetStamps = new Map();
|
|
86
|
+
function assetStamp(url) {
|
|
87
|
+
if (!_assetStamps.has(url)) {
|
|
88
|
+
let v = '';
|
|
89
|
+
try {
|
|
90
|
+
const abs = path.resolve(LANDING_DIR, '.' + url);
|
|
91
|
+
// never let a template's own text walk out of the landing dir
|
|
92
|
+
if (abs.startsWith(LANDING_DIR + path.sep)) {
|
|
93
|
+
v = require('node:crypto').createHash('sha256')
|
|
94
|
+
.update(fs.readFileSync(abs)).digest('hex').slice(0, 8);
|
|
95
|
+
}
|
|
96
|
+
} catch { v = ''; } // not a file → not an asset → no stamp
|
|
97
|
+
_assetStamps.set(url, v);
|
|
98
|
+
}
|
|
99
|
+
return _assetStamps.get(url);
|
|
100
|
+
}
|
|
101
|
+
const LOCAL_ASSET = /(href|src)="(\/[A-Za-z0-9._~/-]+\.[A-Za-z0-9]+)"/g;
|
|
102
|
+
function stampAssets(html) {
|
|
103
|
+
return String(html).replace(LOCAL_ASSET, (m, attr, url) =>
|
|
104
|
+
assetStamp(url) ? `${attr}="${url}?v=${assetStamp(url)}"` : m);
|
|
105
|
+
}
|
|
106
|
+
|
|
63
107
|
const _landingPages = new Map(); // memoized per file; '' if absent (instance ships no landing)
|
|
64
108
|
function landingPage(file) {
|
|
65
109
|
if (!_landingPages.has(file)) {
|
|
66
110
|
let html = '';
|
|
67
111
|
try {
|
|
68
|
-
|
|
112
|
+
// stamped LAST, so anything a later transform introduces is stamped too
|
|
113
|
+
html = stampAssets(injectBrandingGlobal(stripApexOnly(
|
|
114
|
+
fs.readFileSync(path.join(LANDING_DIR, file), 'utf8'))));
|
|
69
115
|
} catch { /* absent → '' */ }
|
|
70
116
|
_landingPages.set(file, html);
|
|
71
117
|
}
|
|
@@ -119,3 +119,62 @@ test('create.html: the poll give-up points at /projects — "the hall has it" is
|
|
|
119
119
|
assert.match(body, /\/projects/, 'the give-up path must hand the owner the projects page');
|
|
120
120
|
assert.doesNotMatch(body, /the hall has it/, 'the false poll-stop copy must be gone');
|
|
121
121
|
});
|
|
122
|
+
|
|
123
|
+
/* ── Content-stamped assets (task 1002766) ──────────────────────────────────
|
|
124
|
+
The landing HTML is never cached but its assets come back max-age=14400 —
|
|
125
|
+
Cloudflare's default browser TTL, not ours. So a returning visitor gets new
|
|
126
|
+
markup with up to four-hour-old CSS: markup whose components that stylesheet
|
|
127
|
+
has never heard of. Twice now that has shipped a visibly broken page. The URL
|
|
128
|
+
carries the version so there is nothing to invalidate. ── */
|
|
129
|
+
|
|
130
|
+
test('the stylesheet URL carries a content stamp, so new markup cannot meet old CSS', async () => {
|
|
131
|
+
const res = await request('/projects');
|
|
132
|
+
assert.equal(res.status, 200);
|
|
133
|
+
const m = res.body.match(/href="\/assets\/cosmos\.css\?v=([0-9a-f]+)"/);
|
|
134
|
+
assert.ok(m, 'cosmos.css must be served with a ?v= content stamp');
|
|
135
|
+
assert.equal(m[1].length, 8, 'the stamp is the first 8 hex of a sha256');
|
|
136
|
+
assert.doesNotMatch(res.body, /href="\/assets\/cosmos\.css"/,
|
|
137
|
+
'no unstamped copy may survive alongside the stamped one');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('the stamp is derived from CONTENT, so it changes only when the file does', async () => {
|
|
141
|
+
const fsMod = await import('node:fs');
|
|
142
|
+
const pathMod = await import('node:path');
|
|
143
|
+
const cryptoMod = await import('node:crypto');
|
|
144
|
+
const url = await import('node:url');
|
|
145
|
+
const root = pathMod.dirname(url.fileURLToPath(import.meta.url));
|
|
146
|
+
const css = fsMod.readFileSync(pathMod.join(root, '..', 'public-landing', 'assets', 'cosmos.css'));
|
|
147
|
+
const want = cryptoMod.createHash('sha256').update(css).digest('hex').slice(0, 8);
|
|
148
|
+
const res = await request('/projects');
|
|
149
|
+
assert.ok(res.body.includes(`cosmos.css?v=${want}`),
|
|
150
|
+
'the stamp must be the hash of the bytes actually on disk');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('every landing surface is stamped, not just the one that broke', async () => {
|
|
154
|
+
for (const p of ['/', '/projects', '/create']) {
|
|
155
|
+
const res = await request(p);
|
|
156
|
+
if (res.status !== 200) continue;
|
|
157
|
+
assert.doesNotMatch(res.body, /(href|src)="\/assets\/[^"?]+"/,
|
|
158
|
+
`${p} still serves an unstamped /assets/ URL`);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('routes and API paths are never mistaken for assets', async () => {
|
|
163
|
+
// stamping resolves the URL against public-landing/ — a route names no file,
|
|
164
|
+
// so it must come back untouched rather than acquiring a meaningless ?v=
|
|
165
|
+
const res = await request('/projects');
|
|
166
|
+
assert.match(res.body, /href="\/create"/, '/create is a route, not an asset');
|
|
167
|
+
assert.doesNotMatch(res.body, /href="\/create\?v=/);
|
|
168
|
+
assert.doesNotMatch(res.body, /href="\/projects\?v=/);
|
|
169
|
+
assert.doesNotMatch(res.body, /\/api\/bongos\/[^"]*\?v=[0-9a-f]{8}/,
|
|
170
|
+
'API paths must never be stamped');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test('a stamped asset still serves — the query string is not part of the filename', async () => {
|
|
174
|
+
const page = await request('/projects');
|
|
175
|
+
const stamped = (page.body.match(/\/assets\/cosmos\.css\?v=[0-9a-f]+/) || [])[0];
|
|
176
|
+
assert.ok(stamped, 'need a stamped URL to fetch');
|
|
177
|
+
const res = await request(stamped);
|
|
178
|
+
assert.equal(res.status, 200, 'express.static must ignore the ?v= when resolving the file');
|
|
179
|
+
assert.match(res.body, /\.shell\{/, 'and it must be the real stylesheet');
|
|
180
|
+
});
|