---
name: step-03-execute
description: Orchestrate code creation via existing skills and MCP tools
model: opus
prev_step: steps/step-02-plan.md
next_step: steps/step-04-examine.md
---

# Step 3: Execute

**CRITICAL PRINCIPLE: /apex ORCHESTRATES. It NEVER writes SmartStack code directly.**
All code goes through skills (/controller, /application, /ui-components, /efcore) or MCP tools.

## LOAD CONDITIONALLY

> **CONTEXT REUSE:** `smartstack-api.md` (loaded in step-01) and `smartstack-layers.md` (loaded in step-02) are already in context. Do NOT re-read them.

- **ALWAYS** read `references/smartstack-frontend.md` — lazy loading, i18n, page structure, CSS variables, EntityLookup, compliance gates (sections 1-9)
- **ALWAYS** read `references/core-seed-data.md` — comprehensive seed data templates (Layer 1)
- If NOT `{economy_mode}` AND a layer has multi-entity work: read `references/agent-teams-protocol.md`
- If build failure during execution: read `references/error-classification.md` — error diagnosis categories A-F

---

## Foundation Mode Detection

**IF `{foundation_mode}` == true:**

This is a foundation-only execution (called by ralph-loop Phase 0). Execute ONLY:
- Layer 0: Domain entities + EF configs + Migration

**SKIP ALL OTHER LAYERS:**
- Layer 1: Seed Data → SKIP
- Layer 2: Backend → SKIP
- Layer 3: Frontend → SKIP
- Layer 4: DevData → SKIP

After Layer 0 completes and builds successfully:
- Commit with message: `chore(foundation): entities for {module_code}`
- Jump to step-04 for POST-CHECKs (domain + infrastructure only)
- End execution

**IF `{foundation_mode}` == false:**
Execute ALL layers normally (Layer 0 → Layer 1 → Layer 2 → Layer 3 → Layer 4).

---

## Layer 0 — Domain + Infrastructure (sequential, agent principal)

### Domain Entities

```
For each entity to create/modify:
  → MCP scaffold_extension (type: "entity", target: entity_name)
  → Verify: inherits BaseEntity, implements ITenantEntity + IAuditableEntity
  → Verify entity matches patterns in references/smartstack-api.md
```

### Person Extension Detection

```
IF entity has personRoleConfig (from PRD or feature.json):
  → LOAD references/person-extension-pattern.md
  → Apply variant: mandatory or optional (from personRoleConfig.userLinkMode)
  → scaffold_extension with options: { isPersonRole: true, userLinkMode: '{variant}' }
  → VERIFY: UserId FK present, no duplicate person fields (mandatory)
  → VERIFY: EF config has unique index on (TenantId, UserId)
  → VERIFY (mandatory): entity has ZERO person fields (FirstName, LastName, Email, PhoneNumber)
  → VERIFY (optional): entity has own person fields + computed Display* properties
```

### EF Core Configurations

```
For each entity:
  → MCP scaffold_extension (type: "configuration", target: entity_name)
  → Verify: table name, relationships, indexes
  → Register DbSet in ExtensionsDbContext if new entity
```

### Migration (BLOCKING)

> **CRITICAL — Migration must cover ALL entities, not just the first batch.**
> Root cause (test-apex-007): Migration was created once for 3 entities, then 4 more entities
> were added later without re-running migration → 4 entities had no database tables.
> **RULE:** Create/update migration AFTER ALL entities and EF configs are registered in DbContext.
> If entities are added incrementally, create a NEW migration for each batch.

```
1. Verify ALL entities have been added as DbSet in ExtensionsDbContext
2. Verify ALL EF configurations are registered (ApplyConfigurationsFromAssembly or individual)
3. MCP suggest_migration → get standardized name
4. dotnet ef migrations add {Name} --project src/{Infra}.csproj --startup-project src/{Api}.csproj -o Persistence/Migrations
5. dotnet ef database update (if local DB)
6. dotnet build → MUST PASS
7. Verify: dotnet ef migrations has-pending-model-changes → MUST report "No pending model changes"
```

**BLOCKING:** If build fails after migration, fix EF configs before proceeding.
**BLOCKING:** If `has-pending-model-changes` reports pending changes, entities are missing from the migration — create a new migration.

### Post-Layer 0 Build Gate

```bash
dotnet build
# Note: WSL bin\Debug cleanup handled by PostToolUse hook (wsl-dotnet-cleanup.sh)
```

**MUST PASS before Layer 1. If NuGet error, run `dotnet restore` first. If file lock (MSB3021), use `--output /tmp/{project}_build`.**

### Layer 0 Commit

```
feat({module}): [domain+infra] {short description}
```

---

## Layer 1 — Seed Data (DEDICATED LAYER — sequential, agent principal)

> **This layer is DEDICATED and MANDATORY.** Seed data makes modules visible in the UI.
> Without seed data, the module exists in code but is invisible to users.
> Reference: `references/core-seed-data.md` (loaded above) for complete C# templates.

### Application-Level Seed Data (ONCE per application)

```
1. NavigationApplicationSeedData.cs
   → Application-level navigation entry (MUST be first)
   → Random GUID via Guid.NewGuid()
   → 4 language translations (fr, en, it, de)

2. ApplicationRolesSeedData.cs
   → 4 roles: admin, manager, contributor, viewer
   → Random GUIDs (Guid.NewGuid())
   → References NavigationApplicationSeedData.ApplicationId
```

### Per-Module Seed Data

```
3. NavigationModuleSeedData.cs
   → Random GUIDs (Guid.NewGuid()), 4 languages (fr, en, it, de)
   → GetModuleEntry() + GetTranslationEntries()
   → If sections defined: GetSectionEntries() + GetSectionTranslationEntries()
   → If resources defined: GetResourceEntries() + resource translations
   → Query actual parent from DB for FK (NOT deterministic GUID)

4. Permissions.cs
   → MCP generate_permissions (PRIMARY tool)
   → Static constants: public static class {Module} { public const string Read = "..."; }
   → Permission paths MUST use kebab-case matching NavRoute codes

5. PermissionsSeedData.cs
   → MCP generate_permissions first, fallback template
   → Paths match Permissions.cs, wildcard + CRUD

6. RolesSeedData.cs
   → Admin=wildcard(*), Manager=CRU, Contributor=CR, Viewer=R
   → Code-based role mapping (NEVER deterministic GUIDs for roles)
   → Look up roles by Code at runtime
```

### Infrastructure Provider (ONCE per application)

```
7. {App}SeedDataProvider.cs
   → Implements IClientSeedDataProvider
   → SeedNavigationAsync(): Application → Module → Section → Resource + translations
   → SeedRolesAsync(): application-scoped roles from ApplicationRolesSeedData
   → SeedPermissionsAsync(): permission entries from PermissionsSeedData
   → SeedRolePermissionsAsync(): maps roles to permissions (by Code, NOT by GUID)
   → DI: services.AddScoped<IClientSeedDataProvider, {App}SeedDataProvider>()
```

### Post-Layer 1 Build Gate

```bash
dotnet build
```

**MUST PASS before Layer 2.**

### Layer 1 Commit

```
feat({module}): [seed] navigation, permissions, roles
```

---

## Layer 2 — Backend (Services + Controllers)

> **Layer 2 rules** are in `references/smartstack-layers.md` (already in context from step-02):
> - NavRoute and permission kebab-case (Layer 2 - API section)
> - Controller route attributes (FORBIDDEN: [Route] alongside [NavRoute])
> - Validators DI registration
> - DateOnly vs string for DTO date fields
> - Code generation patterns (ICodeGenerator<T> registration, see references/code-generation.md)

### Backend Tasks (sequential or parallel within layer)

- **Services/DTOs:** MCP scaffold_extension
- **Controllers:** use /controller skill for complex, MCP scaffold_extension for simple
- **IMPORTANT:** ALL GetAll endpoints MUST support `?search=` query parameter (enables EntityLookup on frontend)

### Skill Delegation

| Skill | When | Context to provide |
|-------|------|--------------------|
| /controller | Custom routes, complex auth, file upload | entity, CRUD actions, permissions, routes |
| /application | Full app/module from scratch | app, module names |
| /ui-components | Complex pages (tables, grids, dashboards) | entity, fields, page type |
| /efcore | Complex EF configs, inheritance | relationships, indexes |
| /notification | In-app or email notifications | trigger, recipients, template |
| /workflow | Automated workflows | trigger, steps, conditions |

### If NOT economy_mode AND multiple entities: Agent Teams (parallel within layer)

> **Protocol:** See `references/agent-teams-protocol.md`

```
TeamCreate("apex-layer2", description: "Execute Layer 2 backend in parallel")

Spawn 1 teammate per entity (Opus, full tools):

For each entity:
  Task: "Execute Layer 2 backend for entity {EntityName}:
    - Application service/DTO: MCP scaffold_extension
    - Controller: /controller skill or MCP scaffold_extension
    - IMPORTANT: GetAll endpoint MUST support ?search= parameter
    - Validators: FluentValidation + DI registration

    After ALL tasks done:
    SendMessage(type:'message', recipient:'team-lead', content:'ENTITY_COMPLETE: {EntityName}', summary:'{EntityName} backend done')"

Wait for all teammates to report completion.
shutdown_request → shutdown_response → TeamDelete("apex-layer2")
```

### Post-Layer 2 Build Gate

```bash
dotnet build
```

**MUST PASS before backend tests.**

### Backend Tests Inline

> **Tests are scaffolded and run WITHIN Layer 2, not deferred to step-07.**

```
1. Scaffold backend tests:
   → MCP scaffold_tests (target_layer: "domain", module: "{module_code}", test_type: "unit")
   → MCP scaffold_tests (target_layer: "application", module: "{module_code}", test_type: "unit")
   → MCP scaffold_tests (target_layer: "api", module: "{module_code}", test_type: "integration")

2. Run tests:
   dotnet test --no-build --verbosity normal

3. Fix loop (max 3 iterations):
   IF tests fail:
     → Identify root cause (ALWAYS code bug, not test bug)
     → Fix production CODE via appropriate skill/MCP
     → dotnet build --no-restore
     → dotnet test --no-build
     → Repeat until pass or max 3

4. If still failing after 3 iterations: note failures, continue to Layer 3.
   Step-07 "Final Test Sweep" will handle remaining failures.
```

### Layer 2 Commits

```
feat({module}): [app+api] {short description}
test({module}): backend unit and integration tests
```

---

## Layer 3 — Frontend (Pages + I18n + Documentation)

> **Frontend patterns** are in `references/smartstack-frontend.md` (already loaded above):
> - API client generation (MCP scaffold_api_client)
> - Route scaffolding (MCP scaffold_routes) — section 1 + 3b
> - Page types (ListPage, DetailPage, CreatePage, EditPage) — section 3
> - FK field handling (EntityLookup component) — section 6
> - Form structure (ZERO modals — all full pages) — section 3b
> - Detail page tabs (local state only) — section 3 "Tab Behavior Rules"
> - Testing patterns (co-located form tests) — section 8
> - Section-level routes and permissions — section 3b
> - I18n JSON structure and registration — section 2
> - Compliance gates (5 mandatory checks) — section 9

### Frontend Tasks (sequential or parallel within layer)

For each module:
- **API client:** MCP scaffold_api_client
- **Routes:** MCP scaffold_routes (outputFormat: 'clientRoutes') → generates lazy imports + Suspense
- **Pages:** use /ui-components skill — MUST follow smartstack-frontend.md patterns:
  → React.lazy() for all page imports (named export wrapping)
  → `<Suspense fallback={<PageLoader />}>` around all lazy components
  → Page structure: hooks → useEffect(load) → loading → error → content
  → CSS variables only: bg-[var(--bg-card)], text-[var(--text-primary)]
  → SmartTable/SmartFilter/EntityCard (NEVER raw HTML)
- **FORM PAGES (CRITICAL):** Create/Edit forms are FULL PAGES with own routes:
  → EntityCreatePage.tsx with route /{module}/create
  → EntityEditPage.tsx with route /{module}/:id/edit
  → ZERO modals/popups/drawers/dialogs for forms
  → Back button with navigate(-1) on every form page
  → See smartstack-frontend.md section 3b for templates
- **TABS ON DETAIL PAGES (CRITICAL):** Tabs MUST switch content LOCALLY:
  → Tab click handler: setActiveTab(tabKey) ONLY — NEVER navigate() to another page
  → Tab content: render inline with {activeTab === 'tabKey' && <TabContent />}
  → Sub-resource data: fetch via API filtered by parent ID, display in tab panel
  → FORBIDDEN: navigate('../leaves?employee=${id}') in tab handler
  → See smartstack-frontend.md section 3 'Tab Behavior Rules'
- **FK FIELDS (CRITICAL):** Foreign key Guid fields (e.g., EmployeeId) MUST use EntityLookup:
  → NEVER render FK fields as `<input>` — users cannot type GUIDs
  → NEVER render FK fields as `<select>` — even with API-loaded options, `<select>` is NOT acceptable
  → ONLY use `<EntityLookup />` from @/components/ui/EntityLookup for searchable selection
  → Each FK field needs: apiEndpoint, mapOption (display name), search support on backend
  → FORBIDDEN: `<select value={formData.departmentId}>`, `<option value={dept.id}>`, `e.target.value` for FK fields
  → See smartstack-frontend.md section 6 for the full EntityLookup pattern
- **I18n:** Generate 4 JSON files per module namespace (fr, en, it, de)
  → Follow JSON template from smartstack-frontend.md
  → Keys: actions, labels, errors, validation, columns, form, messages, empty
  → ALL t() calls MUST use namespace prefix + fallback: t('ns:key', 'Default text')
- **PERMISSIONS:** Call MCP generate_permissions for the module permission root (2 segments: {app}.{module}),
  then also call MCP generate_permissions for EACH section (3 segments: {app}.{module}.{section}).
- **SECTION ROUTES:** Add section child routes to the module's children array.
  Wire PermissionGuard for section routes with section-level permissions.
- MUST use src/pages/{App}/{Module}/ hierarchy (NOT flat)

### Documentation (after frontend pages exist)

> **After frontend pages are created, generate module documentation via `/documentation` skill.**

```
Invoke /documentation {module_code} --type user

This generates:
- src/pages/docs/business/{app}/{module}/doc-data.ts (data file)
- src/pages/docs/business/{app}/{module}/index.tsx (page wrapper)
- src/i18n/locales/fr/docs-{app}-{module}.json (French doc translations)
- Updates App.tsx routing for doc page
- Updates docs-manifest.json
```

**Verify DocToggleButton presence in list/detail pages:**
- Every `*ListPage.tsx` and `*DetailPage.tsx` MUST import and render `DocToggleButton` in their header
- Import: `import { DocToggleButton } from '@/components/docs/DocToggleButton';`
- Placement: inside the header actions area (see `smartstack-frontend.md` section 7)

### If NOT economy_mode AND multiple entities: Agent Teams (parallel within layer)

> **Protocol:** See `references/agent-teams-protocol.md`

```
TeamCreate("apex-layer3", description: "Execute Layer 3 frontend in parallel")

Spawn 1 teammate per entity (Opus, full tools):

For each entity:
  Task: "Execute Layer 3 frontend for entity {EntityName}:
    **MANDATORY: Read references/smartstack-frontend.md FIRST**
    - API client: MCP scaffold_api_client
    - Routes: MCP scaffold_routes (outputFormat: 'clientRoutes')
    - Pages: /ui-components skill (ALL 4 types: List, Detail, Create, Edit)
    - I18n: 4 JSON files (fr, en, it, de)
    - FORM PAGES: Full pages with own routes (ZERO modals)
    - FK FIELDS: EntityLookup for all FK Guid fields
    - TABS: Local state only (NEVER navigate())
    - FORM TESTS: Co-located .test.tsx for Create and Edit pages

    After ALL tasks done:
    SendMessage(type:'message', recipient:'team-lead', content:'ENTITY_COMPLETE: {EntityName}', summary:'{EntityName} frontend done')"

Wait for all teammates to report completion.
shutdown_request → shutdown_response → TeamDelete("apex-layer3")
```

### FRONTEND COMPLIANCE GATE (MANDATORY before commit)

> **Reference:** See `references/smartstack-frontend.md` section 9 "Compliance Gates" for all 5 mandatory checks:
> 1. CSS Variables (theme system)
> 2. Forms as Pages (ZERO modals/drawers/slide-overs)
> 3. I18n File Structure (4 languages, separate JSON files)
> 4. Lazy Loading (React.lazy() — no static imports)
> 5. useTranslation in Pages (all text translated)

**BLOCKING:** Do NOT commit frontend changes until ALL 5 gates pass.

When delegating to `/ui-components` skill, include explicit instructions:
- "CSS: Use CSS variables ONLY — `bg-[var(--bg-card)]`, `text-[var(--text-primary)]`."
- "Forms: Create/Edit forms are FULL PAGES with own routes (e.g., `/create`, `/:id/edit`)."
- "I18n: ALL text must use `t('namespace:key', 'Fallback')`. Generate JSON files in `src/i18n/locales/`."

### Frontend Tests Inline

> **Tests are scaffolded and run WITHIN Layer 3, not deferred to step-07.**

```
1. Scaffold frontend tests:
   → For each module with create/edit pages:
     → Generate EntityCreatePage.test.tsx and EntityEditPage.test.tsx
     → Co-located next to the page component (NOT in __tests__/ folder)
     → Cover: rendering, validation, submit, pre-fill, navigation, errors
     → Tools: Vitest + React Testing Library + @testing-library/user-event
     → Mock API: vi.mock() or MSW — NEVER real API calls

2. Run tests:
   WEB_DIR=$(find . -name "vitest.config.ts" -not -path "*/node_modules/*" -exec dirname {} \; 2>/dev/null | head -1)
   if [ -n "$WEB_DIR" ]; then
     (cd "$WEB_DIR" && npm run test)
   fi

3. Fix loop (max 3 iterations):
   IF tests fail:
     → Identify root cause (ALWAYS code bug, not test bug)
     → Fix production CODE
     → Re-run tests
     → Repeat until pass or max 3

4. If still failing after 3 iterations: note failures, continue.
   Step-07 "Final Test Sweep" will handle remaining failures.
```

### Typecheck Gate

```bash
npm run typecheck
```

**MUST PASS before commit.**

### Layer 3 Commits

```
feat({module}): [frontend] pages, routes, i18n, documentation
test({module}): frontend form tests
```

---

## Layer 4 — DevData (optional)

> **Business test data for development/demo environments.**
> Skip this layer if no meaningful test data is needed.

```
1. Create DevDataSeeder:
   → Infrastructure/Persistence/Seeding/DevData/{Module}DevDataSeeder.cs
   → ALL entities MUST include TenantId
   → Realistic business data for demo purposes

2. Build gate:
   dotnet build → MUST PASS
```

### Layer 4 Commit (if applicable)

```
feat({module}): [devdata] test data for development
```

---

## PRD State Update (delegate mode only)

> **After completing EACH layer**, update the PRD file so `/ralph-loop` can track progress.

```
IF delegate_mode:
  After each layer completes successfully:
    Read {delegate_prd_path}
    For each task executed in this layer:
      task.status = 'completed'  (or 'failed' if errors)
      task.completed_at = new Date().toISOString()
      task.iteration = 1  (delegate mode = single iteration)
      task.commit_hash = $(git rev-parse --short HEAD)
      task.files_changed = { created: [...files created...], modified: [...files modified...] }
      task.validation = "APEX_PASS" (or error details)
    prd.updated_at = new Date().toISOString()
    Write back to {delegate_prd_path}
```

---

## Commits Per Layer

After each layer completes, gates pass, and builds pass:

```
Layer 0: feat({module}): [domain+infra] {short description}
Layer 1: feat({module}): [seed] navigation, permissions, roles
Layer 2: feat({module}): [app+api] {short description}
Layer 2: test({module}): backend unit and integration tests
Layer 3: feat({module}): [frontend] pages, routes, i18n, documentation
Layer 3: test({module}): frontend form tests
Layer 4: feat({module}): [devdata] test data for development  # if applicable
```

**Maximum 7 commits per module.**

---

## Save Output (if save_mode)

Write to `{output_dir}/03-execute.md` with execution log per layer.

---

## NEXT STEP

Load `steps/step-04-examine.md`
