# AGENTS.md

Agent guidance for DocumentOperator service. Read this before working on the codebase.

---

## ⚠️ CRITICAL: Architecture Decision Change

**Previous developer used Minimal API** (`DocumentEndpoints.cs`), but this is **WRONG**.

**YOU MUST use Controller-based approach** as specified in `CONTROLLER_ENDPOINTS.md`.

### Key Differences

| Previous Approach (WRONG) | Required Approach (CORRECT) |
|---------------------------|------------------------------|
| Minimal API (`DocumentEndpoints.cs`) | **Controllers** (`PdfValidationController`, etc.) |
| Only Base64 JSON | **Both multipart/form-data AND Base64 JSON** |
| `/api/v1/documents/validate` | **`/api/pdf/validation/validate`** |

**Do NOT follow ROADMAP.md's "Minimal API" guidance.** It conflicts with the requirements.

### Migration Required

**Existing code that needs replacement:**
- `DocumentOperator.API/Endpoints/v1/DocumentEndpoints.cs` → Delete, replace with Controllers
- `Program.cs` line 72: `app.MapDocumentEndpoints()` → Replace with `app.MapControllers()`
- `Program.cs` line 44: Add `builder.Services.AddControllers()`
- All DTOs → Support **BOTH** `IFormFile` (multipart) AND `Base64String` (JSON)

**Dual Input Support Required:**
- Controllers must accept **BOTH** file upload (multipart/form-data) and Base64 JSON
- Each endpoint should have overloads or flexible parameter binding
- Preserve existing Base64 functionality while adding file upload support

---

## Architecture & Development Approach

**Clean Architecture with Controller-Based API:**
- 4 layers: API → Application → Infrastructure → Domain
- Domain has **ZERO** external dependencies (only standard .NET)
- Feature-driven development: complete one feature end-to-end before starting the next
- Feature = Domain + Infrastructure + Application + **Controller** + Tests + Swagger (all layers)

**Dependency flow (enforced):**
```
API → Application → Domain
API → Infrastructure → Application
Infrastructure → Application (for interfaces only)
Domain → NOTHING
```

**Vertical Slice structure** (NOT horizontal layers):
```
Features/Documents/
├── ValidatePdf/
│   ├── ValidatePdfQuery.cs          (request)
│   ├── ValidatePdfHandler.cs        (logic)
│   └── ValidatePdfValidator.cs      (validation)
└── ExtractSwissQrCode/
    ├── ExtractSwissQrCodeQuery.cs
    ├── ExtractSwissQrCodeHandler.cs
    └── ExtractSwissQrCodeValidator.cs
```

All files for a feature live together. Do NOT create separate Commands/, Handlers/, Validators/ folders.

---

## 🏗️ Architecture Principles

### Clean Architecture (Pragmatic)

**4 Layers with strict dependency rules:**
- API → Application → Domain
- Infrastructure → Application (interfaces only)
- Domain → NOTHING (zero external dependencies)

**Key Principles:**
- ✅ Testability (Application layer mocks Infrastructure services)
- ✅ Replaceability (swap DevExpress without touching Application)
- ✅ Separation of Concerns
- ❌ NO overengineering (only what we need, YAGNI principle)
- ❌ NO speculative abstractions (wait for 2nd use case)

### CQRS with MediatR

**Why MediatR:**
- 1 Command/Query = 1 Handler = 1 Responsibility
- Isolated, testable handlers
- Pipeline Behaviors (Validation, Logging) run centrally
- Avoids bloated services with 20+ methods

**Pattern:**
- **Command:** Modifies data (ApplyStamp, EmbedCertificate)
- **Query:** Reads data (ValidatePdf returns metadata only)

**Pipeline:** ValidationBehavior → LoggingBehavior → Handler

### Vertical Slice Architecture

**NOT Horizontal** (Commands/, Handlers/, Validators/ folders)  
**YES Vertical** (all files for one feature together)

**Benefits:**
- Related code stays together (high cohesion)
- Easier to find ("Where's ValidatePdf?" → one folder!)
- Easier to modify (all files in same folder)
- Fewer merge conflicts in teams

### Exception-Based Error Handling

**NO Result<T> pattern library**

**Flow:**
1. FluentValidation (DTO level) → ValidationException → 400
2. Domain validation → DomainValidationException → 400
3. Business logic → DomainException → 400/404
4. Infrastructure → PdfProcessingException → 500

**Middleware:** Central exception handler maps exceptions to HTTP status codes

**Why exceptions:**
- Simpler code (no `if (result.IsSuccess)` everywhere)
- Less boilerplate (no Result<T> wrapping)
- Standard .NET exception flow
- Centralized error handling (one place to maintain)

### Feature-Driven Development

**Feature-Driven (NOT Layer-by-Layer):**
- Complete one feature end-to-end before starting next
- Feature = Domain + Infrastructure + Application + API + Tests + Swagger
- Feature is DONE when testable in Swagger UI

**Why:**
- Faster value delivery (Feature 1 done in ~1 day)
- Clear definition of done (Swagger testable)
- Less complexity (not all layers in parallel)
- Better learning (pattern repeats)

**Alternative rejected:** Complete all Domain → all Infrastructure → all Application → all API  
**Problem:** Too much speculative code without visible results

### Test-Driven Development (TDD)

**Flow:** Red → Green → Refactor

**Test Pyramid:**
- **Unit Tests (many):** Value Objects, Handlers, Services
- **Integration Tests (some):** Endpoints, MediatR Pipeline
- **E2E Tests (few/none):** API is already top-level

**Why TDD:**
- Tests as documentation
- Tests as safety net for refactoring
- Better design (testable = good code)
- No forgotten tests (test comes FIRST)

### Cross-Cutting Concerns Timing

**Multi-Tenancy Implementation Deferred**

**Decision:** Implement multi-tenancy (X-API-Key header, tenant database, Redis cache) AFTER all synchronous PDF operation features are complete.

**Why:**
- Multi-tenancy affects ALL endpoints
- Better to implement once for all features (avoid repetition)
- Easier to test features first without tenancy, then add tenancy layer
- Cleaner separation: Features first, then cross-cutting concerns

**Impact on current architecture:**
- ❌ NO Entity Framework yet (tenant database comes with multi-tenancy)
- ❌ NO Redis yet (API key caching comes with multi-tenancy)
- ❌ NO X-API-Key authentication yet (comes with multi-tenancy)
- ✅ All features currently work without authentication

**When to implement:**
After completing all Phase 1-3 controllers (PdfValidation, PdfAttachment, SwissQrCode, PdfOperations, PdfConversion), then add multi-tenancy to ALL endpoints in one refactoring phase.

---

## Build, Test, Run

**Build:**
```powershell
dotnet build
```

**Run tests (30 tests as of Feature 3 - PDF/A Validation):**
```powershell
dotnet test
```

**Run API (Development):**
```powershell
dotnet run --project DocumentOperator.API
```
Swagger UI: `https://localhost:<port>/swagger`

**Target framework:** .NET 8.0  
**SDK required:** 8.0.412 or later (repo has 8.0.412–10.0.203 available)

---

## Key Libraries & Their Roles

| Library | Purpose | Where Used |
|---------|---------|------------|
| **DevExpress.Document.Processor** (26.1.3) | PDF operations (validation, QR extraction, attachments) | Infrastructure layer only |
| **Codecrete.SwissQRBill.Generator** (3.4.0) | Swiss QR Bill parsing (Standard 2.0) | Infrastructure.Services.QrCodeProcessing |
| **ZXing.Net.Bindings.Windows.Compatibility** (0.16.14) | QR code image decoding | Infrastructure.Services.QrCodeProcessing |
| **MediatR** (14.1.0) | CQRS: 1 handler per feature | Application layer |
| **FluentValidation** (12.1.1) | Request validation (runs via ValidationBehavior before handlers) | Application layer |
| **Serilog.AspNetCore** (10.0.0) | Structured logging | API layer |

**Critical:** DevExpress requires a license. All PDF operations use `DevExpress.Pdf.PdfDocumentProcessor`.

---

## Exception Handling Strategy

**No Result<T> pattern.** Use exceptions + central middleware.

**Flow:**
1. FluentValidation validates request DTOs → throws `ValidationException` → HTTP 400
2. Domain validation in Value Objects → throws `DomainValidationException` → HTTP 400
3. Business logic errors → throws `DomainException` subtypes → HTTP 400/404/500
4. Infrastructure errors (e.g., PDF parsing) → throws `PdfProcessingException` → HTTP 500

**Middleware maps exceptions to HTTP status codes** (`ExceptionHandlingMiddleware.cs`).

Do NOT add `if (result.IsSuccess)` checks. Throw exceptions for errors. The middleware handles the rest.

---

## Required Controllers & Endpoints

**See `CONTROLLER_ENDPOINTS.md` for complete specification.**

### Priority Order

**Phase 1 (PRIORITY):**
1. `PdfValidationController` – 2 endpoints
   - `POST /api/pdf/validation/validate` (Basic PDF validation)
   - `POST /api/pdf/validation/validate-pdfa` (PDF/A conformance)
2. `PdfAttachmentController` – check endpoint
   - `POST /api/pdf/attachments/check` (Attachment detection)
3. `SwissQrCodeController` – extract endpoint
   - `POST /api/swissqrcode/extract` (Swiss QR Bill extraction)
4. `PdfAttachmentController` – extract endpoint
   - `POST /api/pdf/attachments/extract` (Extract attachments as ZIP)
5. `PdfOperationsController` – merge endpoint
   - `POST /api/pdf/operations/merge` (Merge multiple PDFs)

**Phase 2:**
6. `PdfOperationsController` – stamp & annotate
   - `POST /api/pdf/operations/stamp` (Add stamps)
   - `POST /api/pdf/operations/annotate` (Add annotations)
7. `PdfAttachmentController` – add attachment
   - `POST /api/pdf/attachments/add` (Embed attachments in PDF/A-3)

**Phase 3:**
8. `PdfConversionController` – PDF ↔ PDF/A conversion
   - `POST /api/pdf/conversion/to-pdfa` (Convert to PDF/A)
   - `POST /api/pdf/conversion/from-pdfa` (Convert from PDF/A)

**Removed:**
- `PdfRenderController` – Moved to .NET client library (WinForms/WPF DevExpress controls)

### Current Status

| Controller | Status | Tests |
|-----------|--------|-------|
| **PdfValidationController** | ✅ DONE | 13 (7 validate + 6 validate-pdfa) |
| **SwissQrCodeController** | ✅ DONE | 2 |
| **PdfAttachmentController** | ⏳ Pending | 0 |
| **PdfOperationsController** | ⏳ Pending | 0 |
| **PdfConversionController** | ⏳ Pending | 0 |

**Note:** PdfRenderController removed - moved to .NET client library.

---

## Adding a New Feature

**Required steps (follow CONTROLLER_ENDPOINTS.md):**

1. **Domain:** Value Objects, Exceptions (if needed)
2. **Infrastructure:** Service interface + DevExpress implementation + unit tests
3. **Application:** Query/Command + Handler + FluentValidator + DTOs + unit tests
4. **API:** **Controller** + actions + integration tests
5. **Swagger:** XML comments on controller actions + DTOs

**Example (PdfValidationController):**
```
Step 1: Application/Features/Documents/ValidatePdf/
  - ValidatePdfCommand.cs (record)
  - ValidatePdfHandler.cs (IRequestHandler)
  - ValidatePdfValidator.cs (AbstractValidator)
Step 2: API/Controllers/PdfValidationController.cs
  - [HttpPost("validate")] action
  - Accepts IFormFile (multipart/form-data)
  - Returns ValidatePdfResponse
Step 3: XML comments + [ProducesResponseType] attributes
```

**CRITICAL: Support BOTH multipart/form-data AND Base64 JSON for all file-based endpoints.**

**Input Flexibility:**
- Primary: `IFormFile` (multipart/form-data) - for direct file uploads
- Secondary: `Base64String` (application/json) - for API clients that can't send multipart

Do NOT skip steps. Each feature is done when it's **testable in Swagger UI with both input methods**.

---

## Test Data

**Embedded test PDFs:**
- `TestData/Pdfs/valid.pdf` (simple PDF for validation)
- `TestData/Pdfs/pdfWithSwissQRCode.pdf` (Swiss QR Code on last page)
- `TestData/Pdfs/pdfWithMoreThanOneAttachment.pdf` (6 attachments)

**All test PDFs are EmbeddedResource.** Access via:
```csharp
var stream = Assembly.GetExecutingAssembly()
    .GetManifestResourceStream("DocumentOperator.Tests.TestData.Pdfs.valid.pdf");
```

**Do NOT commit new binary files** without marking them as `<EmbeddedResource>`.

---

## Test Structure & Strategy

**3-folder structure (CORRECT approach by previous developer):**

```
DocumentOperator.Tests/
├── Integration/
│   └── API/
│       ├── PdfValidationControllerTests.cs (13 tests)
│       └── ExtractSwissQrCodeEndpointTests.cs (2 tests)
├── TestData/
│   └── Pdfs/ (EmbeddedResource PDFs)
├── Unit/
│   ├── Application/
│   │   └── Features/
│   │       ├── ValidatePdf/
│   │       │   └── ValidatePdfHandlerTests.cs (2 tests)
│   │       ├── ValidatePdfA/
│   │       │   └── ValidatePdfAQueryHandlerTests.cs (4 tests)
│   │       └── ExtractSwissQrCode/
│   │           └── ExtractSwissQrCodeHandlerTests.cs (2 tests)
│   └── Infrastructure/
│       └── Services/
│           └── PdfProcessing/
│               └── DevExpressPdfProcessorTests.cs (7 tests)
```

**✅ Why this structure is CORRECT:**

1. **Integration vs Unit separation:**
   - **Integration:** WebApplicationFactory → REAL API calls (HTTP, middleware, MediatR pipeline, DevExpress)
   - **Unit:** Mock-based ISOLATED tests (Handler only depends on mocked IPdfProcessor)

2. **TestData centralization:**
   - All 3 layers share same EmbeddedResource PDFs (no duplication)
   - Accessed via `Assembly.GetManifestResourceStream()`

3. **Vertical Slice compliance:**
   - `Unit/Application/Features/ValidatePdf/` → Each feature's tests co-located
   - Matches Application layer structure exactly

4. **Test Pyramid:**
   - **Unit tests (15):** Fast, isolated, many scenarios
   - **Integration tests (15):** Slower, full pipeline, critical paths only

**Test count:** 30 tests total (as of Feature 3 - PDF/A Validation)

**FluentValidation in tests:**
- Base64 format validation happens in `ValidatePdfQueryValidator` and `ValidatePdfAQueryValidator`
- Prevents `FormatException` from reaching handler (caught as 400 Bad Request, not 500)
- Unit tests verify handler behavior with valid inputs only
- Integration tests verify full validation pipeline (including FluentValidation)

---

## Swiss QR Code Feature (Feature 2)

**Swiss QR Bill Standard 2.0** requires:
- QR code is on the **last page** of the PDF (not first!)
- Use `DevExpress.Pdf.PdfDocumentProcessor` to render last page as image
- Use `ZXing` to decode QR code from image
- Use `Codecrete.SwissQRBill.Generator` to parse Swiss QR Bill payload

**Known quirks:**
- PDF must be rendered at **300 DPI** for reliable QR detection
- Alternative procedure parameters (AV1, AV2) are split by newline, not semicolon

---

## MediatR Pipeline Behaviors

**Two behaviors run for EVERY request:**

1. **ValidationBehavior** (runs first): Executes all `IValidator<TRequest>` and throws `ValidationException` if invalid
2. **LoggingBehavior** (runs second): Logs request name + execution time

**Registered in:** `Application/DependencyInjection.cs`

Do NOT manually call validators in handlers. The pipeline does it.

---

## Controller Pattern (CORRECT Approach)

**Controllers must support BOTH file upload and Base64 input.**

### Option 1: Separate Endpoints (Recommended)

```csharp
[ApiController]
[Route("api/pdf/validation")]
public class PdfValidationController : ControllerBase
{
    private readonly IMediator _mediator;

    public PdfValidationController(IMediator mediator)
    {
        _mediator = mediator;
    }

    /// <summary>
    /// Validates a PDF document (multipart/form-data)
    /// </summary>
    [HttpPost("validate")]
    [Consumes("multipart/form-data")]
    [ProducesResponseType(typeof(ValidatePdfResponse), 200)]
    [ProducesResponseType(typeof(ProblemDetails), 400)]
    public async Task<IActionResult> ValidateFromFile(IFormFile file, CancellationToken ct)
    {
        using var ms = new MemoryStream();
        await file.CopyToAsync(ms, ct);
        byte[] pdfBytes = ms.ToArray();

        var command = new ValidatePdfCommand(pdfBytes);
        var result = await _mediator.Send(command, ct);
        
        return Ok(result);
    }

    /// <summary>
    /// Validates a PDF document (Base64 JSON)
    /// </summary>
    [HttpPost("validate")]
    [Consumes("application/json")]
    [ProducesResponseType(typeof(ValidatePdfResponse), 200)]
    [ProducesResponseType(typeof(ProblemDetails), 400)]
    public async Task<IActionResult> ValidateFromBase64(
        [FromBody] ValidatePdfRequest request, 
        CancellationToken ct)
    {
        var query = new ValidatePdfQuery(Base64String.Create(request.Base64Pdf));
        var result = await _mediator.Send(query, ct);
        
        return Ok(result);
    }
}
```

### Option 2: Single Endpoint with Model Binding

```csharp
public class PdfInputModel
{
    public IFormFile? File { get; set; }
    public string? Base64Pdf { get; set; }
}

[HttpPost("validate")]
public async Task<IActionResult> Validate([FromForm] PdfInputModel input, CancellationToken ct)
{
    byte[] pdfBytes = input.File != null 
        ? await GetBytesFromFile(input.File)
        : Base64String.Create(input.Base64Pdf!).ToByteArray();
    
    // Process...
}
```

**Use Controllers, NOT Minimal API endpoints.**

---

## Configuration

**appsettings.json sections:**
- `DocumentOperatorSettings` (future: file size limits, temp paths)
- `RedisSettings` (future: multi-tenancy caching)
- `ApiKeySettings` (future: authentication)

**Currently:** All features work without authentication. Multi-tenancy is deferred until after all sync features are complete.

---

## Coding Standards

### Primary Constructors
**ALWAYS use primary constructors** (C# 12 feature) unless there's a technical limitation.

**✅ Correct:**
```csharp
public class PdfValidationController(IMediator mediator, ILogger<PdfValidationController> logger) : ControllerBase
{
    // Use parameters directly, no field declarations needed
    public async Task<IActionResult> Validate(...)
    {
        await mediator.Send(...);
    }
}
```

**❌ Wrong:**
```csharp
public class PdfValidationController : ControllerBase
{
    private readonly IMediator _mediator;
    private readonly ILogger<PdfValidationController> _logger;
    
    public PdfValidationController(IMediator mediator, ILogger<PdfValidationController> logger)
    {
        _mediator = mediator;
        _logger = logger;
    }
}
```

### Controller Responsibilities
**Controllers should be thin.** Do NOT add mapping logic.

**✅ Correct:**
```csharp
public async Task<IActionResult> Validate([FromBody] ValidatePdfRequest request, CancellationToken ct)
{
    // Direct pass-through to MediatR
    var result = await mediator.Send(request, ct);
    return Ok(result);
}
```

**❌ Wrong:**
```csharp
public async Task<IActionResult> Validate([FromBody] ValidatePdfRequest request, CancellationToken ct)
{
    // Manual mapping (WRONG!)
    var command = new ValidatePdfCommand(request.Base64Pdf);
    var metadata = await mediator.Send(command, ct);
    var response = new ValidatePdfResponse(metadata.PageCount, ...);
    return Ok(response);
}
```

**If mapping is absolutely necessary:** Use AutoMapper.

### Request DTOs - Flexible Input
**Support BOTH `byte[]` and `Base64String` in requests.**

```csharp
public record ValidatePdfRequest
{
    public byte[]? PdfBytes { get; init; }
    public string? Base64Pdf { get; init; }
}
```

**FluentValidation:** Ensure exactly ONE is provided:
```csharp
public class ValidatePdfRequestValidator : AbstractValidator<ValidatePdfRequest>
{
    public ValidatePdfRequestValidator()
    {
        RuleFor(x => x)
            .Must(x => (x.PdfBytes != null && x.PdfBytes.Length > 0) ^ 
                       (!string.IsNullOrWhiteSpace(x.Base64Pdf)))
            .WithMessage("Either PdfBytes or Base64Pdf must be provided, but not both");
    }
}
```

**Handler:** Use `byte[]` if available, otherwise convert Base64:
```csharp
public async Task<PdfMetadata> Handle(ValidatePdfRequest request, CancellationToken ct)
{
    byte[] pdfBytes = request.PdfBytes ?? Convert.FromBase64String(request.Base64Pdf!);
    return await _processor.ValidateAsync(pdfBytes);
}
```

### No Unnecessary Value Objects
**Do NOT create value objects for simple types** (e.g., Base64String).

**❌ Wrong:** Creating `Base64String` value object just to wrap `string`
**✅ Correct:** Use `string` directly + extension methods if needed

**Why:** 
- Performance overhead (validation runs twice: once in value object, once in FluentValidation)
- Unnecessary abstraction (YAGNI principle)
- `Convert.FromBase64String()` already validates format

### Exception Handling in Controllers
**Let FormatException bubble up naturally.** ExceptionHandlingMiddleware will catch it.

```csharp
// Handler
byte[] pdfBytes = request.PdfBytes ?? Convert.FromBase64String(request.Base64Pdf!);
// If Base64 is invalid, FormatException → Middleware → 400 Bad Request
```

**Middleware handles:**
- `FormatException` → 400 Bad Request
- `ValidationException` → 400 Bad Request
- `DomainException` → 400/404
- `PdfProcessingException` → 500

---

## Git Commit Guidelines

### ⚠️ CRITICAL: Never Commit Without Approval
**NEVER run `git commit` without explicit user approval.**

### Systematic Commits
**Do NOT commit everything in one giant commit.**

**✅ Correct approach:**
1. Complete one logical change (e.g., "Add PdfValidationController")
2. Stage only related files: `git add <specific-files>`
3. Ask user: "Ready to commit 'Add PdfValidationController'?"
4. After approval: `git commit -m "Add PdfValidationController with dual input support"`
5. Repeat for next logical change

**❌ Wrong approach:**
```bash
git add -A
git commit -m "Migrate everything to controllers, update tests, add AGENTS.md, delete ROADMAP.md"
# This is TOO MUCH in one commit!
```

**Good commit messages:**
- `feat: Add PdfValidationController with multipart/form-data support`
- `refactor: Replace Base64String value object with direct string usage`
- `test: Add integration tests for PdfValidationController`
- `docs: Add AGENTS.md with architecture guidance`
- `chore: Delete deprecated ROADMAP.md`

**Commit size guideline:** 1-5 files per commit, one logical change

---

## What NOT to Do

- ❌ Do NOT create horizontal folders (Commands/, Handlers/, Validators/)
- ❌ Do NOT add Entity Framework until multi-tenancy phase
- ❌ Do NOT use Minimal API endpoints (use Controllers instead)
- ❌ Do NOT support only ONE input type (must support BOTH multipart AND Base64)
- ❌ Do NOT use Result<T> pattern (use exceptions)
- ❌ Do NOT skip tests (TDD: write test first, then implementation)
- ❌ Do NOT add dependencies to Domain layer (keep it clean!)
- ❌ Do NOT commit without user approval
- ❌ Do NOT use old-style constructors (use primary constructors)
- ❌ Do NOT add mapping logic in controllers (keep them thin)
- ❌ Do NOT create unnecessary value objects (YAGNI principle)

---

## Debugging Tips

**DevExpress PDF errors:**
- Check if file is actually a valid PDF (magic bytes: `%PDF-`)
- DevExpress throws generic exceptions; wrap in try-catch and add context

**Swiss QR Code not found:**
- Verify QR is on **last page** (not first)
- Check DPI setting (300 DPI required, see ROADMAP.md Feature 2)
- Use `ZXing` with `TryHarder` hint enabled

**Attachment count wrong:**
- Search entire PDF stream, not just first 1000 chars (see ROADMAP.md fix log 17.01.2025)
- Count `/EmbeddedFiles` object references correctly (not divided by 2)

---

## References

- **CONTROLLER_ENDPOINTS.md** – **PRIMARY SOURCE** for API specification (all planned endpoints)
- **REQUIRED_FEATURES.md** – Business requirements (what PDF operations are needed and why)
- **ROADMAP.md** – Feature-by-feature implementation plan (1101 lines, detailed) **NOTE: Uses Minimal API, which is incorrect. Follow CONTROLLER_ENDPOINTS.md instead.**
- **DevExpress Docs** – https://docs.devexpress.com/OfficeFileAPI/
- **Swiss QR Bill Standard** – https://www.ferd-net.de/ (ZUGFeRD/XRechnung context)

**When implementing endpoints:** Follow CONTROLLER_ENDPOINTS.md, NOT ROADMAP.md's Minimal API approach.
