|
| 1 | +/** |
| 2 | + * File: src/lib/demo/registry.ts |
| 3 | + * Purpose: Authoritative list of API routes that branch on `isDemoMode()`. |
| 4 | + * |
| 5 | + * Every entry pairs a route path with the demo handler(s) it invokes when |
| 6 | + * `isDemoMode()` returns true. The companion test (registry.test.ts) walks |
| 7 | + * src/app/api/** and fails the build if a route gains or loses an |
| 8 | + * `isDemoMode()` call without a matching registry update. This prevents drift |
| 9 | + * between demo-mode coverage and the real app surface. |
| 10 | + * |
| 11 | + * Governance contract: |
| 12 | + * - Every `isDemoMode()` branch in src/app/api/** MUST be listed here. |
| 13 | + * - Every handler referenced here MUST be exported from mock-api-handlers.ts |
| 14 | + * or declared in the `inline` list for routes that inline their demo path. |
| 15 | + * - New demo handlers added to mock-api-handlers.ts without a registry entry |
| 16 | + * are unreachable from production routes and will flag in the test. |
| 17 | + */ |
| 18 | +export interface DemoRouteEntry { |
| 19 | + /** Route path relative to /api (e.g. '/stats', '/llm/models/[id]'). */ |
| 20 | + route: string; |
| 21 | + /** |
| 22 | + * Demo handler function names imported from './mock-api-handlers'. Empty |
| 23 | + * array when the route inlines its demo response (see `inline`). |
| 24 | + */ |
| 25 | + handlers: ReadonlyArray<string>; |
| 26 | + /** |
| 27 | + * True when the route implements its demo branch inline (no named handler |
| 28 | + * from mock-api-handlers.ts). Used for routes that return minimal static |
| 29 | + * JSON without crossing the handler boundary. |
| 30 | + */ |
| 31 | + inline?: boolean; |
| 32 | +} |
| 33 | + |
| 34 | +/** |
| 35 | + * Exhaustive demo-mode route registry. Keep sorted by route path to make |
| 36 | + * diffs reviewable. When adding a new route that reads `isDemoMode()`, add |
| 37 | + * the entry here in the same PR. |
| 38 | + */ |
| 39 | +export const DEMO_ROUTE_REGISTRY: ReadonlyArray<DemoRouteEntry> = [ |
| 40 | + { route: '/admin/health', handlers: [], inline: true }, |
| 41 | + { route: '/admin/settings', handlers: [], inline: true }, |
| 42 | + { route: '/arena', handlers: ['demoArenaGet', 'demoArenaPost'] }, |
| 43 | + { route: '/arena/[id]', handlers: ['demoArenaMatchById'] }, |
| 44 | + { route: '/arena/warriors', handlers: ['demoArenaWarriorsGet'] }, |
| 45 | + { route: '/attackdna/analyze', handlers: ['demoNoOp'] }, |
| 46 | + { route: '/attackdna/ingest', handlers: ['demoNoOp'] }, |
| 47 | + { route: '/attackdna/query', handlers: ['demoAttackDnaQueryGet'] }, |
| 48 | + { route: '/attackdna/sync', handlers: ['demoNoOp'] }, |
| 49 | + { route: '/auth/login', handlers: [], inline: true }, |
| 50 | + { route: '/auth/logout', handlers: [], inline: true }, |
| 51 | + { route: '/auth/me', handlers: [], inline: true }, |
| 52 | + { route: '/auth/users', handlers: ['demoUsersGet', 'demoNoOpCreated'] }, |
| 53 | + { route: '/compliance', handlers: [], inline: true }, |
| 54 | + { route: '/compliance/export', handlers: [], inline: true }, |
| 55 | + { route: '/compliance/frameworks', handlers: [], inline: true }, |
| 56 | + { route: '/ecosystem/findings', handlers: ['demoEcosystemGet'] }, |
| 57 | + { route: '/fixtures', handlers: ['demoFixturesGet'] }, |
| 58 | + { route: '/health', handlers: ['demoHealthGet'] }, |
| 59 | + { route: '/llm/batch', handlers: [], inline: true }, |
| 60 | + { route: '/llm/batch/[id]', handlers: [], inline: true }, |
| 61 | + { route: '/llm/batch/[id]/executions', handlers: [], inline: true }, |
| 62 | + { route: '/llm/batch/cleanup', handlers: [], inline: true }, |
| 63 | + { route: '/llm/coverage', handlers: ['demoCoverageGet'] }, |
| 64 | + { route: '/llm/fingerprint', handlers: ['demoFingerprintGet', 'demoNoOpAccepted'] }, |
| 65 | + { route: '/llm/guard', handlers: [], inline: true }, |
| 66 | + { route: '/llm/guard/audit', handlers: [], inline: true }, |
| 67 | + { route: '/llm/guard/stats', handlers: [], inline: true }, |
| 68 | + { route: '/llm/leaderboard', handlers: ['demoLeaderboardGet'] }, |
| 69 | + { route: '/llm/local-models', handlers: [], inline: true }, |
| 70 | + { route: '/llm/models', handlers: ['demoModelsGet', 'demoModelsPost'] }, |
| 71 | + { route: '/llm/models/[id]', handlers: ['demoModelById', 'demoNoOp'] }, |
| 72 | + { route: '/llm/obl/alignment', handlers: [], inline: true }, |
| 73 | + { route: '/llm/obl/depth', handlers: [], inline: true }, |
| 74 | + { route: '/llm/obl/geometry', handlers: [], inline: true }, |
| 75 | + { route: '/llm/obl/robustness', handlers: [], inline: true }, |
| 76 | + { route: '/llm/providers', handlers: ['demoProvidersGet', 'demoProvidersPost'] }, |
| 77 | + { route: '/llm/reports', handlers: ['demoReportsGet'] }, |
| 78 | + { route: '/llm/results', handlers: [], inline: true }, |
| 79 | + { route: '/llm/test-cases', handlers: [], inline: true }, |
| 80 | + { route: '/mcp/status', handlers: [], inline: true }, |
| 81 | + { route: '/ronin/programs', handlers: ['demoRoninProgramsGet'] }, |
| 82 | + { route: '/ronin/submissions', handlers: ['demoRoninSubmissionsGet'] }, |
| 83 | + { route: '/scan', handlers: [], inline: true }, |
| 84 | + { route: '/sengoku/campaigns', handlers: [], inline: true }, |
| 85 | + { route: '/sengoku/campaigns/[id]', handlers: [], inline: true }, |
| 86 | + { route: '/sengoku/campaigns/[id]/run', handlers: ['demoNoOpAccepted'] }, |
| 87 | + { route: '/sengoku/campaigns/[id]/runs', handlers: ['demoCampaignRunsGet'] }, |
| 88 | + { route: '/setup/admin', handlers: [], inline: true }, |
| 89 | + { route: '/setup/status', handlers: [], inline: true }, |
| 90 | + { route: '/shingan/formats', handlers: ['demoShinganFormatsGet'] }, |
| 91 | + { route: '/shingan/scan', handlers: ['demoShinganScansGet'] }, |
| 92 | + { route: '/stats', handlers: ['demoStatsGet'] }, |
| 93 | + { route: '/tests', handlers: [], inline: true }, |
| 94 | +]; |
| 95 | + |
| 96 | +/** Number of API routes gated by `isDemoMode()`. */ |
| 97 | +export const DEMO_ROUTE_COUNT: number = DEMO_ROUTE_REGISTRY.length; |
| 98 | + |
| 99 | +/** All handler names referenced by the registry, deduplicated. */ |
| 100 | +export const DEMO_REGISTERED_HANDLERS: ReadonlyArray<string> = Array.from( |
| 101 | + new Set(DEMO_ROUTE_REGISTRY.flatMap((entry) => entry.handlers)), |
| 102 | +).sort(); |
| 103 | + |
| 104 | +/** Lookup by route path. Returns undefined if route is not in registry. */ |
| 105 | +export function getDemoRouteEntry(route: string): DemoRouteEntry | undefined { |
| 106 | + return DEMO_ROUTE_REGISTRY.find((entry) => entry.route === route); |
| 107 | +} |
0 commit comments