---
name: ba-audit-data-model
description: >
  Audits the conceptual data model (MCD) of a `.smartstack/ba/` module — attributes,
  FK integrity, cycles, naming, orphans, traceability, indexes, implicit FKs,
  unreferenced lookups, classification conventions, code-pattern numbering
  (DM-001..017). Reads the module's
  `entité.md` + `use-case.md` + `règles-métier.md`, writes a verdict to
  `<MODULE>/_audit/entité.md`. Run after `/ba-create-data-model` or as part of
  pre-dev readiness.
allowed-tools: [Read, Write, Glob, Grep]
---

# ba-audit-data-model — Data Model (MCD) audit

You audit the conceptual data model of a `.smartstack/ba/` module against the
rules below and write a verdict file. The rules are unchanged from the SmartStack
convention; only the I/O is file-based.

## File model — how to audit

1. **Read state**: `Glob .smartstack/ba/**/index.md` to locate the module folder.
   Read the module's authoritative `entité.md` (anchor `<!-- ba:entité … -->`):
   each `### ENT-NNN — <name>` heading carries the table prefix, the
   **Traçabilité** line, the attribute table (Attribut / Type / Contraintes /
   Calculé), the **Relations** line and the **Index** line.
2. **Read upstream for traceability**: Read the module's `use-case.md` and
   `règles-métier.md` (and Grep section-level `use-case.md` under the module).
   Use them to evaluate DM-010 (rule → entity ref), DM-011 (entity is traced by
   a UC/BR/resource) and DM-013 (cross-module FK documentation).
3. **Apply** the rules DM-001..017 (below) to every entity in the module.
4. **Write** the verdict to `.smartstack/ba/<APP>/<MODULE>/_audit/entité.md` (keep
   the accented filename — use the audit-verdict skeleton from
   `_workflow/doc-templates.md`). Then print a 3–6 line business summary in chat.

There is no database, no `[ACTION]` envelope, no i18n label codes — the `.md`
verdict IS the record. The verdict header (`0 err` = pass) is what a downstream
gate reads.

## Scope

- **Module scope** (default): the MCD is authoritative at the Module level, so a
  data-model audit always targets one module. Apply DM-001..017 to every entity
  declared in that module's `entité.md`. The verdict file lives at the audited
  module's `_audit/entité.md`.
- If asked to audit an application, run the module-scoped audit once per module
  under it (one verdict file each); don't merge them into a single report.

## Rules

### DM-001 — At least 1 entity exists per module
- **Severity**: err (if 0), ok (if >= 1)
- **ok**: the module declares ≥ 1 entity (state the count).
- **err**: the module has 0 entities — it cannot expose any data.
- Fix: `/ba-create-data-model`.

### DM-002 — All entities have at least 1 attribute
- **Severity**: err (if entities without attributes), ok (if all have)
- **err**: list the entities whose attribute table is empty.
- Fix: `/ba-create-data-model`.

### DM-003 — String attributes have maxLength defined
- **Severity**: err (if missing), ok (if all set)
- Check: for each attribute whose dataType is `string`, a maxLength must be set
  and > 0.
- **err**: list the offending `entity.attr` pairs.
- Fix: `/ba-create-data-model`.

### DM-004 — Relationship targets exist in the data model
- **Severity**: err (if broken FKs), ok (if all valid)
- Check: each relationship's target entity code must match an existing entity
  code in the MCD.
- **err**: list the broken references.
- Fix: `/ba-create-data-model`.

### DM-005 — No circular relationship chains
- **Severity**: warn (if cycles found), ok (if none)
- Check: follow the relationship graph A→B→C→…→A. Self-references are OK (skip
  them).
- **warn**: list the entities in each cycle.
- Fix: `/ba-create-data-model`.

### DM-006 — Entity names follow PascalCase convention
- **Severity**: warn (if violations), ok (if all comply)
- Pattern: `/^[A-Z][A-Za-z0-9]*$/` (starts with uppercase, no underscores/hyphens).
- **warn**: list the non-conforming entity names.
- Fix: `/ba-create-data-model`.

### DM-007 — Attribute names follow camelCase convention
- **Severity**: warn (if violations), ok (if all comply)
- Pattern: `/^[a-z][A-Za-z0-9]*$/` (starts with lowercase).
- **warn**: list the non-conforming attributes.
- Fix: `/ba-create-data-model`.

### DM-008 — All entities have a valid table prefix
- **Severity**: warn (if missing or invalid), ok (if all valid)
- Pattern: `/^[a-z]{2,5}_$/` (2-5 lowercase letters + underscore).
- **warn**: list the entities with a missing or invalid prefix.
- Fix: `/ba-create-data-model`.

### DM-009 — No unjustified isolated entities (no relationships at all)
- **Severity**: warn (if unjustified orphans found), ok (if none)
- Check: entity has no outgoing AND no incoming relationships AND classification
  is neither `lookup` NOR `technical` AND its `entité.md` heading block carries
  no `Isolation: by-design — <reason>` line with a non-empty reason.
- Rationale: most isolated entities are modeling mistakes, but two cases are
  legitimately standalone and exempt:
  - classification `lookup` (admin reference table, referenced by `code`/FK) and
    `technical` (sequence/counter/outbox allocator — isolated by nature);
  - any entity carrying an `Isolation: by-design — <reason>` line (reason
    non-empty) — the override is then captured AND justified in the model,
    instead of being waved away in chat at audit time.
- An entity that is isolated, is neither `lookup`/`technical`, and has the marker
  but with an EMPTY reason → still **warn** (justify it or link it).
- **warn**: list the isolated entity names (note whether the marker is missing or empty).
- Fix: `/ba-create-data-model` — link it, reclassify it `technical`, or add an
  `Isolation: by-design — <reason>` line.

### DM-010 — Every entity referenced by a business rule exists in the MCD
- **Severity**: warn (if unknown entities referenced), ok (if all valid)
- Symmetric inverse of the legacy BR-005: at audit-rules time the data model is
  still BLOCKED, so the check has been pulled here where the entity catalogue
  is authoritative. Direction now: rule → entity ref → MCD lookup.
- Check: for each business rule expression, extract PascalCase tokens (skip
  primitives `DateTime`, `DateOnly`, `TimeOnly`, `String`, `Boolean`). Each
  remaining token must match an entity code or name in the current MCD.
- Skip when no business rules exist in scope (rules phase still EMPTY).
- **warn**: list the rule expressions that reference unknown entities.
- Fix: `/ba-create-data-model` (or fix the rule via `/ba-create-business-rules`).

### DM-011 — Every entity must be referenced by ≥1 use case, business rule, or resource
- **Severity**: warn (if untraced entities found), ok (if all entities traceable)
- Reciprocal direction of DM-010: prevents hallucinated entities (entities
  proposed by `/ba-create-data-model` from "domain knowledge" or web search,
  with no anchor in the upstream phases). Anti-hallucination guard.
- Check: for each entity `e` in the current MCD, scan upstream phases for ≥1
  verbatim reference. Tokens to match : `[e.name, e.code]` (NOT `e.tablePrefix`
  — collisions). Junctions inherit traceability from their relationships:
  if `e` has relationships whose target entity codes are themselves
  traced, `e` is traced by inheritance.
- Sources to scan, in order :
  - use case main flow + alternative flow steps + exception flow steps
    + preconditions + postconditions (PascalCase token match,
    case-insensitive)
  - business rule expression (PascalCase tokens, skip primitives — same
    skip-list as DM-010)
  - business rule resource code (string equality with `e.code` OR `e.name`)
  - Resource codes from the menu tree (esp. `SmartListView`)
- Dual parsing of the entity **Traçabilité** line :
  - If it carries structured references (`{ references: [...] }`) — use the array
    directly.
  - If it is prose — extract via regex `/(UC|BR|RES)-[A-Z0-9_-]+/g`.
- Skip when the module has no use cases, no rules and no resources (upstream
  phases empty — nothing to match against, default `ok`).
- **ok**: state the count of traced entities.
- **warn**: list the untraced entity names.
- Fix: `/ba-create-data-model`.
- **solution** (mandatory on warn): "For each listed entity, either add a
  verbatim reference in its **Traçabilité** line (e.g. `deduced from UC-XYZ
  step 3` or structured `{references: ['UC-XYZ', 'BR-NNN']}`), or remove
  the entity if it has no business justification in the upstream phases."

### DM-012 — Lookup entities have a unique index on `code`
- **Severity**: warn (if lookup with `code` attribute but no unique index on it), ok (if all lookups conform or no lookups in scope)
- Lookups are referenced as FK targets by `code`. Without an explicit unique
  index the uniqueness isn't enforced at the DB level (column-level UNIQUE
  on the attribute MAY suffice, but composite tenant-scoped uniqueness
  needs an explicit index entry). Without the index, FK constraint
  generation drifts and seed data can produce duplicates.
- Check: for each entity with classification `lookup` AND an attribute
  whose name is "code" (case-insensitive):
  - The entity's index list MUST contain at least one entry whose
    fields begin with `"code"` AND that is `unique`.
  - The check accepts both single-column `{fields:["code"], unique:true}`
    and composite `{fields:["code", "tenantId"], unique:true}` forms.
- Skip when the entity has no attribute named "code" (e.g., a lookup keyed by
  another natural identifier — flagged separately by DM-015).
- **warn**: list the lookup entities missing the unique index.
- Fix: `/ba-create-data-model`.
- **solution** (mandatory on warn): "Add `{fields:[\"code\"], unique:true}`
  to the index list of each listed lookup entity (and any composite
  tenant-scoped index your domain requires)."

### DM-013 — Every `*Id:guid` attribute is paired with a relationship or documented cross-module
- **Severity**: warn (if implicit FKs detected), ok (if all FK attributes paired)
- Catches the "implicit FK" anti-pattern: an attribute that LOOKS like a
  foreign key (`*Id:guid`) but neither has a sibling relationship declaring
  the cardinality + cascade NOR explicitly states it points to another module
  / SmartStack Core. EF Core code generation needs ONE of those signals to
  produce a proper FK constraint.
- Check: for each entity, for each attribute where dataType is `guid`
  AND name ends with `"Id"` AND name is not `"id"`:
  - PASS if the entity's relationships contain a relationship whose
    foreign-key name (case-insensitive) matches the PascalCase form of the
    attribute name (e.g., `budgetId` → `BudgetId`), OR whose name
    (case-insensitive) matches the attribute name without the trailing `Id`
    (e.g., `budgetId` ↔ `budget`).
  - PASS if the attribute's description explicitly names a foreign module
    or Core entity. Heuristic: the description contains any of the tokens
    `"FK"`, `"cross-module"`, `"Core"`, `"auth_"`, `"nav_"`, `"tnt_"`,
    or names another application code / module code from the menu.
  - Otherwise WARN — flag the `entity.attr` pair for review.
- Skip when the attribute is the implicit primary key (name is `"id"`,
  already reserved at definition time).
- **warn**: list the offending `entity.attr` pairs.
- Fix: `/ba-create-data-model`.
- **solution** (mandatory on warn): "For each listed `entity.attr`, either
  (a) add a relationship whose foreign-key name matches the attribute and
  picks the right cardinality + cascade if the target is in the same module,
  or (b) extend the attribute's description to explicitly name the foreign
  module / Core entity (e.g., \"FK cross-module vers CRM/CONTACTS.Contact\",
  \"FK to auth_Users\")."

### DM-014 — Lookup entities are referenced by ≥1 other entity
- **Severity**: warn (if unreferenced lookup found), ok (if all lookups referenced)
- Reciprocal of DM-009 specifically for lookups. A lookup that no other
  entity references — neither via a relationship nor via an `*Id:guid`
  attribute — is dead code: no UC will ever read or write it. Distinct from
  DM-009 (which fires on isolated NON-lookup entities) and DM-011 (which
  flags hallucinated entities lacking an upstream UC/BR/RES anchor).
- Check: for each entity `L` with classification `lookup`:
  - Count incoming declared relationships : every entity `e` in the MCD
    such that one of `e`'s relationships targets `L.code` or `L.name`.
  - Count plain-guid FK attributes that look like FK to `L` : every entity
    `e` with an attribute `a` where `a` is `guid` AND its name is
    `${camelCase(L.name)}Id` (e.g., `BudgetType` → `budgetTypeId`).
  - If BOTH counts are 0 → WARN. List `L.name` (and `L.code`).
- Skip when the module has no lookup entities.
- **warn**: list the unreferenced lookup names.
- Fix: `/ba-create-data-model`.
- **solution** (mandatory on warn): "For each listed lookup, either declare
  a relationship from the entity that needs it (parent → lookup, N:1,
  cascade `restrict`), add a `${camelCase(name)}Id:guid` attribute on that
  entity, or remove the lookup via `/ba-create-data-model` if no entity
  needs it."

### DM-015 — Classification-based attribute conventions
- **Severity**: warn (if conventions violated), ok (if all entities conform)
- Each classification has a conventional minimum attribute set that downstream
  generation (Domain entity, EF Core mapping, seed data, list/form UI)
  expects. Missing fields don't block development but produce thin scaffolds
  that won't render correctly in the generated app.
- Check by classification:
  - **lookup** : SHOULD declare `code:string,unique:true`, `label:string` (OR `name:string`),
    `isActive:bool`. `sortOrder:int` is recommended (controls display order).
  - **technical** : a sequence / counter / outbox allocator — **EXEMPT** from the
    lookup `code/label/isActive/sortOrder` set. SHOULD declare its scope key
    (e.g. `year:int`, `scope:string`) + the running value (`nextValue:int` or
    `lastValue:int`), and carry the `Isolation: by-design — <reason>` line
    (DM-009 exemption).
  - **component** : SHOULD have ≥1 FK linking it to a parent entity — either
    a relationship (any cardinality) OR a `*Id:guid` attribute pairable
    with DM-013. A component with neither is a misclassified
    lightweight-module candidate.
  - **lightweight-module** / **full-module** : SHOULD have a business
    identifier — either a `code:string` attribute (typically with a declared
    `codePattern`) OR a `name:string` / `label:string` attribute. The list
    UI uses this as the row title.
- Skip entities already flagged by DM-002 (no attributes at all — fix that
  first, the convention check is meaningless on empty entities).
- For each violation, list `entity.code:missing-fields`
  (e.g., `BudgetType:label,isActive`).
- **warn**: list the entities with their missing convention fields.
- Fix: `/ba-create-data-model`.
- **solution** (mandatory on warn): "Add the missing convention fields per
  classification. Lookups : `code:string(50),unique:true`, `label:string(150)`,
  `isActive:bool` (default true), `sortOrder:int`. Components : declare the
  FK to the parent (preferred) or convert to lightweight-module. Modules : add
  a `code` (with `codePattern`) or `name` attribute as the business identifier."

### DM-016 — Computed attributes (`formula`) reference only same-entity properties
- **Severity**: err (if any formula references a foreign attribute), ok (if all conform or no formulas in scope)
- Computed attributes are translated downstream into a LINQ projection
  (`scaffold-repository`) running inside a single SQL query. Cross-entity
  formulas would require a JOIN that EF Core can't infer from the MCD alone
  and produce broken SQL at runtime — the `dev` cascade can't recover.
- Check: for each attribute `a` with a non-empty **Calculé** formula:
  - Tokenize the formula and extract every PascalCase identifier (regex
    `/\b[A-Z][A-Za-z0-9]*\b/g`), skipping C# primitives (`Math`, `Convert`,
    `String`, `DateTime`, `DateOnly`, `TimeOnly`, `Boolean`, `Int32`,
    `Decimal`, `Guid`).
  - Each remaining token MUST match (case-insensitive) the PascalCase form
    of one of the SAME entity's attributes capitalised — `currentBalance`
    → `CurrentBalance`.
  - Otherwise ERR — list `entity.attr→missing-token`.
- Also flag formulas exceeding 1000 characters (defensive: matches the DB
  column constraint).
- Skip attributes without a formula.
- **ok**: state the number of formulas verified.
- **err**: list each `entity.attr→token` whose formula crosses entities.
- Fix: `/ba-create-data-model`.
- **solution** (mandatory on err): "Cross-entity formulas are not supported.
  Either rewrite the formula using only attributes of the SAME entity (denormalize
  the foreign value into a local attribute updated by a business rule), or move
  the computation to the backend service layer (out of the MCD) and remove
  the formula from the attribute."

### DM-017 — Code-pattern entities have a matching numbering rule + coherent scope
- **Severity**: warn (if a codePattern is incomplete or unmatched), ok (if all coherent or none)
- A `codePattern` is the data-model side of a `numbering` business rule. Without a
  matching rule the generated numbering has no spec (format / scope / reset /
  gapless) to follow; an inconsistent scope silently collides across tenants.
- **Check**, for each entity carrying a `codePattern`:
  1. A `numbering` business rule for this entity exists — Grep the module's
     `règles-métier.md` for a `Type: numbering` rule naming the entity, OR a
     `BR-…` ref in the entity's **Traçabilité**. Missing → warn.
  2. The pattern's **scope** is consistent with `tenantMode`: a `tenant`-scoped
     pattern requires `tenantMode` ≠ `none`; a `global` pattern on `strict`
     tenant data must be explicitly intended. Mismatch → warn.
  3. The **reset** period aligns with a date token in the format (yearly ⇒
     `{YY}` / `{YYYY}`). Misaligned → warn.
  4. If the pattern is **gapless**, a `technical` allocator entity keyed per scope
     SHOULD exist in the MCD (else the gapless guarantee is deferred to the
     runtime — acceptable, but flag it). Absent → warn.
- Skip when the module has no entity with a code pattern.
- **warn**: list `entity:missing-numbering-rule` / `entity:scope-mismatch` /
  `entity:reset-misaligned` / `entity:gapless-no-allocator`.
- Fix: `/ba-create-data-model` (or add the rule via `/ba-create-business-rules`).
- **solution** (mandatory on warn): "For each listed entity, add/complete a
  `numbering` rule (format + scope + reset + gapless) in `règles-métier.md` and
  reference it in the entity **Traçabilité**; align the codePattern scope with
  `tenantMode`; model a `technical` allocator entity when the pattern is gapless."

## Output

Write `_audit/entité.md` per the doc-templates skeleton:
- Header `# Audit entité — <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 codes / `entity.attr`
  **bold**), why it matters, and a `→` fix naming `/ba-create-data-model`.
  When a rule defines a **solution**, fold it into the fix bullet.
- The rule codes (`DM-001`, …) stay **bold** so they remain greppable, but they
  are explained in business terms (no i18n label codes).
- 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. If any `err`, state clearly that the data model must be fixed before
moving on.

## Used by the readiness orchestrator

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