@bongos/core 1.19.90 → 1.19.91

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.
@@ -57,17 +57,23 @@ const { branding, clientBranding } = require('../src/branding');
57
57
  // with src/bongos/serve-internal.js applyBrandTokens. `brand` is the client-safe pack
58
58
  // and `publicOrigin` the resolved origin — both injected, never read ambiently, so the
59
59
  // transform stays pure and testable.
60
- function brandTokens(html, brand = {}, publicOrigin = '') {
60
+ function brandTokens(html, brand = {}, publicOrigin = '', buildersOrigin = '') {
61
61
  const world = (brand.identity && brand.identity.worldName) || '';
62
62
  const product = (brand.identity && brand.identity.productName) || '';
63
+ const tagline = (brand.copy && brand.copy.tagline) || '';
63
64
  const cur = (brand.currency && brand.currency.label) || 'credits';
64
65
  const Cur = cur ? cur.charAt(0).toUpperCase() + cur.slice(1) : cur;
66
+ const esc = (s) => String(s == null ? '' : s)
67
+ .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
68
+ .replace(/"/g, '&quot;').replace(/'/g, '&#39;');
65
69
  return html
66
- .split('{{worldName}}').join(world)
67
- .split('{{productName}}').join(product)
68
- .split('{{Currency}}').join(Cur)
69
- .split('{{currency}}').join(cur)
70
- .split('{{publicOrigin}}').join(publicOrigin || '');
70
+ .split('{{worldName}}').join(esc(world))
71
+ .split('{{productName}}').join(esc(product))
72
+ .split('{{Currency}}').join(esc(Cur))
73
+ .split('{{currency}}').join(esc(cur))
74
+ .split('{{tagline}}').join(esc(tagline))
75
+ .split('{{publicOrigin}}').join(esc(publicOrigin || ''))
76
+ .split('{{buildersOrigin}}').join(esc(buildersOrigin || publicOrigin || ''));
71
77
  }
72
78
 
73
79
  // ---- the three off-box index.html injections -------------------------------
@@ -153,21 +153,45 @@ function injectBrandingGlobal(html, brand = clientBranding(), mods = clientModul
153
153
  // stays brand-free (the fitness identity gate). Runs BEFORE the __BRANDING__
154
154
  // script is inserted; the injected JSON holds resolved values (never tokens), so
155
155
  // order is immaterial. Tokens: {{worldName}} {{productName}} {{currency}}
156
- // (lowercase label) {{Currency}} (Capitalized) {{publicOrigin}} (the apex origin,
157
- // for the one functional cross-instance link — sourced from the FULL pack's
158
- // domains, which clientBranding deliberately omits). Pure + exported for the test;
159
- // `origin` is injectable so the test drives it with zero I/O.
160
- function applyBrandTokens(html, brand = clientBranding(), origin = safePublicOrigin()) {
156
+ // (lowercase label) {{Currency}} (Capitalized) {{tagline}} (the pack's one-line
157
+ // pitch), plus the two ORIGINS — {{publicOrigin}} (this instance's apex) and
158
+ // {{buildersOrigin}} (its hall). Both origins are sourced from the FULL pack's
159
+ // `domains`, which clientBranding deliberately omits, so they are the only way a
160
+ // served page can carry a correct cross-host href or canonical/og: URL: a client
161
+ // fill cannot reach them, and a baked one would make every instance advertise
162
+ // whichever instance happened to author the file (task 1002748, direction C5/C6).
163
+ // Pure + exported for the test; both origins are injectable so it drives with zero I/O.
164
+ function applyBrandTokens(html, brand = clientBranding(), origin = safePublicOrigin(), hallOrigin = safeBuildersOrigin(), sourceRepo = safeSourceRepoUrl()) {
161
165
  const world = (brand.identity && brand.identity.worldName) || '';
162
166
  const product = (brand.identity && brand.identity.productName) || '';
167
+ const tagline = (brand.copy && brand.copy.tagline) || '';
163
168
  const cur = (brand.currency && brand.currency.label) || 'credits';
164
169
  const Cur = cur ? cur.charAt(0).toUpperCase() + cur.slice(1) : cur;
165
170
  return String(html == null ? '' : html)
166
- .split('{{worldName}}').join(world)
167
- .split('{{productName}}').join(product)
168
- .split('{{Currency}}').join(Cur)
169
- .split('{{currency}}').join(cur)
170
- .split('{{publicOrigin}}').join(origin || '');
171
+ .split('{{worldName}}').join(esc(world))
172
+ .split('{{productName}}').join(esc(product))
173
+ .split('{{Currency}}').join(esc(Cur))
174
+ .split('{{currency}}').join(esc(cur))
175
+ .split('{{tagline}}').join(esc(tagline))
176
+ .split('{{publicOrigin}}').join(esc(origin || ''))
177
+ // a pack that names no hall of its own resolves to its OWN apex — never to
178
+ // whichever instance authored the file
179
+ .split('{{buildersOrigin}}').join(esc(hallOrigin || origin || ''))
180
+ .split('{{sourceRepoUrl}}').join(esc(sourceRepo || ''));
181
+ }
182
+ // Every token lands in HTML — as element text, or inside a double-quoted
183
+ // attribute (href/content). Escaping is defence in depth, not a live fix: the
184
+ // only source is config/branding.json, which is committed, code-reviewed and
185
+ // Archon-only, never request-supplied. But a pack string carrying a quote or a
186
+ // '<' would otherwise break out of its attribute or close <title> early, and
187
+ // the values now reach four times as many places as when this was written.
188
+ // No token is ever emitted into a <script> or a CSS context (checked across
189
+ // public-landing/, hall-ui/, status-ui/) — that would need different escaping.
190
+ // Ampersand first, or the other replacements get double-escaped.
191
+ function esc(s) {
192
+ return String(s == null ? '' : s)
193
+ .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
194
+ .replace(/"/g, '&quot;').replace(/'/g, '&#39;');
171
195
  }
172
196
  // The instance apex origin from the full branding pack (domains is server-only,
173
197
  // so it never rides clientBranding). Best-effort so a branding hiccup can't crash
@@ -175,6 +199,25 @@ function applyBrandTokens(html, brand = clientBranding(), origin = safePublicOri
175
199
  function safePublicOrigin() {
176
200
  try { return branding().domains.publicOrigin || ''; } catch { return ''; }
177
201
  }
202
+ // The instance's OWN builders' hall origin, same server-only source. Empty is fine:
203
+ // applyBrandTokens falls back to the apex, so a pack that names no hall still gets
204
+ // a same-instance link rather than a dangling href.
205
+ function safeBuildersOrigin() {
206
+ try { return branding().domains.buildersOrigin || ''; } catch { return ''; }
207
+ }
208
+ // The instance's own source repo, from the pack's `repo: {owner, name}` binding —
209
+ // another server-only field (the branding contract keeps repo off clientBranding
210
+ // alongside domains). A page that wants to link "read the source" must take it
211
+ // from here: the authorship audit fails any hardcoded owner/name, and rightly so,
212
+ // since a baked one would point every instance at whoever authored the file.
213
+ // '' when the pack names no repo — the page drops the link rather than emit a
214
+ // dangling href.
215
+ function safeSourceRepoUrl() {
216
+ try {
217
+ const repo = branding().repo;
218
+ return repo && repo.owner && repo.name ? `https://github.com/${repo.owner}/${repo.name}` : '';
219
+ } catch { return ''; }
220
+ }
178
221
  // Instance product/world name for the API-discovery index (task 1918). Best-effort:
179
222
  // a branding hiccup must not break the JSON response.
180
223
  function safeProductName() {
package/src/module-api.js CHANGED
@@ -607,8 +607,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
607
607
  // landed since 1.19.88 with no explicit bump. run 31657467485. (task 1002620)
608
608
  // 1.19.90 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
609
609
  // landed since 1.19.89 with no explicit bump. run 31717551308. (task 1002620)
610
+ // 1.19.91 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
611
+ // landed since 1.19.90 with no explicit bump. run 31722841411. (task 1002620)
610
612
  // ---------------------------------------------------------------------------
611
- const CORE_VERSION = '1.19.90'; // CI auto-patch carrier (ADR 0161); see changelog above
613
+ const CORE_VERSION = '1.19.91'; // CI auto-patch carrier (ADR 0161); see changelog above
612
614
 
613
615
  // A namespaced logger so a module's log lines are attributable + consistent.
614
616
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -65,12 +65,36 @@ function landingPage(file) {
65
65
  if (!_landingPages.has(file)) {
66
66
  let html = '';
67
67
  try {
68
- html = injectBrandingGlobal(fs.readFileSync(path.join(LANDING_DIR, file), 'utf8'));
68
+ html = injectBrandingGlobal(stripApexOnly(fs.readFileSync(path.join(LANDING_DIR, file), 'utf8')));
69
69
  } catch { /* absent → '' */ }
70
70
  _landingPages.set(file, html);
71
71
  }
72
72
  return _landingPages.get(file);
73
73
  }
74
+
75
+ // THE APEX CONDITIONAL (task 1002748). public-landing/ ships wholesale to every
76
+ // instance, so one file has to render two shapes: the platform's own front door,
77
+ // and a child project's. Sections that are true only of the platform — the
78
+ // catalog of OTHER instances, the "capture-resistant commons" claim, the door
79
+ // into the catalog — are wrapped in <!--apex-only:start--> … <!--apex-only:end-->
80
+ // and REMOVED here on any instance that is not the hub.
81
+ //
82
+ // Removed, not CSS-hidden: a child instance must not ship the platform's claims
83
+ // to a crawler, and hiding is not the same as not saying it.
84
+ //
85
+ // Comment sentinels rather than an attribute selector because this is a string
86
+ // transform, not a DOM one — matching a balanced close tag with a regex is the
87
+ // fragile shape, and a sentinel pair has no such failure mode.
88
+ //
89
+ // `platform-identity` IS the hub flag, not a proxy for it: its manifest reads
90
+ // "Enabled ONLY on the hub instance; ordinary hosted + self-hosted instances
91
+ // leave it off", and it is the module that owns the very /projects catalog these
92
+ // sections point at. So this asks the instance what it IS, and nothing anywhere
93
+ // has to know the platform's hostname (direction C5).
94
+ const APEX_ONLY = /<!--apex-only:start-->[\s\S]*?<!--apex-only:end-->/g;
95
+ function stripApexOnly(html, isHub = isModuleEnabled('platform-identity')) {
96
+ return isHub ? html : String(html).replace(APEX_ONLY, '');
97
+ }
74
98
  function landingHtml() { return landingPage('index.html'); }
75
99
  // The interactive landing gate (public-landing/gate/, ported from amazonprimea
76
100
  // in task 1002407). When an instance ships it, it is the apex front door
@@ -232,4 +256,4 @@ if (require.main === module) {
232
256
  });
233
257
  }
234
258
 
235
- module.exports = { buildPlatformApp, startBackgroundServices };
259
+ module.exports = { buildPlatformApp, startBackgroundServices, stripApexOnly };
@@ -34,14 +34,16 @@ test('R74: the vanilla pack carries the Cloud Bongos LOOK (sunrise palette, Balo
34
34
  const c = clientBranding(vanilla());
35
35
  // fired down from the original #f97316 so white-on-accent clears WCAG AA
36
36
  // (2.80:1 -> 4.61:1, task 1002749) — same ember hue, readable
37
- assert.equal(c.theme.colors.accent, '#ce4806', 'sunrise orange accent');
38
- assert.equal(c.theme.colors.bg, '#fff7ed', 'warm sunrise base');
37
+ assert.equal(c.theme.colors.accent, '#ce4806', 'ember orange accent');
38
+ // ember marigold (task 1002748): the base is PIGMENTED, not near-white. The
39
+ // old #fff7ed sat within a few points of the cards and the page read as fog.
40
+ assert.equal(c.theme.colors.bg, '#f9d9a8', 'marigold base');
39
41
  assert.match(c.theme.fonts.display, /Baloo 2/);
40
42
  assert.match(c.theme.fonts.body, /Quicksand/);
41
43
  assert.ok(c.theme.fonts.links.some((l) => /Baloo\+2/.test(l) && /Quicksand/.test(l) && /Fira\+Code/.test(l)), 'loads Baloo 2 + Quicksand + Fira Code');
42
44
  assert.match(c.theme.favicon, /circle/, 'bongo-pair favicon (circles), not the Greek Α');
43
45
  assert.doesNotMatch(c.theme.favicon, /font-family='serif'>Α/);
44
- assert.match(c.theme.backdrop, /linear-gradient\(180deg/, 'sunrise backdrop gradient');
46
+ assert.equal(c.theme.backdrop, '#f9d9a8', 'the marigold ground holds all the way down');
45
47
  assert.equal(c.copy.tagline, 'Building software is laying down a beat.');
46
48
  assert.equal(c.rankLabels.archon, 'Owner', 'plain rank labels, not "Archon"');
47
49
  });
@@ -50,9 +52,9 @@ test('R74: serving the hall on a vanilla boot injects the Cloud Bongos sunrise t
50
52
  const c = clientBranding(vanilla());
51
53
  const html = '<head><link rel="stylesheet" href="/builders/style.css"><title>x</title></head><body></body>';
52
54
  const out = injectBrandingGlobal(html, c);
53
- // palette override + sunrise body backdrop + bongo favicon + the type links
55
+ // palette override + the marigold body backdrop + bongo favicon + the type links
54
56
  assert.match(out, /:root\{[^}]*--accent:#ce4806;/);
55
- assert.match(out, /body\{background:linear-gradient\(180deg/);
57
+ assert.match(out, /body\{background:#f9d9a8;\}/);
56
58
  assert.match(out, /<link rel="icon" href="data:image\/svg\+xml[^"]*circle/);
57
59
  assert.match(out, /fonts\.googleapis\.com\/css2\?family=Baloo\+2/);
58
60
  // and it lands AFTER the stylesheet so the Cloud Bongos look wins
@@ -63,8 +65,10 @@ test('R74: themeMarkup paints the backdrop only when the pack supplies one', ()
63
65
  // OTB-shaped pack (no backdrop) → no body{background} rule.
64
66
  const otb = themeMarkup({ colors: { bg: '#ece0c2' }, fonts: { links: [] }, favicon: '', backdrop: '' });
65
67
  assert.doesNotMatch(otb, /body\{background/);
66
- // Cloud Bongos pack → backdrop present.
67
- assert.match(themeMarkup(clientBranding(vanilla()).theme), /body\{background:linear-gradient/);
68
+ // Cloud Bongos pack → backdrop present. Asserted by PRESENCE, not by shape:
69
+ // this test's subject is whether a backdrop paints at all, so it must not
70
+ // re-break every time the palette legitimately changes.
71
+ assert.match(themeMarkup(clientBranding(vanilla()).theme), /body\{background:.+\}/);
68
72
  });
69
73
 
70
74
  test('customer-0: GDS_BRANDING_FILE selects the cloudbongos.com instance pack (task 1374)', () => {
@@ -0,0 +1,289 @@
1
+ // tests/landing_page.mjs — the apex landing page (task 1002748, goal 1000061).
2
+ //
3
+ // Contract: docs/design/landing-page-direction.md + docs/design/mocks/landing-page-mock.html.
4
+ // These lock the four properties the direction calls load-bearing, each of which
5
+ // fails SILENTLY in a browser and so cannot be caught by looking at the page:
6
+ //
7
+ // * the apex conditional — public-landing/ ships wholesale to every instance,
8
+ // so the platform-only sections must be REMOVED (not hidden) off the hub;
9
+ // * the server-stamped head — social tags are read before JS runs, so they
10
+ // cannot be client-filled, and they must not be baked to one instance;
11
+ // * the branding cascade — a page-local :root silently outranks the pack's
12
+ // injected theme and defeats re-branding (direction C1);
13
+ // * no hardcoded apex — the file is portable or it lies to child instances.
14
+ //
15
+ // Deliberately NOT asserted here: that GET / returns the page. It does not — the
16
+ // apex still 302s into the gate until the hub exists (task 1002743 part 3), which
17
+ // is why tests/platform_boot.mjs stays parked in run-unit-tests KNOWN_FAILING.
18
+ // This file tests the artifact that flip will serve.
19
+
20
+ import { test } from 'node:test';
21
+ import assert from 'node:assert/strict';
22
+ import fs from 'node:fs';
23
+ import path from 'node:path';
24
+ import { fileURLToPath } from 'node:url';
25
+ import { createRequire } from 'node:module';
26
+
27
+ const require = createRequire(import.meta.url);
28
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
29
+ const HTML = fs.readFileSync(path.join(ROOT, 'public-landing', 'index.html'), 'utf8');
30
+
31
+ const { stripApexOnly } = require('../src/platform-server.js');
32
+ const { applyBrandTokens } = require('../src/bongos/serve-internal.js');
33
+
34
+ // ── the apex conditional ───────────────────────────────────────────────────
35
+
36
+ test('the apex-only sentinels are balanced and each one wraps content', () => {
37
+ const starts = HTML.match(/<!--apex-only:start-->/g) || [];
38
+ const ends = HTML.match(/<!--apex-only:end-->/g) || [];
39
+ assert.equal(starts.length, ends.length, 'every apex-only:start needs its end');
40
+ assert.ok(starts.length >= 4, 'the direction marks four apex-only regions');
41
+ // no start after an unclosed start — a nested pair would make the non-greedy
42
+ // strip close on the wrong sentinel and leak the platform's claims.
43
+ let depth = 0;
44
+ for (const m of HTML.matchAll(/<!--apex-only:(start|end)-->/g)) {
45
+ depth += m[1] === 'start' ? 1 : -1;
46
+ assert.ok(depth === 0 || depth === 1, 'apex-only regions must not nest');
47
+ }
48
+ assert.equal(depth, 0);
49
+ });
50
+
51
+ test('the hub keeps every apex-only section', () => {
52
+ assert.equal(stripApexOnly(HTML, true), HTML);
53
+ });
54
+
55
+ test('a child instance is served none of the platform-only claims', () => {
56
+ const child = stripApexOnly(HTML, false);
57
+ assert.doesNotMatch(child, /<!--apex-only:(start|end)-->/, 'no sentinel survives the strip');
58
+ // the four regions, by their load-bearing content
59
+ assert.doesNotMatch(child, /capture-resistant commons/, 'the commons claim is the platform\'s, not a child\'s');
60
+ assert.doesNotMatch(child, /Built on Cloud Bongos/, 'a child has no catalog of other instances');
61
+ assert.doesNotMatch(child, /What you own/, 'the AGPL/ownership pitch is the platform\'s');
62
+ assert.doesNotMatch(child, /See what's building/, 'the catalog CTA has nothing to point at');
63
+ });
64
+
65
+ test('a child instance keeps the whole portable spine', () => {
66
+ const child = stripApexOnly(HTML, false);
67
+ for (const portable of [
68
+ 'Building software is laying', // the hero tagline
69
+ 'the ledger', 'the gates', 'the grade', // S3, the differentiator
70
+ 'How a change reaches your project', // S4
71
+ 'Build a project', 'Join as a builder', // S7, both doors
72
+ 'Built by agents, on Cloud', // the portable half of the footer
73
+ 'id="who"', // the identity chip
74
+ ]) {
75
+ assert.ok(child.includes(portable), `a child instance lost "${portable}"`);
76
+ }
77
+ });
78
+
79
+ test('the footer keeps its attribution half and loses only the commons half', () => {
80
+ const child = stripApexOnly(HTML, false);
81
+ const foot = child.match(/<div class="foot">([\s\S]*?)<\/div>/);
82
+ assert.ok(foot, 'the footer survives on a child instance');
83
+ assert.doesNotMatch(foot[1], /non-profit/);
84
+ assert.match(foot[1], /Built by agents/);
85
+ });
86
+
87
+ // ── the server-stamped head ────────────────────────────────────────────────
88
+
89
+ const BRAND = {
90
+ identity: { productName: 'Spike Harbor', worldName: 'Spike Harbor' },
91
+ copy: { tagline: 'A different beat entirely.' },
92
+ };
93
+
94
+ test('every head tag a crawler reads is stamped, and no token survives', () => {
95
+ const out = applyBrandTokens(HTML, BRAND, 'https://spike-harbor.example', 'https://builders.spike-harbor.example');
96
+ assert.doesNotMatch(out, /\{\{[a-zA-Z]+\}\}/, 'an unstamped {{token}} would render literally');
97
+ assert.match(out, /<title>Spike Harbor — A different beat entirely\.<\/title>/);
98
+ assert.match(out, /<link rel="canonical" href="https:\/\/spike-harbor\.example\/">/);
99
+ for (const prop of ['og:type', 'og:site_name', 'og:url', 'og:title', 'og:description', 'og:image']) {
100
+ assert.match(out, new RegExp(`property="${prop}"`), `missing ${prop}`);
101
+ }
102
+ for (const name of ['twitter:card', 'twitter:title', 'twitter:description', 'twitter:image']) {
103
+ assert.match(out, new RegExp(`name="${name}"`), `missing ${name}`);
104
+ }
105
+ // the head must not advertise the authoring instance to a child
106
+ assert.doesNotMatch(out.split('</head>')[0], /cloudbongos\.com/);
107
+ });
108
+
109
+ test('every cross-host link is stamped to THIS instance, never the platform', () => {
110
+ const out = applyBrandTokens(HTML, BRAND, 'https://spike-harbor.example', 'https://builders.spike-harbor.example');
111
+ const halls = [...out.matchAll(/data-link-hall href="([^"]*)"/g)].map((m) => m[1]);
112
+ assert.ok(halls.length >= 2, 'the header door and the builders door both point at the hall');
113
+ for (const href of halls) assert.equal(href, 'https://builders.spike-harbor.example');
114
+ // C6: this is the bug the stamping fixes — clientBranding() omits domains, so a
115
+ // client fill can never re-point these and every child sent visitors to the platform.
116
+ assert.doesNotMatch(out, /builders\.cloudbongos\.com/);
117
+ });
118
+
119
+ test('a pack with no hall of its own falls back to its own apex, not the platform', () => {
120
+ const out = applyBrandTokens(HTML, BRAND, 'https://spike-harbor.example', '');
121
+ assert.match(out, /data-link-hall href="https:\/\/spike-harbor\.example"/);
122
+ });
123
+
124
+ test('the source link is stamped from the pack repo, never a baked owner/name', () => {
125
+ // the authorship audit fails a hardcoded owner/name, and a baked one would
126
+ // point every instance at whoever authored this file
127
+ assert.doesNotMatch(HTML, /github\.com\/[a-z0-9-]+\/[a-z0-9-]+/i, 'no baked repo path');
128
+ const out = applyBrandTokens(HTML, BRAND, 'https://o.example', '', 'https://github.com/acme/harbor');
129
+ assert.match(out, /href="https:\/\/github\.com\/acme\/harbor"/);
130
+ });
131
+
132
+ test('an unstamped link is removed, not left dangling', () => {
133
+ const out = applyBrandTokens(HTML, BRAND, 'https://o.example', '', '');
134
+ assert.match(out, /href=""/, 'the token resolves empty when the pack names no repo');
135
+ assert.match(CODE, /a\[href=""\]/, 'and the page drops those links');
136
+ assert.match(CODE, /\.remove\(\)/);
137
+ });
138
+
139
+ // ── the branding cascade + portability ─────────────────────────────────────
140
+
141
+ test('the page declares no :root — a page-local one defeats re-branding (C1)', () => {
142
+ const styles = [...HTML.matchAll(/<style>([\s\S]*?)<\/style>/g)].map((m) => m[1]).join('\n');
143
+ assert.ok(styles.length > 0, 'the page ships a local stylesheet');
144
+ // :root[data-mode="dark"] .bongos{…} is a DESCENDANT rule, not a :root declaration
145
+ // block — it scopes component tokens and cannot outrank the pack's own :root.
146
+ assert.doesNotMatch(styles, /:root\s*\{/, 'a bare :root{ } block outranks the injected pack');
147
+ assert.doesNotMatch(styles, /:root\[data-mode="dark"\]\s*\{/, 'same, for the dark tier');
148
+ });
149
+
150
+ test('every dark-mode rule is written twice — the block and its media twin (C2)', () => {
151
+ const styles = [...HTML.matchAll(/<style>([\s\S]*?)<\/style>/g)].map((m) => m[1]).join('\n');
152
+ const explicit = (styles.match(/:root\[data-mode="dark"\]/g) || []).length;
153
+ const media = (styles.match(/:root:not\(\[data-mode="light"\]\)/g) || []).length;
154
+ assert.equal(explicit, media, 'a dark rule with no prefers-color-scheme twin only half-works');
155
+ assert.ok(explicit >= 2, 'the drum skin and the hero glow both flip');
156
+ });
157
+
158
+ test('the drum head never inherits a card colour — it would turn black in dark', () => {
159
+ const styles = [...HTML.matchAll(/<style>([\s\S]*?)<\/style>/g)].map((m) => m[1]).join('\n');
160
+ const skin = styles.match(/\.bongos\{[^}]*\}/);
161
+ assert.ok(skin, '.bongos carries its own skin tokens');
162
+ assert.match(skin[0], /--skin-hi:/);
163
+ assert.doesNotMatch(styles, /--skin-[a-z]+:\s*var\(--bg-card\)/);
164
+ });
165
+
166
+ test('the page ships the .sky layer the pack backdrop would otherwise show through (C3)', () => {
167
+ assert.match(HTML, /<div class="sky"/);
168
+ });
169
+
170
+ // ── the static pose under reduced motion (direction §7) ───────────────────
171
+ // The hero animates by default, so this is an accessibility constraint that
172
+ // fails SILENTLY — nothing about the page looks wrong to a developer who does
173
+ // not have the OS setting on. It holds via a cross-FILE dependency (cosmos.css
174
+ // carries one global motion kill switch rather than every page repeating it),
175
+ // which is the right shape but an invisible one, so both halves are locked here.
176
+ // Verified in a real browser with reducedMotion emulation: every hero element
177
+ // computes animation-name:none, and the ripples additionally display:none.
178
+
179
+ const COSMOS = fs.readFileSync(path.join(ROOT, 'public-landing', 'assets', 'cosmos.css'), 'utf8');
180
+ const PAGE_STYLES = [...HTML.matchAll(/<style>([\s\S]*?)<\/style>/g)].map((m) => m[1]).join('\n');
181
+ // declarations only — the comments quote the very shapes these tests rule out
182
+ const PAGE_CSS = PAGE_STYLES.replace(/\/\*[\s\S]*?\*\//g, '');
183
+
184
+ test('cosmos.css still carries the global motion kill switch the hero relies on', () => {
185
+ const block = COSMOS.match(/@media\s*\(prefers-reduced-motion:\s*reduce\)\s*\{([\s\S]*?)\n\}/);
186
+ assert.ok(block, 'cosmos.css must keep a prefers-reduced-motion block');
187
+ assert.match(block[1], /\*[^{]*\{[^}]*animation:\s*none\s*!important/,
188
+ 'the universal selector + !important is what stops the hero; without it the drums keep beating');
189
+ });
190
+
191
+ test('nothing on the page marks an animation !important and escapes that switch', () => {
192
+ // the ONE way this page could break the constraint on its own
193
+ assert.doesNotMatch(PAGE_CSS, /animation[^;:]*:[^;]*!important/,
194
+ 'an !important animation here would outrank the global kill switch');
195
+ });
196
+
197
+ test('the hero animations are plain declarations, so the switch reaches them', () => {
198
+ // every animated part of the drum kit, as the mock defines the beat
199
+ for (const cls of ['drumBig', 'drumSml', 'headBig', 'headSml', 'glow', 'bshadow', 'ring']) {
200
+ assert.match(PAGE_STYLES, new RegExp(`\\.bongos\\s+\\.${cls}\\s*\\{[^}]*animation:`),
201
+ `.${cls} should be animated in the default (non-reduced) case`);
202
+ }
203
+ });
204
+
205
+ test('the ripples are hidden under reduced motion — they rest at full stroke', () => {
206
+ const reduced = PAGE_STYLES.match(/@media\s*\(prefers-reduced-motion:reduce\)\{([^}]*\})*/);
207
+ assert.ok(reduced, 'the page carries its own reduced-motion block');
208
+ assert.match(reduced[0], /\.bongos\s+\.ring\s*\{\s*display:none/);
209
+ });
210
+
211
+ test('the self-typing eyebrow degrades to real text, not an empty span', () => {
212
+ // the script only decorates; markup must already carry the word
213
+ assert.match(HTML, /<span class="typed" id="typed">bongos<\/span>/);
214
+ assert.match(CODE, /matchMedia\('\(prefers-reduced-motion: reduce\)'\)/);
215
+ assert.match(CODE, /if \(!reduce\) \{/, 'the typing loop is skipped under reduced motion');
216
+ });
217
+
218
+ test('nothing about the platform is hardcoded — the file is portable (C5)', () => {
219
+ // the source repo link is a real, deliberate attribution; every OTHER mention
220
+ // of an origin must be a token.
221
+ const withoutSource = HTML.replace(/https:\/\/github\.com\/[^"]*/g, '');
222
+ assert.doesNotMatch(withoutSource, /cloudbongos\.com/);
223
+ });
224
+
225
+ test('the page reads without JavaScript: the spine is markup, not script-built', () => {
226
+ const body = HTML.split('<body>')[1].split('<script>')[0];
227
+ for (const copy of [
228
+ 'Building software is laying',
229
+ 'Every change is a claimed task',
230
+ 'A rank ladder the server enforces',
231
+ 'Nothing ships ungraded',
232
+ 'How a change reaches your project',
233
+ 'Start a project',
234
+ 'Enter the Hall',
235
+ ]) {
236
+ assert.ok(body.includes(copy), `"${copy}" is only reachable with scripting on`);
237
+ }
238
+ });
239
+
240
+ test('the proof band ships hidden so a failed or empty feed leaves no orphan heading', () => {
241
+ assert.match(HTML, /<section class="band" id="proof" hidden>/);
242
+ // and it is only ever revealed on a non-empty 200
243
+ assert.match(HTML, /if \(!list\.length\) return;/);
244
+ assert.match(HTML, /band\.hidden = false;/);
245
+ });
246
+
247
+ // The page script minus its comments — these assertions are about what the code
248
+ // DOES, and the comments deliberately quote the shapes being ruled out.
249
+ const CODE = HTML.split('<script>').pop().replace(/\/\*[\s\S]*?\*\//g, '');
250
+
251
+ test('the catalog claims no liveness the public feed cannot back', () => {
252
+ // listFeaturedProjects projects {origin, name, tagline, art_url} — no status,
253
+ // so a verification chip here would be a claim nothing checked.
254
+ assert.doesNotMatch(CODE, /chip (live|maybe|down)/);
255
+ assert.doesNotMatch(CODE, /ago/);
256
+ });
257
+
258
+ test('catalog values are never interpolated as HTML, and hrefs are scheme-checked', () => {
259
+ assert.doesNotMatch(CODE, /innerHTML/, 'the feed carries third-party strings');
260
+ assert.match(CODE, /u\.protocol === 'https:' \|\| u\.protocol === 'http:'/);
261
+ });
262
+
263
+ // ── the shared layer gained the primitives, and the page did not fork it ───
264
+
265
+ test('the landing links cosmos.css and does not fork the design system', () => {
266
+ assert.match(HTML, /<link rel="stylesheet" href="assets\/cosmos\.css">/);
267
+ });
268
+
269
+ test('the five primitives graduated into cosmos.css, not the page (§6)', () => {
270
+ const css = fs.readFileSync(path.join(ROOT, 'public-landing', 'assets', 'cosmos.css'), 'utf8');
271
+ assert.match(css, /^\.wrap\{/m, 'the page container');
272
+ assert.match(css, /^h2\.sec-h\{/m, 'the type scale below h1');
273
+ assert.match(css, /--pad-xl:/, 'the spacing scale');
274
+ assert.match(css, /^\.grid\{/m, 'the grid primitive');
275
+ assert.match(css, /@media \(max-width:900px\)/, 'the mid breakpoint');
276
+ // and the page must not re-declare them locally
277
+ const styles = [...HTML.matchAll(/<style>([\s\S]*?)<\/style>/g)].map((m) => m[1]).join('\n');
278
+ assert.doesNotMatch(styles, /^\.wrap\{/m, '.wrap belongs to the shared layer');
279
+ assert.doesNotMatch(styles, /^\.grid\{/m, '.grid belongs to the shared layer');
280
+ });
281
+
282
+ // ── the assertion tests/platform_boot.mjs will make once the apex flips ────
283
+
284
+ test('the page satisfies the parked platform_boot assertions', () => {
285
+ assert.match(HTML, /id="who"/);
286
+ assert.match(HTML, /Enter the Hall/);
287
+ // an ORG-level action that lives only on the gate — asserted absent there
288
+ assert.doesNotMatch(HTML, /Create a project/);
289
+ });