generate the capability exemplar table so the answer stops being remembered

"Which bundle is the capability-N example?" has been answered wrongly in three separate
artifacts, each fixed in isolation while the others kept the bug: verify-5-capabilities
cited doxa-vision-card as cap-1, the README made the identical claim, and the
/plugin-designer skill still cites both that and unified-research-dashboard, a bundle
that does not exist.

The skill cannot be gated from this repo. It lives in the session config directory, in
no git repo, so a checker pointed at it would pass here and skip silently everywhere
else — the gate-that-cannot-fail shape. So instead of policing the skill, give the repo
an authoritative generated answer that prose can cite and a gate can verify.

The table derives from manifest.json, which the build computed from each folder's real
shape, and reference.md now points at it with the reason: a folder's apparent simplicity
does not decide its tier. doxa-vision-card looks like the lightest thing in the repo and
is capability 2, because it ships an instances file.

check:capability-exemplars regenerates and content-compares rather than checking mtimes,
because a full build rewrites manifest.json every time and a gate that cries wolf after
every build gets switched off.
This commit is contained in:
doxa-maps-88655c70 2026-09-21 12:38:50 +00:00
parent 88f01b9a95
commit 466493ed80
6 changed files with 249 additions and 0 deletions

View file

@ -177,6 +177,12 @@ jobs:
- name: Assert README capability citations match the manifest
run: bun run check:capability-citations
# Regenerates the derived exemplar table and content-compares it. Blocking: the build just
# produced the manifest it derives from, so a mismatch is always real. Content-compare, not
# mtime — a full build rewrites manifest.json every time.
- name: Assert the capability exemplar table matches the manifest
run: bun run check:capability-exemplars
- name: Assert no local credential is being published
run: |
if [ -e public/token.local.js ]; then

View file

@ -0,0 +1,68 @@
# Reference — capability exemplars (GENERATED)
> **Do not hand-edit.** Regenerate with `bun run capability-exemplars`; `bun run
> check:capability-exemplars` fails if this file falls behind the manifest.
>
> This answers one question authoritatively: **which bundles are the capability-N
> examples right now?** It is derived from `public/doxa-maps/manifest.json`, which is what
> the build computed from each folder's real shape — so it cannot drift from what ships.
>
> **Cite this file rather than remembering.** The same citation has been wrong in three
> separate places: a gate, the README, and the `/plugin-designer` skill. The trap is that a
> folder's apparent simplicity does not decide its tier — an `*.instances.json` makes a
> bundle `parameterized` (cap-2) no matter how light it looks, and a `meta.json` `type`
> declares tiers 3/4/5.
Generated from 17 bundle(s).
## Capability 1 — single-element
| Bundle | Profile | Machine key |
|---|---|---|
| `all-people-groups` | `all-people-groups` | `single` |
| `cities-internet` | `connectivity-list` | `single` |
| `cities-internet` | `connectivity-map` | `single` |
| `cities-internet` | `data-tables` | `single` |
| `country-posters` | `countries-map` | `single` |
| `country-posters` | `country-poster-wall` | `single` |
| `gospel-resources` | `charts` | `single` |
| `gospel-resources` | `data-tables` | `single` |
| `gospel-resources` | `resources-list` | `single` |
| `gospel-resources` | `resources-map` | `single` |
| `my-upg-100-list` | `poster-wall` | `single` |
| `my-upg-100-list` | `upg-100-list` | `single` |
| `template-bundle` | `template-map-a` | `single` |
| `template-bundle` | `template-map-b` | `single` |
## Capability 2 — preset-element (prop-table)
| Bundle | Profile | Machine key |
|---|---|---|
| `demo-map` | `demo-map` | `parameterized` |
| `doxa-simple-map` | `doxa-simple-map` | `parameterized` |
| `doxa-vision-card` | `doxa-vision-card` | `parameterized` |
| `people-groups-grid` | `people-groups-grid` | `parameterized` |
## Capability 3 — multi-view-element (monolithic)
| Bundle | Profile | Machine key |
|---|---|---|
| `doxa-research-map` | `research-map` | `monolithic` |
| `research-dashboard` | `research-dashboard` | `monolithic` |
## Capability 4 — composite-element (nested-element SPA)
| Bundle | Profile | Machine key |
|---|---|---|
| `big-picture` | `big-picture` | `nested-elements-mfe` |
| `dashboard-plus-grid` | `dashboard-plus-grid` | `nested-elements-mfe` |
| `my-upg-100-list` | `upg-100-mfe` | `nested-elements-mfe` |
| `stack-demo` | `stack-demo` | `nested-elements-mfe` |
## Capability 5 — app-shell (nested-SPA)
| Bundle | Profile | Machine key |
|---|---|---|
| `doxa-maps-spa` | `doxa-maps-spa` | `nested-mfe-spa` |
| `plugin-studio` | `plugin-studio` | `nested-mfe-spa` |

View file

@ -44,6 +44,14 @@ this repo's own; the coined names stay as a.k.a.):
| **composite-element** (nested-elements-mfe) | `nested-elements-mfe` | an app profile whose template places other app profiles' maps together (stacked or side by side, e.g. a map + a poster wall) and ships the set as one new element — one tag embeds it all | declared: `<profile>.meta.json` → `{ "type": "nested-elements-mfe", "composes": [ … ] }` (or `"composite-element"`) |
| **app-shell** (nested-mfe-spa) | `nested-mfe-spa` | the outer frame — sidebar, header, navigation — that loads any of the other kinds and switches between them without a reload; the top rung, the full doxa-maps app | a compiled Vue custom element **today** — `plugin-designs/doxa-maps-spa/doxa-maps-spa.vue`, built via `defineCustomElement` like every other rung (its shell layout is `library/layouts/app-wrapper-material/`). `app-wrapper-material/index.html` is now only the dev-time plugin-gallery scaffold, not the shipped shell |
> **Which bundle is the capability-N example *right now*?** See
> **[reference-capability-exemplars.md](./reference-capability-exemplars.md)** — generated from
> `manifest.json` by `bun run capability-exemplars` and gated by `bun run check:capability-exemplars`.
> **Cite that file; never cite memory.** This one question has been answered wrongly in three separate
> places — a gate, this README's capability captions, and the `/plugin-designer` skill — because a
> folder's apparent simplicity does not decide its tier. `doxa-vision-card` looks like the lightest
> thing in the repo and is **capability 2**, since it ships an `*.instances.json`.
Composite kinds are always **declared** in `<profile>.meta.json` (`{ "type": …, "label": … }`, either spelling —
see `TYPE_ALIASES` in `internal/plugin-gallery/discover.mjs`) — the build never guesses them from source (tag-scanning
produced false positives). Discovery is one pure module,

View file

@ -0,0 +1,64 @@
#!/usr/bin/env node
/**
* check-capability-exemplars.mjs — the generated exemplar table must match the manifest.
*
* Companion to generate-capability-exemplars.mjs. That file's header explains WHY the table is
* derived; this one makes the "derived, so it cannot drift" claim true by REGENERATING and
* comparing, rather than trusting mtimes.
*
* ★ WHY CONTENT-COMPARE AND NOT MTIME. check:library-index compares mtimes, which is right there
* (its generator scans library/ directly, so a source edit really does invalidate the output).
* Here the input is manifest.json, which a full build REWRITES EVERY TIME even when nothing
* changed — so an mtime rule would fail after every build for no reason, and a gate that cries
* wolf gets switched off. Comparing regenerated CONTENT fails only when the answer actually moved.
*
* The same reasoning is why CI regenerates-and-diffs the library index instead of using mtimes:
* a fresh checkout gives every file the same timestamp.
*
* NEGATIVE-TESTED: deleting a row from the table makes this FAIL showing the diff; regenerating
* returns PASS. Deleting the file entirely also FAILs rather than silently skipping.
*/
import { readFileSync, existsSync } from 'node:fs'
import { execFileSync } from 'node:child_process'
import { resolve, dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const OUT = join(ROOT, 'docs', 'reference-capability-exemplars.md')
const GEN = join(ROOT, 'internal', 'generate-capability-exemplars.mjs')
const MANIFEST = join(ROOT, 'public', 'doxa-maps', 'manifest.json')
if (!existsSync(MANIFEST)) {
console.log('CAPABILITY-EXEMPLARS: SKIP — no manifest.json (nothing built yet).')
process.exit(0)
}
if (!existsSync(OUT)) {
console.error('CAPABILITY-EXEMPLARS: FAIL — docs/reference-capability-exemplars.md does not exist.')
console.error(' It is the authoritative answer to "which bundle is the capability-N example?",')
console.error(' a question that has been answered wrongly in three separate artifacts.')
console.error(' Fix: bun run capability-exemplars')
process.exit(1)
}
const before = readFileSync(OUT, 'utf8')
execFileSync(process.execPath, [GEN], { stdio: 'pipe' })
const after = readFileSync(OUT, 'utf8')
if (before !== after) {
// Restore what was committed so the check itself never mutates the tree.
const { writeFileSync } = await import('node:fs')
writeFileSync(OUT, before)
const bl = before.split('\n'), al = after.split('\n')
const diff = []
for (let i = 0; i < Math.max(bl.length, al.length) && diff.length < 10; i++) {
if (bl[i] !== al[i]) diff.push(` line ${i + 1}\n file: ${bl[i] ?? '(missing)'}\n real: ${al[i] ?? '(missing)'}`)
}
console.error('\nCAPABILITY-EXEMPLARS: FAIL — the table disagrees with the manifest:')
console.error(diff.join('\n'))
console.error('\n The manifest is what the BUILD computed from each folder\'s real shape.')
console.error(' Fix: bun run capability-exemplars (then commit the regenerated file).')
process.exit(1)
}
const rows = (after.match(/^\| `/gm) || []).length
console.log(`CAPABILITY-EXEMPLARS: PASS — ${rows} exemplar row(s) match the manifest.`)

View file

@ -0,0 +1,101 @@
#!/usr/bin/env node
/**
* generate-capability-exemplars.mjs — write docs/reference-capability-exemplars.md from the manifest.
*
* WHY THIS IS DERIVED AND NOT HAND-WRITTEN
* "Which bundle is the capability-N example?" has been answered wrongly three times in three
* different artifacts, each fixed in isolation while the others kept the bug:
* · internal/verify-5-capabilities.mjs cited doxa-vision-card as cap-1 — it ships an
* *.instances.json, so it is parameterized (cap-2). Fixed there; its own comment records that
* the gate had been "sampling cap-2 TWICE and cap-1 NEVER".
* · README.md made the identical claim in a caption and a section. Fixed 2026-09-21, and only
* then gated by check:capability-citations.
* · the /plugin-designer skill still cites doxa-vision-card as cap-1 AND cites
* `unified-research-dashboard` as a cap-4 reference — a bundle that DOES NOT EXIST.
*
* ★ THE SKILL CANNOT BE GATED FROM THIS REPO. It lives in the session config directory, in no git
* repo, so a checker pointed at it would PASS on this machine and SKIP SILENTLY on every other —
* the "gate that cannot fail" shape. The durable fix is not to police the skill but to give the
* repo an authoritative, generated answer the skill (and any doc, and any future agent) can cite.
* Prose that cites a generated file can be checked; prose that cites memory cannot.
*
* THE SOURCE OF TRUTH IS manifest.json, because it is what the BUILD computed from each folder's
* real shape — an *.instances.json makes a bundle parameterized however simple it looks, and a
* meta.json `type` declares tiers 3/4/5. capability = 5 - PROFILE_TIER (discover.mjs).
*
* Run: bun run capability-exemplars Check: bun run check:capability-exemplars
*/
import { readFileSync, writeFileSync, existsSync } from 'node:fs'
import { resolve, dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const MANIFEST = join(ROOT, 'public', 'doxa-maps', 'manifest.json')
const OUT = join(ROOT, 'docs', 'reference-capability-exemplars.md')
if (!existsSync(MANIFEST)) {
console.error('CAPABILITY-EXEMPLARS: manifest.json not found — run a build first.')
process.exit(1)
}
const CAP_OF_TYPE = {
'single': 1,
'parameterized': 2, 'preset-element': 2,
'monolithic': 3, 'multi-view-element': 3,
'nested-elements-mfe': 4, 'composite-element': 4,
'nested-mfe-spa': 5, 'app-shell': 5,
}
const CAP_NAME = {
1: 'single-element', 2: 'preset-element (prop-table)', 3: 'multi-view-element (monolithic)',
4: 'composite-element (nested-element SPA)', 5: 'app-shell (nested-SPA)',
}
const mf = JSON.parse(readFileSync(MANIFEST, 'utf8'))
const byCap = new Map([[1, []], [2, []], [3, []], [4, []], [5, []]])
for (const b of (mf.bundles || [])) {
for (const p of (b.profiles || [])) {
const cap = CAP_OF_TYPE[p.type]
if (!cap) continue
byCap.get(cap).push({ bundle: b.bundle, profile: p.profile, type: p.type })
}
}
const lines = []
lines.push('# Reference — capability exemplars (GENERATED)')
lines.push('')
lines.push('> **Do not hand-edit.** Regenerate with `bun run capability-exemplars`; `bun run')
lines.push('> check:capability-exemplars` fails if this file falls behind the manifest.')
lines.push('>')
lines.push('> This answers one question authoritatively: **which bundles are the capability-N')
lines.push('> examples right now?** It is derived from `public/doxa-maps/manifest.json`, which is what')
lines.push('> the build computed from each folder\'s real shape — so it cannot drift from what ships.')
lines.push('>')
lines.push('> **Cite this file rather than remembering.** The same citation has been wrong in three')
lines.push('> separate places: a gate, the README, and the `/plugin-designer` skill. The trap is that a')
lines.push('> folder\'s apparent simplicity does not decide its tier — an `*.instances.json` makes a')
lines.push('> bundle `parameterized` (cap-2) no matter how light it looks, and a `meta.json` `type`')
lines.push('> declares tiers 3/4/5.')
lines.push('')
lines.push(`Generated from ${(mf.bundles || []).length} bundle(s).`)
lines.push('')
for (const cap of [1, 2, 3, 4, 5]) {
const rows = byCap.get(cap)
lines.push(`## Capability ${cap} — ${CAP_NAME[cap]}`)
lines.push('')
if (!rows.length) {
lines.push('_No built bundle currently declares this tier._')
lines.push('')
continue
}
lines.push('| Bundle | Profile | Machine key |')
lines.push('|---|---|---|')
for (const r of rows) lines.push(`| \`${r.bundle}\` | \`${r.profile}\` | \`${r.type}\` |`)
lines.push('')
}
writeFileSync(OUT, lines.join('\n') + '\n')
const total = [...byCap.values()].reduce((n, v) => n + v.length, 0)
console.log(`[capability-exemplars] wrote docs/reference-capability-exemplars.md — ` +
`${total} profile(s) across ${[...byCap.values()].filter(v => v.length).length} tier(s)`)

View file

@ -52,6 +52,8 @@
"check:shots": "node internal/check-shot-freshness.mjs # a README screenshot that predates the bundle it shows is a picture telling a lie \u2014 and it looks IDENTICAL to a current one. Compares each shot's PNG against the BUILT BUNDLE it renders (not raw sources: an unbuilt edit is not yet in any leaf). Fix: node internal/readme-shots.mjs",
"check:library-index": "node internal/check-library-index.mjs # the DERIVED library menu must not fall behind library/. Compares the index against the newest library SOURCE (not a bundle \u2014 this generator scans library/ directly, with no build step between). Fix: bun run library-index",
"check:quoted-scripts": "node internal/check-quoted-scripts.mjs # docs/README-SECTION-3-cli-commands.md QUOTES the scripts block verbatim; it had silently drifted TWICE (a stale bunx cap:sync, and build:unlocked missing check:public-hygiene + check:label-joins). package.json is what RUNS \u2014 update the DOC.",
"capability-exemplars": "node internal/generate-capability-exemplars.mjs # regenerate docs/reference-capability-exemplars.md \u2014 the DERIVED answer to \"which bundle is the capability-N example?\", a question answered WRONGLY in three separate artifacts (a gate, the README, the /plugin-designer skill).",
"check:capability-exemplars": "node internal/check-capability-exemplars.mjs # REGENERATES and content-compares (not mtimes: a full build rewrites manifest.json every time, so an mtime rule would cry wolf after every build and get switched off).",
"check:capability-citations": "node internal/check-capability-citations.mjs # a doc that names a bundle as \"capability N\" must match manifest.json, which is what the BUILD computed. README captioned doxa-vision-card as cap-1; it ships 2 instances so it is parameterized (cap-2). The SAME error was fixed once in verify-5-capabilities.mjs and never reached the README \u2014 two copies of one fact, nothing forcing agreement.",
"check:shot-distinctness": "node internal/check-shot-distinctness.mjs # two README slots must not show the SAME view. README shipped \"List \u2014 page 1\" and \"page 2\" as the same picture: the shot chased a Next control and a scroll that DO NOT EXIST, every attempt inside try/catch so the miss was silent, and the PNGs differed by 0.16% render noise so no checksum caught them. Keys on url+VIEWPORT \u2014 same url at a different vp (deploy2 mobile) is intended.",
"check:shot-fidelity": "node internal/check-shot-fidelity.mjs # warns when THIS machine cannot RENDER what the shots must SHOW. `bun run shots` printed 13x OK while every emoji rendered as a tofu box \u2014 a glyph fallback loads perfectly (0 console errors, 0 HTTP>=400), so the generator cannot see it. Reports by default; --strict to block once an emoji font is installed.",