@bongos/core 1.19.441 → 1.19.443
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 +496 -441
- package/clients/bongos-client/index.d.ts +1 -2
- package/config/branding.neutral.json +1 -1
- package/docs/adr/0218-the-apex-root-web-surface.md +55 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +3 -20
- package/docs/api-reference.md +1 -1
- package/docs/architecture.md +2 -1
- package/docs/copy-inventory.md +363 -359
- package/docs/copy-registry.json +608 -561
- package/docs/design/mocks/hub/mine.html +1 -1
- package/docs/design/mocks/hub/picker.html +1 -1
- package/docs/design/mocks/hub/planets.html +1 -1
- package/docs/design/mocks/hub/v1.html +3 -3
- package/docs/design/mocks/hub/v2.html +2 -2
- package/docs/design/mocks/hub/v3.html +5 -5
- package/docs/design/mocks/hub/wizard.html +1 -1
- package/docs/design/mocks/landing/v1.html +1 -1
- package/docs/design/mocks/landing/v2.html +1 -1
- package/docs/design/mocks/landing/v3.html +1 -1
- package/docs/design/mocks/landing-page-mock.html +3 -3
- package/docs/design/mocks/projects-hub-mock.html +1 -1
- package/docs/design/mocks/projects-hub-variants.html +1 -1
- package/docs/design/mocks/projects-page-mock.html +1 -1
- package/docs/design/projects-hub-direction-v2.md +23 -0
- package/docs/file-map.md +3 -3
- package/docs/module-api-changelog.md +4 -0
- package/docs/modules-contract.md +9 -2
- package/docs/recipes/ui-look-before-you-ship.md +4 -4
- package/modules/copy-desk/tests/copy_flags.mjs +4 -4
- package/modules/platform-identity/routes/projects.js +2 -1
- package/modules/provisioning/provisioning.js +11 -2
- package/modules/provisioning/routes/provisioning.js +10 -5
- package/modules/public-landing/CLAUDE.md +15 -0
- package/modules/public-landing/module.json +15 -0
- package/{public-landing → modules/public-landing/public}/assets/cosmos.css +8 -0
- package/modules/public-landing/public/assets/world/r4-card-chrome.jpg +0 -0
- package/modules/public-landing/public/assets/world/r4-card-chrome.jpg.json +14 -0
- package/modules/public-landing/public/assets/world/r4-card-deepfield.jpg +0 -0
- package/modules/public-landing/public/assets/world/r4-card-deepfield.jpg.json +14 -0
- package/modules/public-landing/public/assets/world/r4-card-dust.jpg +0 -0
- package/modules/public-landing/public/assets/world/r4-card-dust.jpg.json +14 -0
- package/modules/public-landing/public/assets/world/r4-card-nebula.jpg +0 -0
- package/modules/public-landing/public/assets/world/r4-card-nebula.jpg.json +14 -0
- package/{public-landing → modules/public-landing/public}/projects-floor.states.json +28 -1
- package/{public-landing → modules/public-landing/public}/projects.html +101 -1
- package/{public-landing → modules/public-landing/public}/projects.probes.json +6 -0
- package/{public-landing → modules/public-landing/public}/projects.states.json +1 -0
- package/modules/ui-design/kit/fixtures/featured-hub.json +1 -1
- package/modules/ui-design/kit/lib.js +2 -2
- package/modules/ui-design/kit/render.js +1 -1
- package/modules/ui-design/kit/serve.js +32 -7
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/copy-inventory.js +3 -3
- package/scripts/gds/fitness-checks-identity.js +18 -1
- package/scripts/gds/publish-manifest.js +1 -1
- package/src/bongos/module-scope-map.js +9 -7
- package/src/bongos/serve-internal.js +13 -1
- package/src/module-api.js +1 -1
- package/src/module-loader/loader.js +34 -17
- package/src/module-loader/manifest-schema.js +16 -8
- package/src/platform-server.js +50 -18
- package/tests/copy_inventory.mjs +0 -0
- package/tests/cosmos_token_contrast.mjs +6 -6
- package/tests/fitness.mjs +20 -0
- package/tests/landing_legal_pages.mjs +2 -1
- package/tests/landing_page.mjs +7 -5
- package/tests/module-scope-map.mjs +2 -2
- package/tests/module_loader.mjs +4 -1
- package/tests/onboard.mjs +5 -5
- package/tests/projects_hub.mjs +105 -20
- package/tests/projects_hub_pre_uat.mjs +5 -3
- package/tests/provisioning_settings.mjs +49 -4
- package/tests/public_landing_projects.mjs +2 -2
- package/tests/terms_acceptance.mjs +1 -1
- package/tests/ui_design_kit.mjs +3 -3
- package/tests/world_tokens.mjs +3 -2
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_00.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_01.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_02.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_03.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_04.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_05.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_06.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_07.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_08.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_09.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_10.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_11.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_12.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_13.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_14.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_15.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_16.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_17.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_18.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_19.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_20.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_21.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_22.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_23.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_24.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_25.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_26.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_27.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_28.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_29.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_30.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_31.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_32.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_33.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_34.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_35.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_36.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_37.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_38.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_39.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_40.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_41.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_42.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_43.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/verro-medallion.png +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/og-card.jpg +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-bongos-chrome-a-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-bongos-chrome-a-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-bongos-chrome-b.jpg +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-bongos-chrome-b.jpg.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blob-one-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blob-one-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blob-one.jpg +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blob-one.jpg.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blobs-set-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blobs-set-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blobs-set.jpg +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blobs-set.jpg.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-bead-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-bead-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-blob-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-blob-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-pair-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-pair-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-bead-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-bead-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-blob-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-blob-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-pair-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-pair-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-obsidian-bead-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-obsidian-bead-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-obsidian-blob-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-obsidian-blob-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-obsidian-pair-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-obsidian-pair-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-space-nebula.jpg +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-space-nebula.jpg.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world.css +0 -0
- /package/{public-landing → modules/public-landing/public}/buddha.png +0 -0
- /package/{public-landing → modules/public-landing/public}/contact.html +0 -0
- /package/{public-landing → modules/public-landing/public}/index.html +0 -0
- /package/{public-landing → modules/public-landing/public}/index.probes.json +0 -0
- /package/{public-landing → modules/public-landing/public}/index.states.json +0 -0
- /package/{public-landing → modules/public-landing/public}/privacy.html +0 -0
- /package/{public-landing → modules/public-landing/public}/projects-empty.states.json +0 -0
- /package/{public-landing → modules/public-landing/public}/terms.html +0 -0
- /package/{public-landing → modules/public-landing/public}/world/assets/clouds/cloud-island-large.png +0 -0
- /package/{public-landing → modules/public-landing/public}/world/assets/clouds/cloud-puff.png +0 -0
- /package/{public-landing → modules/public-landing/public}/world/assets/creatures/creature-whale-cargo.png +0 -0
- /package/{public-landing → modules/public-landing/public}/world/assets/creatures/creature-whale-glide.png +0 -0
- /package/{public-landing → modules/public-landing/public}/world/assets/plates/plate-clouds-near.png +0 -0
- /package/{public-landing → modules/public-landing/public}/world/assets/plates/scene-establishing.png +0 -0
|
@@ -175,32 +175,49 @@ function hallWidgetScripts({ isEnabled } = {}, opts) {
|
|
|
175
175
|
return out;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
//
|
|
178
|
+
// Static web surfaces contributed by enabled modules (BV1.R84, ADR 0093 §1 /
|
|
179
|
+
// 02-module-map §6 — a web surface is first-class). A module that owns a web surface
|
|
180
|
+
// declares contributes.webSurfaces in its manifest AND ships that static dir at
|
|
181
|
+
// modules/<key>/<dir>/. A surface names WHERE it answers in one of two ways, never
|
|
182
|
+
// both and never neither (ADR 0218):
|
|
183
|
+
// { host: "status.", dir: "public" } → a Host-header PREFIX (status-UI, hall-UI)
|
|
184
|
+
// { apexRoot: true, dir: "public" } → the APEX ROOT (public-landing) — whatever
|
|
185
|
+
// host is left once the subdomains are taken
|
|
186
|
+
// This returns, for each discovered + ENABLED module that declares a surface and
|
|
187
|
+
// actually ships the directory, the SERVING descriptor its server iterates:
|
|
188
|
+
// { key, host, dir } — host is the lowercase Host-header prefix
|
|
189
|
+
// { key, apexRoot: true, dir } — NO host field at all
|
|
190
|
+
// dir is the ABSOLUTE on-disk path of the static directory in both shapes.
|
|
191
|
+
//
|
|
192
|
+
// `host: ""` is REJECTED, not treated as "the root" (ADR 0218 §3): the routers match
|
|
193
|
+
// with host.startsWith(s.host), and every string starts with "" — an empty prefix
|
|
194
|
+
// would answer on builders./status. too and make isModuleSurfaceHost() true for every
|
|
195
|
+
// request, which is the predicate the hall's own /builders guards use to step aside.
|
|
196
|
+
// A boolean cannot be mistaken for a prefix, so apex-root says so with a flag.
|
|
197
|
+
//
|
|
198
|
+
// The twin of mountModuleRoutes / hallWidgetScripts: an enabled module's surface
|
|
199
|
+
// answers, a disabled one vanishes (it's never returned, so nothing routes it — for
|
|
200
|
+
// the apex root that means the front door is simply gone and the 404 tail answers).
|
|
201
|
+
// No-op (empty list) until a module ships a web surface — same SAFE-BY-CONSTRUCTION
|
|
202
|
+
// posture as every other seam here. Pure (reads disk only). Exported + module-agnostic
|
|
203
|
+
// so both servers hardcode no key.
|
|
192
204
|
function moduleWebSurfaces({ isEnabled } = {}, opts) {
|
|
193
205
|
const out = [];
|
|
194
206
|
for (const { key, dir, manifest } of discovered(opts).loaded) {
|
|
195
207
|
if (typeof isEnabled === 'function' && !isEnabled(key)) continue;
|
|
196
208
|
const surfaces = (manifest.contributes && manifest.contributes.webSurfaces) || [];
|
|
197
209
|
for (const s of surfaces) {
|
|
198
|
-
if (!s || typeof s.
|
|
210
|
+
if (!s || typeof s.dir !== 'string') continue;
|
|
211
|
+
const apexRoot = s.apexRoot === true;
|
|
212
|
+
const hasHost = typeof s.host === 'string' && s.host !== '';
|
|
213
|
+
// exactly one of the two, never both and never neither — and `host: ""` counts
|
|
214
|
+
// as NEITHER, which is what keeps an empty prefix from matching every host
|
|
215
|
+
if (apexRoot === hasHost) continue;
|
|
199
216
|
const abs = path.join(dir, s.dir);
|
|
200
217
|
let isDir = false;
|
|
201
218
|
try { isDir = fs.statSync(abs).isDirectory(); } catch { isDir = false; }
|
|
202
|
-
if (!isDir) continue; // declared but not shipped → skip (
|
|
203
|
-
out.push({ key, host: s.host.toLowerCase(), dir: abs });
|
|
219
|
+
if (!isDir) continue; // declared but not shipped → skip (nothing routed)
|
|
220
|
+
out.push(apexRoot ? { key, apexRoot: true, dir: abs } : { key, host: s.host.toLowerCase(), dir: abs });
|
|
204
221
|
}
|
|
205
222
|
}
|
|
206
223
|
return out;
|
|
@@ -34,7 +34,9 @@
|
|
|
34
34
|
// "disciplines": ["artist"], // disciplines this module adds
|
|
35
35
|
// "skills": ["otb-…"], // slash-command skills it ships
|
|
36
36
|
// "uiSections": ["boxes"], // hall/status UI sections it owns
|
|
37
|
-
// "webSurfaces": [{ "host": "status.", "dir": "public" }], //
|
|
37
|
+
// "webSurfaces": [{ "host": "status.", "dir": "public" }], // static web surfaces. Each names a Host
|
|
38
|
+
// // PREFIX (BV1.R84) or, as { "apexRoot": true, "dir": "public" },
|
|
39
|
+
// // the APEX ROOT — never both (ADR 0218).
|
|
38
40
|
// "migrations": true // true => modules/<key>/migrations/ exists (or an array of names)
|
|
39
41
|
// },
|
|
40
42
|
// "provides": ["reward"], // OPTIONAL. seam PORTS this module registers a provider for.
|
|
@@ -84,13 +86,19 @@ const isObj = (v) => v && typeof v === 'object' && !Array.isArray(v);
|
|
|
84
86
|
const isStr = (v) => typeof v === 'string' && v.length > 0;
|
|
85
87
|
const isStrArray = (v) => Array.isArray(v) && v.every(isStr);
|
|
86
88
|
|
|
87
|
-
// A webSurface entry
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
89
|
+
// A webSurface entry names WHERE the surface answers, in exactly one of two ways
|
|
90
|
+
// (ADR 0218), plus the module-relative directory of static files to serve:
|
|
91
|
+
// { host: "status.", dir: "public" } — a lowercase Host-header PREFIX (status.<apex>)
|
|
92
|
+
// { apexRoot: true, dir: "public" } — the APEX ROOT: whatever host is left once
|
|
93
|
+
// the subdomains are taken (the front door)
|
|
94
|
+
// `dir` is required, non-empty, and must stay inside the module (no absolute / "..").
|
|
95
|
+
// A host surface's `host` must be a NON-EMPTY string: routers match with
|
|
96
|
+
// host.startsWith(host), and every string starts with "", so an empty prefix would
|
|
97
|
+
// answer on every host and silently swallow the other surfaces. That is exactly why
|
|
98
|
+
// the apex root is a boolean flag and not the empty prefix it superficially resembles.
|
|
99
|
+
const isWebSurfaceDir = (v) => isStr(v.dir) && !v.dir.startsWith('/') && !v.dir.split(/[\\/]/).includes('..');
|
|
92
100
|
const isWebSurface = (v) =>
|
|
93
|
-
isObj(v) &&
|
|
101
|
+
isObj(v) && isWebSurfaceDir(v) && (v.apexRoot === true ? !('host' in v) : isStr(v.host));
|
|
94
102
|
const isWebSurfaceArray = (v) => Array.isArray(v) && v.every(isWebSurface);
|
|
95
103
|
|
|
96
104
|
function validateManifest(obj) {
|
|
@@ -172,7 +180,7 @@ function validateManifest(obj) {
|
|
|
172
180
|
}
|
|
173
181
|
}
|
|
174
182
|
if ('webSurfaces' in obj.contributes && !isWebSurfaceArray(obj.contributes.webSurfaces)) {
|
|
175
|
-
E('contributes.webSurfaces: must be an array of { host: "<prefix>.", dir: "<module-relative dir>" } objects (dir inside the module
|
|
183
|
+
E('contributes.webSurfaces: must be an array of { host: "<prefix>.", dir: "<module-relative dir>" } or { apexRoot: true, dir: "<module-relative dir>" } objects — a surface names a Host prefix OR the apex root, never both and never neither (ADR 0218); dir stays inside the module (no absolute path or "..")');
|
|
176
184
|
}
|
|
177
185
|
if ('migrations' in obj.contributes) {
|
|
178
186
|
const m = obj.contributes.migrations;
|
package/src/platform-server.js
CHANGED
|
@@ -54,13 +54,40 @@ const { logger, requestIdMiddleware } = require('./bongos/logger');
|
|
|
54
54
|
const PORT = Number(process.env.PORT) || 3000;
|
|
55
55
|
const HOST = process.env.HOST || '127.0.0.1';
|
|
56
56
|
|
|
57
|
-
// The apex
|
|
58
|
-
// apex host used to serve).
|
|
59
|
-
//
|
|
60
|
-
// the
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
|
|
57
|
+
// The apex front door — a game-less instance's home (it replaced the bare 404 the
|
|
58
|
+
// apex host used to serve). Served only on the apex host; status./builders. are
|
|
59
|
+
// handled by mountInternalSurfaces. The branding pack is injected once at boot
|
|
60
|
+
// (theme + window.__BRANDING__), exactly as the hall pages are, so the look and the
|
|
61
|
+
// identity come from config, never from hardcode.
|
|
62
|
+
//
|
|
63
|
+
// THE SURFACE IS A MODULE (task 1003322, ADR 0218). The front door used to be a
|
|
64
|
+
// hardcoded path here; it is now modules/public-landing/public/, resolved through the
|
|
65
|
+
// loader's web-surface seam by the APEX-ROOT declaration ({ apexRoot: true, dir }) —
|
|
66
|
+
// the one shape that seam grew for it, because the apex is not a Host PREFIX like
|
|
67
|
+
// "status."/"builders." but whatever is left once the subdomains are taken.
|
|
68
|
+
//
|
|
69
|
+
// Resolved by PREDICATE, never by key: this file names no module (ADR 0083 §Decision
|
|
70
|
+
// #4), so an instance shipping its own front door gets the apex by declaring apexRoot.
|
|
71
|
+
// Lazy + memoised so importing this module reads no disk and a test can build the app
|
|
72
|
+
// after setting up its own module state.
|
|
73
|
+
//
|
|
74
|
+
// null when NO enabled module claims the apex root — the module is disabled, or the
|
|
75
|
+
// instance ships no landing at all. Every consumer below already treats an absent page
|
|
76
|
+
// as "fall through", so the front door simply vanishes and the 404 tail answers.
|
|
77
|
+
let _landingDir;
|
|
78
|
+
function landingDir() {
|
|
79
|
+
if (_landingDir === undefined) {
|
|
80
|
+
// The apex root is singular by definition — two modules cannot both be "everything
|
|
81
|
+
// that is not a subdomain". Serve the first and SAY so, rather than silently
|
|
82
|
+
// shadowing one of them.
|
|
83
|
+
const claims = moduleLoader.moduleWebSurfaces({ isEnabled: isModuleEnabled }).filter((s) => s.apexRoot);
|
|
84
|
+
if (claims.length > 1) {
|
|
85
|
+
logger.warn({ keys: claims.map((s) => s.key) }, 'more than one enabled module declares an apex-root web surface — serving the first');
|
|
86
|
+
}
|
|
87
|
+
_landingDir = claims.length ? claims[0].dir : null;
|
|
88
|
+
}
|
|
89
|
+
return _landingDir;
|
|
90
|
+
}
|
|
64
91
|
// ── CONTENT-STAMPED STATIC ASSETS (task 1002766) ──
|
|
65
92
|
// The landing HTML is `cf-cache-status: DYNAMIC` and never cached, but the
|
|
66
93
|
// assets it references come back `Cache-Control: public, max-age=14400` — four
|
|
@@ -80,7 +107,7 @@ const LANDING_DIR = path.join(__dirname, '..', 'public-landing');
|
|
|
80
107
|
// shape serve-internal.js already stamps onto the hall's own scripts.
|
|
81
108
|
//
|
|
82
109
|
// Stamped by RESOLUTION, not by a path allow-list: a URL is an asset if it
|
|
83
|
-
// actually names a file under
|
|
110
|
+
// actually names a file under the landing surface. Routes (`/projects`, `/create`)
|
|
84
111
|
// and API paths resolve to nothing and are left alone, so a new asset is
|
|
85
112
|
// stamped the day it is added and nobody has to remember to list it.
|
|
86
113
|
const _assetStamps = new Map();
|
|
@@ -88,9 +115,10 @@ function assetStamp(url) {
|
|
|
88
115
|
if (!_assetStamps.has(url)) {
|
|
89
116
|
let v = '';
|
|
90
117
|
try {
|
|
91
|
-
const
|
|
118
|
+
const root = landingDir();
|
|
119
|
+
const abs = path.resolve(root, '.' + url);
|
|
92
120
|
// never let a template's own text walk out of the landing dir
|
|
93
|
-
if (abs.startsWith(
|
|
121
|
+
if (abs.startsWith(root + path.sep)) {
|
|
94
122
|
v = require('node:crypto').createHash('sha256')
|
|
95
123
|
.update(fs.readFileSync(abs)).digest('hex').slice(0, 8);
|
|
96
124
|
}
|
|
@@ -112,14 +140,14 @@ function landingPage(file) {
|
|
|
112
140
|
try {
|
|
113
141
|
// stamped LAST, so anything a later transform introduces is stamped too
|
|
114
142
|
html = stampAssets(injectBrandingGlobal(stripApexOnly(
|
|
115
|
-
fs.readFileSync(path.join(
|
|
116
|
-
} catch { /* absent → '' */ }
|
|
143
|
+
fs.readFileSync(path.join(landingDir(), file), 'utf8'))));
|
|
144
|
+
} catch { /* absent, or no module claims the apex root → '' */ }
|
|
117
145
|
_landingPages.set(file, html);
|
|
118
146
|
}
|
|
119
147
|
return _landingPages.get(file);
|
|
120
148
|
}
|
|
121
149
|
|
|
122
|
-
// THE APEX CONDITIONAL (task 1002748).
|
|
150
|
+
// THE APEX CONDITIONAL (task 1002748). The landing surface ships wholesale to every
|
|
123
151
|
// instance, so one file has to render two shapes: the platform's own front door,
|
|
124
152
|
// and a child project's. Sections that are true only of the platform — the
|
|
125
153
|
// catalog of OTHER instances, the "capture-resistant commons" claim, the door
|
|
@@ -231,11 +259,15 @@ function buildPlatformApp() {
|
|
|
231
259
|
res.type('html').send(html);
|
|
232
260
|
});
|
|
233
261
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
262
|
+
// Assets fall through to the 404 tail when no module claims the apex root — the
|
|
263
|
+
// handler is not mounted at all rather than pointed at a missing directory.
|
|
264
|
+
const landingAssets = landingDir() && express.static(landingDir(), { index: false, fallthrough: true });
|
|
265
|
+
if (landingAssets) {
|
|
266
|
+
app.use((req, res, next) => {
|
|
267
|
+
if (!isApexHost(req)) return next();
|
|
268
|
+
return landingAssets(req, res, next);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
239
271
|
|
|
240
272
|
// Non-game tail. Anything that did not resolve to /api/gds, the status host,
|
|
241
273
|
// or the builders' hall lands here. There is NO game to fall through to, so:
|
package/tests/copy_inventory.mjs
CHANGED
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Guards the WCAG AA contrast of the cosmos.css token layer (task 1002749).
|
|
2
2
|
//
|
|
3
|
-
// The tokens are the ONLY input here: this test PARSES
|
|
3
|
+
// The tokens are the ONLY input here: this test PARSES the landing surface’s assets/cosmos.css
|
|
4
4
|
// and resolves var()/color-mix() itself, so it measures what the surfaces actually paint.
|
|
5
5
|
// Duplicating the hex values would prove nothing.
|
|
6
6
|
//
|
|
@@ -13,10 +13,10 @@ import fs from 'node:fs';
|
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
15
15
|
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
);
|
|
16
|
+
const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
17
|
+
// the front door is a module since task 1003322 (ADR 0218) — one constant, so the path moves once
|
|
18
|
+
const LANDING = path.join(ROOT, 'modules', 'public-landing', 'public');
|
|
19
|
+
const CSS = path.join(LANDING, 'assets', 'cosmos.css');
|
|
20
20
|
const src = fs.readFileSync(CSS, 'utf8');
|
|
21
21
|
|
|
22
22
|
// The two sheets that actually ship, comment-stripped. Several static guards
|
|
@@ -76,7 +76,7 @@ const darkMedia = declarations(blockAfter(':root:not([data-mode="light"]){'));
|
|
|
76
76
|
// The dark blocks are :root[data-mode="dark"] and :root:not([data-mode="light"]) —
|
|
77
77
|
// both (0,2,0) — so they outrank the pack's (0,1,0) and win back.
|
|
78
78
|
const packColors = JSON.parse(
|
|
79
|
-
fs.readFileSync(path.join(
|
|
79
|
+
fs.readFileSync(path.join(ROOT, 'config', 'branding.neutral.json'), 'utf8')
|
|
80
80
|
).theme.ui.colors;
|
|
81
81
|
|
|
82
82
|
// mirrors COLOR_VARS in src/bongos/serve-internal.js
|
package/tests/fitness.mjs
CHANGED
|
@@ -660,6 +660,26 @@ test('identity gate skips comments + instance-injected tokens (no false positive
|
|
|
660
660
|
assert.equal(scanPromptIdentity('x.html', '<title>{{worldName}} — the gate</title>').length, 0, 'a {{worldName}} token carries no literal');
|
|
661
661
|
});
|
|
662
662
|
|
|
663
|
+
// task 1003322 / ADR 0218 — the front door became modules/public-landing/, which put
|
|
664
|
+
// the landing pages under this gate for the first time. Its apex-only regions name
|
|
665
|
+
// the projects HOSTED on the hub; the server deletes them on every non-hub instance
|
|
666
|
+
// (stripApexOnly), so they cannot produce the failure this gate names — "a non-OTB
|
|
667
|
+
// instance renders ITS OWN brand". Everything outside the sentinels still fails.
|
|
668
|
+
test('identity gate: an apex-only region is exempt, and only that region (ADR 0218)', () => {
|
|
669
|
+
const { scanPromptIdentity } = fitness;
|
|
670
|
+
const inside = '<ul>\n<!--apex-only:start-->\n<li>Off the Boats</li>\n<!--apex-only:end-->\n</ul>';
|
|
671
|
+
assert.equal(scanPromptIdentity('x.html', inside).length, 0, 'a literal the server strips on a child instance is exempt');
|
|
672
|
+
|
|
673
|
+
const outside = '<h1>Off the Boats</h1>\n<!--apex-only:start-->\n<li>ok</li>\n<!--apex-only:end-->';
|
|
674
|
+
const hits = scanPromptIdentity('x.html', outside);
|
|
675
|
+
assert.equal(hits.length, 1, 'a literal OUTSIDE the sentinels still fails — the exemption is the region, not the file');
|
|
676
|
+
assert.match(hits[0], /^x\.html:1 /, 'blanking preserves line numbers so the violation still points at the real line');
|
|
677
|
+
|
|
678
|
+
// an UNCLOSED start sentinel must not blank the rest of the file into silence
|
|
679
|
+
const unclosed = '<!--apex-only:start-->\n<li>ok</li>\n<h1>Off the Boats</h1>';
|
|
680
|
+
assert.equal(scanPromptIdentity('x.html', unclosed).length, 1, 'an unterminated region exempts nothing');
|
|
681
|
+
});
|
|
682
|
+
|
|
663
683
|
// task 1002756 — the two holes that let migrate.sh's founder DB default survive:
|
|
664
684
|
// (1) the stripper could be OPENED by a `/*` inside a `//` line comment (a real
|
|
665
685
|
// `*/` hundreds of lines later then swallowed everything between — provision.js
|
|
@@ -22,7 +22,8 @@ import { createRequire } from 'node:module';
|
|
|
22
22
|
|
|
23
23
|
const require = createRequire(import.meta.url);
|
|
24
24
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
25
|
-
|
|
25
|
+
// the front door is a module since task 1003322 (ADR 0218) — one constant, so the path moves once
|
|
26
|
+
const LANDING = path.join(ROOT, 'modules', 'public-landing', 'public');
|
|
26
27
|
const read = (f) => fs.readFileSync(path.join(LANDING, f), 'utf8');
|
|
27
28
|
|
|
28
29
|
const platformMod = require('../src/platform-server.js');
|
package/tests/landing_page.mjs
CHANGED
|
@@ -40,8 +40,10 @@ import { createRequire } from 'node:module';
|
|
|
40
40
|
|
|
41
41
|
const require = createRequire(import.meta.url);
|
|
42
42
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
43
|
-
|
|
44
|
-
const
|
|
43
|
+
// the front door is a module since task 1003322 (ADR 0218) — one constant, so the path moves once
|
|
44
|
+
const LANDING = path.join(ROOT, 'modules', 'public-landing', 'public');
|
|
45
|
+
const HTML = fs.readFileSync(path.join(LANDING, 'index.html'), 'utf8');
|
|
46
|
+
const WORLD = fs.readFileSync(path.join(LANDING, 'assets', 'world.css'), 'utf8');
|
|
45
47
|
|
|
46
48
|
const { stripApexOnly } = require('../src/platform-server.js');
|
|
47
49
|
const { applyBrandTokens } = require('../src/bongos/serve-internal.js');
|
|
@@ -186,8 +188,8 @@ test('the social card is the world card, stamped onto THIS instance', () => {
|
|
|
186
188
|
assert.equal(m[1], 'https://spike-harbor.example/assets/world/og-card.jpg');
|
|
187
189
|
}
|
|
188
190
|
assert.ok(
|
|
189
|
-
fs.existsSync(path.join(
|
|
190
|
-
'the og card the head names must exist under
|
|
191
|
+
fs.existsSync(path.join(LANDING, 'assets', 'world', 'og-card.jpg')),
|
|
192
|
+
'the og card the head names must exist under the landing surface',
|
|
191
193
|
);
|
|
192
194
|
});
|
|
193
195
|
|
|
@@ -210,7 +212,7 @@ test('a pack with no hall of its own falls back to its own apex, not the platfor
|
|
|
210
212
|
|
|
211
213
|
test('a pack with NO domains drops the hall pill server-side rather than stamping href="" (task 1003242)', () => {
|
|
212
214
|
// the hub has no client scrubber, so an empty token must never reach it as a dead link
|
|
213
|
-
const HUB = fs.readFileSync(path.join(
|
|
215
|
+
const HUB = fs.readFileSync(path.join(LANDING, 'projects.html'), 'utf8');
|
|
214
216
|
assert.match(HUB, /data-link-hall href="\{\{buildersOrigin\}\}"/, 'the hub pill is token-stamped');
|
|
215
217
|
const none = applyBrandTokens(HUB, BRAND, '', '');
|
|
216
218
|
assert.doesNotMatch(none, /href=""/, 'no empty href survives the stamp');
|
|
@@ -100,7 +100,7 @@ test('provisioning + public-landing joined the roster (task 1002690) — territo
|
|
|
100
100
|
const prov = scope.globsForModules(['provisioning']);
|
|
101
101
|
assert.ok(prov.includes('modules/provisioning/'), 'provisioning owns its module directory');
|
|
102
102
|
assert.ok(prov.includes('scripts/gds/provision.js'), 'the control-plane runner travels with its domain');
|
|
103
|
-
assert.ok(scope.globsForModules(['public-landing']).includes('public-landing/'), 'public-landing owns its surface directory');
|
|
103
|
+
assert.ok(scope.globsForModules(['public-landing']).includes('modules/public-landing/'), 'public-landing owns its surface directory');
|
|
104
104
|
});
|
|
105
105
|
|
|
106
106
|
test('platform-identity joined the roster (task 1002322) — the catalog territory goal 1000046 walls', () => {
|
|
@@ -127,7 +127,7 @@ test('platform-identity joined the roster (task 1002322) — the catalog territo
|
|
|
127
127
|
'modules/platform-identity/routes/projects.js', // the feed + invite routes
|
|
128
128
|
'modules/status-ui/public/explore.js', // orb render-type surface
|
|
129
129
|
'modules/hall-ui/public/my-projects.js', // owner settings / applications queue
|
|
130
|
-
'public-landing/projects.html', // the creation step, ?view=new since task 1002763
|
|
130
|
+
'modules/public-landing/public/projects.html', // the creation step, ?view=new since task 1002763
|
|
131
131
|
'modules/provisioning/', // module-bundle selection at provision time
|
|
132
132
|
]) {
|
|
133
133
|
assert.equal(covers(touch), true, `wall does not cover ${touch}`);
|
package/tests/module_loader.mjs
CHANGED
|
@@ -179,6 +179,9 @@ test('the live tree discovers the extracted modules cleanly (BV1.R43/R48/R47/R49
|
|
|
179
179
|
// And ui-design (task 1003324 / ADR 0197) — the design contract, its adapters
|
|
180
180
|
// and the /design playbook as a platform capability: declaration-only (three
|
|
181
181
|
// skills, no routes/port), default-on because every instance has a face.
|
|
182
|
+
// And public-landing (task 1003322 / ADR 0218) — the front door, the THIRD web
|
|
183
|
+
// surface after status-ui and hall-ui and the first to declare apexRoot instead
|
|
184
|
+
// of a Host prefix; default-on because every instance answers at its own apex.
|
|
182
185
|
// All are discovered; none has validation
|
|
183
186
|
// errors or coreVersion mismatches. Order is the readdir sort.
|
|
184
187
|
//
|
|
@@ -203,7 +206,7 @@ test('the live tree discovers the extracted modules cleanly (BV1.R43/R48/R47/R49
|
|
|
203
206
|
// That is precisely how the agents regression reached an open PR (task 1002485).
|
|
204
207
|
loader._reset();
|
|
205
208
|
const snap = loader.loaderSnapshot();
|
|
206
|
-
assert.deepEqual(snap.loaded, ['agents', 'autonomy', 'builder-settings', 'copy-desk', 'dev-box', 'discord', 'economy', 'government', 'grading', 'hall-ui', 'ideas', 'lifecycle', 'memory', 'onboarding', 'platform-identity', 'provisioning', 'security', 'sessions', 'status-ui', 'ui-design']);
|
|
209
|
+
assert.deepEqual(snap.loaded, ['agents', 'autonomy', 'builder-settings', 'copy-desk', 'dev-box', 'discord', 'economy', 'government', 'grading', 'hall-ui', 'ideas', 'lifecycle', 'memory', 'onboarding', 'platform-identity', 'provisioning', 'public-landing', 'security', 'sessions', 'status-ui', 'ui-design']);
|
|
207
210
|
assert.deepEqual(snap.errors, []);
|
|
208
211
|
assert.deepEqual(snap.skipped, []);
|
|
209
212
|
loader._reset();
|
package/tests/onboard.mjs
CHANGED
|
@@ -64,7 +64,7 @@ test('every form OFFERS the optional step and names what skipping costs — skil
|
|
|
64
64
|
assert.match(cli, /off the public map/, 'and says what a missing description means');
|
|
65
65
|
|
|
66
66
|
// the web form
|
|
67
|
-
const hub = read('public-landing/projects.html');
|
|
67
|
+
const hub = read('modules/public-landing/public/projects.html');
|
|
68
68
|
assert.match(hub, /id="panel-detail"/, 'the wizard ships the panel');
|
|
69
69
|
assert.match(hub, /id="skipDetail"/, 'with a skip control of its own');
|
|
70
70
|
|
|
@@ -168,7 +168,7 @@ test('every render surface carries the terminal hall step (task 1002703)', () =>
|
|
|
168
168
|
const cli = read('scripts/gds/onboard.js');
|
|
169
169
|
assert.match(cli, /builders hall/i, 'the CLI walkthrough ends at the hall');
|
|
170
170
|
assert.match(cli, /bongos dev/, 'the CLI offers the local-hall one-liner when no domain');
|
|
171
|
-
const hubHtml = read('public-landing/projects.html');
|
|
171
|
+
const hubHtml = read('modules/public-landing/public/projects.html');
|
|
172
172
|
const enterHall = hubHtml.match(/data-onboard-step="enter-hall"[^]*?<\/li>/g) || [];
|
|
173
173
|
assert.ok(enterHall.length >= 1, 'the ?view=new wizard tags the enter-hall step');
|
|
174
174
|
assert.match(enterHall.join(''), /\/builders/, 'the wizard points at the hall address');
|
|
@@ -180,7 +180,7 @@ test('the wizard done panel is plan-driven, manifest-driven, and owner-honest (t
|
|
|
180
180
|
// The wizard lives inside the hub page since task 1002763.
|
|
181
181
|
const { readFileSync } = require('node:fs');
|
|
182
182
|
const path = require('node:path');
|
|
183
|
-
const src = readFileSync(path.join(import.meta.dirname, '..', 'public-landing/projects.html'), 'utf8');
|
|
183
|
+
const src = readFileSync(path.join(import.meta.dirname, '..', 'modules/public-landing/public/projects.html'), 'utf8');
|
|
184
184
|
// gap 2 (idea 1000675): the checklist renders FROM the canonical plan endpoint
|
|
185
185
|
assert.match(src, /provisioning\/onboard-plan/, 'the done panel fetches the canonical plan');
|
|
186
186
|
// gap 3 (idea 1000676): the ADR 0133 one-click manifest flow has a UI caller again
|
|
@@ -203,7 +203,7 @@ test('the wizard gate trust link points at a live artifact, not the dead private
|
|
|
203
203
|
// "open source" is the license itself.
|
|
204
204
|
const { readFileSync } = require('node:fs');
|
|
205
205
|
const path = require('node:path');
|
|
206
|
-
const src = readFileSync(path.join(import.meta.dirname, '..', 'public-landing/projects.html'), 'utf8');
|
|
206
|
+
const src = readFileSync(path.join(import.meta.dirname, '..', 'modules/public-landing/public/projects.html'), 'utf8');
|
|
207
207
|
assert.doesNotMatch(src, /Claude-OTB/, 'the dead repo link is gone');
|
|
208
208
|
assert.match(src, /https:\/\/www\.gnu\.org\/licenses\/agpl-3\.0/, 'the trust link resolves (the AGPL-3.0 license text)');
|
|
209
209
|
});
|
|
@@ -266,7 +266,7 @@ test('the web form (?view=new) declares EXACTLY the canonical human steps — dr
|
|
|
266
266
|
const path = require('node:path');
|
|
267
267
|
const humanKeys = ob.onboardPlan().filter((s) => s.kind === 'human').map((s) => s.key).sort();
|
|
268
268
|
const surfaces = {
|
|
269
|
-
'/projects?view=new wizard': 'public-landing/projects.html',
|
|
269
|
+
'/projects?view=new wizard': 'modules/public-landing/public/projects.html',
|
|
270
270
|
};
|
|
271
271
|
for (const [name, rel] of Object.entries(surfaces)) {
|
|
272
272
|
const src = readFileSync(path.join(import.meta.dirname, '..', rel), 'utf8');
|