{
  "steward": "interface-steward",
  "project": "Azure-AI-RAG-CSharp-Semantic-Kernel-Functions",
  "runDate": "2026-03-21",
  "runId": "2026-03-21T00-00-00",
  "findings": [
    {
      "id": "INTF-MISS-001",
      "title": "ChatService injected as concrete type with no interface seam",
      "severity": "notable",
      "category": "MISS",
      "file": "src/ChatAPI/Services/ChatService.cs",
      "description": "ChatController takes a direct dependency on ChatService with no interface. This eliminates the ability to unit-test the controller without instantiating all real infrastructure dependencies (Semantic Kernel, Azure OpenAI, Cosmos DB).",
      "recommendation": "Introduce IChatService with a single GetResponseAsync method. Register ChatService as its implementation and inject IChatService into ChatController.",
      "status": "open"
    },
    {
      "id": "INTF-MISS-002",
      "title": "ProductData has no interface — Cosmos DB boundary is not abstracted",
      "severity": "notable",
      "category": "MISS",
      "file": "src/ChatAPI/Data/ProductData.cs",
      "description": "ProductData wraps CosmosClient directly. Without an interface, any class depending on ProductData cannot be tested without a live Cosmos DB connection.",
      "recommendation": "Introduce IProductRepository with GetProductAsync and GetProductByNameAsync methods. Register ProductData as its implementation.",
      "status": "open"
    },
    {
      "id": "INTF-MISS-003",
      "title": "ChatHistoryData has no interface — Cosmos DB boundary is not abstracted",
      "severity": "notable",
      "category": "MISS",
      "file": "src/ChatAPI/Data/ChatHistoryData.cs",
      "description": "ChatHistoryData wraps CosmosClient directly. Without an interface, ChatService cannot be tested without a live Cosmos DB connection for chat history.",
      "recommendation": "Introduce IChatHistoryRepository abstracting the persistence operations. Register ChatHistoryData as its implementation.",
      "status": "open"
    },
    {
      "id": "INTF-MISS-004",
      "title": "AISearchData has no interface — AI Search boundary is not abstracted",
      "severity": "notable",
      "category": "MISS",
      "file": "src/ChatAPI/Data/AISearchData.cs",
      "description": "AISearchData wraps the Azure SearchClient directly. Without an interface, AISearchDataPlugin cannot be tested without a live Azure AI Search connection.",
      "recommendation": "Introduce IDocumentRetriever (or IDocumentSearchService) with RetrieveDocumentationAsync. Register AISearchData as its implementation.",
      "status": "open"
    },
    {
      "id": "INTF-SIG-001",
      "title": "All async I/O methods lack CancellationToken",
      "severity": "notable",
      "category": "SIG",
      "file": "src/ChatAPI/Services/ChatService.cs",
      "description": "Every public and private async method performing I/O (Cosmos DB, Azure AI Search, Semantic Kernel chat completion) omits CancellationToken. In ASP.NET Core, this prevents propagation of client disconnection (HttpContext.RequestAborted) to in-flight cloud calls, wasting compute and potentially causing resource leaks.",
      "recommendation": "Add CancellationToken cancellationToken = default to all public async methods (ChatService.GetResponseAsync, ChatHistoryData.AddMessageToHistoryAndSaveAsync, ChatHistoryData.InitializeChatHistoryFromCosmosDBAsync, ProductData.GetProductAsync, ProductData.GetProductByNameAsync, AISearchData.RetrieveDocumentationAsync) and thread the token through to SDK calls.",
      "status": "open"
    },
    {
      "id": "INTF-DI-002",
      "title": "ChatHistory registered as Singleton consumed by Scoped ChatService — mutable shared state",
      "severity": "notable",
      "category": "DI",
      "file": "src/ChatAPI/Program.cs",
      "line": 30,
      "description": "ChatHistory is registered as AddSingleton, but ChatService is AddScoped. All concurrent HTTP requests share the same mutable ChatHistory instance. This causes a multi-user race condition where concurrent requests read and write each other's conversation history.",
      "recommendation": "Register ChatHistory as AddScoped so each request gets its own instance, or remove it from DI entirely and construct it within ChatService for the duration of the request.",
      "status": "open"
    },
    {
      "id": "INTF-SIG-002",
      "title": "ProductData.GetProductByNameAsync returns unannotated nullable Dictionary",
      "severity": "minor",
      "category": "SIG",
      "file": "src/ChatAPI/Data/ProductData.cs",
      "line": 36,
      "description": "GetProductByNameAsync returns null when no product is found (line 61) but the declared return type is Task<Dictionary<string, object>> without nullable annotation. The compiler cannot warn callers about the possible null return, risking NullReferenceException.",
      "recommendation": "Change the return type to Task<Dictionary<string, object>?> and enable nullable reference types at the project level.",
      "status": "open"
    },
    {
      "id": "INTF-SIG-003",
      "title": "ChatHistoryData public methods accept ChatHistory (Semantic Kernel infrastructure type)",
      "severity": "minor",
      "category": "SIG",
      "file": "src/ChatAPI/Data/ChatHistoryData.cs",
      "line": 58,
      "description": "AddMessageToHistoryAndSaveAsync and InitializeChatHistoryFromCosmosDBAsync both accept Microsoft.SemanticKernel.ChatCompletion.ChatHistory as a parameter. This couples the data-access class to a Semantic Kernel infrastructure type, preventing any interface over ChatHistoryData from being defined without importing the SK package.",
      "recommendation": "Refactor ChatHistoryData methods to accept and return plain ChatMessage domain objects. Move the mapping between ChatMessage and ChatHistory into ChatService, keeping the data-access contract free of infrastructure types.",
      "status": "open"
    },
    {
      "id": "INTF-DI-001",
      "title": "SearchIndexClient registered in DI but never injected into any class",
      "severity": "minor",
      "category": "DI",
      "file": "src/ChatAPI/Program.cs",
      "line": 53,
      "description": "SearchIndexClient is registered as a singleton in Program.cs but is not injected into any discovered class. This is a dead registration that adds unnecessary startup overhead and obscures the active dependency graph.",
      "recommendation": "Remove the SearchIndexClient registration, or add a comment explaining the intended future consumer if it is being retained for a planned feature.",
      "status": "open"
    },
    {
      "id": "INTF-STRCT-001",
      "title": "Product class defined twice in different files with different property sets",
      "severity": "info",
      "category": "STRCT",
      "file": "src/ChatAPI/Data/Product.cs",
      "description": "Product is defined in Data/Product.cs (global namespace, 9 properties including Rid/Self/Etag) and again in Data/sample_data/products/GenerateProductInfo.cs (namespace ChatAPI.Data, 3 properties: id/name/description). The two definitions diverge significantly and the root-level definition appears to be a stale artifact.",
      "recommendation": "Remove the root-level Data/Product.cs or merge it into the ChatAPI.Data namespace. Use only the Product class in GenerateProductInfo.cs for seeding.",
      "status": "open"
    },
    {
      "id": "INTF-STRCT-002",
      "title": "ChatRequest DTO properties lack nullability annotations",
      "severity": "info",
      "category": "STRCT",
      "file": "src/ChatAPI/Controllers/ChatController.cs",
      "line": 9,
      "description": "ChatRequest.Input and ChatRequest.SessionId are declared as non-nullable string but will be null if the JSON body omits those fields, potentially causing NullReferenceException in the controller.",
      "recommendation": "Mark properties as string? or add [Required] data annotations and enable model validation error responses for missing fields.",
      "status": "open"
    }
  ],
  "summary": {
    "critical": 0,
    "notable": 6,
    "minor": 3,
    "info": 2,
    "total": 11
  }
}
