---
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
- Single-module shortcut: auto-approve consolidation if only 1 module

## 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, application: application_name,
        applicationCode, workflowMode: workflow_mode, language } = config;
```

## 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
```

### 2. Cross-Module Interaction Analysis

**2a. Load Module Index Files**

For each completed module, read `index.json` and discover granular files:

```javascript
const moduleFiles = {
  entities: "entities.json",
  rules: "rules.json",
  usecases: "usecases.json",
  permissions: "permissions.json",
  screens: "screens.json"
};

for (const module of completedModules) {
  const index = ba-reader.readSection(module.code, "index");
  module.files = index.files;
  // Load only files needed for consolidation
  module.entities = ba-reader.readSection(module.code, "entities");
  module.permissions = ba-reader.readSection(module.code, "permissions");
}
```

**2b. Foreign Key References & Shared Entities**

Build entity registry and validate cross-module references:

```javascript
const entityRegistry = {};
for (const module of completedModules) {
  entityRegistry[module.code] = {
    entities: module.entities.map(e => ({
      name: e.name,
      attributes: e.attributes.map(a => a.name),
      pk: e.attributes.find(a => a.name === "Id" || a.unique)?.name || "Id"
    }))
  };
}

// Detect shared entities
const sharedEntities = [];
for (const module of completedModules) {
  for (const entity of module.entities) {
    for (const rel of entity.relationships || []) {
      if (rel.target.includes(".")) {
        const [targetModule, targetEntity] = rel.target.split(".");
        if (!entityRegistry[targetModule]) {
          BLOCKING_ERROR(`Module ${module.code}: references non-existent module "${targetModule}"`);
        }
        sharedEntities.push({
          entity: targetEntity,
          definedIn: targetModule,
          referencedBy: module.code,
          referenceType: rel.type
        });
      }
    }
  }
}
```

**2c. Detect Circular Dependencies**

```javascript
const cycles = detectCycles(dependencyGraph);
if (cycles.length > 0) {
  BLOCKING_ERROR("Circular dependencies detected:");
  for (const cycle of cycles) {
    ERROR(`  ${cycle.join(" → ")}`);
  }
  ACTIONS: Review dependencies, move shared entities to Core module, or use event-driven communication
  STOP - DO NOT PROCEED
}
```

**2d. Display Cross-Module Interaction Map**

```
═══════════════════════════════════════════════════════════════
  CROSS-MODULE INTERACTIONS VALIDATED
═══════════════════════════════════════════════════════════════

| Source Module | Target | Type | Status |
|---------------|--------|------|--------|
| Orders        | Customers.Customer | FK (N:1) | ✓ RESOLVED |
| Invoices      | Customers.Customer | FK (N:1) | ✓ RESOLVED |

Total: {count} cross-module references
Shared entities: {count}
═══════════════════════════════════════════════════════════════
```

### 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)

Execute 13 semantic checks per module:

```javascript
const semanticChecks = [
  { check: "permission-orpheline", severity: "WARNING" },
  { check: "uc-sans-fr", severity: "WARNING" },
  { check: "entity-sans-endpoint", severity: "WARNING" },
  { check: "uc-sans-scenario", severity: "WARNING" },
  { check: "role-sans-permissions", severity: "WARNING" },
  { check: "navigation-sans-traduction", severity: "ERROR" },
  { check: "lifecycle-terminal", severity: "WARNING" },
  { check: "schema-conformity", severity: "ERROR" },
  { check: "wireframe-coverage", severity: "ERROR" },
  { check: "entity-doublon", severity: "ERROR" },
  { check: "permission-fantome", severity: "ERROR" },
  { check: "dashboard-coverage", severity: "WARNING" },
  { check: "permission-uc-alignment", severity: "WARNING" }
];

for (const module of completedModules) {
  for (const check of semanticChecks) {
    const result = executeSemanticCheck(module, check);
    if (result.status === "ERROR") {
      errors.push({module: module.code, check: check.check, details: result.details});
    } else if (result.status === "WARNING") {
      warnings.push({module: module.code, check: check.check, details: result.details});
    }
  }
}

// Blocking rule: 0 ERROR → PASS, ≥1 ERROR → BLOCK (ask user for each)
if (errors.length > 0) {
  BLOCKING_ERROR("Semantic validation failed");
  for (const error of errors) {
    ERROR(`  [${error.module}] ${error.check}: ${error.details}`);
  }
  STOP - User must fix before proceeding
}
```

### 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}")
```

### 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)

> **Challenge all names before final approval.** Every code, label, and route generated
> during the BA must be reviewed for coherence, convention compliance, and clarity.

**7ter-a. Collect all names from the BA:**

```javascript
const namingRegistry = {
  application: {
    label: application_name,
    code: applicationCode,              // PascalCase
    route: toKebabCase(applicationCode)  // kebab-case
  },
  modules: completedModules.map(m => ({
    label: m.name,
    code: m.code,                        // PascalCase
    route: toKebabCase(m.code)           // kebab-case
  })),
  entities: completedModules.flatMap(m =>
    m.entities.map(e => ({
      name: e.name,                      // PascalCase (C# class name)
      module: m.code,
      tableName: e.tableName || pluralize(e.name)  // Plural convention
    }))
  ),
  permissionRoots: [...new Set(
    permissionPaths.map(p => p.path.split('.').slice(0, 2).join('.'))
  )]
};
```

**7ter-b. Display naming recap table:**

```
═══════════════════════════════════════════════════════════════
  NAMING AUDIT — {application_name}
═══════════════════════════════════════════════════════════════

APPLICATION
| Label | Code (PascalCase) | Route (kebab-case) |
|-------|-------------------|--------------------|
| {application_name} | {applicationCode} | /{route} |

MODULES
| # | Label | Code (PascalCase) | Route (kebab-case) |
|---|-------|-------------------|--------------------|
{for each module: index | label | code | /app-route/module-route}

ENTITIES
| Entity (PascalCase) | Module | Table (plural) |
|----------------------|--------|----------------|
{for each entity: name | module | tableName}

PERMISSION ROOTS
{for each root: path}
═══════════════════════════════════════════════════════════════
```

**7ter-c. Coherence checks:**

```javascript
const namingIssues = [];

// 1. Duplicate entity names across modules
const entityNames = namingRegistry.entities.map(e => e.name);
const duplicates = entityNames.filter((n, i) => entityNames.indexOf(n) !== i);
if (duplicates.length > 0) {
  namingIssues.push({ severity: "ERROR", issue: `Duplicate entity names: ${[...new Set(duplicates)].join(', ')}` });
}

// 2. Module code vs label coherence (code should derive logically from label)
for (const mod of namingRegistry.modules) {
  if (!mod.code || mod.code.length < 2) {
    namingIssues.push({ severity: "ERROR", issue: `Module "${mod.label}" has invalid code: "${mod.code}"` });
  }
}

// 3. Permission root alignment with module codes
for (const root of namingRegistry.permissionRoots) {
  const [appSegment, moduleSegment] = root.split('.');
  const matchingModule = namingRegistry.modules.find(m =>
    m.code.toLowerCase() === moduleSegment.toLowerCase()
  );
  if (!matchingModule) {
    namingIssues.push({ severity: "WARNING", issue: `Permission root "${root}" has no matching module code` });
  }
}

// 4. Route collision detection
const allRoutes = namingRegistry.modules.map(m =>
  `/${namingRegistry.application.route}/${m.route}`
);
const routeDuplicates = allRoutes.filter((r, i) => allRoutes.indexOf(r) !== i);
if (routeDuplicates.length > 0) {
  namingIssues.push({ severity: "ERROR", issue: `Route collisions: ${routeDuplicates.join(', ')}` });
}
```

**7ter-d. MCP validation:**

```
mcp__smartstack__validate_conventions({
  checks: ["tables"],
  context: {
    applicationCode: applicationCode,
    modules: namingRegistry.modules.map(m => m.code),
    entities: namingRegistry.entities.map(e => e.name)
  }
})

→ Merge MCP findings into namingIssues[]
```

**7ter-e. Present findings and confirm:**

```
IF namingIssues.length > 0:
  Display issues table:
  | # | Severity | Issue |
  |---|----------|-------|
  {for each issue}
```

Ask via AskUserQuestion:
```
question: "{language == 'fr'
  ? 'Validez-vous les noms ci-dessus pour l\\'ensemble de l\\'application ?'
  : 'Do you approve all the names above for the entire application?'}"
header: "Naming Audit"
options:
  - label: "{language == 'fr' ? 'Approuvé' : 'Approved'}"
    description: "{language == 'fr' ? 'Tous les noms sont corrects' : 'All names are correct'}"
  - label: "{language == 'fr' ? 'Renommer certains éléments' : 'Rename some elements'}"
    description: "{language == 'fr' ? 'Corriger des noms avant de finaliser' : 'Fix names before finalizing'}"
```

**IF "Renommer certains éléments":**

Ask via AskUserQuestion (open-ended):
```
question: "{language == 'fr'
  ? 'Quels éléments souhaitez-vous renommer ? (ex: \"Module Ventes → Commerce\", \"Entity Invoice → BillingDocument\")'
  : 'Which elements do you want to rename? (e.g., \"Module Sales → Commerce\", \"Entity Invoice → BillingDocument\")'}"
header: "Renaming"
```

Process user response:
1. Parse rename instructions
2. Update `applicationCode`, module codes, entity names, permission paths accordingly in JSON files via ba-writer
3. Re-run 7ter-c and 7ter-d checks on updated names
4. Re-display the naming recap table for final confirmation

**IF "Approuvé":**
→ Store naming audit result in `validation.json`:
```javascript
ba-writer.enrichSection({
  featureId: {feature_id},
  section: "namingAudit",
  data: {
    auditedAt: now(),
    issues: namingIssues,
    approved: true,
    renames: []  // or list of applied renames
  }
});
```
→ 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)

**Single-Module Mode:** Auto-approve (no cross-module concerns)

```
IF completedModules.length === 1:
  Display: "Module unique - consolidation automatique ✓"
  approval = { approved: true, reason: "Single module", approvedAt: now() }
  SKIP to section 10
```

**Multi-Module Mode:** 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

```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,
    globalRiskAssessment: risks,
    semanticChecks: [
      { check: "permission-orpheline", status: "PASS|WARNING", details: "..." }
    ],
    decision: {
      approved: approval.approved,
      reason: approval.reason,
      approvedBy: "Client",
      approvedAt: approval.approvedAt
    }
  }
});

// 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 (or auto-approved for single module)
- ✓ 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
