---
name: audit-routing-dynamic
description: READ-ONLY audit of the SmartStack development skills to verify their conformance to the DB-driven dynamic routing architecture (PageRegistry + componentRegistry + DynamicRouter). Produces a textual report without modifying any skill.
group: AUDIT
allowed-tools: [Read, Glob, Grep]
---

# Skill: Dynamic Routing Audit — SmartStack development skills

## Role

You are a **read-only** audit agent. You inspect the `SKILL.md` files in the
`development/` directory and evaluate whether each skill correctly documents
SmartStack's **DB-driven dynamic routing** architecture.

**Absolute constraint**: you modify NO skill file. You produce a textual report
with recommendations that the user will apply manually.

## Input

```
audit-routing-dynamic [skill-path | all]
```

- No argument or `all`: full audit of the 10 target skills
- With a path (e.g. `frontend/component`): targeted audit

## Reference architecture (source of truth)

SmartStack is an NPM/NuGet SDK. Every React page must be registered via
`PageRegistry.register(componentKey, LazyComponent)`. `DynamicRouter` resolves
routes from the DB menu API (`GET /api/navigation/menu`).

**5 mandatory elements**:
1. `componentRegistry.generated.ts` — page registry (imported in `main.tsx`)
2. `PageRegistry` — registration + resolution API
3. `DynamicRouter` — single routing engine (replaces static routes)
4. `ProtectedRoute` — API-driven guard (permissions + features)
5. `useRouteConfig` — hook that turns the menu API into React routes

**25 IMPLICIT_SUFFIXES** (auto-generated by DynamicRouter — machine SSOT:
`skills/lib/implicit-suffixes.ts`, consumed by uat-plan and DEV-UI-046):

<!-- implicit-suffixes:v1 — drift-tested against lib/implicit-suffixes.ts (edit BOTH or the suite fails) -->
```
.detail  .edit  .create  .new  .duplicate  .settings  .configure
.permissions  .members  .history  .logs  .analytics  .preview
.versions  .comments  .attachments  .audit  .export  .notifications
.schedule  .workflow  .summary  .test  .runs  .import
```
<!-- /implicit-suffixes:v1 -->
Any OTHER 4th-level componentKey segment must exist as a seeded
`NavigationResource`, or the URL falls into `ProtectedCatchAll` (silent
redirect to `/applications`) — audited by DEV-UI-046.

**Mandatory conventions**:
- Dot-separated `componentKey`: `{app}.{module}.{section}[.{resource}]`
- `useParams<{ id: string }>()` — ALWAYS `:id`, never `:userId`/`:ticketId`
- Lazy imports: `lazyWithRetry(() => import('./MyPage').then(m => ({ default: m.MyPage })))` (re-exported from `@atlashub/smartstack` — wraps `React.lazy` with exponential retry on transient ChunkLoadError)
- Backend seed `ComponentKey` = frontend PageRegistry key
- **Visibility & routing via `hasNavAccess(permissions, nodePath)` (SmartStack ≥ 3.62)** — NEVER a `hasFullAccess` bypass or a raw string comparison. Rules:
  - a module/section is visible/routable iff the role holds `{nodePath}.access` (exact), a DESCENDANT's `.access` (a granted child reveals its ancestors), or a covering wildcard (`*`, `app.*`, `app.module.*`)
  - data actions (`read`, `create`, `lookup`, …) NEVER reveal a menu node or open a route
  - no downward cascade: `app.module.access` shows the module, not its sections — each visible section carries its own `.access` (deny-by-default at every grain)
  - `hasRouteAccess` (any-permission-under-path) is DIAGNOSTIC only — it attributes the AccessDenied reason (`permission` vs `tenant`); the audit must flag any use of it as a route guard
- **`IsOpen` (DB column `core.nav_Applications.IsOpen`)** is a domain property **on Application only** (NOT on Module/Section/Resource). It bypasses the permission/feature check at the Application level (e.g. `myspace.*` always accessible). The audit must verify that the seed sets `IsOpen` at the right level.
- **Menu API 4-level DTOs**: `ApplicationMenuDto > ModuleMenuDto > SectionMenuDto > ResourceMenuDto`. Each level carries its own `ComponentKey` + `PermissionPath` (computed at runtime from the hierarchical path). `IsOpen` exists only on `ApplicationMenuDto`. `RequiredFeature` exists only on `Application` and `Module` (2 levels, from `LicenseFeatureMapping`).
- **`OUTLET_SECTIONS` config** (in `DynamicRouter`) declares the sections that render their sub-pages via React Router `<Outlet>` (tabs). Components are resolved from `PageRegistry` (not hardcoded). Anti-pattern: Outlet routes hardcoded in the router.

**Reference files in SmartStack.app** (code source of truth):
- `web/smartstack-web/src/extensions/PageRegistry.tsx`
- `web/smartstack-web/src/router/DynamicRouter.tsx` (IMPLICIT_SUFFIXES L86-119)
- `web/smartstack-web/src/generated/componentRegistry.generated.ts`
- `web/smartstack-web/src/hooks/useRouteConfig.ts`
- `web/smartstack-web/src/components/routing/ProtectedRoute.tsx`

## Target skills (10)

| Priority | Skill | Category |
|----------|-------|-----------|
| P0 | `frontend/component` | React pages |
| P0 | `frontend/structure` | Folder tree + main.tsx |
| P0 | `audit` | Existing general-purpose audit |
| P1 | `backend/seed-data` | Navigation seed |
| P1 | `backend/controller` | NavRoute attribute |
| P1 | `frontend/extension-config` | Slots |
| P2 | `frontend/routes` | PageRegistry scaffolding |
| P2 | `frontend/api-client` | getRoute() + React Query |
| P2 | `testing` | Tests per layer |
| P2 | `backend/structure` | Clean Architecture |

## Audit criteria (10 rules A-J)

For each skill, apply the criteria. `N/A` if outside the skill's scope.

| ID | Rule | Applies to | Detection pattern |
|----|-------|--------------|----------------------|
| A | Mentions `PageRegistry.register()` | frontend/* | grep `PageRegistry.register` OR `componentRegistry.generated` |
| B | Uses `lazyWithRetry(() => import(...))` (or legacy `lazy(`) | frontend/component, frontend/routes | grep `(lazy|lazyWithRetry)\(` with `import(` |
| C | Documents at least 3 IMPLICIT_SUFFIXES | frontend/routes, frontend/component | grep `.detail`, `.edit`, `.create` |
| D | Enforces `useParams<{ id: string }>()` | frontend/component | grep `useParams<{ id:` — anti-pattern if `userId`/`ticketId` |
| E | Documents the dot-separated ComponentKey format | frontend/routes, frontend/component, backend/seed-data | grep `componentKey.*\.` with an `app.module` example |
| F | Aligns ComponentKey backend -> frontend | backend/seed-data | grep `ComponentKey` in a seed example |
| G | Mentions the `[NavRoute]` attribute | backend/controller | grep `NavRoute` |
| H | Forbids static routes `<Route path=...>` | frontend/* | absence of `<Route path=` outside DynamicRouter |
| I | _(removed — MCP validate_frontend_routes was deleted in v5)_ | — | — |
| J | Documents the client NPM integration (`main.tsx` wiring) | frontend/structure | grep `main.tsx` + `componentRegistry` |

## Execution plan

1. **Inventory** — Glob the target `SKILL.md` files:
   ```
   templates/skills/development/**/SKILL.md
   ```
   Filter down to the 10 targets above.

2. **Evaluate** — For each skill:
   - `Read` the full content
   - For each applicable criterion A-J: `Grep` the pattern
   - Record `OK` / `PARTIAL` / `MISSING` / `N/A` with the line number

3. **Score** — `X/Y` per skill (Y = number of applicable criteria)

4. **Report** — Print the report in the defined format (Output section)

**Strict rules**:
- NEVER `Edit` or `Write` skill files
- NEVER modify the audited skill files directly
- Allowed tools: `Read`, `Grep`, `Glob`, `Bash` (ls only)
- If the user explicitly asks to write the report to disk:
  create ONE SINGLE file `audit-routing-dynamic-report.md` in the CWD (never in the skills directory)

## Expected output

```
━━━ Dynamic Routing Audit — Report ━━━
Date: 2026-04-19
Skills audited: 10
Overall conformance: 48/100

## Top 3 cross-cutting gaps
1. A. PageRegistry: 8/10 skills never mention PageRegistry.register()
2. J. NPM integration: 0/10 skills document the main.tsx wiring
3. C. IMPLICIT_SUFFIXES: 1/10 skills documents them

## Top 3 critical skills (P0)
- frontend/component (6/10)
- frontend/structure (4/10)
- audit (2/10)

## Per-skill detail

### frontend/component — 6/10
What it generates: React pages (List/Detail/Form), i18n, Slot/Fill
Criteria:
  A. PageRegistry        MISSING   (0 occurrences in SKILL.md)
  B. Lazy imports        MISSING   (template uses a direct import)
  C. IMPLICIT_SUFFIXES   MISSING
  D. useParams id        PARTIAL   (L112 mentions "key rules", no explicit :id)
  E. ComponentKey format MISSING
  H. Static routes       OK        (no <Route> in SKILL.md)
  I. _(removed in v5)_         N/A
Recommendations (to apply manually):
  1. Add a "PageRegistry Registration" section after L24 with a
     lazyWithRetry() + register('app.module.section', Lazy) example
  2. Add the rule "ALWAYS useParams<{ id: string }>()" — never :userId
  3. Mention IMPLICIT_SUFFIXES (.detail/.edit/.create) with the full list

### frontend/structure — 4/10
[...same format...]

[... etc. for the 10 skills ...]

━━━ End of report ━━━
```

## Detailed format of a per-skill entry

For each skill, use this structure:

```
### {skill-path} — {X}/{Y}
What it generates: {1 sentence}
Criteria:
  A. {criterion name}       {OK|PARTIAL|MISSING|N/A}   [Line NN if applicable]
  B. ...
Recommendations (to apply manually):
  1. {concrete action + line to modify + exact suggested wording}
  2. ...
  3. ...
```

Maximum 3 recommendations per skill (the most impactful ones).

## Rules

- **Absolutely READ-ONLY**: no Edit, no Write on the skills. A violation = a bug in this skill.
- **Cite line numbers** whenever possible (helps the user apply the recommendation)
- **No value judgment** on the generated code (we audit the skill's documentation, not its execution)
- **Explicit N/A** when a criterion does not apply (e.g. G for a frontend skill)
- **Transparent score**: show the denominator (X/Y) so the user can see how many criteria were applicable
- **No automatic follow-up**: this skill is read-only, do not modify the audited files
- **Escalation**: if the user asks for fixes after the audit, redirect to `/apex` or manual editing — this skill does not fix
