---
name: audit-dev-wire
description: Static parity gate between the generated frontend service URLs and the generated backend controller routes. Catches the "frontend calls a URL the backend never serves" class of bugs (the 404 the user sees in the browser) BEFORE runtime. Phase 3e gate of `/ba-develop`.
group: D
phase: devApi
kind: audit
audit_only: true
section_label: 'AUDIT-DEV-WIRE (frontend service URL ↔ backend controller route parity)'
allowed-tools: [Read, Glob, Grep, Bash]  # Bash: CLI invocation
---

# audit-dev-wire — Frontend ↔ Backend URL Parity Gate

## Context

You are running the static parity gate of `/ba-develop` Phase 3e. The CLI
enumerates every URL the generated frontend services call AND every route
the generated backend controllers expose, then diffs the two sets.

A `wire.frontend-orphan` finding means the frontend will receive a 404 at
runtime — exactly the failure the user reported with `GET /api/affaires/accueil/dashboard`.
A `wire.backend-orphan` finding is informational — a route nobody calls
(e.g. health probe, integration-stratum lookup) is not a defect.

The audit reads:

- `web/**/src/features/**/services/*Service.ts` — frontend service URLs
- `web/**/src/features/**/hooks/use*.ts` — additional URL call sites
- `web/**/*Page.tsx` — the `apiEndpoint` of every `<EntityLookup>` (static JSX
  the api.* extraction never sees; a FK lookup off the integration stratum 404s
  the combobox AND leaves the field showing the raw Guid). Core platform lookups
  (`/api/core/{plural}/lookup`) are allow-listed.
- `src/<Ns>.Api/Controllers/**/*Controller.cs` — backend routes, **resolved the
  way the runtime serves them**: an integration controller's route is computed
  from its `[NavRoute("module.section")]` → `/api/{module}/{section}` (the platform
  rewrites it; a literal `[Route]` is discarded), a screen controller keeps its
  `[Route("/api/screens/...")]`.

It writes a structured JSON envelope on stdout (`auditReport.findings[]`)
and a Markdown report under `_audit/dev-wire-<module>.md`.

## When to run

- **Automatically**: as Phase 3e of `/ba-develop`, after Phase 3 (Frontend)
  has scaffolded all the api-clients and controllers. The orchestrator
  calls the CLI; a single `err` finding blocks promotion to Phase 4.
- **Manually**: when a developer suspects a 404 in their generated app,
  run `audit-dev-wire --mode audit` to localise the drift in < 1 second.

## Rules

### DEV-WIRE-001 — Every frontend URL call has a backend route serving it

- **Severity**: `err` (BLOCKING) on frontend orphans; `warn` on backend orphans
  with no caller; `ok` when both sets align.
- **Check**: cross-reference every `api.<verb>(<url>)` call in the frontend (plus
  every `<EntityLookup apiEndpoint>`) against the backend route index — where each
  controller route is **resolved the way the runtime serves it** (integration:
  `/api/{module}/{section}` from `[NavRoute]`; screens: the literal `[Route]`).
  Comparing against the static `[Route]` alone was the hole that let the
  `/api/v1/integration` → `/api/{module}/{section}` mismatch ship as "0 errors".
- **Frontend orphan = blocking 404**: a frontend URL that matches no resolved
  backend route is a guaranteed runtime 404. A frontend still on the dead
  `/api/v1/integration/{plural}` literal is the canonical case — the platform
  rewrote that route away, so the fix is the FRONTEND.
- **fixSkill**:
  - frontend still on the dead `/api/v1/integration/...` literal → `frontend-api-client`
    (re-run `scaffold-api-client`; it derives `API_PATH` from the entity's navRoute
    via `buildNavApiPath`).
  - a non-`/api` URL → `frontend-api-client`.
  - otherwise (a plausible route the backend never emitted) →
    `backend-screen-controller` (path under `/api/screens/...`) or `backend-controller`.
- **fixPhaseKey**: `api`
- **solution**: "Re-run the API phase. If the frontend is on the dead
  `/api/v1/integration` literal, re-invoke `scaffold-api-client` (it derives the URL
  from the entity's navRoute); for a genuinely missing route, re-invoke
  `scaffold-controller` / `scaffold-screen-controller` for the entity."

### DEV-WIRE-002 — Verb mismatch on the same path

- **Severity**: `err` (BLOCKING)
- **Check**: when frontend and backend agree on the path but disagree on
  the HTTP verb (frontend `POST`, backend `[HttpPut]`), surface the
  discrepancy. The pagespec is the source of truth — the side that
  diverges must be re-scaffolded.
- **fixSkill**: `backend-screen-controller` if backend is wrong, otherwise
  `frontend-api-client`.
- **solution**: "Reconcile the pagespec.action.httpMethod with the controller
  route attribute. Re-run the appropriate scaffolder; do not edit the
  generated file by hand."

### DEV-WIRE-003 — Strata mismatch

- **Severity**: `err` (BLOCKING)
- **Check** (reserved): frontend calls an entity's integration route
  (`/api/{module}/{section}/...`, NavRoute-resolved) but only
  `{EntityPlural}ScreenController.cs` exists for that entity — the screen-driven
  backend has no integration counterpart.
- **fixSkill**: `frontend-api-client`
- **fixPhaseKey**: `frontend`
- **solution**: "Re-run `scaffold-api-client` for this entity with
  `useScreens: true`. The Phase 3a strata detection (stat the screen
  controller file) must have been out of order — verify the orchestrator's
  step ordering."

## Output contract

The CLI emits a JSON envelope identical to `audit-dev-api`:

```json
{
  "success": true,
  "command": "audit-dev-wire",
  "data": { "mode": "audit", "rulesRun": ["DEV-WIRE-001"], "valid": false },
  "report": { /* full AuditReport with findings, counts, byRule, markdown */ },
  "auditReport": {
    "scope": "devWire",
    "applicationCode": "gaf",
    "moduleCode": "affaires",
    "findings": [
      {
        "dimension": "devWire",
        "code": "DEV-WIRE-001",
        "severity": "err",
        "label": "DEV_WIRE_001_err",
        "params": { "calls": "GET /api/affaires/accueil/dashboard|src/features/affaires/demande/services/demandeService.ts" },
        "solution": "Re-run the API phase ...",
        "fixSkill": "backend-screen-controller",
        "fixPhaseKey": "api"
      }
    ]
  },
  "errors": [],
  "warnings": [],
  "nextSteps": ["1 ERROR finding(s) — blocking Phase 3e gate of /ba-develop."]
}
```

Exit code: `0` when no `err` findings, `1` otherwise.

## CLI invocation

```bash
npx --prefer-offline tsx skills/development/audit-dev-wire/cli/audit-dev-wire/index.ts \
  --project-path "<dotnet-root>"     \
  --web-path     "<web-root>"        \
  --module-code  "<MODULE>"          \
  --app-code     "<APP>"             \
  --mode audit
```

`<web-root>` defaults to `<project-path>/web/<appCode>-web` if omitted.

## Auto-healing mapping

This audit emits failure kinds the `/ba-develop` orchestrator's auto-healing
protocol picks up (see `ba-develop/references/auto-healing.md` rows 23-25):

| Finding | Failure kind | Auto-fix |
|---------|--------------|----------|
| `DEV-WIRE-001 err` (path starts with `/api/screens/`) | `wire.frontend-orphan` (screen) | Re-invoke `scaffold-screen-controller` for the entity |
| `DEV-WIRE-001 err` (frontend on the dead `/api/v1/integration/` literal, or a missing integration route) | `wire.frontend-orphan` (integration) | Legacy literal → re-invoke `scaffold-api-client` (fixSkill `frontend-api-client`); genuinely missing → `scaffold-controller` |
| `DEV-WIRE-001 err` (`<EntityLookup>` `apiEndpoint` not a resolved lookup route) | `wire.frontend-orphan` (lookup) | Re-invoke `scaffold-component` — set `fkTo.navRoute`/`apiEndpoint` to the target's `/api/{module}/{section}/lookup` (fixSkill `frontend-component`) |
| `DEV-WIRE-002 err` | `wire.verb-mismatch` | HALT for now — pagespec adjudication needed |
| `DEV-WIRE-003 err` | `wire.strata-mismatch` | Re-invoke `scaffold-api-client` with `useScreens=true` |
