---
name: backend-core-lookups
description: Scaffold (or retire) the core-lookups BRIDGE of a generated client app — the read-only /api/core/{users,roles,departments,job-titles,offices,languages,groups}/lookup endpoints the generated use{E}Lookup hooks call, until the socle serves them natively.
group: B
phase: api
allowed-tools: [Read, Glob, Grep, Bash]
---

# backend-core-lookups — the core catalogues' server side (BRIDGE)

## The hole it closes

Extension entities legitimately reference the platform's catalogues (`User`,
`Department`, `Office`, …) and `scaffold-component` emits `use{E}Lookup`
hooks + `<EntityLookup>` against `/api/core/{catalogue}/lookup` — but the
platform serves NO such endpoint: `/api/core/departments` never existed, and
`/api/administration/users` sits behind an administration permission a module
user has no reason to hold. Every core FK control rendered EMPTY (the hook
swallowed the 404): « on ne peut pas donner un service ni un site à un
conducteur depuis l'interface ».

## The decision (« les deux »)

The **socle owns these endpoints** — the closed spec handed to the
SmartStack.app chantier: `GET /api/core/{users,departments,offices}/lookup`
(+ recommended job-titles/languages/roles/groups), `[Authorize]` without a
module permission, tenant-scoped, `?search&page&pageSize` →
`{ items: [{ id, displayName }], totalCount }`. Until that release ships, this
CLI scaffolds a client-side **bridge** with the exact same contract, querying
the core DbSets `SmartStackExtensionDbContext` already exposes.

## CLI — `cli/scaffold-core-lookups`

```bash
npx --prefer-offline tsx skills/development/backend/core-lookups/cli/scaffold-core-lookups/index.ts \
  --spec '{"projectPath":"<backend-root>","namespace":"<Ns>"}'
```

- Emits `src/{Ns}.Application/Common/Core/ICoreLookupsService.cs` (+
  `CoreRefDto`), `src/{Ns}.Infrastructure/Services/Core/CoreLookupsService.cs`
  and `src/{Ns}.Api/Controllers/Core/CoreLookupsController.cs` — every file
  headed by the `@generated-by scaffold-core-lookups` marker.
- Unions its `AddScoped` into the `BUSINESS-SERVICES-DI` marker block
  (lib/di-markers — idempotent, hand registrations honoured).
- `entities: [...]` restricts the catalogue subset; `TenantOrganisation`
  keeps its `organization-references` adaptateur and `Tenant` is never a
  lookup — both refused.
- **ADR — authorisation**: read-only, `[Authorize]`, deliberately NO module
  permission: the same catalogues feed every module's forms; gating them on
  one module's grant would make another module's filter depend on it. Tenant
  isolation rides the DbContext's global query filters. No `[NavRoute]` (the
  route belongs to no menu node); the literal `[Route("api/core")]` is exactly
  what the generated hooks call.

## Retirement (the day the socle ships)

Flip `lib/socle-version.MIN_SOCLE_CORE_LOOKUPS_VERSION` to the real release —
ONE constant. From then on:
- `validate` refuses `mode: "ensure"` on a project referencing that socle;
- `mode: "remove"` deletes the three files (marker-guarded — a hand-written
  controller is never touched) and drops the DI line;
- `audit-dev-api DEV-API-027` errs on a lingering bridge (AmbiguousMatch with
  the socle's routes) and on a missing bridge below the floor when core FKs
  exist.
