# POST-CHECK Validation (step-03-specify)

Before advancing to step 04, run these validations for EACH module:

```javascript
const errors = [];
const warnings = [];

for (const mod of modules) {
  // mod.dir = resolveModuleDir(applicationCode, mod.code)
  // e.g., docs/projet-rh/v1.0/human-resources/employees/
  const ent = READ(mod.dir + '/entities.json')?.entities || [];
  const ucs = READ(mod.dir + '/usecases.json')?.useCases || [];
  const brs = READ(mod.dir + '/rules.json')?.rules || [];
  const perms = READ(mod.dir + '/permissions.json');
  const sections = mod.anticipatedSections || [];
  const sectionCodes = new Set(sections.map(s => s.code));

  // Mandatory checks (BLOCKING)
  if (ent.length === 0) errors.push(mod.code + ": 0 entities");
  if (ucs.length === 0) errors.push(mod.code + ": 0 use cases");
  if (brs.length === 0) errors.push(mod.code + ": 0 business rules");
  if (!perms?.permissionPaths?.length) errors.push(mod.code + ": no permissions");

  // sectionCode validation
  for (const uc of ucs) {
    if (!uc.sectionCode) errors.push(mod.code + ": UC '" + uc.id + "' missing sectionCode");
    else if (!sectionCodes.has(uc.sectionCode)) errors.push(mod.code + ": UC '" + uc.id + "' sectionCode '" + uc.sectionCode + "' not in anticipatedSections");
  }
  for (const br of brs) {
    if (!br.sectionCode) errors.push(mod.code + ": BR '" + br.id + "' missing sectionCode");
    else if (!sectionCodes.has(br.sectionCode)) errors.push(mod.code + ": BR '" + br.id + "' sectionCode '" + br.sectionCode + "' not in anticipatedSections");
  }

  // Entity attribute checks
  for (const e of ent) {
    for (const a of (e.attributes || [])) {
      if (!a.type) warnings.push(mod.code + ": entity " + e.name + " attr '" + a.name + "' missing type");
      if (a.type === 'enum' && !a.defaultValue) errors.push(mod.code + ": enum attr '" + a.name + "' missing defaultValue");
      // FK naming convention: must end with "Id"
      if ((a.type === 'guid' || a.type === 'string') && a.foreignKey && !a.name.endsWith('Id'))
        warnings.push(mod.code + ": entity " + e.name + " FK attr '" + a.name + "' should end with 'Id'");
    }
    // Person Extension Pattern check
    const personFields = ['firstName', 'lastName', 'email', 'phoneNumber', 'phone'];
    const foundPersonFields = (e.attributes || []).filter(a => personFields.includes(a.name));
    if (foundPersonFields.length >= 3 && !e.personRoleConfig) {
      errors.push(mod.code + ": entity '" + e.name + "' has " + foundPersonFields.length +
        " person fields (" + foundPersonFields.map(f => f.name).join(", ") +
        ") but no personRoleConfig — use Person Extension Pattern (entity-architecture-decision.md section 0). " +
        "Person fields (firstName, lastName, email) belong on auth_Users, not on the domain entity.");
    }
  }

  // Canonical usecases key check
  const rawUCFile = READ(mod.dir + '/usecases.json');
  if (rawUCFile && !rawUCFile.useCases && rawUCFile.usecases) {
    errors.push(mod.code + ": usecases.json uses 'usecases' key instead of canonical 'useCases' — fix before proceeding");
  }
  // Check steps serialization format
  for (const uc of ucs) {
    if (uc.steps && Array.isArray(uc.steps) && uc.steps.length > 0 && typeof uc.steps[0] === 'object') {
      errors.push(mod.code + ": UC '" + uc.id + "' uses steps[] with objects — must use mainScenario[] with strings");
    }
    if (uc.actor && !uc.primaryActor) {
      warnings.push(mod.code + ": UC '" + uc.id + "' uses 'actor' instead of canonical 'primaryActor'");
    }
  }

  // Cross-reference checks
  for (const uc of ucs) {
    for (const brRef of (uc.businessRules || [])) {
      if (!brs.find(br => br.id === brRef)) warnings.push(mod.code + ": UC '" + uc.id + "' references non-existent BR '" + brRef + "'");
    }
  }

  // Section permission checks
  for (const section of sections) {
    if (section.sectionType === 'view') {
      const viewPerms = (perms?.permissionPaths || []).filter(p => p.includes('.' + section.code + '.'));
      if (viewPerms.length > 0) errors.push(mod.code + ": view section '" + section.code + "' has " + viewPerms.length + " permissions (should inherit)");
    }
  }

  // Quality gate: UC count (C6 — BLOCKING if < 4, WARNING if < 6)
  if (ucs.length > 0 && ucs.length < 4) {
    errors.push(mod.code + ": only " + ucs.length + " use cases (minimum: 4)");
  } else if (ucs.length > 0 && ucs.length < 6) {
    warnings.push(mod.code + ": only " + ucs.length + " use cases (recommended: >= 6)");
  }

  // Quality gate: Permission path count (C7 — WARNING if < 5)
  const permPaths = perms?.permissionPaths || [];
  if (permPaths.length > 0 && permPaths.length < 5) {
    warnings.push(mod.code + ": only " + permPaths.length + " permission paths (recommended: >= 5 — check for missing Export/Import)");
  }

  // Quality gate: Entity count (C9 — WARNING if single entity)
  if (ent.length === 1) {
    warnings.push(mod.code + ": single entity module — verify this is intentional (config/lookup OK)");
  }

  // Quality gate: BR-CALC must have formula (C13 — BLOCKING)
  for (const br of brs) {
    if ((br.category === 'calculation' || (br.id && br.id.includes('CALC'))) && !br.formula) {
      errors.push(mod.code + ": BR '" + br.id + "' is a calculation rule but missing 'formula' field");
    }
  }

  // Quality gate: Computed attributes must have BR-CALC (C11 — BLOCKING)
  for (const e of ent) {
    for (const a of (e.attributes || [])) {
      if (a.computed === true) {
        const hasBRCalc = brs.some(br =>
          (br.category === 'calculation' || (br.id && br.id.includes('CALC'))) &&
          (br.entities || []).includes(e.name)
        );
        if (!hasBRCalc) errors.push(mod.code + ": " + e.name + ".'" + a.name + "' is computed:true but no BR-CALC references " + e.name);
      }
    }
  }

  // Quality gate: Versioned attributes detection (C15 — WARNING)
  for (const e of ent) {
    for (const a of (e.attributes || [])) {
      if (['salary', 'rate', 'grade', 'price', 'tarif'].some(k => a.name.toLowerCase().includes(k))) {
        if (!e.versionedAttributes?.length)
          warnings.push(mod.code + ": " + e.name + ".'" + a.name + "' may need versioning but no versionedAttributes defined");
      }
    }
  }

  // ── Business Rules Schema Conformity (C16-C21) ──────────────────────

  // Quality gate: BR schema field conformity (C16 — BLOCKING)
  for (const br of brs) {
    if (!br.id) errors.push(mod.code + ": BR missing 'id' field");
    else if (!/^BR-(VAL|CALC|WF|SEC|DATA|NOTIF)-[A-Z]{2,4}-\d{3}$/.test(br.id))
      errors.push(mod.code + ": BR '" + br.id + "' does not match ID pattern BR-{CAT}-{MOD}-{NNN}");
    if (!br.name) errors.push(mod.code + ": BR '" + (br.id||'?') + "' missing 'name'");
    if (!br.category) errors.push(mod.code + ": BR '" + (br.id||'?') + "' missing 'category'");
    if (!br.statement) errors.push(mod.code + ": BR '" + (br.id||'?') + "' missing 'statement'");
    if (!br.severity) errors.push(mod.code + ": BR '" + (br.id||'?') + "' missing 'severity'");
    if (!br.entities || br.entities.length === 0)
      errors.push(mod.code + ": BR '" + (br.id||'?') + "' missing 'entities[]'");
    // Reject deprecated field names (common drift from sub-agent schema ignorance)
    if (br.code && !br.id) errors.push(mod.code + ": BR uses 'code' instead of canonical 'id'");
    if (br.label && !br.name) errors.push(mod.code + ": BR uses 'label' instead of canonical 'name'");
    if (br.description && !br.statement) errors.push(mod.code + ": BR uses 'description' instead of canonical 'statement'");
    if (br.rule && !br.statement) errors.push(mod.code + ": BR uses 'rule' instead of canonical 'statement'");
    if (br.type && !br.category) errors.push(mod.code + ": BR uses 'type' instead of canonical 'category'");
  }

  // Quality gate: BR examples format (C17 — BLOCKING)
  for (const br of brs) {
    if (!br.examples || br.examples.length === 0)
      errors.push(mod.code + ": BR '" + (br.id||'?') + "' missing 'examples[]' (min 1 required)");
    if (br.example && !br.examples)
      errors.push(mod.code + ": BR '" + (br.id||'?') + "' uses deprecated 'example' string — use 'examples[]' array of {input, expected}");
    if (br.examples) {
      for (const ex of br.examples) {
        if (typeof ex === 'string')
          errors.push(mod.code + ": BR '" + (br.id||'?') + "' has string in examples[] — must be {input, expected}");
      }
    }
  }

  // Quality gate: BR count minimum (C18 — BLOCKING if < 4)
  if (brs.length > 0 && brs.length < 4) {
    errors.push(mod.code + ": only " + brs.length + " business rules (minimum: 4)");
  }

  // Quality gate: Domain-specific ratio (C19 — WARNING if < 2)
  const domainBRs = brs.filter(br => br.domainSpecific === true);
  if (brs.length >= 4 && domainBRs.length < 2) {
    warnings.push(mod.code + ": only " + domainBRs.length + " domain-specific rules out of " + brs.length +
      " (minimum: 2 — see _shared.md rule 3)");
  }

  // Quality gate: domainSpecific flag presence (C20 — WARNING)
  const brsMissingFlag = brs.filter(br => typeof br.domainSpecific === 'undefined');
  if (brsMissingFlag.length > 0) {
    warnings.push(mod.code + ": " + brsMissingFlag.length + "/" + brs.length +
      " BRs missing 'domainSpecific' flag");
  }

  // Quality gate: Structured conditions for conditional rules (C21 — WARNING)
  const conditionalBRs = brs.filter(br =>
    br.category === 'workflow' || br.category === 'security' ||
    (br.statement && /\b(IF|Si |si |Quand |quand |Lorsqu)/i.test(br.statement)));
  const brsWithConditions = conditionalBRs.filter(br => br.conditions && br.conditions.length > 0);
  if (conditionalBRs.length > 2 && brsWithConditions.length < 2) {
    warnings.push(mod.code + ": " + brsWithConditions.length + "/" + conditionalBRs.length +
      " conditional BRs have structured conditions[] (recommended: >= 2)");
  }
}

if (errors.length > 0) {
  Display("POST-CHECK FAILED (" + errors.length + " errors):");
  errors.forEach(e => Display("  ✗ " + e));
  BLOCKING_ERROR("Fix all errors before advancing to step 04");
}
if (warnings.length > 0) {
  Display("POST-CHECK warnings (" + warnings.length + "):");
  warnings.forEach(w => Display("  ⚠ " + w));
}
Display("POST-CHECK PASS: " + modules.length + " modules validated");
```
