{
  "steward": "rest-steward",
  "project": "Azure-AI-RAG-CSharp-Semantic-Kernel-Functions",
  "runDate": "2026-03-21",
  "runId": "2026-03-21T00-00-00",
  "findings": [
    {
      "id": "REST-VERB-001",
      "title": "State-creating GET: /session endpoint uses GET for session creation",
      "severity": "critical",
      "category": "VERB",
      "file": "src/ChatAPI/Controllers/SessionController.cs",
      "line": 10,
      "description": "SessionController.GetSession() uses [HttpGet] to generate and return a new session ID. Although the handler itself does not write to a data store, the endpoint's semantic purpose is resource creation — it mints a new identity that clients then use to write data. Using GET for resource creation violates HTTP semantics (GET must be safe and idempotent) and may cause repeat invocations via browser prefetch, proxies, or caching.",
      "recommendation": "Change to POST /sessions. Return 201 Created with a Location header pointing to the new session resource.",
      "status": "open"
    },
    {
      "id": "REST-CONTRACT-001",
      "title": "Double-JSON serialization in POST /chat response — client receives a JSON string containing JSON",
      "severity": "critical",
      "category": "CONTRACT",
      "file": "src/ChatAPI/Services/ChatService.cs",
      "line": 58,
      "description": "ChatService.GetResponseAsync returns JsonSerializer.Serialize(new { resp }), which is already a JSON string. ChatController.Post declares return type Task<string>, so ASP.NET Core serializes this string again as a JSON string literal. The HTTP response body is a JSON-encoded string (e.g., \"{\\\"resp\\\":\\\"...\\\"}\" ) not a JSON object. Every client must double-parse the response, which is an undocumented, non-standard contract.",
      "recommendation": "Return the response object directly from ChatService (not pre-serialized). Change ChatController.Post return type to Task<IActionResult> and return Ok(new { response = result }).",
      "status": "open"
    },
    {
      "id": "REST-VALIDATION-001",
      "title": "No input validation on POST /chat — unbounded Input string with no null or length guard",
      "severity": "critical",
      "category": "VALIDATION",
      "file": "src/ChatAPI/Controllers/ChatController.cs",
      "line": 9,
      "description": "ChatRequest.Input has no [Required] attribute, no [MaxLength] constraint, and no null guard. A null or empty Input is forwarded to Azure OpenAI unchanged. An unbounded input string (megabytes of text) can be submitted, causing excessive API latency, cost overruns, or upstream service errors. The [ApiController] attribute is present but will not trigger automatic 400 responses because no validation attributes are declared on the DTO.",
      "recommendation": "Add [Required] and [MaxLength(4000)] (or an appropriate limit) to ChatRequest.Input. Add [Required] to SessionId. Annotate properties with proper nullability (string Input { get; set; } = null!; or make nullable with null checks).",
      "status": "open"
    },
    {
      "id": "REST-STATUS-001",
      "title": "POST /chat returns raw string not IActionResult — no typed error status codes possible from controller",
      "severity": "notable",
      "category": "STATUS",
      "file": "src/ChatAPI/Controllers/ChatController.cs",
      "line": 20,
      "description": "ChatController.Post declares return type Task<string>. This removes the ability to return typed HTTP status codes (400, 404, 422, 500) from the controller action. Any service-layer exception propagates as an unhandled 500. Validation failures cannot produce a structured 400 response. The contract is opaque to Swagger and to clients.",
      "recommendation": "Change return type to Task<IActionResult>. Return Ok(result) on success, BadRequest(new { error = \"...\" }) for validation, and let global exception middleware handle 500s.",
      "status": "open"
    },
    {
      "id": "REST-STATUS-002",
      "title": "GET /session returns 200 OK instead of 201 Created for resource creation",
      "severity": "notable",
      "category": "STATUS",
      "file": "src/ChatAPI/Controllers/SessionController.cs",
      "line": 16,
      "description": "Even accounting for the GET verb issue (REST-VERB-001), the response uses Ok() which returns 200. When converted to POST, the correct response for resource creation is 201 Created with a Location header pointing to the created resource.",
      "recommendation": "After converting to POST, return CreatedAtAction or Created with a 201 status code and a Location header.",
      "status": "open"
    },
    {
      "id": "REST-ERROR-001",
      "title": "No global exception-handling middleware — unhandled exceptions may return HTML or expose internal details",
      "severity": "notable",
      "category": "ERROR",
      "file": "src/ChatAPI/Program.cs",
      "line": 79,
      "description": "Program.cs does not configure app.UseExceptionHandler() or any equivalent. In Development mode, ASP.NET Core's developer exception page returns an HTML response, breaking JSON content negotiation for API clients. In Production mode, default error handling may expose internal exception messages. Neither ChatController nor SessionController have try/catch blocks returning structured error responses.",
      "recommendation": "Add app.UseExceptionHandler(\"/error\") and a minimal /error endpoint that returns ProblemDetails JSON. Alternatively configure app.UseProblemDetails() via an exception-handling middleware package. Ensure all error responses use RFC 7807 ProblemDetails format.",
      "status": "open"
    },
    {
      "id": "REST-VERSION-001",
      "title": "No API versioning strategy — no version prefix, no versioning package, no ApiVersion attributes",
      "severity": "notable",
      "category": "VERSION",
      "file": "src/ChatAPI/ChatAPI.csproj",
      "description": "No API versioning is implemented. Routes are /chat and /session with no /api/v1/ prefix. No Asp.Versioning.Mvc package is referenced. No [ApiVersion] attributes are applied. Any breaking change to request/response contracts will immediately affect all clients with no migration path.",
      "recommendation": "Add the Asp.Versioning.Mvc NuGet package. Apply [ApiVersion(\"1.0\")] to controllers. Update route templates to [Route(\"api/v{version:apiVersion}/[controller]\")].",
      "status": "open"
    },
    {
      "id": "REST-CONTRACT-002",
      "title": "Mixed JSON casing conventions — snake_case in session response, PascalCase in chat request DTO",
      "severity": "notable",
      "category": "CONTRACT",
      "file": "src/ChatAPI/Controllers/SessionController.cs",
      "line": 16,
      "description": "SessionController returns an anonymous type with snake_case property session_id. ChatController's ChatRequest uses PascalCase properties Input and SessionId. No global JsonSerializerOptions with a consistent PropertyNamingPolicy is configured in Program.cs. Clients must handle two different casing conventions within the same API.",
      "recommendation": "Configure builder.Services.AddControllers().AddJsonOptions(o => o.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase) in Program.cs to normalize all JSON responses to camelCase. Apply [JsonPropertyName] attributes to DTOs as needed.",
      "status": "open"
    },
    {
      "id": "REST-VALIDATION-002",
      "title": "SessionId field has no [Required] or format validation — null/empty value causes CosmosDB failures",
      "severity": "notable",
      "category": "VALIDATION",
      "file": "src/ChatAPI/Controllers/ChatController.cs",
      "line": 10,
      "description": "ChatRequest.SessionId has no [Required] attribute and no format validation. A null or empty SessionId is passed through to CosmosDB queries and chat history lookups. This will cause runtime exceptions or data corruption (messages persisted under an empty partition key). The [ApiController] attribute will not guard against this without a [Required] annotation.",
      "recommendation": "Add [Required] to ChatRequest.SessionId. Consider adding [RegularExpression(@\"^[a-f0-9]{32}$\")] to enforce the hexadecimal GUID format generated by the session endpoint.",
      "status": "open"
    },
    {
      "id": "REST-DOCS-001",
      "title": "Swagger UI enabled unconditionally in all environments including Production",
      "severity": "notable",
      "category": "DOCS",
      "file": "src/ChatAPI/Program.cs",
      "line": 86,
      "description": "The if (app.Environment.IsDevelopment()) guard around app.UseSwagger() and app.UseSwaggerUI() was commented out. The OpenAPI JSON spec and Swagger UI are accessible in all environments, including Production. This exposes the full API contract (route structure, parameter names, DTO shapes) publicly without authentication.",
      "recommendation": "Restore the IsDevelopment() guard, or protect the Swagger endpoint with authentication if public documentation is intentional.",
      "status": "open"
    },
    {
      "id": "REST-URL-001",
      "title": "Singular route nouns — /chat and /session should be /chats and /sessions",
      "severity": "minor",
      "category": "URL",
      "file": "src/ChatAPI/Controllers/ChatController.cs",
      "description": "REST convention requires plural nouns for resource collections: /chats not /chat, /sessions not /session. Both controllers use singular nouns either via [Route(\"[controller]\")] (which resolves to the singular controller name) or hardcoded [Route(\"session\")].",
      "recommendation": "Update ChatController route to [Route(\"chats\")] and SessionController route to [Route(\"sessions\")].",
      "status": "open"
    },
    {
      "id": "REST-URL-002",
      "title": "No /api/ prefix on routes — API endpoints not distinguished from other middleware paths",
      "severity": "minor",
      "category": "URL",
      "file": "src/ChatAPI/Controllers/ChatController.cs",
      "description": "Neither controller uses an /api/ route prefix. REST convention uses /api/ to scope API endpoints away from other paths (health checks, static files, Swagger UI). Absence of the prefix also makes versioning harder to add consistently.",
      "recommendation": "Change route templates to [Route(\"api/[controller]\")] on both controllers. Combined with versioning (REST-VERSION-001), the final form should be [Route(\"api/v{version:apiVersion}/[controller]\")].",
      "status": "open"
    },
    {
      "id": "REST-DOCS-002",
      "title": "No ProducesResponseType annotations on any endpoint — Swagger spec only shows default 200",
      "severity": "minor",
      "category": "DOCS",
      "file": "src/ChatAPI/Controllers/ChatController.cs",
      "description": "Neither ChatController.Post nor SessionController.GetSession have [ProducesResponseType] attributes. The generated Swagger/OpenAPI spec will only document a 200 response with no typed schema. Error status codes (400, 500) are undocumented. XML doc comments (///) are absent on all controller actions.",
      "recommendation": "Add [ProducesResponseType(typeof(ChatResponse), StatusCodes.Status200OK)], [ProducesResponseType(StatusCodes.Status400BadRequest)], and [ProducesResponseType(StatusCodes.Status500InternalServerError)] to each action. Add XML summary comments.",
      "status": "open"
    },
    {
      "id": "REST-CONTRACT-003",
      "title": "ChatRequest DTO defined inside the controller file — should be in a dedicated models folder",
      "severity": "minor",
      "category": "CONTRACT",
      "file": "src/ChatAPI/Controllers/ChatController.cs",
      "line": 7,
      "description": "The ChatRequest class is defined at the top of ChatController.cs outside the class but inside the same file. This couples the DTO to the controller, hinders reuse, and makes it harder to find. The nullable project setting is enabled but the class properties have no nullability annotations.",
      "recommendation": "Move ChatRequest to a Models/ or Requests/ folder in a dedicated file. Annotate string properties with proper nullability and validation attributes.",
      "status": "open"
    },
    {
      "id": "REST-INFO-001",
      "title": "ApiController attribute is present on both controllers — automatic model validation is enabled",
      "severity": "info",
      "category": "VALIDATION",
      "file": "src/ChatAPI/Controllers/ChatController.cs",
      "description": "Both ChatController and SessionController have [ApiController]. This is a positive finding: automatic 400 Bad Request responses for model validation failures are enabled. Once [Required] and other validation attributes are added to DTOs (per REST-VALIDATION-001, REST-VALIDATION-002), the framework will enforce them without manual controller code.",
      "status": "open"
    },
    {
      "id": "REST-INFO-002",
      "title": ".http sample file uses incorrect route /PostChatRequest/ — does not match actual endpoint /chat",
      "severity": "info",
      "category": "DOCS",
      "file": "src/ChatAPI/ChatAPI.http",
      "line": 3,
      "description": "The ChatAPI.http file sends GET /PostChatRequest/ which resolves to no route. The Name parameter in [HttpPost(Name = \"PostChatRequest\")] is a link-generation name, not a route segment. The actual POST endpoint is /chat. The .http file also uses GET for a POST endpoint.",
      "recommendation": "Update ChatAPI.http to: POST http://localhost:8080/chat with Content-Type: application/json and body { \"input\": \"your question\", \"sessionId\": \"your-session-id\" }.",
      "status": "open"
    }
  ],
  "summary": {
    "critical": 3,
    "notable": 7,
    "minor": 4,
    "info": 2,
    "total": 16
  }
}
