---
name: step-03c-layer2-backend
description: "Layer 2: Backend services, controllers, and tests"
model: opus
parent_step: steps/step-03-execute.md
---

**ULTRA THINK about backend services: which services/controllers are NEW vs which need MODIFICATION for new entities/endpoints?**

## Layer 2 — Backend (Services + Controllers)

### Companion Specs Loading (delegate mode)

```javascript
if (delegate_mode && specification_loading_plan) {
  const specsDir = path.dirname(delegate_prd_path);
  // Layer 2 loads: rules + usecases companions
  for (const file of specification_loading_plan.layer2_backend) {
    const specPath = path.join(specsDir, file);
    if (fileExists(specPath)) {
      const specData = readJSON(specPath);
      if (specData.rules) {
        // Full rule definitions: statement, example, formula, conditions
        // Use for: Validator rules (BR-VAL-*), calculation logic (BR-CALC-*), workflow guards (BR-WF-*)
        console.log(`Loaded rules: ${specData.rules.length} business rules with full definitions`);
      }
      if (specData.useCases || specData.usecases) {
        const ucs = specData.useCases || specData.usecases;
        // Full UC definitions: steps[], preconditions[], result, alternativeFlows
        // Use for: Service method implementation, controller actions, error handling
        console.log(`Loaded usecases: ${ucs.length} use cases with steps and preconditions`);
      }
    }
  }
}
```

> When companion specs are loaded:
> - **BR-VAL-*** rules: use `rule.statement` + `rule.conditions[]` to implement FluentValidation rules with exact business logic
> - **BR-CALC-*** rules: use `rule.formula` to implement calculation methods with the exact formula
> - **Use cases**: use `uc.steps[]` to implement service methods following the exact flow, and `uc.preconditions[]` for guard clauses

### Task Progress
TaskUpdate(taskId: layer2_task_id, status: "in_progress")
TaskUpdate(taskId: progress_tracker_id,
  description: "Module: {module_code}. Current: step-03 (Execute), Layer 2",
  activeForm: "Executing Layer 2")

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

### HARD RULE — MCP scaffold_extension is NON-NEGOTIABLE for backend generation

> **VIOLATION CHECK:** If ANY service, controller, or DTO file was created by Write tool
> WITHOUT a prior MCP scaffold_extension call in this execution, the backend layer is INVALID.
>
> **You MUST NOT:**
> - Generate service/controller/DTO code in Agent prompts and dispatch to Snipper agents
> - Write controller content directly via the Write tool
> - Copy-paste controller templates from your knowledge
>
> **You MUST:**
> - Call MCP scaffold_extension for each entity (service, DTO, controller)
> - Let MCP generate all files with correct NavRoute, permissions, conventions
>
> **Why this matters:** Without MCP, controllers get both [Route] and [NavRoute] (causes 404s),
> NavRoute typos, incorrect permission scoping, and missing convention compliance.
>
> **Agent boundary rule:** Snipper sub-agents DO NOT have access to MCP tools.
> Therefore, MCP scaffolding MUST NEVER be delegated to Snipper agents.
> MCP calls are ALWAYS executed by the principal agent.
> Snipper agents handle: DI registration, business logic adjustments, validator customization.

### 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)
- Guard: DTO mapping in services MUST use inline construction (`new ResponseDto(...)`) inside IQueryable `.Select()`. Never use helper methods (MapToDto, ToDto) inside `.Select()` — EF Core cannot translate them. Helper methods are allowed only after materialization (ToListAsync, FirstAsync).

### Code Generation Service Registration (per entity)

```
For each entity where {code_patterns} defines strategy != "manual":
  1. Verify DI registration in DependencyInjection.cs:
     → services.AddScoped<ICodeGenerator<{Entity}>>(sp => new CodeGenerator<{Entity}>(...))
     → Use CodePatternConfig matching {code_patterns[entity]} values
     → See references/code-generation.md "DI Registration Pattern"
     → Guard: Do NOT duplicate if ICodeGenerator<{Entity}> is already registered
  2. Verify service injection:
     → {Entity}Service constructor receives ICodeGenerator<{Entity}>
     → CreateAsync uses _codeGenerator.NextCodeAsync(ct) instead of dto.Code
     → See references/code-generation.md "Service Integration Pattern"
  3. Verify CreateDto:
     → Code property MUST NOT be in Create{Entity}Dto
     → Code property MUST be in {Entity}ResponseDto
     → See references/code-generation.md "CreateDto Changes"
  4. Verify Validator:
     → Create{Entity}Validator has NO Code rule (auto-generated)
     → Update{Entity}Validator has Code rule with regex ^[a-z0-9_-]+$ (if Code is mutable)
```
- FK inter-entity pattern: When an entity has FK to another business entity (e.g., Absence → Employee), use navigation properties directly inside `.Select()` (e.g., `x.Employee.Code`, `x.Employee.User!.LastName`). EF Core translates navigation access to SQL JOINs automatically. Do NOT use `.Include()` with `.Select()` — it is ignored. See `references/smartstack-api.md` "Service Pattern — Entity with FK to another business entity" for the full pattern.

### 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: Phased Execution

> **Protocol:** See `references/parallel-execution.md`
> **AGENT BOUNDARY:** Snipper agents do NOT have access to MCP tools or Bash.
> MCP scaffolding and bash validation MUST be executed by the principal agent.

#### Phase A — Sequential: MCP scaffolding (principal agent)

For each entity, the principal agent calls MCP directly:

```
IF NOT economy_mode AND entities.length > 1:

  FOR EACH entity in entities:
    1. mcp__smartstack__scaffold_extension(type: "dto", name: "{Entity}", options: { navRoute: "{navRoute}" })
    2. mcp__smartstack__scaffold_extension(type: "controller", name: "{Entity}", options: { navRoute: "{navRoute}" })

    # Immediate NavRoute verification (principal has Bash access)
    3. Verify: [NavRoute] present, [Route] absent, segment count >= 2
       If [Route] found alongside [NavRoute] → remove [Route] immediately
       If [NavRoute] missing → add it with correct value
```

#### Phase B — Parallel: post-scaffold adjustments (Snipper agents)

After ALL Phase A scaffolding completes, launch Snipper agents in parallel:

```
  For each entity, launch in parallel (single message):
    Agent(subagent_type='Snipper', model='opus',
      prompt='Adjust Layer 2 backend for {EntityName}:
        - Service: implement business logic in {EntityName}Service (FK patterns, search, inline Select)
        - Validator: customize FluentValidation rules for Create{EntityName}Dto and Update{EntityName}Dto
        - CODE GENERATION: {code_patterns[EntityName] summary — e.g., "strategy: sequential, prefix: emp, digits: 5" or "manual"}
          If strategy != "manual": read references/code-generation.md, then:
          → Inject ICodeGenerator<{EntityName}> in {EntityName}Service constructor
          → Use _codeGenerator.NextCodeAsync(ct) in CreateAsync
          → Remove Code from Create{EntityName}Dto, keep in {EntityName}ResponseDto
          → Create{EntityName}Validator: NO Code rule. Update{EntityName}Validator: Code rule with regex ^[a-z0-9_-]+$
        - Your task ID is {task_id}. Call TaskUpdate(status: "in_progress") before starting.
        - Call TaskUpdate(status: "completed", metadata: { files_created: [...] }) when done.

        DO NOT regenerate controller or DTO files — already created by MCP in Phase A.
        DO NOT add [Route] attribute — NavRoute is already set correctly.
        Files already created: {list of paths from Phase A}')
  # All agents launched in parallel
```

#### Phase C — Sequential: shared file edits (principal agent)

After ALL Phase B agents complete, the principal agent handles shared-file edits:

```
  1. DependencyInjection.cs: register ALL services + validators in a single pass
     → Avoids concurrent edit conflicts on shared file
  2. Code-generation DI: register ICodeGenerator<T> for each entity with strategy != "manual"

ELSE:
  # Agent principal handles all entities sequentially (economy_mode or single entity)
```

### Parallel Agents + TaskCreate Integration

When launching agents for multi-entity layers:
- Each agent receives its task ID and metadata context in the prompt
- Agent must call TaskUpdate(status: "in_progress") before starting
- Agent must call TaskUpdate(status: "completed", metadata: { files_created: [...] }) when done
- Agent principal monitors via TaskList() after all agents complete
- Agent principal updates activeForm after each entity completion:
  `TaskUpdate(taskId: layer2_task_id, activeForm: "Building {EntityName} backend (2/5 entities)")`

### Controller NavRoute Attribute (MANDATORY)

After controller generation, verify `[NavRoute]` attribute is present on every controller:
- Expected: `[NavRoute("{app_name}.{module_code}.{section_code}")]` on the controller class
- If missing: Add it manually above `[Microsoft.AspNetCore.Authorization.Authorize]`
- When calling `scaffold_extension(type: "controller")`, always pass `navRoute` in options
- This is REQUIRED for `scaffold_routes` to auto-detect routes in Layer 3

### Guard: NavRoute Uniqueness and Segment Count (MANDATORY)

**BEFORE proceeding past Layer 2**, verify for EACH controller:

1. **Unique NavRoute:** No two controllers may share the same `[NavRoute("...")]` value. Duplicate NavRoutes cause routing conflicts → 404s on one of the controllers.

2. **Segment count matches hierarchy:** Count the dots in the NavRoute value:
   - 1 dot = 2 segments (module-level, e.g., `human-resources.employees`) — controller is at `Controllers/{App}/`
   - 2 dots = 3 segments (section-level, e.g., `human-resources.employees.contracts`) — controller is at `Controllers/{App}/{Module}/` or in a section subfolder
   - **If a controller is in a section subfolder** (e.g., `Controllers/{App}/Employees/ContractsController.cs`) **but has only 2 segments** → the API route will be wrong → 404. It MUST have 3 segments.
   - 0 dots = INVALID → BLOCK

> **Execution context:** These bash scripts run from the PRINCIPAL AGENT after Phase C.
> Snipper agents cannot execute Bash. Do NOT include these checks in Snipper prompts.

```bash
# Quick validation
CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
for f in $CTRL_FILES; do
  NAVROUTE=$(grep -oP '\[NavRoute\("\K[^"]+' "$f")
  if [ -n "$NAVROUTE" ]; then
    DOTS=$(echo "$NAVROUTE" | tr -cd '.' | wc -c)
    if [ "$DOTS" -eq 0 ]; then
      echo "BLOCKING: NavRoute '$NAVROUTE' has only 1 segment (need minimum 2): $f"
      exit 1
    fi
    # Check if controller is in a section subfolder but NavRoute has only 2 segments
    DEPTH=$(echo "$f" | grep -oP 'Controllers/[^/]+/[^/]+/' | wc -l)
    if [ "$DEPTH" -gt 0 ] && [ "$DOTS" -eq 1 ]; then
      echo "WARNING: Controller in section subfolder but NavRoute has only 2 segments: $f"
      echo "  NavRoute: $NAVROUTE — expected 3 segments (app.module.section)"
    fi
  fi
done
```

```bash
# Quick check: all controllers must have [NavRoute] (not just [Route])
CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
for f in $CTRL_FILES; do
  if ! grep -q "\[NavRoute" "$f" && grep -q "\[Route" "$f"; then
    echo "WARNING: $f has [Route] but no [NavRoute] — add [NavRoute] for route auto-detection"
  fi
  if grep -q "\[NavRoute" "$f" && grep -q "\[Route(" "$f"; then
    echo "WARNING: $f has BOTH [Route] and [NavRoute] — remove [Route] (causes 404s)"
  fi
done
```

### Layer 2 Build Gate — NavRoute Verification

> Fail-fast check: detect MCP generation errors BEFORE Layer 3.
> Does NOT auto-fix — flags as BLOCKING for manual correction.
> Full validation in step-04 POST-CHECK C43 + C56.

After all controllers are generated and build passes:

```bash
# Check only controllers generated in this run (match against {entities})
for ENTITY in $ENTITIES; do
  CTRL=$(find src/ -path "*/Controllers/*" -name "${ENTITY}*Controller.cs" 2>/dev/null | head -1)
  [ -z "$CTRL" ] && continue

  HAS_NAVROUTE=$(grep -c "\[NavRoute" "$CTRL")
  HAS_ROUTE=$(grep -cP "^\s*\[Route\(" "$CTRL")  # class-level only

  if [ "$HAS_NAVROUTE" -eq 0 ] && [ "$HAS_ROUTE" -gt 0 ]; then
    echo "BLOCKING: $CTRL has [Route] but NO [NavRoute]"
    echo "MCP scaffold_extension generated [Route] instead of [NavRoute]"
    echo "Fix: Replace [Route(\"api/...\")] with [NavRoute(\"{app}.{module}.{section}\")]"
    echo "  NavRoute value: derive from naming rules (section 4f)"
    echo "  Add: using SmartStack.Api.Routing;"
  fi

  if [ "$HAS_NAVROUTE" -eq 0 ] && [ "$HAS_ROUTE" -eq 0 ]; then
    echo "BLOCKING: $CTRL has NO route attribute at all"
    echo "Fix: Add [NavRoute(\"{app}.{module}.{section}\")] from naming rules"
  fi

  if [ "$HAS_NAVROUTE" -gt 0 ] && [ "$HAS_ROUTE" -gt 0 ]; then
    echo "BLOCKING: $CTRL has BOTH [Route] and [NavRoute] — causes 404s"
    echo "Fix: Remove [Route(\"...\")], keep only [NavRoute(\"...\")]"
  fi
done

# Check NavRoute uniqueness across generated controllers
ALL_NAVROUTES=""
for ENTITY in $ENTITIES; do
  CTRL=$(find src/ -path "*/Controllers/*" -name "${ENTITY}*Controller.cs" 2>/dev/null | head -1)
  [ -z "$CTRL" ] && continue
  NR=$(grep -oP '\[NavRoute\("\K[^"]+' "$CTRL" 2>/dev/null || true)
  [ -z "$NR" ] && continue
  if echo "$ALL_NAVROUTES" | grep -qx "$NR"; then
    echo "BLOCKING: Duplicate NavRoute '$NR' on $CTRL — each controller needs a unique NavRoute"
  fi
  ALL_NAVROUTES="$ALL_NAVROUTES
$NR"
done
```

> IF any BLOCKING found: fix the controller(s) BEFORE proceeding to Layer 3.
> The fix is: remove [Route], add [NavRoute] with the correct dot-separated value,
> add `using SmartStack.Api.Routing;`.

### Post-Layer 2 Build Gate

```bash
dotnet build
```

Must pass before backend tests.

TaskUpdate(taskId: layer2_task_id, status: "completed",
  metadata: { build_gate: "pass" })
TaskUpdate(taskId: progress_tracker_id,
  activeForm: "Running build gate (Layer 2)")

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

> **Context release:** `references/smartstack-api.md` entity patterns and `references/smartstack-layers.md` Layer 2 rules have been consumed. Layer 3 only needs their frontend sections (already covered by `smartstack-frontend.md`).

---

## NEXT SUB-STEP (MANDATORY — IMMEDIATE)

> **DO NOT STOP.** DO NOT ask the user. DO NOT present options. IMMEDIATELY load the next sub-step.
> Stopping here is a VIOLATION of the execution guarantee.

Load `steps/step-03d-layer3-frontend.md`
