---
name: step-04-consolidate
description: Cross-module validation - data model, permissions, E2E flows, approval
model: opus
next_step: none
---

> **Context files:** `_shared.md`

# Step 4: Consolidate - Cross-Module Validation & Approval

## MANDATORY EXECUTION RULES
- ALWAYS load granular JSON files via index.json (entities.json, rules.json, permissions.json, etc.)
- ALWAYS validate permission coherence across modules
- ALWAYS execute semantic validation checks
- NEVER duplicate module content - work with summaries only
- ALWAYS ask for client approval, even for single-module projects (play devil's advocate)

## Context Recovery

Re-read session variables from `.business-analyse/config.json`:
```javascript
const config = readJSON('.business-analyse/config.json').currentFeature;
const { id: feature_id, docsDir: docs_dir, projectSlug, application: application_name,
        applicationCode, workflowMode: workflow_mode, language } = config;
```

> **Consolidation output (ba-006):** Written to `{projectDir}/consolidation.json`.

## YOUR TASK
Consolidate all modules by validating cross-module interactions, permission coherence, data model integrity, E2E flows, and global risks. Obtain client approval and write consolidation data.

---

## EXECUTION SEQUENCE

### 1. Load Completed Modules & Validate Status

```
ba-reader.getCompletedModulesSummary({feature_id})
→ For each module: code, entities[], key BRs, permissions[], dependencies
→ Max 100 lines per module summary

ba-reader.readApplicationContext({feature_id})
→ cadrage.applicationRoles, modules[], dependencyGraph

IF status already "consolidated":
  Display: "Consolidation already completed. BA workflow finished."
  Display: "Next: invoke /business-analyse-design to design interfaces, then /business-analyse-html for client review."
  STOP
```

### 1b. Analysis Quality Self-Assessment (MANDATORY)

> **Score the analysis quality BEFORE presenting to the client.**
> This catches low-quality output before it reaches consolidation.

#### 1b-i. Research Coverage
```javascript
let researchScore = 0;
if (domain_research?.source === "built-in-only") {
  researchScore = 2;
} else {
  // +2 per application with Level 1 research
  for (const app of applications) {
    if (domain_research?.applications?.[app.code]) researchScore += 2;
  }
  // +1 per module with Level 2 research (cap at 4)
  let moduleResearchCount = 0;
  for (const module of completedModules) {
    if (module.domainResearchSource !== "built-in-only") moduleResearchCount++;
  }
  researchScore += Math.min(moduleResearchCount, 4);
}
researchScore = Math.min(10, researchScore);
```

#### 1b-ii. Module Completeness (per app)
```javascript
for (const app of applications) {
  const archetype = loadArchetype(app.type); // from module-completeness-challenge.md
  const corePresent = archetype.coreModules.filter(m => appHasModule(app, m)).length;
  const coreTotal = archetype.coreModules.length;
  app.completenessScore = (corePresent / coreTotal) * 10;
  // Penalty if CORE excluded without justification
  const unjustified = archetype.coreModules.filter(m =>
    !appHasModule(app, m) && !isExplicitlyExcluded(app, m)
  );
  if (unjustified.length > 0) app.completenessScore -= 3;
}
const avgModuleScore = average(applications.map(a => a.completenessScore));
```

#### 1b-iii. Section Depth (per module)
```javascript
for (const module of completedModules) {
  const sectionCount = module.anticipatedSections?.length || 0;
  if (module.featureType === "workflow") {
    if (sectionCount < 5) module.sectionScore = 4;
    else if (sectionCount < 7) module.sectionScore = 7;
    else module.sectionScore = 9;
    // Bonus for contextual views
    const hasContextual = module.anticipatedSections?.some(s =>
      ["open-requests", "rejected", "history", "my-balance", "my-tasks", "overdue"].includes(s.code)
    );
    if (hasContextual) module.sectionScore = Math.min(10, module.sectionScore + 1);
  } else {
    module.sectionScore = sectionCount >= 3 ? 8 : sectionCount >= 2 ? 6 : 4;
  }
}
const avgSectionScore = average(completedModules.map(m => m.sectionScore));
```

#### 1b-iv. Business Rule Depth (per module)
```javascript
for (const module of completedModules) {
  const rules = module.rules || [];
  const domainSpecific = rules.filter(r => r.domainSpecific === true || r.source === "domain-research").length;
  const total = rules.length;
  if (total === 0) { module.ruleScore = 0; continue; }
  const genericRatio = (total - domainSpecific) / total;
  if (genericRatio > 0.8) module.ruleScore = 3;       // >80% generic = poor
  else if (genericRatio > 0.5) module.ruleScore = 6;   // 50-80% generic = acceptable
  else module.ruleScore = 9;                           // <50% generic = excellent
}
const avgRuleScore = average(completedModules.map(m => m.ruleScore));
```

#### 1b-v. Global Quality Score
```javascript
const qualityScore = Math.round(
  (researchScore + avgModuleScore + avgSectionScore + avgRuleScore) / 4
);
```

Display:
```
### Auto-evaluation qualite de l'analyse

| Dimension | Score | Detail |
|-----------|-------|--------|
| Recherche web | {researchScore}/10 | {N} apps recherchees, {M} modules recherches |
| Completude modules | {avgModuleScore}/10 | {details per app} |
| Profondeur sections | {avgSectionScore}/10 | {details per module} |
| Profondeur regles metier | {avgRuleScore}/10 | {domainSpecific}/{total} regles specifiques domaine |
| **GLOBAL** | **{qualityScore}/10** | |
```

```
IF qualityScore < 7:
  WARNING: "Score qualite inferieur a 7/10.
    Recommandation : relancer les etapes deficientes avec WebSearch active."
  Display: "Dimensions a ameliorer: {list dimensions < 7}"
```

Store in `validation.json` as `qualityAssessment`.

### 1c. Business Process Coverage Validation (BLOCKING for multi-app)

> **Verify that all identified business processes are covered by modules.**
> A process step without a covering module = a GAP in the application.

```javascript
IF cadrage._preAnalysis?.businessProcesses?.length > 0:
  const gaps = [];
  for (const process of cadrage._preAnalysis.businessProcesses) {
    for (const step of process.steps) {
      if (!step.covered) {
        gaps.push({ process: process.name, step: step.name, apps: process.apps });
      }
    }
  }

  Display:
  ### Couverture des processus metier
  | Processus | Etapes | Couvertes | Gaps |
  |-----------|--------|-----------|------|
  {for each process: name, steps.length, covered count, gap names}

  IF gaps.length > 0:
    WARNING: "{gaps.length} etapes de processus non couvertes."
    // Not BLOCKING at consolidation (should have been caught in step-01/02)
    // But display as warning for final visibility
```

Store in `validation.json` as `processCoverage`.

### 2. Cross-Module Interaction Analysis

→ **Load** `references/04-cross-module-validation.md` for FK validation rules, cycle detection, and interaction mapping.

**Execution:**
- Load module index files and granular JSON (entities, permissions)
- Build entity registry and detect shared entities
- Validate foreign key references across modules
- Detect circular dependencies (blocking error if any found)
- Display cross-module interaction map with validation status

### 3. Permission Coherence Check

Load permissions from all modules and validate:

```javascript
const allRoles = {};
const permissionPaths = [];

for (const module of completedModules) {
  for (const perm of module.permissions) {
    // Verify path format: {app}.{module}.{action} or {app}.{module}.{section}.{action}
    if (!perm.path.match(/^[A-Za-z]+\.[A-Za-z]+\.[A-Za-z]+(\.[A-Za-z]+)?$/)) {
      ERROR(`Invalid permission path: "${perm.path}"`);
    }

    // Verify role exists in cadrage.applicationRoles
    if (!cadrage.applicationRoles.find(r => r.role === perm.role)) {
      WARNING(`Permission assigned to undefined role: ${perm.role}`);
    }

    permissionPaths.push(perm);
  }
}

// Verify role hierarchy: Admin ⊃ Manager ⊃ Contributor ⊃ Viewer
const hierarchy = ["Admin", "Manager", "Contributor", "Viewer"];
// For each admin permission, verify manager/contributor/viewer exist with same path
```

**3a. Role Consistency** — All modules use same applicationRoles ✓

**3b. Permission Path Format** — 3-segment or 4-segment patterns ✓

**3c. Role Hierarchy Coherence** — Admin ⊃ Manager ⊃ Contributor ⊃ Viewer ✓

**3d. Permission Conflicts** — Present to user via AskUserQuestion if Manager can approve in one module but not another

**3e. Permission-Role Coherence** — Verify pattern consistency across modules (store as WARNING if intentional)

### 4. Semantic Validation (MANDATORY)

→ **Load** `references/04-semantic-validation-matrix.md` and execute ALL semantic validation checks.

This includes:
- 13 semantic checks per module (permission orphans, UC coverage, entity doubles, permission ghosts, etc.)
- 5 contract verification checks (BR formulas, computed attributes, UC counts, permission counts, versioned attributes)
- Blocking rule: 0 ERROR → PASS, ≥1 ERROR → BLOCK (user must fix before proceeding)

**BR-EXAMPLES-COMPLETE check — qualité test-ready (NEW):**
```javascript
for (const module of completedModules) {
  const rules = module.rules || [];
  if (rules.length === 0) continue;

  // 1. Couverture : toutes les règles doivent avoir des exemples
  const withExamples = rules.filter(r => r.examples?.length > 0);
  const exampleRate = withExamples.length / rules.length;

  if (exampleRate < 0.4) {
    ERROR(`Module ${module.code}: ${withExamples.length}/${rules.length} BRs ont des exemples (${Math.round(exampleRate*100)}% < 40% minimum) — BLOQUANT`);
  } else if (exampleRate < 0.7) {
    WARNING(`Module ${module.code}: ${withExamples.length}/${rules.length} BRs ont des exemples (${Math.round(exampleRate*100)}% — cible: 100%)`);
  }

  // 2. Format test-ready : exemples avec given/when/then
  const testReady = rules.filter(r =>
    r.examples?.some(e => e.given && e.when && e.then)
  );
  const proseOnly = rules.filter(r =>
    r.examples?.length > 0 && !r.examples.some(e => e.given && e.when && e.then)
  );

  if (testReady.length < 4 && module.featureType !== 'portal') {
    WARNING(`Module ${module.code}: ${testReady.length}/4 BRs au format test-ready {given, when, then}`);
  }
  if (proseOnly.length > 0) {
    WARNING(`Module ${module.code}: ${proseOnly.length} BRs avec exemples en prose — convertir en format test-ready pour génération de tests`);
  }

  // 3. BR-CALC : formule + exemple chiffré obligatoires
  const calcRules = rules.filter(r => r.category === 'calculation');
  const calcWithoutFormula = calcRules.filter(r => !r.formula);
  const calcWithoutNumericExample = calcRules.filter(r =>
    !r.examples?.some(e =>
      (e.scenario === 'calculation') ||
      (e.then && Object.values(e.then).some(v => typeof v === 'number'))
    )
  );

  if (calcWithoutFormula.length > 0) {
    ERROR(`Module ${module.code}: ${calcWithoutFormula.length} BR-CALC sans champ formula`);
  }
  if (calcWithoutNumericExample.length > 0) {
    WARNING(`Module ${module.code}: ${calcWithoutNumericExample.length} BR-CALC sans exemple chiffré — les tests de calcul nécessitent des valeurs numériques`);
  }

  // 4. Couverture scénarios : chaque règle devrait avoir happy_path + error
  const withBothScenarios = rules.filter(r =>
    r.examples?.some(e => e.scenario === 'happy_path' || e.then?.result === 'success') &&
    r.examples?.some(e => e.scenario === 'error' || e.then?.result === 'error')
  );
  if (withBothScenarios.length < rules.length * 0.5 && module.featureType !== 'portal') {
    WARNING(`Module ${module.code}: ${withBothScenarios.length}/${rules.length} BRs couvrent happy_path + error — cible: 100%`);
  }
}
```

### 4b. Language Coherence Check (MANDATORY)

Verify that ALL generated content respects `{language}` from config:

```javascript
const expectedLang = language; // from config.json

// Heuristics for language detection
const frIndicators = /\b(le|la|les|un|une|des|du|de|est|sont|doit|peut|dans|pour|avec|sur|par|cette|tout|qui)\b/i;
const enIndicators = /\b(the|a|an|is|are|must|can|in|for|with|on|by|this|all|who|should|when|each)\b/i;

function detectLang(text) {
  if (!text || text.length < 10) return null;
  const frScore = (text.match(frIndicators) || []).length;
  const enScore = (text.match(enIndicators) || []).length;
  if (frScore > enScore * 1.5) return 'fr';
  if (enScore > frScore * 1.5) return 'en';
  return null; // ambiguous
}

const langIssues = [];
for (const module of completedModules) {
  // Sample entity descriptions
  (module.entities || []).forEach(e => {
    const detected = detectLang(e.description);
    if (detected && detected !== expectedLang) {
      langIssues.push({ module: module.code, type: 'entity', name: e.name, detected, text: e.description?.substring(0, 60) });
    }
  });
  // Sample BR statements
  (module.rules || []).forEach(r => {
    const detected = detectLang(r.statement);
    if (detected && detected !== expectedLang) {
      langIssues.push({ module: module.code, type: 'rule', name: r.id, detected, text: r.statement?.substring(0, 60) });
    }
  });
  // Sample UC names
  (module.useCases || module.usecases || []).forEach(u => {
    const detected = detectLang(u.name);
    if (detected && detected !== expectedLang) {
      langIssues.push({ module: module.code, type: 'useCase', name: u.id || u.name, detected, text: u.name });
    }
  });
}

if (langIssues.length > 0) {
  WARNING(`${langIssues.length} content items detected in wrong language (expected: ${expectedLang})`);
  Display(langIssues.slice(0, 10).map(i => `  - ${i.module}/${i.type} "${i.name}": detected ${i.detected}, text: "${i.text}"`).join('\n'));

  if (langIssues.length > completedModules.length * 3) {
    BLOCKING_ERROR(`Too many language violations (${langIssues.length}). Re-specify affected modules in ${expectedLang}.`);
  }
}
```

### 5. Data Model Consolidation

Generate global entity relationship diagram:

```javascript
const globalEntities = [];
const globalRelationships = [];

for (const module of completedModules) {
  for (const entity of module.entities) {
    globalEntities.push({
      name: entity.name,
      module: module.code,
      attributes: entity.attributes.length,
      pk: entity.pk || "Id"
    });

    for (const rel of entity.relationships || []) {
      globalRelationships.push({
        sourceEntity: entity.name,
        sourceModule: module.code,
        targetEntity: rel.target.split(".")[1] || rel.target,
        targetModule: rel.target.split(".")[0] || module.code,
        cardinality: rel.cardinality || "N:1",
        cascadeDelete: rel.cascadeDelete || false
      });
    }
  }
}

// Display ERD summary
Display("Global Entity Count: {count}")
Display("Global Relationships: {count}")
```

### 5a. Cross-Module Contracts (MANDATORY)

> **Generate explicit contracts for every cross-module FK dependency.**
> These contracts ensure both producer and consumer modules agree on the data shape.
> They prevent the #1 cross-module integration failure: wrong field type/name/nullability.

```javascript
const crossModuleContracts = [];

for (const rel of globalRelationships) {
  // Only for cross-module relationships
  if (rel.sourceModule !== rel.targetModule) {
    // Resolve the FK field from the source entity's attributes
    const sourceEntity = findEntity(rel.sourceModule, rel.sourceEntity);
    const fkField = sourceEntity.attributes.find(a =>
      a.name.toLowerCase() === (rel.targetEntity.toLowerCase() + 'id') ||
      a.description?.includes('FK vers ' + rel.targetEntity)
    );

    crossModuleContracts.push({
      consumer: `${rel.sourceModule}.${rel.sourceEntity}`,
      provider: `${rel.targetModule}.${rel.targetEntity}`,
      field: fkField?.name || `${rel.targetEntity.charAt(0).toLowerCase() + rel.targetEntity.slice(1)}Id`,
      type: fkField?.type || "guid",
      required: fkField?.required || false,
      nullable: !fkField?.required,
      resolvedVia: `FK → ${rel.targetEntity}.Id`,
      cardinality: rel.cardinality,
      cascadeDelete: rel.cascadeDelete,
      description: `${rel.sourceEntity} references ${rel.targetEntity} from ${rel.targetModule}`
    });
  }
}
```

Display:
```
### Contrats cross-module ({crossModuleContracts.length})

| Consumer | Provider | FK Field | Type | Required | Cascade |
|----------|----------|----------|------|----------|---------|
{for each contract: | {consumer} | {provider} | {field} | {type} | {required} | {cascadeDelete} |}
```

Store in `consolidation.json` as `crossModuleContracts[]`.

> These contracts are consumed by `/business-analyse-handoff` and `/business-analyse-develop`
> to ensure correct FK generation, DTO mapping, and navigation property creation across modules.

### 5b. Mermaid Diagram Generation (MANDATORY)

> Generate 3 types of Mermaid diagrams as text, stored in consolidation.json.
> These diagrams are rendered in the interactive HTML and exported with handoff.

#### 5b-i. ERD (Entity-Relationship Diagram)

From `globalEntities` and `globalRelationships` built in section 5, generate Mermaid ERD:

```javascript
let erd = "erDiagram\n";
for (const entity of globalEntities) {
  erd += `  ${entity.name} {\n`;
  for (const attr of entity.topAttributes) { // top 5-8 attributes
    erd += `    ${attr.type} ${attr.name}\n`;
  }
  erd += "  }\n";
}
for (const rel of globalRelationships) {
  const card = rel.cardinality === "1:N" ? "||--o{" :
               rel.cardinality === "N:1" ? "}o--||" :
               rel.cardinality === "N:M" ? "}o--o{" : "||--||";
  erd += `  ${rel.sourceEntity} ${card} ${rel.targetEntity} : "${rel.description}"\n`;
}
```

Store as `consolidation.mermaidDiagrams.erd` (string).

#### 5b-ii. State Machine Diagrams (per entity lifecycle)

For EACH `lifeCycle` found in all modules' `usecases.json`:

```javascript
let sm = "stateDiagram-v2\n";
sm += `  [*] --> ${lifecycle.initialState}\n`;
for (const transition of lifecycle.transitions) {
  sm += `  ${transition.from} --> ${transition.to} : ${transition.action}\n`;
}
for (const state of lifecycle.states.filter(s => s.isTerminal)) {
  sm += `  ${state.id} --> [*]\n`;
}
```

Store as `consolidation.mermaidDiagrams.stateMachines[{entity}]` (string).

#### 5b-iii. Sequence Diagrams (per E2E flow)

For EACH e2eFlow identified in section 6:

```javascript
let seq = "sequenceDiagram\n";
for (const step of flow.steps) {
  seq += `  ${step.actor || step.module}->>+${step.targetModule || "System"}: ${step.action}\n`;
  if (step.result) {
    seq += `  ${step.targetModule || "System"}-->>-${step.actor || step.module}: ${step.result}\n`;
  }
}
```

Store as `consolidation.mermaidDiagrams.sequences[{flowName}]` (string).

#### 5b-iv. Use Case Diagrams (per module)

For EACH module, generate a Mermaid use case diagram showing actors and their interactions:

```javascript
const useCaseDiagrams = {};

for (const module of completedModules) {
  const usecases = module.useCases || module.usecases || [];
  if (usecases.length === 0) continue;

  // Collect unique actors
  const actors = [...new Set(usecases.map(uc => uc.primaryActor || uc.actor).filter(Boolean))];

  // Build flowchart-based UC diagram (Mermaid does not have native usecase syntax)
  let diagram = "flowchart LR\n";

  // Add actors (left side)
  actors.forEach((actor, i) => {
    const actorId = 'actor_' + actor.replace(/[^a-zA-Z0-9]/g, '_');
    diagram += `  ${actorId}[/"👤 ${actor}"\\]\n`;
    diagram += `  style ${actorId} fill:#e1f5fe,stroke:#0288d1,color:#01579b\n`;
  });

  // Group UCs by section
  const sections = [...new Set(usecases.map(uc => uc.sectionCode).filter(Boolean))];

  sections.forEach(section => {
    const sectionUCs = usecases.filter(uc => uc.sectionCode === section);
    diagram += `  subgraph ${section}["${section}"]\n`;
    sectionUCs.forEach(uc => {
      const ucId = (uc.id || uc.name).replace(/[^a-zA-Z0-9]/g, '_');
      diagram += `    ${ucId}(("${uc.name}"))\n`;
    });
    diagram += `  end\n`;
  });

  // UCs without section
  const noSectionUCs = usecases.filter(uc => !uc.sectionCode);
  noSectionUCs.forEach(uc => {
    const ucId = (uc.id || uc.name).replace(/[^a-zA-Z0-9]/g, '_');
    diagram += `  ${ucId}(("${uc.name}"))\n`;
  });

  // Connect actors to UCs
  usecases.forEach(uc => {
    const actor = uc.primaryActor || uc.actor;
    if (!actor) return;
    const actorId = 'actor_' + actor.replace(/[^a-zA-Z0-9]/g, '_');
    const ucId = (uc.id || uc.name).replace(/[^a-zA-Z0-9]/g, '_');
    diagram += `  ${actorId} --> ${ucId}\n`;
  });

  useCaseDiagrams[module.code] = diagram;
}
```

Store as `consolidation.mermaidDiagrams.useCases` (object: moduleCode → diagram string).

#### 5b-v. MCD (Modèle Conceptuel de Données)

Generate a simplified conceptual data model showing ONLY entity names and named relationships with cardinalities. Unlike the ERD which shows all attributes, the MCD focuses on the conceptual structure.

```javascript
let mcd = "erDiagram\n";

// Entities: name + PK only (no attributes)
for (const ent of globalEntities) {
  mcd += `  ${ent.name} {\n`;
  mcd += `    ${ent.pk || 'guid'} Id PK\n`;
  mcd += `  }\n`;
}

// Named relationships with cardinalities
for (const rel of globalRelationships) {
  const card = rel.cardinality === "1:N" ? "||--o{" :
               rel.cardinality === "N:1" ? "}o--||" :
               rel.cardinality === "N:M" ? "}o--o{" :
               rel.cardinality === "1:1" ? "||--||" : "||--o{";

  // Use description as relationship label, or derive from cardinality
  const label = rel.description ||
    (rel.cardinality === "1:N" ? "contient" :
     rel.cardinality === "N:1" ? "appartient" : "associe");

  mcd += `  ${rel.sourceEntity} ${card} ${rel.targetEntity} : "${label}"\n`;
}
```

Store as `consolidation.mermaidDiagrams.mcd` (string).

#### 5b-vi. Storage

Write to `consolidation.json`:

```json
{
  "mermaidDiagrams": {
    "erd": "erDiagram\n  Employee {\n    guid id\n    string code\n    ...\n  }\n  ...",
    "mcd": "erDiagram\n  Employee {\n    guid Id PK\n  }\n  Employee ||--o{ Contract : \"contient\"\n  ...",
    "useCases": {
      "Employees": "flowchart LR\n  actor_RH[/\"👤 RH Admin\"\\]\n  ...",
      "Absences": "flowchart LR\n  ..."
    },
    "stateMachines": {
      "Absence": "stateDiagram-v2\n  [*] --> Draft\n  Draft --> Submitted : submit\n  ...",
      "Invoice": "stateDiagram-v2\n  [*] --> Draft\n  ..."
    },
    "sequences": {
      "CycleCommercial": "sequenceDiagram\n  Commercial->>+CRM: Creer opportunite\n  ...",
      "CycleFacturation": "sequenceDiagram\n  ..."
    }
  }
}
```

Display diagrams as markdown Mermaid code blocks for the client to preview before approval (section 9).

### 6. E2E Flow Validation

Identify business processes spanning multiple modules:

```javascript
const e2eFlows = [
  {
    name: "Order to Invoice",
    modules: ["Orders", "Customers", "Invoices"],
    steps: [
      { module: "Orders", action: "Create order", permission: "sales.orders.create", dataFlow: "Customer → Order" },
      { module: "Invoices", action: "Generate invoice", permission: "sales.invoices.create", dataFlow: "Order → Invoice" }
    ]
  }
];

for (const flow of e2eFlows) {
  // Verify all permissions exist
  // Verify entities in dataFlow are defined
  // Verify module sequence respects dependency graph
}
```

Display E2E flows and actors involved.

### 7. Global Risk Assessment

Evaluate application-level risks:

```javascript
const riskMetrics = {
  totalEntities: globalEntities.length,
  totalBRs: completedModules.reduce((sum, m) => sum + (m.rules?.length || 0), 0),
  crossModuleDependencies: dependencyGraph.edges.length,
  sharedEntities: sharedEntities.length,
  maxE2EFlowLength: Math.max(...e2eFlows.map(f => f.steps.length)),
  permissionPaths: permissionPaths.length
};

const risks = [
  riskMetrics.totalEntities > 20 ? {risk: "High entity count", severity: "medium"} : null,
  riskMetrics.totalBRs > 50 ? {risk: "High BR count", severity: "medium"} : null,
  riskMetrics.crossModuleDependencies > 10 ? {risk: "Complex module coupling", severity: "high"} : null,
  riskMetrics.sharedEntities > 5 ? {risk: "Many shared entities", severity: "medium"} : null,
  riskMetrics.maxE2EFlowLength > 8 ? {risk: "Long E2E flow", severity: "medium"} : null
].filter(r => r !== null);

// OBLIGATOIRE : risks[] ne peut pas être vide
if (risks.length === 0) {
  // Force au minimum un risque auto-détecté basé sur les métriques
  risks.push({
    risk: `Complexity baseline: ${riskMetrics.totalEntities} entities, ${riskMetrics.totalBRs} BRs, ${riskMetrics.crossModuleDependencies} cross-module deps`,
    severity: "low",
    mitigation: "Monitor implementation velocity and adjust estimates if needed"
  });
}
```

### 7bis. Critères d'acceptation globaux

> **OBLIGATOIRE** : Générer au minimum 1 critère d'acceptation par module.

```json
"acceptanceCriteria": [
  { "module": "Invoices", "criteria": "Une facture créée depuis un devis reprend automatiquement le client, les lignes et le montant", "type": "functional" },
  { "module": "Invoices", "criteria": "Le statut passe automatiquement à 'Paid' quand paidAmount >= total", "type": "business-rule" }
]
```

Les types possibles : `functional`, `business-rule`, `performance`, `security`.

Les critères d'acceptation sont écrits dans `validation.json` au même niveau que `globalRiskAssessment`.

### 7ter. Naming Audit (MANDATORY)

→ **Load** `references/04-naming-audit-checks.md` for all naming validation rules and user interaction flows.

This includes:
- Collect all application, module, entity, and permission names into a registry
- Display naming recap table (application code, module routes, entity tables, permission roots)
- Execute coherence checks (duplicate entities, route collisions, permission root alignment)
- Run MCP `validate_conventions` on entity/module names
- Present findings via AskUserQuestion with approval and rename options
- Process rename instructions if needed, re-validate, and continue to section 8

### 8. Consolidation Summary Display

```
═══════════════════════════════════════════════════════════════
  CONSOLIDATION SUMMARY - {application_name}
═══════════════════════════════════════════════════════════════

MODULES ({count})
| Module | Entities | BRs | UCs | Status |
|--------|----------|-----|-----|--------|
{for each module}

INTERACTIONS
- Cross-module references: {count}
- Shared entities: {count}
- E2E flows: {count}
- Circular dependencies: {count}

PERMISSIONS
- Roles: {count} (coherent: ✓)
- Total paths: {count}
- Conflicts: {count} (resolved)

SEMANTIC CHECKS
- Errors: {count}
- Warnings: {count}
- Pass rate: {percent}%

RISKS
{risk table}

═══════════════════════════════════════════════════════════════
```

### 9. Client Approval (BLOCKING)

**Toujours demander l'approbation** — meme pour un module unique, jouer l'avocat du diable :
proposer au moins 1 cas non mentionne ou 1 question de completude avant validation.

Ask via AskUserQuestion:

```
question: "La consolidation est complète. Validez-vous l'ensemble ?"
header: "Approbation Finale"
options:
  - label: "Approuvé"
    description: "Passer au handoff pour la phase d'implémentation"
  - label: "Réviser un module"
    description: "Retourner à la spécification d'un module spécifique"
  - label: "Modifier les interactions"
    description: "Ajuster les interactions cross-module"

IF "Réviser un module":
  Ask which module → set currentModuleIndex → load step-03-specify.md

IF "Modifier les interactions":
  RESTART consolidation (section 2-4)
```

### 10. Write Consolidation Data

→ **Load** `references/04-file-allocation.md` for strict data allocation rules.

**MANDATORY:** Data must be written to correct files:
- `consolidation.json`: crossModuleInteractions, sharedEntities, permissionCoherence, e2eFlows, decision
- `validation.json`: semanticChecks, contractChecks, namingAudit, acceptanceCriteria, globalRiskAssessment

Mixing contents across files = BLOCKING ERROR.

```javascript
ba-writer.enrichSection({
  featureId: {feature_id},
  section: "consolidation",
  data: {
    crossModuleInteractions: globalRelationships.map(rel => ({
      fromModule: rel.sourceModule,
      toModule: rel.targetModule,
      interactionType: "FK-reference|event|shared-lookup",
      description: `${rel.sourceEntity} → ${rel.targetEntity} (${rel.cardinality})`,
      entities: [`${rel.sourceEntity}→${rel.targetEntity}`]
    })),
    sharedEntities: sharedEntities.map(se => ({
      entity: se.entity,
      ownerModule: se.definedIn,
      referencedBy: [se.referencedBy],
      sharedFields: []
    })),
    permissionCoherence: {
      rolesConsistent: true,
      pathFormatConsistent: true,
      hierarchyRespected: true,
      conflicts: [],
      warnings: []
    },
    e2eFlows: e2eFlows,
    decision: {
      approved: approval.approved,
      reason: approval.reason,
      approvedBy: "Client",
      approvedAt: approval.approvedAt
    }
  }
});

// Write validation.json — quality/audit data for downstream skills (C1)
ba-writer.enrichSection({
  featureId: {feature_id},
  section: "validation",
  data: {
    semanticChecks: semanticChecks.map(check => ({
      check: check.check,
      status: errors.some(e => e.check === check.check) ? "ERROR"
            : warnings.some(w => w.check === check.check) ? "WARNING" : "PASS",
      details: [...errors, ...warnings]
        .filter(r => r.check === check.check)
        .map(r => `[${r.module}] ${r.details}`)
    })),
    contractChecks: [...errors, ...warnings].filter(r =>
      ["br-calc-sans-formula", "computed-sans-br-calc", "uc-count-critical",
       "uc-count-low", "permission-count-low", "missing-versioned-attributes"].includes(r.check)
    ),
    namingAudit: {
      auditedAt: now(),
      issues: namingIssues,
      approved: true,
      renames: []
    },
    acceptanceCriteria: acceptanceCriteria,  // from section 7bis
    globalRiskAssessment: risks              // from section 7
  }
});

// Add changelog entry
ba-writer.enrichSection({
  featureId: {feature_id},
  section: "metadata.changelog",
  data: {
    step: "step-04-consolidate",
    timestamp: now(),
    changes: [
      `Cross-module interactions: ${globalRelationships.length} FK references`,
      `Permission coherence: validated (${permissionPaths.length} paths)`,
      `E2E flows: ${e2eFlows.length} identified`,
      `Global risk: ${risks.length > 0 ? 'MEDIUM' : 'LOW'}`,
      `Semantic checks: PASSED`,
      `Naming audit: ${namingIssues.length} issues found, APPROVED`,
      `Client approval: APPROVED`
    ]
  }
});

// Update status
ba-writer.updateStatus({feature_id}, "consolidated");

// Clean up any forbidden files that may have been created at app level
ba-writer.cleanupAppLevelFiles({ featureId: {feature_id} });

// Save workflow state for resume support
ba-writer.enrichSection({
  featureId: {feature_id},
  section: "metadata.workflow",
  data: {
    lastCompletedStep: "step-04-consolidate",
    readyForHandoff: true
  }
});
```

### 11. Summary & Proceed

Display final summary:

```
✓ Consolidation completed
✓ All validations passed
✓ Client approval obtained
✓ Status: consolidated

BA workflow complete. Next steps:
→ Invoke /business-analyse-design to design interfaces (screens, wireframes, navigation)
→ Invoke /business-analyse-html to generate the interactive HTML for client review
→ Invoke /business-analyse-review to apply client corrections (if needed)
→ Invoke /business-analyse-handoff → /business-analyse-develop for implementation
```

---

## CONTEXT MANAGEMENT

| Loaded | Not Loaded |
|--------|------------|
| _shared.md | Full module JSON files |
| Module summaries | Questionnaires |
| cadrage.json | Templates |
| Granular JSON files via index.json | React viewer files |
| dependencyGraph | |

---

## SUCCESS CRITERIA

- ✓ All cross-module references resolved
- ✓ No circular dependencies
- ✓ Permission coherence validated
- ✓ Semantic checks: 0 errors
- ✓ Naming audit completed and approved
- ✓ Client approval obtained
- ✓ Consolidation section written to validation.json
- ✓ Status updated to "consolidated"
- ✓ Workflow state saved for resume support

---

## NEXT STEPS

BA workflow ends here. After consolidation:
1. Invoke `/business-analyse-design` to design interfaces (screens, wireframes, navigation)
2. Invoke `/business-analyse-html` to generate the interactive HTML document
3. Invoke `/business-analyse-review` to apply client corrections (if needed)
4. Invoke `/business-analyse-handoff` then `/business-analyse-develop` for implementation
