---
name: ba-audit-cross-ref-code
description: >
  Cross-references the BA data model of a module against the EXISTING project
  code — detects entities that extend an existing class, reference a Core/existing
  entity as FK, duplicate one, collide with the SmartStack Core catalogue,
  re-implement a platform capability (file storage, search, …), or model a
  PERSON without the auth_Users extension link (CODE-007, err — fail-closed
  mirror of C-4/DM-018c)
  (CODE-001..007). Reads the module's `entité.md`, scans `src/*.Domain/**/*.cs`,
  checks the static Core + capability catalogues (no scan needed), writes a verdict to
  `_audit/cross-ref-code.md`. Run after `/ba-create-data-model` or as part of
  pre-dev readiness.
allowed-tools: [Read, Write, Glob, Grep, Bash]  # Bash: scan existing .cs code files
---

# ba-audit-cross-ref-code — BA data model × existing code cross-reference

You cross-reference the conceptual data model of one module (its `entité.md`)
against the **existing source code** of the project, then write a verdict file.
The rules are unchanged from the SmartStack convention; only the I/O is
file-based and the code scan now uses Grep/Bash on the project tree.

## Deterministic engine — how this audit runs

The MECHANICAL rules of this dimension are evaluated by the shared `audit-ba`
CLI (see `/ba-audit-run`) — **never by reading the corpus yourself, never by
spawning per-module subagents** (the 394M-token incident shape). Your only
job here is the judgment residue.

1. **Run the engine, scoped to this dimension**:

   ```bash
   npx --prefer-offline tsx skills/business-analyse/audit-run/cli/audit-ba/index.ts \
     --spec '{"baRoot":".smartstack/ba","scope":{"app":"<APP>","module":"<MODULE>"},"dimensions":["cross-ref-code"],"projectRoot":"."}'
   ```

2. **Exit 3 = parsing suspect -> STOP.** A control counter disagrees with the
   parser (`report.parseControl.perDoc`): fix the doc's form or report the
   parser bug, then re-run. Never « complete by hand » — no green verdict may
   be born from a silent parser.
3. **No judgment rules in this dimension** — a single engine run suffices; the verdict is final.
4. **Chat summary** (3-6 lines, business terms): the PARSE TOTALS (say the
   counts — that is how a « 0 erreur » stays verifiable), err/warn counts,
   remaining judgments, and the fix skill each finding names.

The CLI writes the verdict to `.smartstack/ba/<APP>/<MODULE>/_audit/cross-ref-code.md`
(existing format — anchor, `Verdict :` header, emoji sections; `0 err` =
pass for the downstream gate). The rule texts below remain the AUTHORITATIVE
spec — the CLI registry is drift-tested against them.

## Pre-check — greenfield project (client code only)

Before applying the code-scan rules, check whether the project has existing
domain code:

- `Glob src/*.Domain/**/*.cs` (then `**/Entities/**/*.cs`, `**/Domain/**/*.cs`).
- If **no** entity files are found, the project is greenfield **for client code
  only**: skip CODE-002..004 and emit the trivial-pass note for CODE-001.
- A client project is NEVER greenfield relative to SmartStack Core —
  `core.auth_Users`, `core.tenant_TenantOrganisations`, … always exist via the NuGet,
  invisible to the file scan — so **CODE-005, CODE-006 AND CODE-007 always run** (they
  need no source scan). Say so in the chat summary.

## Scope

- **Module scope** (the only scope): audit one module — apply CODE-001..007 to
  every entity in that module's `entité.md`. Don't scan sibling-module BA models
  except to know which FK targets are already BA-declared.
- The verdict file lives at the audited module's `_audit/cross-ref-code.md`.

## Rules

### CODE-001 — Entity already exists in code
- **Severity**: `warn` (a BA entity name matches an existing code class), else `ok`.
- Check: for each BA data model entity, Grep for `class {EntityName}` in the `.cs`
  files. If found, the BA entity is an **extension** of existing code, not a brand
  new entity.
- `warn`: state that **`<EntityName>`** already exists at `<path>`. Why it matters:
  the development phase should EXTEND this entity (add properties) rather than
  create a duplicate one — the data model must mark it as an extension.
- → Fix: `/ba-create-data-model` (declare `<EntityName>` as an extension of the
  existing class).

### CODE-002 — Entity references existing entity as FK
- **Severity**: `ok` (informational — confirms the FK target exists in code).
- Check: for each BA relationship whose FK target points to an entity **not** in
  the current module's BA data model, Grep for `class {targetName}` in the `.cs`
  files. If found, the FK target is a valid reference into existing/Core code.
- `ok`: note **`<EntityName>`** → **`<targetName>`** resolves to the existing class
  at `<path>` (a legitimate cross-reference, no action needed).

### CODE-003 — FK target not found in BA or code
- **Severity**: `err` (the FK target entity is not found anywhere).
- Check: for each BA relationship, if the FK target is **not** in the module's BA
  data model **and not** found via Grep in the `.cs` files, the FK is broken.
- `err`: state that the relationship target **`<targetName>`** (referenced by
  **`<EntityName>`**) is not found in the BA data model or in existing code. Why
  it matters: a dangling FK blocks entity/migration generation downstream.
- → Fix: `/ba-create-data-model` (either add the target entity to the data model
  or correct the target name).

### CODE-004 — Duplicate table prefix
- **Severity**: `warn` (a BA entity table prefix collides with existing code), else `ok`.
- Check: for each BA entity that declares a table prefix, Grep for
  `ToTable("{prefix}` in the `.cs` files (EF Core configurations). If found, there
  is a table-name collision risk.
- `warn`: state that the prefix **`<prefix>`** (used by **`<EntityName>`**) is
  already used in `<conflictFile>`. Why it matters: two entities mapping to the
  same table prefix collide at the database level.
- → Fix: `/ba-create-data-model` (choose a distinct table prefix for
  `<EntityName>`).

### CODE-005 — BA entity duplicates a SmartStack Core entity (catalogue check — no code scan)
- **Severity**: `err` on collision, else `ok`.
- Core entity classes ship inside the SmartStack NuGet — they are NOT under
  `src/*.Domain/**/*.cs`, so CODE-001..004 can never see them. This rule checks
  each BA entity name against the static Core catalogue below (drift-tested
  against `lib/core-catalog.ts`). It needs NO source scan and therefore applies
  to greenfield projects too.
- Check: compare each BA entity name (whole token, accent/case/plural-insensitive
  — never substring: `UserStory` is not a hit) against every catalogue name +
  alias, against the reserved table, and against the platform HR module entities
  (below).

<!-- core-catalog:v1 — drift-tested against lib/core-catalog.ts (edit BOTH or the suite fails) -->
| Core entity | Table | Tenant scope | Detect as duplicate (FR/EN aliases) |
|---|---|---|---|
| User | `core.auth_Users` | none | Utilisateur, Usager, AppUser, ApplicationUser |
| Role | `core.auth_Roles` | none | — |
| Tenant | `core.tenant_Tenants` | strict | Locataire |
| TenantOrganisation | `core.tenant_TenantOrganisations` | optional | Organisation, Organization, Société, Entreprise, Compagnie, Company |
| Department | `core.ref_Departments` | optional | Département |
| JobTitle | `core.ref_JobTitles` | optional | Fonction, Poste, JobFunction |
| Office | `core.ref_Offices` | optional | Bureau, Bureaux |
| Language | `core.loc_Languages` | none | Langue |
| Group | `core.auth_Groups` | none | Groupe |
<!-- /core-catalog:v1 -->

<!-- core-reserved:v1 — drift-tested against lib/core-catalog.ts (edit BOTH or the suite fails) -->
| Reserved Core name (aliases) | Not FK-able — use instead |
|---|---|
| Permission (Droit) | IPermissionService (permission resolution) |
| UserSession (Session) | security-internal — never modeled nor FK-ed |
| UserProfile (Profil) | ICoreDataService.GetUserBasicInfoAsync |
| UserPreference (Préférence) | ICoreDataService |
| Setting (Paramètre, Configuration) | platform settings (cfg_) — not a client entity |
| Notification | Core notifications feature (ntf_) |
| Ticket (SupportTicket) | Core support/ticketing feature (tkt_) |
| Workflow | Core workflow feature (wkf_) |
| EmailTemplate (ModèleEmail) | Core email templates (email_) |
| AuditLog (JournalAudit) | Core audit logs — read-only platform feature |
| License (Licence) | Core licensing (lic_) |
| Navigation (Menu) | INavigationService (menu / nav tree) |
<!-- /core-reserved:v1 -->

**Platform HR module (v3.63)** — the platform also ships a full HR module. Its
transactional entities are owned by the built-in `hr` app; a client re-modeling one
is (almost always) rebuilding a platform feature. Check each BA entity name against
this table (whole-token, same matching) and emit a **`warn`** (not `err` — a client
MAY run without the HR module, but should EXTEND rather than fork):

<!-- platform-hr-entities:v1 — drift-tested against lib/platform-catalog.ts (edit BOTH or the suite fails) -->
| Platform HR concept | Platform table | Detect as duplicate (FR/EN aliases) |
|---|---|---|
| Absence | `core.hr_AbsenceRequests` | Congé, AbsenceRequest, DemandeAbsence, LeaveRequest, Leave |
| AbsenceType | `core.hr_AbsenceTypes` | TypeAbsence, TypeCongé |
| TimeEntry | `core.hr_TimeEntries` | SaisieTemps, Imputation, Pointage, TimeLog |
| Timesheet | `core.hr_TimesheetWeeks` | FeuilleDeTemps, FeuilleTemps, TimesheetWeek |
| ContractType | `core.hr_ContractTypes` | TypeContrat |
<!-- /platform-hr-entities:v1 -->

- `warn`: **`<EntityName>`** overlaps the platform HR concept **`<Concept>`**
  (`core.hr_…`). Why it matters: the `hr` app already provides absences / time
  entries / timesheets; a parallel `extensions` table forks HR data. Prefer
  EXTENDING the `hr` app (new modules under it) or plugging projects/mandates into
  the time-entry-refs seam (`AddExtensionTimeEntryRefs`) — see the data-layer skill.
  `Employee` is deliberately NOT flagged here: it is a person-extension
  (`PERSON_TRIGGER`) — a client owns its own `Employee` FK-ing `core.auth_Users`.
- → Fix: `/ba-create-data-model` (drop/rename, or declare the HR extension intent).

- `err`: **`<EntityName>`** collides with Core **`<CoreEntity>`**
  (`core.<table>`). Why it matters: the Core table already exists (e.g.
  `tenant_TenantOrganisations` is the shared organisation directory); generating
  a parallel `extensions` table forks the data with no way to reconcile. The
  module must reference Core — `Relations … scope core` FK for the 9 whitelisted
  entities, `ICoreDataService` / the named service for reserved names — never
  recreate it.
- → Fix: `/ba-create-data-model` (remove or rename the entity; declare the
  `scope core` reference).

### CODE-006 — BA entity re-implements a platform CAPABILITY (capability-catalogue check — no code scan)
- **Severity**: `warn` (capability semantics without the canonical pattern),
  `ok` (metadata entity conforming to the pattern — say so).
- Platform capabilities are SERVICES shipped inside the NuGet (file storage,
  global search, time-entry refs, outbound email) — they carry no entity name
  CODE-005 could match and are invisible to any source scan. This rule checks
  each BA entity (name + attributes) against the static capability catalogue
  below (drift-tested against `lib/capability-catalog.ts`). It needs NO source
  scan and therefore applies to greenfield projects too — **always runs**, like
  CODE-005.
- Check: compare each BA entity name — whole-token OR the TRAILING word of a
  compound name (`InteractionDocuments` → `Documents` = hit; `Documentation` is
  a single word, never a hit) — against the entity/section triggers, and each
  attribute name/type against the attribute triggers.

<!-- platform-capabilities:v1 — drift-tested against lib/capability-catalog.ts (edit BOTH or the suite fails) -->
| Capability | Entity/section/tab triggers (FR/EN) | Attribute triggers (name or type) | Socle provides | Use instead — canonical extension pattern | Reference |
|---|---|---|---|---|---|
| file-storage | Document, Attachment, PièceJointe, Pièce jointe, Pièces jointes, Fichier, File, GED, DMS, Justificatif, Annexe, Scan, Upload, Téléversement, Media, Média, Photo | binary, blob, varbinary, byte[], image, filestream, fileContent, fileData, contenu, contenuFichier | IFileStorageService (SmartStack.Application.Common.Interfaces) — Scoped via AddSmartStack, injectable from any extension handler/controller; StorageType Normal/Legal; Local + Azure Blob (config shipped in every generated appsettings) | Client METADATA entity in extensions.* (FileName, StoredFileName, ContentType, FileSizeBytes + parent FK) + IFileStorageService for the bytes + dedicated AUTHENTICATED upload/download endpoints. NEVER binary content in the DB, never raw disk I/O outside the service. | `development/backend/data-layer/references/file-storage.md` |
| global-search | GlobalSearch, RechercheGlobale, Recherche globale, SearchIndex, SearchEngine, Moteur de recherche, Moteurs de recherche, Index de recherche | — | AddExtensionSearch<ExtensionsDbContext> (socle search seam) — extension entities plug into the platform global search | Register searchable entities through the search seam (scaffold-extension-search) — never a client-built search index or engine. | `development/backend/data-layer/references/global-search.md` |
| time-entry-refs | Bookable, Imputable, TimeEntryRef, TimeEntryTarget | — | AddExtensionTimeEntryRefs<ExtensionsDbContext> — client entities become bookable targets of the platform HR time module | Register the entity via the time-entry-refs seam (scaffold-time-entry-refs) — never re-model time entries (PLATFORM_HR_ENTITIES / CODE-005 covers those names). | `development/backend/data-layer/references/time-entry-refs.md` |
| code-generation | Sequence, Séquence, Sequences, Compteur, Counter, Numerotation, Numérotation, Numbering, CodePattern, Code pattern, Allocator, Allocateur, NumberSequence, CodeSequence | nextValue, nextNumber, nextSeq, lastValue, lastNumber, prochainNumero, dernierNumero, compteur | ICodedEntity + ICodeKeyDescriptor registered via AddSmartStackCodeKey<T>() — the shared CodedEntitySaveHandler allocates the Code atomically at insert on core.seq_Sequences (UPDLOCK/SERIALIZABLE = gapless by default), scope Tenant/Global, reset None/Yearly/Monthly/Daily; the key surfaces in Administration → Configuration → Code patterns, where a CodePattern DB row only OVERRIDES the built-in default. No seed, no migration | Declare the `**Code pattern**` on the entity itself (format with {SEQ:n} + scope + reset + gapless) and register the key through scaffold-coded-entity — the socle allocates. NEVER model a counter/sequence/allocator entity, a nextValue column or a client numbering service: the gapless guarantee and the admin-side retuning are the platform's. | `development/backend/data-layer/references/coded-entities.md` |
| email-sending | EnvoiEmail, EmailSortant, OutgoingEmail, EmailQueue, Mailing | — | IEmailService (Scoped) + Core email templates (email_) | Send mail through IEmailService with Core email templates — never a client SMTP client, outbound-mail table or template store. | — |
<!-- /platform-capabilities:v1 -->

- The client METADATA entity is **LEGITIMATE** — contrast CODE-005, where the
  duplicate is removed. Here the finding steers the SHAPE and the downstream
  implementation, never asks to delete the entity.
- `warn` when the capability semantics are matched but the pattern intent is
  absent — e.g. a binary-content attribute, no storage-metadata fields, or a
  generic "GED"/engine/index entity. Message shape: "**`<EntityName>`** relève
  du **stockage fichiers** : le socle fournit `IFileStorageService` — garder
  l'entité comme MÉTADONNÉES (`FileName`, `StoredFileName`, `ContentType`,
  `FileSizeBytes` + FK parent), octets via le service, endpoints dédiés
  authentifiés (jamais `varbinary`, jamais l'`api/files` anonyme)."
- `ok` when the metadata shape is present — note "conforme au pattern
  `IFileStorageService`" so the dev phase knows the intent is deliberate.
- → Fix: `/ba-create-data-model` (align the entity with the capability's
  `Use instead` pattern; reference doc in the table).

### CODE-007 — Every PERSON entity is an extension of `auth_Users` (fail-closed mirror of C-4/DM-018c)

- **Severity**: `err` (a person entity with no `**Personne**` declaration or no
  User link — and no sanctioned override), `ok` otherwise.
- The rationale (decision of 2026-08-27): as soon as an entity REFERENCES a
  person, it must be an extension of `core.auth_Users` — that FK is what wires
  the row to everything hanging off User (login, roles, tenant memberships,
  HR record, notification targeting, "Me" shortcuts). A person directory
  decorrelated from auth_Users produces accounts that cannot log in, do not
  appear in user administration and cannot receive alerts « au porteur ».
  C-4 (create-data-model) is a PROPOSE at authoring time and DM-018c audits
  the BA doc; THIS rule is the a-posteriori mirror on the CODE side — the net
  under both when the deterministic leg misses (a métier word absent from
  PERSON_TRIGGERS) and the model's judgment was the only cover.
- Check, for every BA entity of the module:
  1. **Person signal** — the entity name matches a `PERSON_TRIGGERS` word
     (whole-token, singular-tolerant, accent-insensitive —
     `lib/core-catalog.matchPersonTrigger`; mirrored in create-data-model's
     `person-triggers:v1` block) OR the entity declares ≥2
     `PERSON_IDENTITY_ATTRIBUTES` (`email`, `firstName`, `lastName`,
     `displayName`).
  2. **Declaration** — a triggered entity carries a `- **Personne** :` line
     (`mandatory` / `optional`), or the sanctioned decorrelation override
     `- **Personne** : none — décision client : <raison>`. Silence = `err`.
  3. **Link** — `mandatory`/`optional` modes carry the paired
     `Relations … *→1 User — FK UserId, scope core (auth_Users)` entry; when
     the backend exists, Grep the Domain `*.cs` for the generated leg
     (`public Guid`/`Guid?` `UserId` + a `User` navigation on the entity's
     class). A declaration whose code half is absent is `err` (the modeling
     was right and the scaffold dropped it — re-run scaffold-entity with the
     `scope core` relation).
- **ok** findings name the verified entities ("`Driver` — Personne mandatory,
  FK `UserId` → auth_Users présent en code").
- → Fix: `/ba-create-data-model` (author the `**Personne**` line + the User
  relation — or the explicit `none` override), then re-run Phase 1 for the
  entity.

## Output

Write `_audit/cross-ref-code.md` per the doc-templates skeleton:
- Header `# Audit cross-ref-code — <APP> / <MODULE>` + `_<date> · Verdict :
  <emoji> N warn · M err · K ok_`.
- `## ✅ Conforme`, `## ⚠️ Avertissements`, `## ❌ Bloquants` sections; one bullet
  per finding. For `warn`/`err`: what's wrong (offending entity/prefix codes
  **bold**), why it matters, and a `→` fix naming `/ba-create-data-model`.
- Re-Write the whole file each run (overwrite — it's a fresh verdict).

Then a 3–6 line chat summary in the user's language — business terms, not rule
codes. State whether the data model lines up with the existing code (extensions
correctly flagged, FK targets resolved) and call out any `err` (a broken FK that
must be fixed before development). If the project is greenfield (no existing
code), say so and that the audit passes trivially.

## Used by the readiness orchestrator

`/ba-audit-pre-dev` runs every dimension and aggregates the verdicts. When invoked
by it, still write `_audit/cross-ref-code.md` as usual — the orchestrator reads
these files.
