# Module Acceptance Criteria (Shared Reference)

> **Loaded by:** step-03-specify (post-check), business-analyse-develop step-01-task (input validation)
> **Purpose:** Define measurable, bash-verifiable acceptance criteria that a module MUST pass before being marked "specified".
> **Key principle:** These checks read the REAL JSON files on disk — NOT in-memory data the model "thinks" it wrote.

---

## Why Bash-Verifiable?

All previous checks were **pseudocode interpreted by the model**. The model can "decide" a check passes even when it fails (hallucination of validation). Bash checks are **objective** — they read the actual file and count real elements.

---

## Acceptance Criteria Table

| # | Criterion | Minimum | Field Path | Blocking | Category |
|---|-----------|---------|------------|----------|----------|
| AC-01 | Entities present | 2 | `analysis.entities[]` | YES | Data Model |
| AC-02 | Entity attributes have `type` field | ALL | `analysis.entities[].attributes[].type` | YES | Data Model |
| AC-03 | Use cases present | 2 | `specification.useCases[]` | YES | Requirements |
| AC-04 | Functional requirements present | 4 | `specification.functionalRequirements[]` | YES | Requirements |
| AC-05 | Wireframes present | 1 | `specification.uiWireframes[] \|\| specification.wireframes[]` | YES | UI |
| AC-06 | Wireframes >= sections | count match | wireframes.length >= sections.length | YES | UI |
| AC-07 | Wireframe content non-empty | ALL | `wireframe.mockup \|\| wireframe.ascii \|\| wireframe.content` | YES | UI |
| AC-08 | Sections present | 1 | `specification.sections[]` | YES | UI |
| AC-09 | SeedDataCore 9 arrays non-empty | 9/9 | `specification.seedDataCore.*` | YES | Seed Data |
| AC-10 | Gherkin scenarios is array | `Array.isArray` | `specification.gherkinScenarios` | YES | Format |
| AC-11 | API endpoints present | 1 | `specification.apiEndpoints[]` | YES | API |
| AC-12 | Messages present | 4 | `specification.messages[]` | YES | Messages |
| AC-13 | Validations present | 1 | `specification.validations[]` | YES | Validation |
| AC-14 | Wireframe field naming | canonical | `screen` not `title`, `mockup` not `ascii` | WARNING | Convention |
| AC-15 | Validation rules format | ALL array | `specification.validations[].rules` is `Array` | YES | Format |
| AC-16 | Messages have `message` field | ALL present | `specification.messages[].message` truthy | YES | Format |
| AC-17 | Gherkin content structure | ALL valid | Each element has `feature` (string) + `scenarios` (array) | YES | Format |

---

## Bash Verification Script (node -e)

> **Usage:** Called from step-03 post-check after writing module JSON files.
> Also callable standalone for debugging: `node -e "..." path/to/index.json`

```bash
MODULE_JSON="{module_feature_json_path}"
node -e "
const fs = require('fs');
const data = JSON.parse(fs.readFileSync(process.argv[1], 'utf-8'));
const spec = data.specification || {};
const analysis = data.analysis || {};
const wf = spec.uiWireframes || spec.wireframes || [];
const sections = spec.sections || [];
const sdc = spec.seedDataCore || {};

const checks = [
  ['AC-01: entities >= 2',          (analysis.entities||[]).length,               2],
  ['AC-03: useCases >= 2',          (spec.useCases||[]).length,                   2],
  ['AC-04: FRs >= 4',               (spec.functionalRequirements||[]).length,     4],
  ['AC-05: wireframes >= 1',        wf.length,                                    1],
  ['AC-06: wireframes >= sections', wf.length,                                    sections.length],
  ['AC-08: sections >= 1',          sections.length,                               1],
  ['AC-09: seedDataCore 9 arrays',  Object.keys(sdc).filter(k => Array.isArray(sdc[k]) && sdc[k].length > 0).length, 9],
  ['AC-10: gherkin is array',       Array.isArray(spec.gherkinScenarios) ? 1 : 0,  1],
  ['AC-11: apiEndpoints >= 1',      (spec.apiEndpoints||[]).length,                1],
  ['AC-12: messages >= 4',          (spec.messages||[]).length,                    4],
  ['AC-13: validations >= 1',       (spec.validations||[]).length,                 1]
];

const fails = [];
checks.forEach(c => {
  if (c[1] < c[2]) {
    fails.push(c);
    console.error('FAIL: ' + c[0] + ' = ' + c[1] + ' (min: ' + c[2] + ')');
  }
});

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

// AC-07: Wireframe content non-empty
const emptyWf = wf.filter(w => !w.mockup && !w.ascii && !w.content);
if (emptyWf.length > 0) {
  fails.push(['AC-07: wireframe content empty', emptyWf.length, 0]);
  console.error('FAIL: AC-07: ' + emptyWf.length + ' wireframes have EMPTY content');
}

// AC-14: Wireframe field naming (WARNING only)
const badFields = wf.filter(w => w.title || w.ascii || (w.name && !w.screen));
if (badFields.length > 0) {
  console.warn('WARNING: AC-14: ' + badFields.length + ' wireframes use non-canonical field names (title/ascii/name)');
}

// AC-15: Validation rules format (rules must be array, not string)
const badRules = (spec.validations||[]).filter(v => v.rules && !Array.isArray(v.rules));
if (badRules.length > 0) {
  fails.push(['AC-15: validations[].rules not array', badRules.length, 0]);
  console.error('FAIL: AC-15: ' + badRules.length + ' validations have rules as string instead of array');
}

// AC-16: Messages must have 'message' field
const noMsg = (spec.messages||[]).filter(m => !m.message);
if (noMsg.length > 0) {
  fails.push(['AC-16: messages[].message missing', noMsg.length, 0]);
  console.error('FAIL: AC-16: ' + noMsg.length + ' messages missing "message" field');
}

// AC-17: Gherkin content structure (each element needs feature + scenarios[])
if (Array.isArray(spec.gherkinScenarios)) {
  const badGherkin = spec.gherkinScenarios.filter(g => !g.feature || !Array.isArray(g.scenarios));
  if (badGherkin.length > 0) {
    fails.push(['AC-17: gherkin content invalid', badGherkin.length, 0]);
    console.error('FAIL: AC-17: ' + badGherkin.length + ' gherkin entries missing feature or scenarios[]');
  }
}

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

---

## Integration Points

### 1. step-03-specify.md (post-check)

After writing module JSON files, run the bash script above.
IF FAIL → re-execute section 11 write with corrected data → re-run until PASS.

### 2. business-analyse-develop step-01-task.md (input validation)

At PRD load time, run the bash script on each module index.json.
IF FAIL → BLOCKING: "Module {name} does not meet acceptance criteria — run /business-analyse to fix".
This prevents business-analyse-develop from generating code from incomplete specifications.

---

## Recovery Actions per Criterion

| Criterion | Recovery |
|-----------|----------|
| AC-01/02 | Re-run step-03 entity specification |
| AC-03/04 | Re-run step-03 use case / functional requirements |
| AC-05/06/07 | Re-run step-03 wireframe generation |
| AC-08 | Re-run step-03 section definition |
| AC-09 | Re-run step-03 seedDataCore transform |
| AC-10 | Auto-fix: wrap in array `[gherkinScenarios]` |
| AC-11 | Re-run step-03 API endpoints |
| AC-12 | Re-run step-03 messages |
| AC-13 | Re-run step-03 validations |
| AC-14 | Auto-fix: rename title→screen, ascii→mockup |
| AC-15 | Auto-fix: wrap string in array `[rules]` |
| AC-16 | Auto-fix: copy `description` → `message` |
| AC-17 | Re-run step-03 gherkin — ensure each entry has `feature` string + `scenarios` array |
