/** * capabilities.mjs — the five capability groups of PLAN.md §10, as data. * * --------------------------------------------------------------------------------------- * WHY THIS IS DATA AND NOT MARKUP * --------------------------------------------------------------------------------------- * The homepage names these groups, `/features/` (phase 4) expands them, and `/modules/` * explains the core/module split they encode. Three pages listing the same capabilities in * three hand-maintained lists is how a site ends up advertising something that was removed, * which §1 forbids. One list, read by all three. * * --------------------------------------------------------------------------------------- * THE PART THAT IS A CHECK, NOT A LIST * --------------------------------------------------------------------------------------- * "Game intelligence" is the only group core does not supply — it comes from whichever * module is installed, and today that is `module-uo`. Its items therefore carry the * capability slugs the module actually declares in its `module.json`, and * `assertCapabilityCoverage()` fails the build if the two lists drift apart. * * That closes a real gap. `platform.json` holds `moduleUoCapabilities` and * `scripts/checkFacts.mjs` re-reads it from the module's manifest on every build — so the * day `module-uo` gains a capability, the JSON goes red and someone updates it. Before this * function, updating the JSON was the end of it and the page kept the old list. Now the * page is what goes red next. * * Note that slugs are NOT one-per-item in either direction: `shard` is the source of four * separate user-facing capabilities, and the marketplace draws on `market` and `cliloc` * together (item names arrive as cliloc ids and are resolved against the shard's own * string table). The check is coverage in both directions, not a bijection. */ /** * Community — core, game-agnostic. Everything here works on a deployment with no game * module installed at all. */ const community = { id: 'community', /** * Core, not module-supplied. Stated on every group rather than only on the one that is * true, so the shape of a group is uniform — the homepage reads this field on all five, * and an inferred union that carries it on one member is an error waiting for the next * template that touches it. */ moduleSupplied: false, title: 'Community', summary: 'The site your players actually use, none of which knows what game you run.', items: [ { label: 'Teams' }, { label: 'Team forums' }, { label: 'Notifications' }, { label: 'Wiki' }, { label: 'News and newsletter' }, { label: 'Player self-service' }, ], }; /** * Game intelligence — module-supplied. The `caps` arrays are the contract with * `platform.json`; see `assertCapabilityCoverage` below. */ const gameIntelligence = { id: 'game-intelligence', title: 'Game intelligence', moduleSupplied: true, summary: 'Supplied by the installed game module, not by the core site. Today that module is ' + 'module-uo, and this is what it publishes from a live shard.', items: [ { label: 'Live server status', caps: ['shard'] }, { label: 'Economy and activity', caps: ['shard'] }, { label: 'Character sheets', caps: ['shard'] }, { label: 'Points and loyalty boards', caps: ['shard'] }, { label: 'Player-vendor marketplace', caps: ['market', 'cliloc'] }, { label: 'Houses and IDOC decay', caps: ['houses'] }, { label: 'Spawn atlas', caps: ['atlas'] }, { label: 'Champion boards', caps: ['champs'] }, { label: 'Guilds', caps: ['guilds'] }, { label: 'City governors', caps: ['governors'] }, ], }; const administration = { id: 'administration', moduleSupplied: false, title: 'Administration', summary: 'Running the place, with a record of who did what.', items: [ { label: 'Roles and permissions' }, { label: 'Moderation and appeals' }, { label: 'Content reports' }, { label: 'Append-only audit log' }, { label: 'Bot scoring and IP bans' }, { label: 'Module management' }, { label: 'The game-server connection' }, ], }; const integration = { id: 'integration', moduleSupplied: false, title: 'Integration', summary: 'The seams that let other things reach in — and one game reach out.', items: [ { label: 'Modules' }, { label: 'The sidecar bridge' }, { label: 'Discord: slash commands, notifications, voice' }, { label: 'Mobile and push' }, { label: 'SSO over OAuth2 / OIDC' }, ], }; const infrastructure = { id: 'infrastructure', moduleSupplied: false, title: 'Infrastructure', summary: 'How it runs, and who it answers to.', items: [ { label: 'Self-hosted, start to finish' }, { label: 'Docker, with prebuilt pull-only images' }, { label: 'Branding as data, not a rebuild' }, { label: 'OpenAPI 3.0 for the whole API' }, ], }; export const capabilityGroups = [ community, gameIntelligence, administration, integration, infrastructure, ]; /** * Fails the build when the module's declared capabilities and this page's list disagree. * * Called from the component rather than from a check script on purpose: the failure needs * to reach whoever is editing the page, and an Astro build error names the component. It * also means the rule cannot be skipped by running `astro build` without `npm run verify`. */ export function assertCapabilityCoverage(declared) { const claimed = new Set(); for (const item of gameIntelligence.items) { for (const cap of item.caps || []) claimed.add(cap); } const known = new Set(declared); const unlisted = declared.filter((cap) => !claimed.has(cap)); const invented = [...claimed].filter((cap) => !known.has(cap)); if (!unlisted.length && !invented.length) return; const lines = []; if (unlisted.length) { lines.push( `the installed module declares ${unlisted.map((c) => `"${c}"`).join(', ')}, which no ` + `capability on the homepage claims — the site is under-selling what it can show.` ); } if (invented.length) { lines.push( `the homepage claims ${invented.map((c) => `"${c}"`).join(', ')}, which the module no ` + `longer declares — the site is advertising something that is gone (§1).` ); } throw new Error( `src/data/capabilities.mjs disagrees with platform.json's moduleUoCapabilities:\n` + lines.map((line) => ` - ${line}`).join('\n') + `\n\nUpdate the "Game intelligence" items, or the JSON if the module itself changed.\n` ); }