@bongos/core 1.24.59 → 1.24.60

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/bongos/package-core.js",
5
- "core_version": "1.24.59",
6
- "core_contract": "1.24.59",
7
- "source_commit": "a6ac4e31607a82978349eba6f0d2c113bdc48345",
5
+ "core_version": "1.24.60",
6
+ "core_contract": "1.24.60",
7
+ "source_commit": "fef925d217da9148be0808c73ee46288043dc2de",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-11-07T15:21:01.170Z",
9
+ "built_at": "2026-11-07T15:39:07.882Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 638,
@@ -17,7 +17,7 @@
17
17
  "gate": "passed"
18
18
  },
19
19
  "file_count": 4571,
20
- "tree_sha256": "0d58836fc7200aa61d37a68fc94e1fef65ba9556f5afb9e9c7105fcc474f0d4b",
20
+ "tree_sha256": "02fb38f9111b66794f346c5bb82422a3f315f456b1c9d73a5b3eb1c136097fc1",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -3007,7 +3007,7 @@
3007
3007
  {
3008
3008
  "path": "docs/module-api-changelog.md",
3009
3009
  "mode": "0000644",
3010
- "sha256": "b68ba24d900494bac49e77694a228c04794d1c0b5a43c4733159ee5429cbec4f"
3010
+ "sha256": "71913622d0d94c4120e620d5aea32ff476a6e879c5e166da3144eedc75619dda"
3011
3011
  },
3012
3012
  {
3013
3013
  "path": "docs/modules-contract.md",
@@ -11902,12 +11902,12 @@
11902
11902
  {
11903
11903
  "path": "package-lock.json",
11904
11904
  "mode": "0000644",
11905
- "sha256": "38c4ec31d6cb51fd92185003e550832f29d0e44f18f3a2df2b7d3594bdca972e"
11905
+ "sha256": "dfea60d54c9a25b94f32d02e56aecb552db65f0a68d3ca9470db04acbd8b3fd3"
11906
11906
  },
11907
11907
  {
11908
11908
  "path": "package.json",
11909
11909
  "mode": "0000644",
11910
- "sha256": "0a9fbd43bcf2caa7e6c2b6faeef134a0dc96d7c5f1ee29519edf5d48ffb78db9"
11910
+ "sha256": "fe9083b4b86df7ab6427311717f99a1bba6d8c7e079e2f37c32b4722a3bc4186"
11911
11911
  },
11912
11912
  {
11913
11913
  "path": "public-docs/index.html",
@@ -11927,7 +11927,7 @@
11927
11927
  {
11928
11928
  "path": "release-notes.json",
11929
11929
  "mode": "0000644",
11930
- "sha256": "9735ab47bbd28140534686e37156442e586d1a7c5abce86f23ab0d546c9da0a7"
11930
+ "sha256": "2735b7f5e5d4c0bf2b24fc78d79738c5b57a9eaf4734823a79e67ea8e0fb39b2"
11931
11931
  },
11932
11932
  {
11933
11933
  "path": "scripts/bongos/CLAUDE.md",
@@ -12267,7 +12267,7 @@
12267
12267
  {
12268
12268
  "path": "scripts/bongos/css-literal-scan.js",
12269
12269
  "mode": "0000644",
12270
- "sha256": "8da46806402a840192f29f93d98afd6b5018715f6fd4ce6fbe060e23ffc977de"
12270
+ "sha256": "1dbcdb46cfd195c1080bdd37bc8337b0926cd5f6e281cd131656801f6c92b236"
12271
12271
  },
12272
12272
  {
12273
12273
  "path": "scripts/bongos/cutover-codemod.js",
@@ -15877,7 +15877,7 @@
15877
15877
  {
15878
15878
  "path": "src/module-api.js",
15879
15879
  "mode": "0000644",
15880
- "sha256": "06c54ea826895a4de9a90970d7b72e6479fada3880d45e1c1f164f29e7c319c5"
15880
+ "sha256": "66eed53ac99b04f6c4c0b5e33b13b44c2b1d53239198bf8b41fe13a454d866d9"
15881
15881
  },
15882
15882
  {
15883
15883
  "path": "src/module-loader/catalog.js",
@@ -17412,7 +17412,7 @@
17412
17412
  {
17413
17413
  "path": "tests/design_counts_ratchet.mjs",
17414
17414
  "mode": "0000644",
17415
- "sha256": "2fd9ecdc86bb0a9b2392db350568eb4776a27b3d3bd43e896686475707a61a85"
17415
+ "sha256": "3e7457685c915d01d11ba4dd1481e726f82c83a3ce481f5190893e4b0636b7bc"
17416
17416
  },
17417
17417
  {
17418
17418
  "path": "tests/design_reference.mjs",
@@ -3372,5 +3372,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
3372
3372
  landed since 1.24.57 with no explicit bump. run 42380593295. (task 1002620)
3373
3373
  1.24.59 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3374
3374
  landed since 1.24.58 with no explicit bump. run 42385816081. (task 1002620)
3375
+ 1.24.60 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3376
+ landed since 1.24.59 with no explicit bump. run 42387285187. (task 1002620)
3375
3377
  ---------------------------------------------------------------------------
3376
3378
  ```
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.24.59",
3
+ "version": "1.24.60",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.24.59",
9
+ "version": "1.24.60",
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.24.59",
3
+ "version": "1.24.60",
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",
@@ -11329,5 +11329,11 @@
11329
11329
  "id": "1005133",
11330
11330
  "text": "The logo files and the link-preview pictures are now checked automatically, so a logo change can no longer ship with out-of-date copies; the docs now say plainly that the off-site status page is updated by hand."
11331
11331
  }
11332
+ ],
11333
+ "1.24.60": [
11334
+ {
11335
+ "id": "1005134",
11336
+ "text": "The automatic check that stops pages from typing in their own colours and sizes now also catches colours given a custom name and styles written directly inside a page, so those shortcuts can no longer slip new colours past it."
11337
+ }
11332
11338
  ]
11333
11339
  }
@@ -21,16 +21,28 @@
21
21
  // tokens; the count stops the set of breakpoints sprawling).
22
22
  // Sizes, radii, shadows and breakpoints are counted per DECLARATION / RULE.
23
23
  //
24
- // WHAT IT DOES NOT COUNT. A custom-property declaration (`--x: #123`) is a token
25
- // DEFINITION — tokens.css and the hall's token block are exactly where literals
26
- // belong — so its value is skipped. Page-private palettes are guarded separately
27
- // (tests/hall_private_palettes.mjs). Custom-property NAMES are removed from a
28
- // value before it is read (so --ds-space-12 is not a "12"), and url(...) bodies
29
- // are emptied (a data URI is not a colour). A literal inside a var() fallback
30
- // DOES count: a fallback is a typed-in value.
24
+ // CUSTOM PROPERTIES (task 1005134). A colour typed into a custom-property
25
+ // declaration (`:root{--mine:#123456}`, `body{--x:rgb(…)}`) COUNTS as a colour,
26
+ // so a new sheet cannot dodge its zero start by naming its colours. Only the
27
+ // shared token definitions are exempt, because they are exactly where literals
28
+ // belong: the whole of the shared token sheet and the hall's token block (its
29
+ // :root and the two byte-identical dark twins) — TOKEN_SHEETS. The pack splice
30
+ // (serve-internal.js themeMarkup) is built in JavaScript at serve time and is
31
+ // never scanned. A custom property's sizes/radii/shadows are not counted — those
32
+ // kinds are read off the property that SPENDS a value, and a --x is not one.
33
+ // Page-private palettes are also guarded by tests/hall_private_palettes.mjs.
34
+ //
35
+ // WHAT IT DOES NOT COUNT. Custom-property NAMES are removed from a value before
36
+ // it is read (so --ds-space-12 is not a "12"), and url(...) bodies are emptied (a
37
+ // data URI is not a colour). A literal inside a var() fallback DOES count: a
38
+ // fallback is a typed-in value.
31
39
  //
32
40
  // Scope: every tracked *.css outside docs/ (the design mocks there are not
33
- // served), node_modules and vendor/.
41
+ // served), node_modules and vendor/; the inline <style> blocks of every served
42
+ // .html (one under a public/, public-docs/, views/ or content/ folder); and the
43
+ // <style> blocks a server file writes as a string (STYLE_STRING_SOURCES: the
44
+ // branded error page). Task 1005134: a page could otherwise type its colours into
45
+ // a <style> tag and never be counted.
34
46
  //
35
47
  // Print the counts: node scripts/bongos/css-literal-scan.js [--all]
36
48
 
@@ -42,6 +54,18 @@ const ROOT = path.resolve(__dirname, '..', '..');
42
54
  const KINDS = ['colours', 'font_sizes', 'radii', 'shadows', 'breakpoints'];
43
55
 
44
56
  const EXCLUDE_RE = /^docs\/|(^|\/)(node_modules|vendor)\//;
57
+ // A served page: an .html under a folder the server serves or renders from.
58
+ const SERVED_HTML_RE = /(^|\/)(public|public-docs|views|content)\/.*\.html$/;
59
+ // Server files that write a page's <style> block as a string.
60
+ const STYLE_STRING_SOURCES = ['src/error-page.js'];
61
+ const STYLE_BLOCK_RE = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
62
+ // The shared token definitions, where a typed-in colour belongs: `true` exempts
63
+ // every custom property in the sheet; a RegExp exempts those in a rule whose
64
+ // selector matches it (the hall's token block: :root and the two dark twins).
65
+ const TOKEN_SHEETS = {
66
+ 'modules/ui-design/public/tokens.css': true,
67
+ 'modules/hall-ui/public/style.css': /^(?::root|html\[data-theme="dark"\]|html:not\(\[data-theme\]\))$/,
68
+ };
45
69
  const COMMENT_RE = /\/\*[\s\S]*?\*\//g;
46
70
  const URL_RE = /url\(\s*(?:"[^"]*"|'[^']*'|[^)]*)\)/gi;
47
71
  const VAR_NAME_RE = /--[\w-]+/g;
@@ -84,17 +108,43 @@ function hasLength(value) {
84
108
  return false;
85
109
  }
86
110
 
87
- /** The five counts for one stylesheet's text. */
88
- function countSheet(text) {
111
+ // The selector of the innermost rule around a position, read by walking the text
112
+ // forward (positions must be asked in increasing order). '' at the top level.
113
+ function selectorWalker(css) {
114
+ const stack = [];
115
+ let pos = 0;
116
+ let segStart = 0;
117
+ return (idx) => {
118
+ for (; pos < idx; pos += 1) {
119
+ const ch = css[pos];
120
+ if (ch === '{') { stack.push(css.slice(segStart, pos).replace(/\s+/g, ' ').trim()); segStart = pos + 1; }
121
+ else if (ch === '}') { stack.pop(); segStart = pos + 1; }
122
+ else if (ch === ';') segStart = pos + 1;
123
+ }
124
+ return stack.length ? stack[stack.length - 1] : '';
125
+ };
126
+ }
127
+
128
+ /**
129
+ * The five counts for one stylesheet's text. `tokens` marks the shared token
130
+ * definitions: true (the whole sheet) or a RegExp over a rule's selector.
131
+ */
132
+ function countSheet(text, { tokens = false } = {}) {
89
133
  const counts = Object.fromEntries(KINDS.map((k) => [k, 0]));
90
134
  const css = String(text).replace(COMMENT_RE, ' ').replace(URL_RE, 'url()');
91
135
  AT_RULE_RE.lastIndex = 0;
92
136
  let m;
93
137
  while ((m = AT_RULE_RE.exec(css))) if (MEDIA_LENGTH_RE.test(m[2])) counts.breakpoints += 1;
138
+ const selectorAt = selectorWalker(css);
94
139
  DECL_RE.lastIndex = 0;
95
140
  while ((m = DECL_RE.exec(css))) {
96
141
  const prop = m[1].toLowerCase();
97
- if (prop.startsWith('--')) continue; // a token definition
142
+ if (prop.startsWith('--')) {
143
+ // A custom property: its colours count unless it is a shared token definition.
144
+ const exempt = tokens === true || (tokens instanceof RegExp && tokens.test(selectorAt(m.index + m[0].indexOf(m[1]))));
145
+ if (!exempt) counts.colours += colourCount(m[2].replace(VAR_NAME_RE, ' '));
146
+ continue;
147
+ }
98
148
  const value = m[2].replace(VAR_NAME_RE, ' ');
99
149
  counts.colours += colourCount(value);
100
150
  if ((prop === 'font-size' || prop === 'font') && hasLength(value)) counts.font_sizes += 1;
@@ -106,8 +156,16 @@ function countSheet(text) {
106
156
  }
107
157
 
108
158
  function trackedStylesheets(root = ROOT) {
109
- const out = execFileSync('git', ['ls-files', '-z', '--', '*.css'], { cwd: root, encoding: 'utf8' });
110
- return out.split('\0').filter((f) => f && !EXCLUDE_RE.test(f)).sort();
159
+ const out = execFileSync('git', ['ls-files', '-z', '--', '*.css', '*.html', ...STYLE_STRING_SOURCES], { cwd: root, encoding: 'utf8' });
160
+ return out.split('\0').filter((f) => f && !EXCLUDE_RE.test(f)
161
+ && (f.endsWith('.css') || SERVED_HTML_RE.test(f) || STYLE_STRING_SOURCES.includes(f))).sort();
162
+ }
163
+
164
+ // The CSS a file carries: a stylesheet is all CSS; a page or a server file
165
+ // carries only what sits inside its <style> blocks.
166
+ function cssOf(file, text) {
167
+ if (file.endsWith('.css')) return text;
168
+ return [...String(text).matchAll(STYLE_BLOCK_RE)].map((b) => b[1]).join('\n');
111
169
  }
112
170
 
113
171
  /**
@@ -123,7 +181,7 @@ function scanCssLiterals({ root = ROOT, files = null, read = null } = {}) {
123
181
  let text;
124
182
  try { text = reader(f); } catch { continue; }
125
183
  scanned += 1;
126
- const c = countSheet(text);
184
+ const c = countSheet(cssOf(f, text), { tokens: TOKEN_SHEETS[f] || false });
127
185
  if (KINDS.some((k) => c[k] > 0)) sheets[f] = c;
128
186
  }
129
187
  return { sheets, scanned };
package/src/module-api.js CHANGED
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
81
81
  // MAJOR (see allowBoxScope below): passes the request through untouched.
82
82
  function deprecatedNoopMiddleware(_req, _res, next) { next(); }
83
83
 
84
- const CORE_VERSION = '1.24.59'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
84
+ const CORE_VERSION = '1.24.60'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
85
85
 
86
86
  // A namespaced logger so a module's log lines are attributable + consistent.
87
87
  // Usage: const log = api.logger('discord'); log.info('mounted');
@@ -28,7 +28,7 @@ const readReal = (f) => fs.readFileSync(path.join(ROOT, f), 'utf8');
28
28
 
29
29
  test('the detector counts typed-in values and leaves tokens alone', () => {
30
30
  const c = scan.countSheet(`
31
- :root { --ds-x: 14px; --tone: #123456; } /* token definitions: not counted */
31
+ :root { --ds-x: 14px; --tone: #123456; } /* a token sheet's definitions: not counted */
32
32
  .a { font-size: 13px; border-radius: 6px; color: #abc; }
33
33
  .b { font-size: var(--ds-text-sm); border-radius: var(--ds-radius-tile); color: var(--ink); }
34
34
  .c { box-shadow: 0 2px 4px rgba(0, 0, 0, .2); } /* a shadow; the black scrim is allowed */
@@ -36,7 +36,7 @@ test('the detector counts typed-in values and leaves tokens alone', () => {
36
36
  .e { background: color-mix(in srgb, var(--ink) 40%, rgb(12 34 56)); padding: 0; }
37
37
  @media (max-width: 720px) { .f { border-radius: 0; } }
38
38
  @media (prefers-reduced-motion: reduce) { .g { transition: none; } }
39
- `);
39
+ `, { tokens: true });
40
40
  assert.deepEqual(c, { colours: 2, font_sizes: 1, radii: 1, shadows: 1, breakpoints: 1 });
41
41
  });
42
42
 
@@ -170,3 +170,60 @@ test('the freshness gate covers the logo set: mark.svg, the pack and both PNGs',
170
170
  fs.rmSync(dir, { recursive: true, force: true });
171
171
  }
172
172
  });
173
+
174
+ // task 1005134: two loopholes closed. (1) A sheet that names its colours as custom
175
+ // properties used to count zero, so a new sheet could bypass its zero start; now
176
+ // only the shared token definitions are exempt. (2) A page's inline <style> and the
177
+ // error page's CSS string were never scanned; now they are, with baselines.
178
+ const HALL_TOKENS = /^(?::root|html\[data-theme="dark"\]|html:not\(\[data-theme\]\))$/;
179
+
180
+ test('a colour typed into a custom property counts, outside the shared token definitions', () => {
181
+ assert.equal(scan.countSheet(':root { --mine: #123456; }').colours, 1, ':root{--mine:#123456}');
182
+ assert.equal(scan.countSheet('body { --x: rgb(10, 20, 30); --y: hsl(1 2% 3%); }').colours, 2, 'body{--x:rgb(...)}');
183
+ assert.equal(scan.countSheet('.v { --veil: rgba(0, 0, 0, .4); --tone: var(--ink); }').colours, 0, 'a scrim and a reference are not typed-in colours');
184
+ assert.equal(scan.countSheet(':root { --mine: #123456; } .x { --y: #654321; }', { tokens: true }).colours, 0, 'the shared token sheet is exempt');
185
+ const hall = ':root { --a: #111111; } html[data-theme="dark"] { --a: #222222; } '
186
+ + '@media (prefers-color-scheme: dark) { html:not([data-theme]) { --a: #222222; } } '
187
+ + '.brand { --mark-ink: #333333; } @media (max-width: 1px) { .y { --z: #444444; } }';
188
+ assert.equal(scan.countSheet(hall, { tokens: HALL_TOKENS }).colours, 2, 'the hall token block is exempt; its other rules are not');
189
+ });
190
+
191
+ test('a NEW sheet that names its colours as custom properties fails fitness', () => {
192
+ const NEW = 'modules/x/public/new.css';
193
+ const { sheets } = scan.scanCssLiterals({ files: [NEW], read: () => ':root { --mine: #123456; } body { --x: rgb(1, 2, 3); }' });
194
+ const d = ratchets.compareDesignCounts(sheets, { _comment: 'note' });
195
+ assert.ok(d.violations.some((v) => v.includes(`colours in ${NEW} regressed: 2 > baseline 0`)), d.violations.join('\n'));
196
+ // …while a definition planted in the shared token sheet, or the hall's :root, stays exempt.
197
+ const planted = scan.scanCssLiterals({
198
+ read: (f) => readReal(f) + (f === 'modules/ui-design/public/tokens.css' || f === HALL_SHEET ? '\n:root { --mine: #123456; }\n' : ''),
199
+ });
200
+ assert.deepEqual(ratchets.checkQualityRatchets({ metrics: {}, designCounts: planted.sheets }).violations, []);
201
+ const leak = scan.scanCssLiterals({ read: (f) => readReal(f) + (f === HALL_SHEET ? '\n.planted { --mine: #123456; }\n' : '') });
202
+ const r = ratchets.checkQualityRatchets({ metrics: {}, designCounts: leak.sheets });
203
+ assert.ok(r.violations.some((v) => v.includes(`colours in ${HALL_SHEET} regressed`)), r.violations.join('\n'));
204
+ });
205
+
206
+ test('inline <style> blocks in served pages and the error page CSS are scanned', () => {
207
+ const PAGE = 'modules/public-landing/public/terms.html';
208
+ const ERR = 'src/error-page.js';
209
+ const { sheets } = scan.scanCssLiterals();
210
+ assert.ok(sheets[ERR], `${ERR} is scanned`);
211
+ assert.ok(sheets[PAGE], `${PAGE} is scanned`);
212
+ assert.ok(!Object.keys(sheets).some((f) => /\/styles\/[^/]+\/mock\.html$|\/specimens\/.*\.html$/.test(f)), 'unserved mocks and specimens are not');
213
+ assert.equal(scan.countSheet('').colours, 0);
214
+ const planted = scan.scanCssLiterals({
215
+ read: (f) => {
216
+ const t = readReal(f);
217
+ if (f === PAGE || f === ERR) return t.replace('</style>', '.planted { color: #123456; font-size: 13px; }\n</style>');
218
+ return t;
219
+ },
220
+ });
221
+ const r = ratchets.checkQualityRatchets({ metrics: {}, designCounts: planted.sheets });
222
+ for (const f of [PAGE, ERR]) {
223
+ assert.ok(r.violations.some((v) => v.includes(`colours in ${f} regressed`)), `${f} colours: ${r.violations.join('\n')}`);
224
+ assert.ok(r.violations.some((v) => v.includes(`font_sizes in ${f} regressed`)), `${f} sizes: ${r.violations.join('\n')}`);
225
+ }
226
+ // Text outside a <style> block is not CSS: a hex in a page's prose is not counted.
227
+ const prose = scan.scanCssLiterals({ read: (f) => readReal(f) + (f === PAGE ? '\n<p>color: #123456;</p>\n' : '') });
228
+ assert.deepEqual(prose.sheets[PAGE], sheets[PAGE]);
229
+ });