# Module Acceptance Criteria (Shared Reference)

> **Loaded by:** step-00-validate (pre-gate), step-01-transform (post-check)
> **Purpose:** Define measurable, bash-verifiable acceptance criteria that a module MUST pass.
> **Key principle:** These checks read the REAL JSON files on disk — NOT in-memory data.

---

## Architecture: Flat-File Format

`/business-analyse` generates **separate JSON files** per module:

| File | Content | Root key |
|------|---------|----------|
| `index.json` | Metadata, file references, summary | `metadata`, `files`, `summary` |
| `entities.json` | Entity definitions | `entities[]` |
| `rules.json` | Business rules | `rules[]` |
| `usecases.json` | Use cases | `useCases[]` |
| `permissions.json` | Roles and permission matrix | `roles[]`, `permissionPaths[]`, `matrix[]` |
| `screens.json` | UI screen specifications | `screens[]` |

The validation script **assembles** these files before checking.

---

## AC Split: Input vs Output

ACs are split into two groups based on **when** they can be validated:

### Input ACs (checked in step-00-validate)

These verify data that `/business-analyse` produces:

| # | Criterion | Minimum | Source File | Blocking |
|---|-----------|---------|-------------|----------|
| AC-01 | Entities present | 2 | `entities.json > entities[]` | YES |
| AC-02 | Entity attributes have `type` field | ALL | `entities.json > entities[].attributes[].type` | YES |
| AC-03 | Use cases present | 2 | `usecases.json > useCases[]` | YES |
| AC-04 | Business rules present | 2 | `rules.json > rules[]` | YES |
| AC-05 | Screens present | 1 | `screens.json > screens[]` | YES |
| AC-06 | Permissions present | 1 | `permissions.json > permissionPaths[]` | YES |
| AC-07 | Module status consolidated or specified | match | `index.json > metadata.status` | YES |

### Output ACs (checked in step-01 post-transform)

These verify data that `/derive-prd` step-01 generates:

| # | Criterion | Minimum | Location | Blocking |
|---|-----------|---------|----------|----------|
| AC-08 | Handoff has 8 categories | 8 | `handoff.filesToCreate` | YES |
| AC-09 | BR-to-code mapping present | 1 | `handoff.brToCodeMapping[]` | YES |
| AC-10 | API endpoint summary present | 1 | `handoff.apiEndpointSummary[]` | YES |
| AC-11 | SeedData entries have category | ALL | `handoff.filesToCreate.seedData[].category` | YES |

---

## Input Validation Script (step-00)

> **Usage:** Validates BA flat files before transformation.
> Resolves file paths from module `index.json > files` references.

```bash
MODULE_DIR="{module_ba_dir}"
node -e "
const fs = require('fs');
const path = require('path');
const dir = process.argv[1];

// Load index.json
const index = JSON.parse(fs.readFileSync(path.join(dir, 'index.json'), 'utf-8'));
const files = index.files || {};

// Load flat files (with fallbacks)
function loadFile(key) {
  const ref = files[key];
  if (!ref || !ref.path) return null;
  const filePath = path.join(dir, ref.path);
  if (!fs.existsSync(filePath)) return null;
  return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
}

const entitiesData = loadFile('entities') || {};
const usecasesData = loadFile('usecases') || {};
const rulesData = loadFile('rules') || {};
const permissionsData = loadFile('permissions') || {};
const screensData = loadFile('screens') || {};

const entities = entitiesData.entities || [];
const useCases = usecasesData.useCases || [];
const rules = rulesData.rules || [];
const permissions = permissionsData.permissionPaths || permissionsData.permissions || [];
const screens = screensData.screens || [];
const status = index.metadata?.status || 'unknown';

const fails = [];

// AC-01: Entities >= 2
if (entities.length < 2) {
  fails.push('AC-01: entities = ' + entities.length + ' (min: 2)');
  console.error('FAIL: AC-01: entities >= 2 = ' + entities.length + ' (min: 2)');
}

// AC-02: Entity attributes have type
const badAttrs = entities.flatMap(e =>
  (e.attributes || []).filter(a => !a.type).map(a => e.name + '.' + a.name)
);
if (badAttrs.length > 0) {
  fails.push('AC-02: ' + badAttrs.length + ' attributes without type');
  console.error('FAIL: AC-02: ' + badAttrs.length + ' attributes without type: ' + badAttrs.slice(0, 5).join(', '));
}

// AC-03: Use cases >= 2
if (useCases.length < 2) {
  fails.push('AC-03: useCases = ' + useCases.length + ' (min: 2)');
  console.error('FAIL: AC-03: useCases >= 2 = ' + useCases.length + ' (min: 2)');
}

// AC-04: Business rules >= 2
if (rules.length < 2) {
  fails.push('AC-04: rules = ' + rules.length + ' (min: 2)');
  console.error('FAIL: AC-04: rules >= 2 = ' + rules.length + ' (min: 2)');
}

// AC-05: Screens >= 1
if (screens.length < 1) {
  fails.push('AC-05: screens = ' + screens.length + ' (min: 1)');
  console.error('FAIL: AC-05: screens >= 1 = ' + screens.length + ' (min: 1)');
}

// AC-06: Permissions >= 1
if (permissions.length < 1) {
  fails.push('AC-06: permissions = ' + permissions.length + ' (min: 1)');
  console.error('FAIL: AC-06: permissions >= 1 = ' + permissions.length + ' (min: 1)');
}

// AC-07: Status consolidated or specified
if (status !== 'consolidated' && status !== 'specified') {
  fails.push('AC-07: status = ' + status + ' (expected: consolidated or specified)');
  console.error('FAIL: AC-07: status = ' + status + ' (expected: consolidated or specified)');
}

// Summary
if (fails.length > 0) {
  console.error('\\nBLOCKING: ' + fails.length + ' input acceptance criteria FAILED');
  process.exit(1);
}
console.log('PASS: All input acceptance criteria met (AC-01 to AC-07)');
console.log('  entities: ' + entities.length + ', useCases: ' + useCases.length + ', rules: ' + rules.length + ', screens: ' + screens.length + ', permissions: ' + permissions.length);
" "$MODULE_DIR"
```

---

## Output Validation Script (step-01 post-check)

> **Usage:** Validates handoff data after step-01 transformation.

```bash
MODULE_JSON="{module_index_json_path}"
node -e "
const fs = require('fs');
const data = JSON.parse(fs.readFileSync(process.argv[1], 'utf-8'));
const handoff = data.handoff || {};
const ftc = handoff.filesToCreate || {};

const fails = [];

// AC-08: All 8 categories present
const categories = ['domain', 'application', 'infrastructure', 'api', 'frontend', 'seedData', 'tests', 'documentation'];
const missing = categories.filter(cat => !ftc[cat]);
if (missing.length > 0) {
  fails.push('AC-08: missing categories: ' + missing.join(', '));
  console.error('FAIL: AC-08: missing categories: ' + missing.join(', '));
}

// AC-09: BR-to-code mapping present
if (!handoff.brToCodeMapping || handoff.brToCodeMapping.length === 0) {
  fails.push('AC-09: brToCodeMapping is empty');
  console.error('FAIL: AC-09: brToCodeMapping is empty');
}

// AC-10: API endpoint summary present
if (!handoff.apiEndpointSummary || handoff.apiEndpointSummary.length === 0) {
  fails.push('AC-10: apiEndpointSummary is empty');
  console.error('FAIL: AC-10: apiEndpointSummary is empty');
}

// AC-11: SeedData entries have category
const seedEntries = ftc.seedData || [];
const noCat = seedEntries.filter(s => !s.category);
if (noCat.length > 0) {
  fails.push('AC-11: ' + noCat.length + ' seedData entries missing category');
  console.error('FAIL: AC-11: ' + noCat.length + ' seedData entries missing category field');
}

if (fails.length > 0) {
  console.error('\\nBLOCKING: ' + fails.length + ' output acceptance criteria FAILED');
  process.exit(1);
}
console.log('PASS: All output acceptance criteria met (AC-08 to AC-11)');
" "$MODULE_JSON"
```

---

## Recovery Actions

| Criterion | Recovery |
|-----------|----------|
| AC-01/02 | Re-run `/business-analyse` entity specification |
| AC-03 | Re-run `/business-analyse` use case specification |
| AC-04 | Re-run `/business-analyse` business rules specification |
| AC-05 | Re-run `/business-analyse` screen specification |
| AC-06 | Re-run `/business-analyse` permission specification |
| AC-07 | Run `/business-analyse` step-04 consolidation |
| AC-08 | Re-run `/derive-prd` step-01 transform (missing categories) |
| AC-09 | Re-run `/derive-prd` step-01 transform (BR mapping) |
| AC-10 | Re-run `/derive-prd` step-01 transform (API endpoints) |
| AC-11 | Re-run `/derive-prd` step-01 transform (seedData categories) |
