[← Back to Reviews Index](../Stewards%20Reviews%20Index.md)

# Bicep Module Review — Azure-AI-RAG-CSharp-Semantic-Kernel-Functions

| Field | Value |
|---|---|
| Project | Azure-AI-RAG-CSharp-Semantic-Kernel-Functions |
| Review Date | 2026-03-22 |
| Steward | Bicep Module Steward |
| Bicep Files Audited | 20 |
| Critical | 2 |
| Notable | 9 |
| Minor | 6 |
| Info | 2 |
| Total Findings | 19 |

---

## 1. Infrastructure Overview

All Bicep files reside under `infra/`. The project follows a two-level layout: `infra/app/` for application-tier orchestration modules and `infra/core/` for reusable primitive modules. No `.bicepparam` or `parameters.json` environment files exist.

| File | Purpose |
|---|---|
| `infra/main.bicep` | Subscription-scoped entry point; creates resource group, wires all modules |
| `infra/abbreviations.json` | Short name map for resource prefixes (cosmosDbAccount, openAI, aiSearch) |
| `infra/app/api-app.bicep` | Deploys the ASP.NET Core App Service (Windows) with app settings |
| `infra/app/database.bicep` | Orchestration module: Cosmos DB account, database, and containers |
| `infra/app/loader-function.bicep` | Deploys the Python Azure Function (Linux) with app settings |
| `infra/app/web-app.bicep` | Deploys the React frontend App Service (Linux Node) |
| `infra/core/ai/openai/openai-account.bicep` | Cognitive Services OpenAI account + two model deployments + RBAC |
| `infra/core/database/cosmos-db/account.bicep` | Generic Cosmos DB account (supports MongoDB, NoSQL, Parse) |
| `infra/core/database/cosmos-db/nosql/account.bicep` | NoSQL-specific thin wrapper over `account.bicep` |
| `infra/core/database/cosmos-db/nosql/container.bicep` | Cosmos DB NoSQL container with indexing/vector/TTL |
| `infra/core/database/cosmos-db/nosql/database.bicep` | Cosmos DB NoSQL database |
| `infra/core/host/app-service.bicep` | App Service Plan (shared by Linux and Windows tiers) |
| `infra/core/monitor/applicationinsights-dashboard.bicep` | Portal dashboard for Application Insights |
| `infra/core/monitor/applicationinsights.bicep` | Application Insights instance (workspace-based) |
| `infra/core/monitor/loganalytics.bicep` | Log Analytics workspace |
| `infra/core/monitor/monitoring.bicep` | Orchestration module: Log Analytics + App Insights + Dashboard |
| `infra/core/search/search-services.bicep` | Azure AI Search service + three RBAC role assignments |
| `infra/core/security/data-plane-role-assignment.bicep` | Cosmos DB SQL role assignment helper |
| `infra/core/security/key-vault.bicep` | Empty file (1 line, no content) |
| `infra/core/security/managed-identity.bicep` | User-assigned managed identity |
| `infra/core/storage/blob-storage-account.bicep` | Storage account + containers + five RBAC role assignments |

---

## 2. Module Decomposition Assessment

The project has a reasonable two-tier structure. `infra/core/` contains focused single-resource primitives and `infra/app/` contains thin orchestration modules that compose those primitives. `infra/main.bicep` is the subscription-scope entry point that creates the resource group and calls all app-tier modules.

**Concerns:**

- `infra/core/ai/openai/openai-account.bicep` deploys a Cognitive Services account, two model deployments (`text-embedding` and `gpt-chat`), and one RBAC role assignment — three distinct logical concerns in one file. Model deployments and RBAC role assignment would be better placed in separate child modules.
- `infra/core/storage/blob-storage-account.bicep` deploys a storage account, a blob service, four containers, and five RBAC role assignments. Container provisioning and role assignments are co-mingled with storage account creation.
- `infra/core/search/search-services.bicep` deploys a search service and three RBAC role assignments inline.
- `infra/core/security/key-vault.bicep` is an empty file — it appears to be a placeholder that was never implemented. Its presence is misleading.
- `infra/app/loader-function.bicep` and `infra/app/api-app.bicep` each hardcode all application settings inline rather than accepting a structured `appSettings` parameter. This reduces reusability and inflates the module size.

---

## 3. Parameter Design Assessment

**Missing `@description()` decorators:**

The following parameters have no `@description()` decorator at all:

- `infra/app/api-app.bicep`: all 8 parameters (`appServicePlanName`, `appServiceNameAPI`, `location`, `identityName`, `OpenAIEndPoint`, `logAnalyticsWorkspaceName`, `appInsightsName`, `AZURE_AI_SEARCH_ENDPOINT`)
- `infra/app/loader-function.bicep`: all 10 parameters
- `infra/app/web-app.bicep`: all 4 parameters
- `infra/core/host/app-service.bicep`: all 3 parameters (`name`, `location`, `linuxMachine`)
- `infra/core/ai/openai/openai-account.bicep`: all 4 parameters (`name`, `location`, `identityName`, `customSubdomain`)
- `infra/core/storage/blob-storage-account.bicep`: all 3 parameters
- `infra/core/security/managed-identity.bicep`: both parameters
- `infra/core/monitor/monitoring.bicep`: `logAnalyticsName`, `applicationInsightsName`
- `infra/core/database/cosmos-db/nosql/container.bicep`: `ttlValue`

**Naming convention violations in parameters:**

- `infra/app/loader-function.bicep`: `StorageBlobURL`, `StorageAccountName`, `OpenAIEndPoint`, `AZURE_AI_SEARCH_ENDPOINT` — parameters should be camelCase. `AZURE_AI_SEARCH_ENDPOINT` is SCREAMING_SNAKE_CASE and `OpenAIEndPoint` is PascalCase.
- `infra/app/api-app.bicep`: `OpenAIEndPoint`, `AZURE_AI_SEARCH_ENDPOINT` — same violations.
- `infra/core/storage/blob-storage-account.bicep`: output `StorageAccountName` is PascalCase.

**Hardcoded values that should be parameters:**

- `infra/app/api-app.bicep` line 54: `AZURE_OPENAI_EMBEDDING` value `'text-embedding'` hardcoded.
- `infra/app/api-app.bicep` line 58: `AZURE_OPENAI_DEPLOYMENT` value `'gpt-chat'` hardcoded.
- `infra/app/api-app.bicep` line 62: `AZURE_OPENAI_API_VERSION` value `'2023-05-15'` hardcoded.
- `infra/app/api-app.bicep` line 69: `CosmosDb_Database` value `'chatdatabase'` hardcoded.
- `infra/app/api-app.bicep` line 73: `CosmosDb_ProductContainer` value `'products'` hardcoded.
- `infra/app/api-app.bicep` line 77: `CosmosDb_ChatContainer` value `'chathistory'` hardcoded.
- `infra/app/api-app.bicep` line 86: `AZURE_AI_SEARCH_INDEX` value `'azure-support'` hardcoded.
- `infra/app/loader-function.bicep` line 84: `AZURE_OPENAI_API_VERSION` value `'2023-05-15'` hardcoded.
- `infra/app/loader-function.bicep` line 95: `AZURE_AI_SEARCH_INDEX` value `'azure-support'` hardcoded.
- `infra/core/ai/openai/openai-account.bicep` line 36: deployment name `'text-embedding'` hardcoded.
- `infra/core/ai/openai/openai-account.bicep` line 55: deployment name `'gpt-chat'` hardcoded.
- `infra/core/ai/openai/openai-account.bicep` line 62: model version `'2024-05-13'` hardcoded.
- `infra/main.bicep` line 83: `location: 'centralus'` hardcoded for the database module.
- `infra/core/host/app-service.bicep` lines 8-14: SKU (`B2`, Basic tier) hardcoded.

**Empty connection string:**

- `infra/app/api-app.bicep` line 65-67: `CosmosDb_ConnectionString` is explicitly set to empty string `''`. This suggests Cosmos DB connectivity is not properly wired — either the connection string should be sourced from Cosmos DB outputs or the setting should be removed if managed identity is used exclusively.

**No parameter files exist:**

No `.bicepparam` or `parameters.json` files were found anywhere in the repository. There is no way to deploy this infrastructure to different environments (dev, staging, prod) without manually overriding parameters each time.

---

## 4. Variable and Resource Naming Assessment

**Variable naming:**

Variables in `infra/main.bicep` follow camelCase correctly (`resourceToken`, `abbreviations`, `apiAppName`, `webAppName`).

`infra/app/loader-function.bicep` uses `blob_uri` and `queue_uri` — these use snake_case instead of camelCase.

**Resource naming:**

- `infra/main.bicep` line 45: storage account name `'sa${projectName}${environmentName}'` — no `uniqueString()` and no separator characters. Storage account names must be 3-24 characters, lowercase alphanumeric only. If `projectName` + `environmentName` exceeds 22 characters, deployment fails. There is no `resourceToken` applied to the storage account name, meaning a second deployment with the same project/environment names to a different subscription would conflict globally.
- `infra/main.bicep` line 35: managed identity name `'id-${projectName}-${environmentName}'` has no uniqueness token, creating potential conflicts across subscriptions.
- `infra/main.bicep` line 93: Log Analytics name `'log-${projectName}-${environmentName}'` — no `resourceToken`, will conflict across subscriptions.
- `infra/main.bicep` line 94: App Insights name `'appi-${projectName}-${environmentName}'` — same issue.
- `infra/main.bicep` line 95: Dashboard name `'appid-${projectName}-${environmentName}'` — same issue.
- `infra/core/database/cosmos-db/account.bicep` line 70: role definition display name `'My Read Write Role'` is informal and misleading — the role only grants read data actions, not write.

**Output naming inconsistency:**

- `infra/core/storage/blob-storage-account.bicep` output `StorageAccountName` uses PascalCase whereas all other outputs across the project use camelCase. The caller in `main.bicep` references it as `storage.outputs.StorageAccountName` — the inconsistency propagates.

---

## 5. API Version Assessment

| Resource Type | Version Used | Status |
|---|---|---|
| `Microsoft.Resources/resourceGroups` | `2024-03-01` | Current |
| `Microsoft.ManagedIdentity/userAssignedIdentities` | `2023-01-31` | Current |
| `Microsoft.ManagedIdentity/userAssignedIdentities` | `2018-11-30` | Very old — over 5 years |
| `Microsoft.Storage/storageAccounts` | `2023-05-01` | Current |
| `Microsoft.Storage/storageAccounts/blobServices` | `2023-01-01` | Acceptable |
| `Microsoft.Storage/storageAccounts/blobServices/containers` | `2023-04-01` | Current |
| `Microsoft.Web/serverfarms` | `2022-03-01` | Acceptable |
| `Microsoft.Web/sites` | `2022-03-01` | Acceptable |
| `Microsoft.Insights/components` | `2020-02-02` | Stable recommended version |
| `Microsoft.Insights/diagnosticSettings` | `2021-05-01-preview` | Preview |
| `Microsoft.OperationalInsights/workspaces` | `2021-12-01-preview` | Preview |
| `Microsoft.OperationalInsights/workspaces` | `2021-06-01` | Older stable, acceptable |
| `Microsoft.Portal/dashboards` | `2020-09-01-preview` | Preview (acknowledged in code comment) |
| `Microsoft.CognitiveServices/accounts` | `2023-05-01` | Current |
| `Microsoft.CognitiveServices/accounts/deployments` | `2023-05-01` | Acceptable |
| `Microsoft.Authorization/roleAssignments` | `2020-04-01-preview` | Preview, used in 4 files |
| `Microsoft.DocumentDB/databaseAccounts` | `2023-04-15` | Acceptable |
| `Microsoft.DocumentDB/databaseAccounts` | `2024-05-15` | Current |
| `Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions` | `2021-07-01-preview` | Preview |
| `Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments` | `2024-05-15` | Current |
| `Microsoft.Search/searchServices` | `2021-04-01-preview` | Preview from 2021 |

The `Microsoft.Authorization/roleAssignments@2020-04-01-preview` preview API is used across four separate files: `openai-account.bicep`, `blob-storage-account.bicep`, and `search-services.bicep`. The stable replacement `2022-04-01` has been available since 2022.

`Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30` in `managed-identity.bicep` is more than five years old.

`Microsoft.Search/searchServices@2021-04-01-preview` is a preview API from 2021. A stable version is available.

`Microsoft.OperationalInsights/workspaces@2021-12-01-preview` in `loganalytics.bicep` is a preview API.

`Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions@2021-07-01-preview` in `account.bicep` is a preview API.

---

## 6. Output Design Assessment

**Positive observations:**

- Modules correctly export resource names, endpoints, and IDs needed by callers.
- `main.bicep` re-exports key outputs (`resourceGroupName`, `functionAppName`, `apiAppName`, `webAppName`, `appServiceURL`).
- `data-plane-role-assignment.bicep` exports `assignmentId`.
- `database.bicep` exports structured `database` and `containers` objects.

**Concerns:**

- `infra/core/monitor/applicationinsights.bicep` line 30 exports `instrumentationKey` as a plain output string. The instrumentation key is a sensitive credential that should be stored in Key Vault and referenced via a Key Vault secret reference rather than passed as a plain output. `api-app.bicep` and `loader-function.bicep` set it directly as an app setting `APPINSIGHTS_INSTRUMENTATIONKEY`.
- `infra/app/api-app.bicep` line 159: `appServiceURL` is constructed via string interpolation rather than reading from the resource's `defaultHostName` property. If a custom domain is configured later, the output becomes stale.
- `infra/app/web-app.bicep` line 38: same issue with `appServiceURL`.
- `infra/app/loader-function.bicep` line 168: `functionAppName` output returns the input parameter value unchanged — this is a no-value passthrough pattern.
- `infra/core/storage/blob-storage-account.bicep` line 111: `storageBlobURL` constructs a URL via string interpolation rather than reading from `storageAcct.properties.primaryEndpoints.blob`. If the account is in a sovereign cloud, the URL would be incorrect.
- `infra/core/search/search-services.bicep` line 103: endpoint returned as hardcoded string interpolation `'https://${name}.search.windows.net/'` instead of resource property.
- `infra/app/database.bicep` does not export the Cosmos DB connection string or an endpoint that the API app can use — which explains why `CosmosDb_ConnectionString` is empty in the API app settings.

---

## 7. Idempotency Assessment

**Positive observations:**

- `main.bicep` uses `uniqueString(environmentName, projectName, location, subscriptionId)` as a `resourceToken` for most resource names, which ensures stable names across reruns.
- Role assignments use `guid()` with deterministic inputs — they are idempotent.
- Cosmos DB role definition uses `guid('sql-role-definition-', account.id)` — deterministic.

**Concerns:**

- `infra/main.bicep` line 45: the storage account name `'sa${projectName}${environmentName}'` does not include `resourceToken`. Storage account names are globally unique across all Azure tenants. If another team deploys with the same project/environment names to a different subscription, it will collide. The omission of `resourceToken` (which includes the subscription ID) is an inconsistency — all other resources use it.
- `infra/main.bicep` line 83: `location: 'centralus'` is hardcoded for the database module. All other resources use the `location` parameter, making this a silent configuration divergence. The database will always deploy to `centralus` regardless of the operator's chosen region.
- `infra/core/database/cosmos-db/account.bicep`: the custom Cosmos DB role definition is named `'My Read Write Role'` but only contains read data actions. This name/permission mismatch could cause confusion during maintenance and any future permission escalation to match the role name.

---

## 8. Findings

| Severity | ID | Title | File |
|---|---|---|---|
| 🔴 | BICM-PARAM-001 | `CosmosDb_ConnectionString` set to empty string — API app will fail to connect to Cosmos DB at runtime | `infra/app/api-app.bicep` |
| 🔴 | BICM-PARAM-002 | No parameter files exist for any environment — no reproducible multi-environment deployments possible | repository-wide |
| 🟡 | BICM-APIVER-001 | `Microsoft.Authorization/roleAssignments@2020-04-01-preview` preview API used in four files; stable `2022-04-01` available | Multiple |
| 🟡 | BICM-APIVER-002 | `Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30` is over 5 years old; current is `2023-01-31` | `infra/core/security/managed-identity.bicep` |
| 🟡 | BICM-APIVER-003 | `Microsoft.Search/searchServices@2021-04-01-preview` is a 2021 preview API; stable `2023-11-01` available | `infra/core/search/search-services.bicep` |
| 🟡 | BICM-APIVER-004 | `Microsoft.OperationalInsights/workspaces@2021-12-01-preview` is a preview API; stable `2023-09-01` available | `infra/core/monitor/loganalytics.bicep` |
| 🟡 | BICM-APIVER-005 | `Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions@2021-07-01-preview` is a preview API; stable `2023-04-15` available | `infra/core/database/cosmos-db/account.bicep` |
| 🟡 | BICM-PARAM-003 | Hardcoded `location: 'centralus'` for Cosmos DB bypasses the deployment location parameter | `infra/main.bicep` |
| 🟡 | BICM-PARAM-004 | Storage account name missing `uniqueString()` token — globally unique name not guaranteed across subscriptions | `infra/main.bicep` |
| 🟡 | BICM-PARAM-005 | Multiple app settings hardcoded inline (model names, API versions, index names, DB names) across two modules | `infra/app/api-app.bicep`, `infra/app/loader-function.bicep` |
| 🟡 | BICM-OUTPUT-001 | `instrumentationKey` exported as plain string output — sensitive credential should use Key Vault reference | `infra/core/monitor/applicationinsights.bicep` |
| 🟡 | BICM-MODULE-001 | `key-vault.bicep` is an empty placeholder file — never implemented despite `KeyVaultUri` app setting being present | `infra/core/security/key-vault.bicep` |
| 🟢 | BICM-PARAM-006 | Parameter names use non-camelCase conventions (`OpenAIEndPoint`, `AZURE_AI_SEARCH_ENDPOINT`, `StorageBlobURL`, `StorageAccountName`) | Multiple |
| 🟢 | BICM-PARAM-007 | Missing `@description()` decorators on parameters across all `infra/app/` modules and several `infra/core/` modules | Multiple |
| 🟢 | BICM-NAMING-001 | Variables `blob_uri` and `queue_uri` use snake_case instead of camelCase | `infra/app/loader-function.bicep` |
| 🟢 | BICM-NAMING-002 | Custom Cosmos DB role display name `'My Read Write Role'` is informal and misleading — the role grants only read data actions | `infra/core/database/cosmos-db/account.bicep` |
| 🟢 | BICM-OUTPUT-002 | `appServiceURL` and `storageBlobURL` constructed via string interpolation instead of reading resource properties | `infra/app/api-app.bicep`, `infra/app/web-app.bicep`, `infra/core/storage/blob-storage-account.bicep` |
| ℹ️ | BICM-INFO-001 | Module decomposition into `core/` primitives and `app/` orchestration is a sound and maintainable pattern | repository-wide |
| ℹ️ | BICM-INFO-002 | Managed identity plus RBAC-only access pattern correctly avoids connection string secrets for storage, search, and OpenAI | repository-wide |

---

## 9. Recommended Improvements

| Finding | Recommended Action | Priority |
|---|---|---|
| BICM-PARAM-001 | Wire `database.outputs.endpoint` into the API app settings as `AZURE_COSMOS_DB_ENDPOINT` and use managed identity authentication. Remove the empty `CosmosDb_ConnectionString` app setting. | High |
| BICM-PARAM-002 | Create `infra/main.dev.bicepparam` and `infra/main.prod.bicepparam` files defining `environmentName`, `projectName`, and `location` per environment. | High |
| BICM-APIVER-001 | Replace `Microsoft.Authorization/roleAssignments@2020-04-01-preview` with `@2022-04-01` in all four files. | Medium |
| BICM-APIVER-002 | Update `Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30` to `@2023-01-31` in `managed-identity.bicep`. | Medium |
| BICM-APIVER-003 | Update `Microsoft.Search/searchServices@2021-04-01-preview` to `@2023-11-01` in `search-services.bicep`. | Medium |
| BICM-APIVER-004 | Update `Microsoft.OperationalInsights/workspaces@2021-12-01-preview` to `@2023-09-01` in `loganalytics.bicep`. | Medium |
| BICM-APIVER-005 | Update `Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions@2021-07-01-preview` to `@2023-04-15` in `account.bicep`. | Medium |
| BICM-PARAM-003 | Remove the hardcoded `location: 'centralus'` in `main.bicep` and pass the `location` parameter so all resources deploy to the same region. | Medium |
| BICM-PARAM-004 | Update storage account name to include `resourceToken`: e.g. `'sa${take(projectName,8)}${take(environmentName,4)}${take(resourceToken,10)}'` keeping within the 24-character limit. | Medium |
| BICM-PARAM-005 | Extract hardcoded model deployment names, API versions, search index names, and DB/container names into parameters with sensible defaults so they can be overridden per environment. | Medium |
| BICM-OUTPUT-001 | Store the Application Insights instrumentation key in Key Vault and reference it in app settings via `@Microsoft.KeyVault(SecretUri=...)` syntax, or remove `APPINSIGHTS_INSTRUMENTATIONKEY` in favour of the connection string setting only. | Medium |
| BICM-MODULE-001 | Either implement `key-vault.bicep` to provision a Key Vault and wire up the `KeyVaultUri` app setting (recommended), or delete the empty file and remove the placeholder app setting. | Medium |
| BICM-PARAM-006 | Rename parameters to camelCase: `openAiEndpoint`, `azureAiSearchEndpoint`, `storageBlobUrl`, `storageAccountName`. Update all callers accordingly. | Low |
| BICM-PARAM-007 | Add `@description()` decorators to all parameters that are missing them across `infra/app/` and the flagged `infra/core/` modules. | Low |
| BICM-NAMING-001 | Rename `blob_uri` to `blobUri` and `queue_uri` to `queueUri` in `loader-function.bicep`. | Low |
| BICM-NAMING-002 | Rename `'My Read Write Role'` to a descriptive name that reflects actual permissions, such as `'${accountName}-ReadData'`. | Low |
| BICM-OUTPUT-002 | Replace string-interpolated URL outputs with resource property reads: `appServiceNode.properties.defaultHostName` for app services, `storageAcct.properties.primaryEndpoints.blob` for blob URLs. | Low |

---

## Cross-Cutting Observations

The `KeyVaultUri` app setting in `loader-function.bicep` is set to an empty string, and `infra/core/security/key-vault.bicep` is empty. This strongly suggests Key Vault integration was planned but never completed. Together with `CosmosDb_ConnectionString` being empty in `api-app.bicep`, the API application may have runtime connectivity issues. The Cosmos DB endpoint is never forwarded to the API App Service, and the Cosmos DB connection string is blank — managed identity authentication requires the endpoint to be passed, not a connection string.

---

*This review is based on static analysis of Bicep source files. It does not represent a runtime deployment validation. No deployment was executed as part of this audit.*

*Generated by: Bicep Module Steward | Run date: 2026-03-22*
