feat(pro): generalize pro tier to multi-surface extension points

Move the pro mount decnet/services/pro/ -> decnet/pro/ so the Professional tier
can contribute to more than honeypots. The core wires each surface only when
decnet/pro/ is present (absence stays the entitlement gate):

* services  — registry scans decnet/pro/services/ (was decnet/services/pro/)
* API routes — decnet/pro/routes.py exposes ROUTERS, mounted under /api/v1
* web pages  — Vite aliases @pro to the pro frontend (community -> empty stub),
               App.tsx maps proRoutes into <Route>s, Layout renders a
               PROFESSIONAL nav group; both tree-shake out of the community build

Frontend gate mirrors the existing VITE_DECNET_DEVELOPER tree-shake pattern.
Tests: registry + router seams (backend), empty-stub contract (frontend).
This commit is contained in:
2026-06-17 15:02:28 -04:00
parent 80c92a6f80
commit a47f99c449
13 changed files with 151 additions and 57 deletions

View File

@@ -0,0 +1,10 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import { proRoutes } from '@pro';
// In the community build, `@pro` resolves to the stub: no Professional pages,
// so App's route map and Layout's nav group both tree-shake to nothing.
describe('pro tier — community build', () => {
it('ships no pro routes', () => {
expect(proRoutes).toEqual([]);
});
});

View File

@@ -0,0 +1,8 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Community build: no Professional pages. `@pro` resolves here unless the build
// sets VITE_DECNET_PRO=1 with decnet/pro/web/ present, in which case Vite
// aliases `@pro` to the real registry. proRoutes being empty lets the router
// and nav tree-shake the pro surface out of the community bundle.
import type { ProRoute } from './types';
export const proRoutes: ProRoute[] = [];

View File

@@ -0,0 +1,15 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Contract for Professional-tier UI pages. The pro build aliases `@pro` to the
// real registry in decnet/pro/web/; the community build resolves it to ./stub.
import type { ReactElement, ReactNode } from 'react';
export interface ProRoute {
/** Router path, e.g. "/pro/intel". Convention: prefix pro routes with /pro. */
path: string;
/** Sidebar label. */
label: string;
/** Sidebar icon (lucide-react element), optional. */
icon?: ReactNode;
/** Page element rendered at `path`. May be a lazy component (App wraps Suspense). */
element: ReactElement;
}