Skip to content

Fix/apply migrations in cd - #6

Merged
Affan2900 merged 2 commits into
mainfrom
fix/apply-migrations-in-cd
Jul 26, 2026
Merged

Fix/apply migrations in cd#6
Affan2900 merged 2 commits into
mainfrom
fix/apply-migrations-in-cd

Conversation

@Affan2900

@Affan2900 Affan2900 commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Deployments now automatically apply database schema updates before release validation.
    • Deployment configuration supports dedicated CI/CD database access while preserving runtime identities.
  • Bug Fixes

    • Improved load-shed testing by correctly interpreting API call statuses, preventing incorrect job results.
    • Added stronger compatibility and validation for automated load testing, including TLS support and audio file checks.
  • Chores

    • Updated infrastructure artifact handling to keep generated files out of source control.

Affan2900 and others added 2 commits July 26, 2026 17:47
No JsonStringEnumConverter is registered, so /api/calls/{id} returns
"status": 2 rather than "Completed". Comparing against the names matched
nothing, so every job would have been counted as never reaching a terminal
state and the run would have reported a false loss of all 50.

Also forces TLS 1.2 (5.1 negotiates 1.0, which Azure refuses) and silences the
per-request progress bar that dominates a multi-megabyte upload loop.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019UnusAvj1QtPwNnd28udLm
The deployed database has no schema. Program.cs only calls Database.Migrate()
inside if (app.Environment.IsDevelopment()), and cd.yml had no migration step,
so InitialCreate and SeedRubrics have never been applied to Azure SQL. Every
write to the deployed API returns 500 with "Invalid object name 'Calls'".

Applies the schema from cd.yml, after the Bicep deploy that creates the server
and before any traffic shift:

- sql.bicep grants a new contained user (cognilens-cicd) db_ddladmin, created
  by SID like the existing ones so no Graph lookup is needed. Azure SQL allows
  exactly one AAD admin and that is the deploy MI, so the pipeline principal
  cannot be an admin too. Not db_owner: the pipeline has no business granting
  permissions or dropping users. The Api and Worker identities keep
  db_datareader/db_datawriter only and still cannot touch the schema.
- cd.yml generates an idempotent script and applies it with go-sqlcmd, opening
  the SQL firewall for the runner IP and closing it again in an always() step.

Rejected running Migrate() at app startup: it needs DDL rights on the runtime
identity, races across replicas, and turns a bad migration into a crash-loop
rather than a failed deploy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019UnusAvj1QtPwNnd28udLm
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The deployment workflow now provisions a CI/CD SQL identity and runs EF migrations with temporary firewall access. Bicep wiring supports the identity, and the load-shed test normalizes API call status values during polling.

Changes

Deployment SQL migration flow

Layer / File(s) Summary
CI/CD SQL identity wiring
.gitignore, infra/main.bicep
Bicep accepts and forwards cicdPrincipalClientId; generated infrastructure JSON is ignored except parameter files.
Contained CI/CD SQL user bootstrap
infra/modules/sql.bicep
The SQL bootstrap script conditionally creates cognilens-cicd with DDL, reader, and writer roles.
Deployment-time EF migration
.github/workflows/cd.yml
The deployment job opens the SQL firewall, applies idempotent EF migrations, and removes the firewall rule during cleanup.

Load-shed status handling

Layer / File(s) Summary
Call status normalization and polling
scripts/loadshed-test.ps1
The test maps numeric or string statuses to names and marks jobs terminal only for Completed or Failed; it also enforces TLS 1.2 and suppresses upload progress.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GitHubRunner
  participant BicepDeployment
  participant AzureSQL
  GitHubRunner->>BicepDeployment: Deploy infrastructure with cicdPrincipalClientId
  BicepDeployment->>AzureSQL: Create cognilens-cicd contained user
  GitHubRunner->>AzureSQL: Open runner firewall rule
  GitHubRunner->>AzureSQL: Apply idempotent EF migrations with sqlcmd
  GitHubRunner->>AzureSQL: Remove runner firewall rule
Loading

Suggested reviewers: intellema

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: adding migration handling to the CD workflow.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/apply-migrations-in-cd

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

Azure what-if (rg-cognilens-dev)

Show plan
Note: The result may contain false positive predictions (noise).
You can help us improve the accuracy of the result by opening an issue here: https://aka.ms/WhatIfIssues

Resource and property changes are indicated with these symbols:
  - Delete
  + Create
  ~ Modify
  = Nochange
  x Unsupported
  * Ignore
  x Noeffect

The deployment will update the following scopes:

Scope: /















Scope: /subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev

  + Microsoft.Resources/deploymentScripts/cognilens-dev-sql-64sgu7kbbhj5e-bootstrap-users [2023-08-01]

      apiVersion:                                                                                                                                                                                                               "2023-08-01"
      id:                                                                                                                                                                                                                       "/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.Resources/deploymentScripts/cognilens-dev-sql-64sgu7kbbhj5e-bootstrap-users"
      identity.type:                                                                                                                                                                                                            "UserAssigned"
      identity.userAssignedIdentities./subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cognilens-dev-sql-64sgu7kbbhj5e-deploy-mi: {}
      kind:                                                                                                                                                                                                                     "AzureCLI"
      location:                                                                                                                                                                                                                 "centralus"
      name:                                                                                                                                                                                                                     "cognilens-dev-sql-64sgu7kbbhj5e-bootstrap-users"
      properties.azCliVersion:                                                                                                                                                                                                  "2.65.0"
      properties.cleanupPreference:                                                                                                                                                                                             "OnSuccess"
      properties.environmentVariables: [
        0:

          name:  "SERVER"
          value: "cognilens-dev-sql-64sgu7kbbhj5e.database.windows.net"

        1:

          name:  "DATABASE"
          value: "CogniLens"

        2:

          name:  "API_IDENTITY_NAME"
          value: "cognilens-dev-api-mi"

        3:

          name:  "WORKER_IDENTITY_NAME"
          value: "cognilens-dev-worker-mi"

        4:

          name:  "API_IDENTITY_CLIENT_ID"
          value: "[reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cognilens-dev-api-mi', '2024-11-30').clientId]"

        5:

          name:  "WORKER_IDENTITY_CLIENT_ID"
          value: "[reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cognilens-dev-worker-mi', '2024-11-30').clientId]"

        6:

          name:  "DEPLOY_CLIENT_ID"
          value: "[reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cognilens-dev-sql-64sgu7kbbhj5e-deploy-mi', '2024-11-30').clientId]"

        7:

          name: "CICD_CLIENT_ID"

      ]
      properties.retentionInterval:                                                                                                                                                                                             "PT1H"
      properties.scriptContent:                                                                                                                                                                                                 "      set -e

      # mssql-tools is NOT installed from packages.microsoft.com here: the AzureCLI
      # deployment-script image is Azure Linux, which has no apt-get, so the Debian/Ubuntu repo
      # route fails outright. go-sqlcmd ships a single statically-linked binary instead, so this
      # depends on nothing in the base image beyond curl.
      curl -sSL -o sqlcmd.tar.bz2 https://github.com/microsoft/go-sqlcmd/releases/download/v1.10.0/sqlcmd-linux-amd64.tar.bz2
      # `tar -j` shells out to a bzip2 binary that isn't guaranteed to be present; Python's
      # tarfile decompresses bz2 in-process, so it works even on a stripped-down image.
      tar -xjf sqlcmd.tar.bz2 2>/dev/null || python3 -c "import tarfile; tarfile.open('sqlcmd.tar.bz2').extractall('.')"
      chmod +x ./sqlcmd

      # CREATE USER ... FROM EXTERNAL PROVIDER is deliberately NOT used here. That form makes the
      # SQL server call Microsoft Graph to resolve the identity name, which requires the server to
      # have a managed identity holding the Directory Readers role — granting that needs Privileged
      # Role Administrator in the tenant, which isn't available on this (university-managed) tenant.
      # Creating the user from the identity's client ID converted to a binary SID is the documented
      # equivalent and needs no directory permissions at all.
      cat <<SQL > bootstrap.sql
DECLARE @apiUser sysname = N'$API_IDENTITY_NAME';
DECLARE @workerUser sysname = N'$WORKER_IDENTITY_NAME';
DECLARE @apiSid varbinary(16) = CONVERT(varbinary(16), CAST(N'$API_IDENTITY_CLIENT_ID' AS uniqueidentifier));
DECLARE @workerSid varbinary(16) = CONVERT(varbinary(16), CAST(N'$WORKER_IDENTITY_CLIENT_ID' AS uniqueidentifier));
DECLARE @cmd nvarchar(max);

IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE name = @apiUser)
BEGIN
  SET @cmd = N'CREATE USER [' + @apiUser + N'] WITH SID = 0x' + CONVERT(varchar(100), @apiSid, 2) + N', TYPE = E;';
  EXEC(@cmd);
END
EXEC('ALTER ROLE db_datareader ADD MEMBER [' + @apiUser + ']');
EXEC('ALTER ROLE db_datawriter ADD MEMBER [' + @apiUser + ']');

IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE name = @workerUser)
BEGIN
  SET @cmd = N'CREATE USER [' + @workerUser + N'] WITH SID = 0x' + CONVERT(varchar(100), @workerSid, 2) + N', TYPE = E;';
  EXEC(@cmd);
END
EXEC('ALTER ROLE db_datareader ADD MEMBER [' + @workerUser + ']');
EXEC('ALTER ROLE db_datawriter ADD MEMBER [' + @workerUser + ']');
SQL

      # The CI/CD principal applies EF migrations from cd.yml, so it needs DDL rights that no
      # runtime identity should ever have. db_ddladmin covers CREATE/ALTER/DROP; the data roles
      # are for the SeedRubrics migration, which inserts rows. Deliberately not db_owner: the
      # pipeline has no business granting permissions or dropping users.
      if [ -n "$CICD_CLIENT_ID" ]; then
        cat <<SQL >> bootstrap.sql
DECLARE @cicdUser sysname = N'cognilens-cicd';
DECLARE @cicdSid varbinary(16) = CONVERT(varbinary(16), CAST(N'$CICD_CLIENT_ID' AS uniqueidentifier));
DECLARE @cicdCmd nvarchar(max);

IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE name = @cicdUser)
BEGIN
  SET @cicdCmd = N'CREATE USER [' + @cicdUser + N'] WITH SID = 0x' + CONVERT(varchar(100), @cicdSid, 2) + N', TYPE = E;';
  EXEC(@cicdCmd);
END
EXEC('ALTER ROLE db_ddladmin ADD MEMBER [' + @cicdUser + ']');
EXEC('ALTER ROLE db_datareader ADD MEMBER [' + @cicdUser + ']');
EXEC('ALTER ROLE db_datawriter ADD MEMBER [' + @cicdUser + ']');
SQL
      fi

      # -U carries the user-assigned identity's client ID (how go-sqlcmd disambiguates which MI to
      # use); -b makes a failed T-SQL batch exit non-zero, otherwise sqlcmd returns 0 on SQL errors
      # and a broken bootstrap would silently report success.
      ./sqlcmd -S "tcp:$SERVER,1433" -d "$DATABASE" -l 30 -b \
        --authentication-method=ActiveDirectoryManagedIdentity -U "$DEPLOY_CLIENT_ID" \
        -i bootstrap.sql
    "
      properties.timeout:                                                                                                                                                                                                       "PT15M"
      type:                                                                                                                                                                                                                     "Microsoft.Resources/deploymentScripts"

  ~ Microsoft.App/containerApps/cognilens-dev-api [2025-01-01]
    - properties.configuration.ingress.exposedPort: 0
    - properties.template.revisionSuffix:           "3263d72"
    - properties.template.scale.cooldownPeriod:     300
    - properties.template.scale.pollingInterval:    30
    ~ properties.configuration.ingress.traffic: [
      ~ 0:

        - revisionName:   "cognilens-dev-api--3263d72"
        + latestRevision: true

      ]
    ~ properties.template.containers: [
      ~ 0:

        ~ env: [
          ~ 2:

            ~ value: "InstrumentationKey=0bff4d9d-e3df-485a-92a1-b7a9a21d9a9a;IngestionEndpoint=https://eastus2-3.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus2.livediagnostics.monitor.azure.com/;ApplicationId=c678843e-04f4-4447-8dcb-51c6e1abd7e6" => "[reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.Insights/components/cognilens-dev-appi', '2020-02-02').ConnectionString]"

          ~ 3:

            ~ value: "58ceba81-ddd9-439a-9f3e-15ec5ff32387" => "[reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cognilens-dev-api-mi', '2024-11-30').clientId]"

          ~ 4:

            ~ value: "Server=tcp:cognilens-dev-sql-64sgu7kbbhj5e.database.windows.net,1433;Initial Catalog=CogniLens;Authentication=Active Directory Default;User Id=58ceba81-ddd9-439a-9f3e-15ec5ff32387;Encrypt=True;TrustServerCertificate=False;" => "[format('Server=tcp:{0},1433;Initial Catalog={1};Authentication=Active Directory Default;User Id={2};Encrypt=True;TrustServerCertificate=False;', 'cognilens-dev-sql-64sgu7kbbhj5e.database.windows.net', 'CogniLens', reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cognilens-dev-api-mi', '2024-11-30').clientId)]"

          ~ 5:

            ~ value: "https://cognilensdevst.blob.core.windows.net/" => "[reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.Storage/storageAccounts/cognilensdevst', '2023-05-01').primaryEndpoints.blob]"

          ~ 6:

            ~ value: "https://cognilensdevst.queue.core.windows.net/" => "[reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.Storage/storageAccounts/cognilensdevst', '2023-05-01').primaryEndpoints.queue]"

          ~ 10:

            ~ value: "https://cognilens-speech-dev.cognitiveservices.azure.com/" => "[reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.CognitiveServices/accounts/cognilens-speech-dev', '2024-10-01').endpoint]"

          ~ 11:

            ~ value: "https://cognilens-openai-dev.openai.azure.com/" => "[reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.CognitiveServices/accounts/cognilens-openai-dev', '2024-10-01').endpoint]"

          ]
        ~ image: "ghcr.io/affan2900/cognilens-api:3263d720ea202759204717dafb90ee9b6e0b67ed" => "ghcr.io/affan2900/cognilens-api:latest"

      ]

  ~ Microsoft.App/containerApps/cognilens-dev-worker [2025-01-01]
    - properties.configuration.ingress.exposedPort: 0
    - properties.configuration.ingress.traffic: [
        0:

          latestRevision: true
          weight:         100

      ]
    - properties.template.scale.cooldownPeriod:     300
    - properties.template.scale.pollingInterval:    30
    ~ properties.template.containers: [
      ~ 0:

        ~ env: [
          ~ 2:

            ~ value: "InstrumentationKey=0bff4d9d-e3df-485a-92a1-b7a9a21d9a9a;IngestionEndpoint=https://eastus2-3.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus2.livediagnostics.monitor.azure.com/;ApplicationId=c678843e-04f4-4447-8dcb-51c6e1abd7e6" => "[reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.Insights/components/cognilens-dev-appi', '2020-02-02').ConnectionString]"

          ~ 3:

            ~ value: "0d11a77c-d181-4239-ada4-c8376afe8be2" => "[reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cognilens-dev-worker-mi', '2024-11-30').clientId]"

          ~ 4:

            ~ value: "Server=tcp:cognilens-dev-sql-64sgu7kbbhj5e.database.windows.net,1433;Initial Catalog=CogniLens;Authentication=Active Directory Default;User Id=0d11a77c-d181-4239-ada4-c8376afe8be2;Encrypt=True;TrustServerCertificate=False;" => "[format('Server=tcp:{0},1433;Initial Catalog={1};Authentication=Active Directory Default;User Id={2};Encrypt=True;TrustServerCertificate=False;', 'cognilens-dev-sql-64sgu7kbbhj5e.database.windows.net', 'CogniLens', reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cognilens-dev-worker-mi', '2024-11-30').clientId)]"

          ~ 5:

            ~ value: "https://cognilensdevst.blob.core.windows.net/" => "[reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.Storage/storageAccounts/cognilensdevst', '2023-05-01').primaryEndpoints.blob]"

          ~ 6:

            ~ value: "https://cognilensdevst.queue.core.windows.net/" => "[reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.Storage/storageAccounts/cognilensdevst', '2023-05-01').primaryEndpoints.queue]"

          ~ 10:

            ~ value: "https://cognilens-speech-dev.cognitiveservices.azure.com/" => "[reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.CognitiveServices/accounts/cognilens-speech-dev', '2024-10-01').endpoint]"

          ~ 11:

            ~ value: "https://cognilens-openai-dev.openai.azure.com/" => "[reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.CognitiveServices/accounts/cognilens-openai-dev', '2024-10-01').endpoint]"

          ]
        ~ image: "ghcr.io/affan2900/cognilens-worker:3263d720ea202759204717dafb90ee9b6e0b67ed" => "ghcr.io/affan2900/cognilens-worker:latest"

      ]
    ~ properties.template.scale.rules: [
      ~ 0:

        - azureQueue:

            accountName: "cognilensdevst"
            identity:    "/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cognilens-dev-worker-mi"
            queueLength: 1
            queueName:   "analyze-jobs"

        + custom:

            identity:             "/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cognilens-dev-worker-mi"
            metadata.accountName: "cognilensdevst"
            metadata.queueLength: "1"
            metadata.queueName:   "analyze-jobs"
            type:                 "azure-queue"


      ]

  ~ Microsoft.App/managedEnvironments/cognilens-dev-env [2025-01-01]
    - properties.peerAuthentication:

        mtls.enabled: false

    - properties.peerTrafficConfiguration:

        encryption.enabled: false

    ~ properties.appLogsConfiguration.logAnalyticsConfiguration.customerId: "e03bc1f3-bd5d-4b68-8c5a-3bea7d5d67e2" => "[reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.OperationalInsights/workspaces/cognilens-dev-law', '2023-09-01').customerId]"

  ~ Microsoft.CognitiveServices/accounts/cognilens-openai-dev/deployments/gpt-5-mini [2024-10-01]
    - properties.currentCapacity:      10
    - properties.raiPolicyName:        "Microsoft.DefaultV2"
    - properties.versionUpgradeOption: "OnceNewDefaultVersionAvailable"

  ~ Microsoft.CognitiveServices/accounts/cognilens-openai-dev/deployments/text-embedding-3-small [2024-10-01]
    - properties.currentCapacity:      10
    - properties.raiPolicyName:        "Microsoft.DefaultV2"
    - properties.versionUpgradeOption: "OnceNewDefaultVersionAvailable"

  ~ Microsoft.Consumption/budgets/cognilens-dev-budget [2023-11-01]
    ~ properties.timePeriod.endDate:   "2036-06-28T00:00:00Z" => "[dateTimeAdd(utcNow('yyyy-MM-01''T''00:00:00Z'), 'P10Y', 'yyyy-MM-dd''T''00:00:00Z')]"
    ~ properties.timePeriod.startDate: "2026-07-01T00:00:00Z" => "[utcNow('yyyy-MM-01''T''00:00:00Z')]"

  ~ Microsoft.Insights/components/cognilens-dev-appi [2020-02-02]
    + properties.Flow_Type:      "Bluefield"
    + properties.Request_Source: "rest"

  ~ Microsoft.ManagedIdentity/userAssignedIdentities/cognilens-dev-api-mi [2024-11-30]
    - properties:

        isolationScope: "None"


  ~ Microsoft.ManagedIdentity/userAssignedIdentities/cognilens-dev-sql-64sgu7kbbhj5e-deploy-mi [2024-11-30]
    - properties:

        isolationScope: "None"


  ~ Microsoft.ManagedIdentity/userAssignedIdentities/cognilens-dev-worker-mi [2024-11-30]
    - properties:

        isolationScope: "None"


  ~ Microsoft.Search/searchServices/cognilens-search-dev [2024-06-01-preview]
    - properties.encryptionWithCmk:

        enforcement: "Unspecified"

    - properties.networkRuleSet:

        bypass: "None"

    - properties.semanticSearch: "free"

  ~ Microsoft.Storage/storageAccounts/cognilensdevst/blobServices/default [2023-05-01]
    - properties.deleteRetentionPolicy:

        allowPermanentDelete: false
        enabled:              false


  ~ Microsoft.Storage/storageAccounts/cognilensdevst/blobServices/default/containers/call-audio [2023-05-01]
    - properties.defaultEncryptionScope:      "$account-encryption-key"
    - properties.denyEncryptionScopeOverride: false

  ~ Microsoft.Storage/storageAccounts/cognilensdevst/queueServices/default [2023-05-01]
    - properties:

        logging.delete:                  false
        logging.read:                    false
        logging.retentionPolicy.enabled: false
        logging.version:                 "1.0"
        logging.write:                   false


  = Microsoft.CognitiveServices/accounts/cognilens-openai-dev [2024-10-01]
  = Microsoft.CognitiveServices/accounts/cognilens-speech-dev [2024-10-01]
  = Microsoft.Insights/actionGroups/cognilens-dev-budget-ag [2023-01-01]
  = Microsoft.KeyVault/vaults/cognilens-dev-kv [2023-07-01]
  = Microsoft.OperationalInsights/workspaces/cognilens-dev-law [2023-09-01]
  = Microsoft.Sql/servers/cognilens-dev-sql-64sgu7kbbhj5e [2023-08-01-preview]
  = Microsoft.Sql/servers/cognilens-dev-sql-64sgu7kbbhj5e/databases/CogniLens [2023-08-01-preview]
    x properties.minCapacity: 0.5
    x sku.capacity:           1
    x sku.tier:               "GeneralPurpose"

  = Microsoft.Sql/servers/cognilens-dev-sql-64sgu7kbbhj5e/firewallRules/AllowAllWindowsAzureIps [2023-08-01-preview]
  = Microsoft.Storage/storageAccounts/cognilensdevst [2023-05-01]
  = Microsoft.Storage/storageAccounts/cognilensdevst/queueServices/default/queues/analyze-jobs [2023-05-01]
  = Microsoft.Storage/storageAccounts/cognilensdevst/queueServices/default/queues/analyze-jobs-poison [2023-05-01]
  * Microsoft.Sql/servers/cognilens-dev-sql-64sgu7kbbhj5e/databases/master
  * microsoft.insights/actiongroups/Application Insights Smart Detection

Resource changes: 1 to create, 14 to modify, 11 no change, 13 unsupported, 2 to ignore.

Diagnostics (13): 
[extensionResourceId('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.Storage/storageAccounts/cognilensdevst', 'Microsoft.Authorization/roleAssignments', guid('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.Storage/storageAccounts/cognilensdevst', reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cognilens-dev-api-mi', '2024-11-30').principalId, 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'))] (Unsupported) Changes to the resource declared at 'properties.template.resources[1].properties.template.resources[6]' on line 1 and column 10940 cannot be analyzed because its resource ID or API version cannot be calculated until the deployment is under way. Please see https://aka.ms/WhatIfUnidentifiableResource for more guidance.
[extensionResourceId('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.Storage/storageAccounts/cognilensdevst', 'Microsoft.Authorization/roleAssignments', guid('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.Storage/storageAccounts/cognilensdevst', reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cognilens-dev-worker-mi', '2024-11-30').principalId, 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'))] (Unsupported) Changes to the resource declared at 'properties.template.resources[1].properties.template.resources[6]' on line 1 and column 10940 cannot be analyzed because its resource ID or API version cannot be calculated until the deployment is under way. Please see https://aka.ms/WhatIfUnidentifiableResource for more guidance.
[extensionResourceId('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.Storage/storageAccounts/cognilensdevst', 'Microsoft.Authorization/roleAssignments', guid('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.Storage/storageAccounts/cognilensdevst', reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cognilens-dev-api-mi', '2024-11-30').principalId, 'db58b8e5-c6ad-4a2a-8342-4190687cbf4a'))] (Unsupported) Changes to the resource declared at 'properties.template.resources[1].properties.template.resources[7]' on line 1 and column 11732 cannot be analyzed because its resource ID or API version cannot be calculated until the deployment is under way. Please see https://aka.ms/WhatIfUnidentifiableResource for more guidance.
[extensionResourceId('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.Storage/storageAccounts/cognilensdevst', 'Microsoft.Authorization/roleAssignments', guid('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.Storage/storageAccounts/cognilensdevst', reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cognilens-dev-worker-mi', '2024-11-30').principalId, 'db58b8e5-c6ad-4a2a-8342-4190687cbf4a'))] (Unsupported) Changes to the resource declared at 'properties.template.resources[1].properties.template.resources[7]' on line 1 and column 11732 cannot be analyzed because its resource ID or API version cannot be calculated until the deployment is under way. Please see https://aka.ms/WhatIfUnidentifiableResource for more guidance.
[extensionResourceId('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.Storage/storageAccounts/cognilensdevst', 'Microsoft.Authorization/roleAssignments', guid('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.Storage/storageAccounts/cognilensdevst', reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cognilens-dev-api-mi', '2024-11-30').principalId, '974c5e8b-45b9-4653-ba55-5f855dd0fb88'))] (Unsupported) Changes to the resource declared at 'properties.template.resources[1].properties.template.resources[8]' on line 1 and column 12506 cannot be analyzed because its resource ID or API version cannot be calculated until the deployment is under way. Please see https://aka.ms/WhatIfUnidentifiableResource for more guidance.
[extensionResourceId('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.Storage/storageAccounts/cognilensdevst', 'Microsoft.Authorization/roleAssignments', guid('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.Storage/storageAccounts/cognilensdevst', reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cognilens-dev-worker-mi', '2024-11-30').principalId, '974c5e8b-45b9-4653-ba55-5f855dd0fb88'))] (Unsupported) Changes to the resource declared at 'properties.template.resources[1].properties.template.resources[8]' on line 1 and column 12506 cannot be analyzed because its resource ID or API version cannot be calculated until the deployment is under way. Please see https://aka.ms/WhatIfUnidentifiableResource for more guidance.
[extensionResourceId('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.KeyVault/vaults/cognilens-dev-kv', 'Microsoft.Authorization/roleAssignments', guid('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.KeyVault/vaults/cognilens-dev-kv', reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cognilens-dev-api-mi', '2024-11-30').principalId, '4633458b-17de-408a-b874-0445c86b69e6'))] (Unsupported) Changes to the resource declared at 'properties.template.resources[3].properties.template.resources[1]' on line 1 and column 18227 cannot be analyzed because its resource ID or API version cannot be calculated until the deployment is under way. Please see https://aka.ms/WhatIfUnidentifiableResource for more guidance.
[extensionResourceId('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.KeyVault/vaults/cognilens-dev-kv', 'Microsoft.Authorization/roleAssignments', guid('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.KeyVault/vaults/cognilens-dev-kv', reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cognilens-dev-worker-mi', '2024-11-30').principalId, '4633458b-17de-408a-b874-0445c86b69e6'))] (Unsupported) Changes to the resource declared at 'properties.template.resources[3].properties.template.resources[1]' on line 1 and column 18227 cannot be analyzed because its resource ID or API version cannot be calculated until the deployment is under way. Please see https://aka.ms/WhatIfUnidentifiableResource for more guidance.
[extensionResourceId('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.CognitiveServices/accounts/cognilens-speech-dev', 'Microsoft.Authorization/roleAssignments', guid('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.CognitiveServices/accounts/cognilens-speech-dev', reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cognilens-dev-worker-mi', '2024-11-30').principalId, 'f2dc8367-1007-4938-bd23-fe263f013447'))] (Unsupported) Changes to the resource declared at 'properties.template.resources[4].properties.template.resources[4]' on line 1 and column 22972 cannot be analyzed because its resource ID or API version cannot be calculated until the deployment is under way. Please see https://aka.ms/WhatIfUnidentifiableResource for more guidance.
[extensionResourceId('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.CognitiveServices/accounts/cognilens-openai-dev', 'Microsoft.Authorization/roleAssignments', guid('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.CognitiveServices/accounts/cognilens-openai-dev', reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cognilens-dev-worker-mi', '2024-11-30').principalId, '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd'))] (Unsupported) Changes to the resource declared at 'properties.template.resources[4].properties.template.resources[5]' on line 1 and column 23666 cannot be analyzed because its resource ID or API version cannot be calculated until the deployment is under way. Please see https://aka.ms/WhatIfUnidentifiableResource for more guidance.
[extensionResourceId('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.Search/searchServices/cognilens-search-dev', 'Microsoft.Authorization/roleAssignments', guid('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.Search/searchServices/cognilens-search-dev', reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cognilens-dev-worker-mi', '2024-11-30').principalId, '7ca78c08-252a-4471-8644-bb5ff32d4ba0'))] (Unsupported) Changes to the resource declared at 'properties.template.resources[5].properties.template.resources[1]' on line 1 and column 27141 cannot be analyzed because its resource ID or API version cannot be calculated until the deployment is under way. Please see https://aka.ms/WhatIfUnidentifiableResource for more guidance.
[extensionResourceId('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.Search/searchServices/cognilens-search-dev', 'Microsoft.Authorization/roleAssignments', guid('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.Search/searchServices/cognilens-search-dev', reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cognilens-dev-worker-mi', '2024-11-30').principalId, '8ebe5a00-799e-43f5-93ac-243d3dce84a7'))] (Unsupported) Changes to the resource declared at 'properties.template.resources[5].properties.template.resources[2]' on line 1 and column 27814 cannot be analyzed because its resource ID or API version cannot be calculated until the deployment is under way. Please see https://aka.ms/WhatIfUnidentifiableResource for more guidance.
[extensionResourceId('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.Search/searchServices/cognilens-search-dev', 'Microsoft.Authorization/roleAssignments', guid('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.Search/searchServices/cognilens-search-dev', reference('/subscriptions/163d0954-8f1c-442e-88f9-e11864c54b0f/resourceGroups/rg-cognilens-dev/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cognilens-dev-api-mi', '2024-11-30').principalId, '1407120a-92aa-4202-b7e9-c0e197c71c8f'))] (Unsupported) Changes to the resource declared at 'properties.template.resources[5].properties.template.resources[3]' on line 1 and column 28491 cannot be analyzed because its resource ID or API version cannot be calculated until the deployment is under way. Please see https://aka.ms/WhatIfUnidentifiableResource for more guidance.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/cd.yml:
- Around line 142-182: Add a bounded retry loop between the firewall-rule
creation step and the migration execution in the “Apply EF migrations” workflow,
using the same authenticated SQL connection parameters as the final sqlcmd call
to verify access before running migrate.sql. Wait between attempts, fail after
the timeout with a clear error, and preserve the existing migration command once
connectivity succeeds.
- Around line 129-136: Update the “Resolve SQL connection details” step to
capture the api.ipify.org response, validate that it is exactly one IPv4 address
with no extra lines or content, and only then append RUNNER_IP to GITHUB_ENV;
fail the step on invalid output while preserving the existing SQL_FQDN and
SQL_SERVER_NAME exports.

In `@infra/modules/sql.bicep`:
- Around line 163-178: Update the bootstrap SQL generation around the
CICD_CLIENT_ID handling so cognilens-cicd reflects desired state: when the value
is empty, remove the existing user and its db_ddladmin, db_datareader, and
db_datawriter memberships; when provided, compare the existing principal SID
with the requested client ID and drop/recreate the user if they differ before
assigning the roles. Preserve creation and role assignment when the user is
absent or already has the matching SID.

In `@scripts/loadshed-test.ps1`:
- Around line 46-72: Save scripts/loadshed-test.ps1 as UTF-8 with a BOM,
preserving the existing script content and non-ASCII characters so
PSUseBOMForUnicodeEncodedFile passes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: dd9faef3-31d7-4084-91ad-5664c3a525f8

📥 Commits

Reviewing files that changed from the base of the PR and between 3263d72 and 6edfcd1.

📒 Files selected for processing (5)
  • .github/workflows/cd.yml
  • .gitignore
  • infra/main.bicep
  • infra/modules/sql.bicep
  • scripts/loadshed-test.ps1

Comment thread .github/workflows/cd.yml
Comment on lines +129 to +136
- name: Resolve SQL connection details
run: |
set -euo pipefail
SQL_FQDN=$(az sql server list --resource-group "${{ env.RESOURCE_GROUP }}" \
--query "[0].fullyQualifiedDomainName" -o tsv)
echo "SQL_FQDN=$SQL_FQDN" >> "$GITHUB_ENV"
echo "SQL_SERVER_NAME=${SQL_FQDN%%.*}" >> "$GITHUB_ENV"
echo "RUNNER_IP=$(curl -sf https://api.ipify.org)" >> "$GITHUB_ENV"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## tracked workflow"
git ls-files .github/workflows/cd.yml || true

echo
echo "## relevant workflow section"
if [ -f .github/workflows/cd.yml ]; then
  nl -ba .github/workflows/cd.yml | sed -n '100,160p'
fi

echo
echo "## all GITHUB_ENV writes in workflow"
if [ -f .github/workflows/cd.yml ]; then
  rg -n 'GITHUB_ENV|to_json|RUNNER_IP|curl -sf https://api.ipify|ipify' .github/workflows/cd.yml
fi

echo
echo "## behavioral probe: multi-line API response would append multiple lines to env-like assignment"
python3 - <<'PY'
payloads = [
    "1.2.3.4\n",
    "1.2.3.4\r\n",
    "1.2.3.4\nOTHER=value\n",
]
for p in payloads:
    lines = p.splitlines()
    print(f"payload={payloads.index(p)+1}: lines={lines}")
    print("append result if interpreted as echo \"KEY=${lines[0]}\" >> GITHUB_ENV:")
    for line in lines:
        if '=' in line:
            print(f"  env file line: {line}")
    print()
PY

Repository: Affan2900/cognilens

Length of output: 273


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## workflow exists"
[ -f .github/workflows/cd.yml ] && echo yes || echo no

echo
echo "## relevant workflow section"
python3 - <<'PY'
from pathlib import Path
p = Path('.github/workflows/cd.yml')
lines = p.read_text().splitlines()
for i in range(100, min(len(lines), 165)):
    print(f"{i+1:5d}: {lines[i]}")
PY

echo
echo "## GITHUB_ENV / ipify usages"
python3 - <<'PY'
import re
from pathlib import Path
lines = Path('.github/workflows/cd.yml').read_text().splitlines()
for i, line in enumerate(lines, 1):
    if 'GITHUB_ENV' in line or 'ipify' in line or 'curl' in line or 'RUNNER_IP' in line:
        print(f"{i:5d}: {line}")
PY

echo
echo "## behavioral probe"
python3 - <<'PY'
payloads = [
    "1.2.3.4\n",
    "1.2.3.4\r\n",
    "1.2.3.4\nOTHER=value\n",
]
for payload in payloads:
    print("payload repr:", repr(payload))
    key = payload.splitlines()[0]
    print("current echo assignment:")
    print(f'RUNNER_IP={key}')
    print("implicit env-file append lines:")
    for line in payload.splitlines():
        print(repr(line))
    print()
PY

Repository: Affan2900/cognilens

Length of output: 5583


Validate RUNNER_IP before appending it to GITHUB_ENV.

External output from api.ipify.org is embedded in the echoed assignment, so a multi-line response can append extra lines to the generated environment file and affect later steps. Parse the response as exactly one IPv4 address before writing it.

Proposed fix
-          echo "RUNNER_IP=$(curl -sf https://api.ipify.org)" >> "$GITHUB_ENV"
+          RUNNER_IP="$(curl -fsS https://api.ipify.org)"
+          python3 -c 'import ipaddress, sys; ipaddress.IPv4Address(sys.argv[1])' "$RUNNER_IP"
+          printf 'RUNNER_IP=%s\n' "$RUNNER_IP" >> "$GITHUB_ENV"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Resolve SQL connection details
run: |
set -euo pipefail
SQL_FQDN=$(az sql server list --resource-group "${{ env.RESOURCE_GROUP }}" \
--query "[0].fullyQualifiedDomainName" -o tsv)
echo "SQL_FQDN=$SQL_FQDN" >> "$GITHUB_ENV"
echo "SQL_SERVER_NAME=${SQL_FQDN%%.*}" >> "$GITHUB_ENV"
echo "RUNNER_IP=$(curl -sf https://api.ipify.org)" >> "$GITHUB_ENV"
- name: Resolve SQL connection details
run: |
set -euo pipefail
SQL_FQDN=$(az sql server list --resource-group "${{ env.RESOURCE_GROUP }}" \
--query "[0].fullyQualifiedDomainName" -o tsv)
echo "SQL_FQDN=$SQL_FQDN" >> "$GITHUB_ENV"
echo "SQL_SERVER_NAME=${SQL_FQDN%%.*}" >> "$GITHUB_ENV"
RUNNER_IP="$(curl -fsS https://api.ipify.org)"
python3 -c 'import ipaddress, sys; ipaddress.IPv4Address(sys.argv[1])' "$RUNNER_IP"
printf 'RUNNER_IP=%s\n' "$RUNNER_IP" >> "$GITHUB_ENV"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/cd.yml around lines 129 - 136, Update the “Resolve SQL
connection details” step to capture the api.ipify.org response, validate that it
is exactly one IPv4 address with no extra lines or content, and only then append
RUNNER_IP to GITHUB_ENV; fail the step on invalid output while preserving the
existing SQL_FQDN and SQL_SERVER_NAME exports.

Source: Linters/SAST tools

Comment thread .github/workflows/cd.yml
Comment on lines +142 to +182
- name: Open the SQL firewall for this runner
run: |
set -euo pipefail
az sql server firewall-rule create \
--resource-group "${{ env.RESOURCE_GROUP }}" \
--server "${{ env.SQL_SERVER_NAME }}" \
--name "gh-runner-${{ github.run_id }}" \
--start-ip-address "${{ env.RUNNER_IP }}" \
--end-ip-address "${{ env.RUNNER_IP }}" >/dev/null
echo "Opened SQL firewall for ${{ env.RUNNER_IP }}"

- name: Apply EF migrations
run: |
set -euo pipefail
# Pinned to the EF Core version in CogniLens.Infrastructure.csproj. A floating version
# here would let a tooling release change what a deploy does without a commit.
dotnet tool install --global dotnet-ef --version 10.0.10 >/dev/null
export PATH="$PATH:$HOME/.dotnet/tools"

# --idempotent wraps every migration in an "if not already applied" guard, so this is
# safe to run on every deploy and safe to re-run after a partial failure.
# Infrastructure is both project and startup project: it declares
# Microsoft.EntityFrameworkCore.Design with PrivateAssets, so the package does not flow
# to CogniLens.Api and the tools reject the Api as a startup project. CogniLensDbContextFactory
# is the design-time factory that makes the class library usable on its own.
dotnet ef migrations script --idempotent \
--project src/CogniLens.Infrastructure \
--startup-project src/CogniLens.Infrastructure \
--output migrate.sql

curl -sSL -o sqlcmd.tar.bz2 \
https://github.com/microsoft/go-sqlcmd/releases/download/v1.10.0/sqlcmd-linux-amd64.tar.bz2
tar -xjf sqlcmd.tar.bz2
chmod +x ./sqlcmd

# ActiveDirectoryAzCli reuses the token from azure/login above — the same principal the
# bootstrap script granted db_ddladmin to. -b makes a failed batch exit non-zero;
# without it sqlcmd returns 0 on SQL errors and a broken migration reports success.
./sqlcmd -S "tcp:${{ env.SQL_FQDN }},1433" -d CogniLens -l 30 -b \
--authentication-method=ActiveDirectoryAzCli \
-i migrate.sql

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate workflow =="
if [ -f .github/workflows/cd.yml ]; then
  nl -ba .github/workflows/cd.yml | sed -n '120,200p'
else
  echo ".github/workflows/cd.yml not found"
fi

echo "== azure/login and sqlcmd / firewall context =="
rg -n "azure/login|Open the SQL firewall|Firewall|sqlcmd|migrations script|WAIT|sleep|az sql server firewall-rule" .github/workflows cd.yml 2>/dev/null || true

echo "== relevant docs/source references in repo =="
rg -n "firewall-rule|Azure SQL firewall|sqlcmd\)|--authentication-method=ActiveDirectoryAzCli|wait .* firewall|propagat" .github src 2>/dev/null || true

echo "== deterministic sequence check from workflow text =="
python3 - <<'PY'
from pathlib import Path
p=Path('.github/workflows/cd.yml')
if not p.exists():
    print("missing workflow")
    raise SystemExit
text=p.read_text()
terms=["Open the SQL firewall for this runner","az sql server firewall-rule create","dotnet ef migrations script","./sqlcmd"]
pos=[text.find(t) for t in terms]
for t,p_ in zip(terms,pos):
    print(f"{t}: {p_}")
print("firewall_before_ef:", pos[:2]<None and pos[1]>pos[0])
print("ef_before_sqlcmd:", 2 < pos[3] or None)
print("firewall_and_sqlcmd_present:", all(p_!=-1 for p_ in pos))
PY

echo "== docs lookup =="
web_search "Azure SQL server firewall rules maximum time to take effect allow traffic after firewall rule created"

Repository: Affan2900/cognilens

Length of output: 222


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate workflow candidates =="
find . -path '.git' -prune -o -type f \( -name 'cd.yml' -o -name 'cd.yaml' \) -print | sort

echo "== inspect relevant workflow excerpt =="
if [ -f .github/workflows/cd.yml ]; then
  awk 'NR>=120 && NR<=210 {printf "%4d  %s\n", NR, $0}' .github/workflows/cd.yml
else
  echo ".github/workflows/cd.yml not found"
fi

echo "== search for firewall/login/sqlcmd patterns =="
rg -n "azure/login|Open the SQL firewall|sql server firewall-rule|dotnet ef migrations script|sqlcmd|authentication-method=ActiveDirectoryAzCli|sleep|wait|retry" .github src 2>/dev/null || true

echo "== deterministic sequence check from workflow text =="
python3 - <<'PY'
from pathlib import Path
p = Path('.github/workflows/cd.yml')
if not p.exists():
    print("missing workflow")
    raise SystemExit
text = p.read_text()
terms = [
    "Step: Build CogniLens solution",
    "az sql server firewall-rule create",
    "dotnet ef migrations script",
    "--authentication-method=ActiveDirectoryAzCli",
    "./sqlcmd"
]
for term in terms:
    idx = text.find(term)
    print(f"{term!r}: {idx}")
PY

Repository: Affan2900/cognilens

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== list tracked workflow files =="
git ls-files .github/workflows | sort

echo "== inspect .github/workflows/cd.yml excerpt near firewall/migration path =="
awk 'NR>=135 && NR<=190 {printf "%4d  %s\n", NR, $0}' .github/workflows/cd.yml

echo "== exact target markers in workflow =="
python3 - <<'PY'
from pathlib import Path
p=Path('.github/workflows/cd.yml')
text=p.read_text()
for term in [
    "AZURE_CREDENTIALS",
    "azure/login",
    "Open the SQL firewall",
    "az sql server firewall-rule create",
    "dotnet ef migrations script",
    "--authentication-method=ActiveDirectoryAzCli",
    "./sqlcmd"
]:
    print(f"{term!r}: {text.find(term)}")
PY

echo "== source references to firewall propagation handling =="
rg -n "firewall|firewall-rule|propaga|sqlcmd|dotnet ef migrations script|migrations script|ActiveDirectoryAzCli" .github/workflows src package.json package-lock.json -g '!node_modules' 2>/dev/null | head -200

Repository: Affan2900/cognilens

Length of output: 5430


🌐 Web query:

Azure SQL database firewall firewall rules propagation time after creating rule to take effect

💡 Result:

In Azure SQL Database, changes to firewall rules can take up to 5 minutes to propagate and take effect [1][2][3]. This latency occurs because server-level firewall rules are temporarily cached at the database level to improve performance [1][4][5]. The specific behavior and latency depend on the database configuration: - Server Authentication Model: Changes have a 5-minute latency for all databases [1][3]. - Contained Database (with a failover partner): Changes are instantaneous on the primary database, but the secondary database may take up to 5 minutes to reflect the updates [1][3]. - Contained Database (no failover partner): Changes are instantaneous [1][3]. If you need a firewall rule to take effect immediately, you can manually refresh the authentication cache by executing the following command in the target database: DBCC FLUSHAUTHCACHE [1][4][3] This command forces the database to flush its local cache and immediately reload the latest firewall rules [1][5].

Citations:


Add a firewall propagation wait before migrating.

az sql server firewall-rule create returns before the database-level authorization cache is refreshed, and Azure SQL firewall changes can take up to 5 minutes to take effect. Running ./sqlcmd can return immediately after the rule create, causing intermittent migration failures; add a retry/waits for a successful authenticated sqlcmd or another lightweight connection check before applying migrate.sql.

🧰 Tools
🪛 zizmor (1.26.1)

[warning] 147-147: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[warning] 149-149: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[warning] 149-149: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[warning] 150-150: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[warning] 180-180: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/cd.yml around lines 142 - 182, Add a bounded retry loop
between the firewall-rule creation step and the migration execution in the
“Apply EF migrations” workflow, using the same authenticated SQL connection
parameters as the final sqlcmd call to verify access before running migrate.sql.
Wait between attempts, fail after the timeout with a clear error, and preserve
the existing migration command once connectivity succeeds.

Comment thread infra/modules/sql.bicep
Comment on lines +163 to +178
if [ -n "$CICD_CLIENT_ID" ]; then
cat <<SQL >> bootstrap.sql
DECLARE @cicdUser sysname = N'cognilens-cicd';
DECLARE @cicdSid varbinary(16) = CONVERT(varbinary(16), CAST(N'$CICD_CLIENT_ID' AS uniqueidentifier));
DECLARE @cicdCmd nvarchar(max);

IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE name = @cicdUser)
BEGIN
SET @cicdCmd = N'CREATE USER [' + @cicdUser + N'] WITH SID = 0x' + CONVERT(varchar(100), @cicdSid, 2) + N', TYPE = E;';
EXEC(@cicdCmd);
END
EXEC('ALTER ROLE db_ddladmin ADD MEMBER [' + @cicdUser + ']');
EXEC('ALTER ROLE db_datareader ADD MEMBER [' + @cicdUser + ']');
EXEC('ALTER ROLE db_datawriter ADD MEMBER [' + @cicdUser + ']');
SQL
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
rg -n -C4 'CICD_CLIENT_ID|cognilens-cicd|CREATE USER|ALTER USER|DROP USER' infra/modules/sql.bicep

Repository: Affan2900/cognilens

Length of output: 3778


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
wc -l infra/modules/sql.bicep
sed -n '1,240p' infra/modules/sql.bicep

Repository: Affan2900/cognilens

Length of output: 9150


Treat the CI/CD user as desired state.

When cicdPrincipalClientId is empty, the script skips the existing cognilens-cicd user, leaving it and its db_ddladmin/data roles in place. When it changes, the new SID is assigned to roles by name, but the existing row keeps the old SID. Add targeted cleanup for an empty/disable case and update/recreate the SID when the provided value differs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@infra/modules/sql.bicep` around lines 163 - 178, Update the bootstrap SQL
generation around the CICD_CLIENT_ID handling so cognilens-cicd reflects desired
state: when the value is empty, remove the existing user and its db_ddladmin,
db_datareader, and db_datawriter memberships; when provided, compare the
existing principal SID with the requested client ID and drop/recreate the user
if they differ before assigning the roles. Preserve creation and role assignment
when the user is absent or already has the matching SID.

Comment thread scripts/loadshed-test.ps1
Comment on lines +46 to +72
# Windows PowerShell 5.1 negotiates TLS 1.0 by default, which Azure Storage and Container Apps
# both refuse — without this the first upload fails with an unhelpful "connection closed".
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
# Invoke-WebRequest renders a progress bar per call in 5.1 and it dominates the runtime of a
# multi-megabyte upload loop.
$ProgressPreference = 'SilentlyContinue'

if (-not (Test-Path $AudioPath)) { throw "Audio file not found: $AudioPath" }
$audioBytes = [System.IO.File]::ReadAllBytes($AudioPath)
$audioName = Split-Path $AudioPath -Leaf
Write-Host "Audio: $audioName ($([math]::Round($audioBytes.Length / 1MB, 2)) MB), $Count jobs" -ForegroundColor Cyan

# CallStatus has no JsonStringEnumConverter registered, so the API serialises it as an ordinal:
# Pending=0, Processing=1, Completed=2, Failed=3. Comparing against the names directly matches
# nothing and reports every job as lost. Both forms are accepted so this keeps working if a
# string converter is added later.
function ConvertTo-CallStatusName($value) {
if ($value -is [string]) { return $value }
switch ([int]$value) {
0 { 'Pending' }
1 { 'Processing' }
2 { 'Completed' }
3 { 'Failed' }
default { "Unknown($value)" }
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Save the script with a UTF-8 BOM.

PSScriptAnalyzer reports PSUseBOMForUnicodeEncodedFile. Since this Windows PowerShell script contains non-ASCII characters, add a UTF-8 BOM to ensure consistent parsing and avoid CI analyzer failures.

🧰 Tools
🪛 PSScriptAnalyzer (1.25.0)

[warning] Missing BOM encoding for non-ASCII encoded file 'loadshed-test.ps1'

(PSUseBOMForUnicodeEncodedFile)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/loadshed-test.ps1` around lines 46 - 72, Save
scripts/loadshed-test.ps1 as UTF-8 with a BOM, preserving the existing script
content and non-ASCII characters so PSUseBOMForUnicodeEncodedFile passes.

Source: Linters/SAST tools

@Affan2900
Affan2900 merged commit 4b847e4 into main Jul 26, 2026
6 checks passed
Affan2900 added a commit that referenced this pull request Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant