Merge AI Semantic Search Foundation R1 #417
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: SDAP CI | |
| on: | |
| pull_request: | |
| push: | |
| branches: | |
| - main | |
| - master # Current main branch | |
| permissions: | |
| contents: read | |
| security-events: write # For security scanning | |
| actions: read | |
| concurrency: | |
| group: sdap-ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| DOTNET_NOLOGO: true | |
| DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true | |
| DOTNET_CLI_TELEMETRY_OPTOUT: true | |
| jobs: | |
| security-scan: | |
| name: Security Scan | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Run Trivy vulnerability scanner | |
| uses: aquasecurity/trivy-action@master | |
| with: | |
| scan-type: 'fs' | |
| scan-ref: '.' | |
| format: 'sarif' | |
| output: 'trivy-results.sarif' | |
| - name: Upload Trivy scan results to GitHub Security tab | |
| uses: github/codeql-action/upload-sarif@v4 | |
| if: always() | |
| with: | |
| sarif_file: 'trivy-results.sarif' | |
| build-test: | |
| name: Build & Test | |
| runs-on: windows-latest | |
| strategy: | |
| matrix: | |
| configuration: [Debug, Release] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 # Required for GitVersion and better diff analysis | |
| - name: Setup .NET 8 | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '8.x' | |
| - name: Setup .NET Framework targeting pack | |
| shell: pwsh | |
| run: | | |
| # Ensure .NET Framework 4.8 targeting pack is available for plugin projects | |
| if (!(Test-Path "C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8")) { | |
| Write-Warning ".NET Framework 4.8 targeting pack not found. Plugin build may fail." | |
| } | |
| - name: Cache NuGet packages | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.nuget/packages | |
| key: ${{ runner.os }}-nuget-${{ hashFiles('**/Directory.Packages.props', '**/*.csproj') }} | |
| restore-keys: | | |
| ${{ runner.os }}-nuget- | |
| - name: Restore dependencies | |
| run: dotnet restore --verbosity minimal | |
| - name: Build | |
| run: dotnet build -c ${{ matrix.configuration }} --no-restore -warnaserror | |
| - name: Test with coverage | |
| run: | | |
| dotnet test -c ${{ matrix.configuration }} --no-build --logger trx --results-directory ./TestResults ` | |
| --collect:"XPlat Code Coverage" --settings coverlet.runsettings | |
| continue-on-error: true # Don't fail build on test failures during CI hardening phase | |
| - name: Upload test results | |
| uses: actions/upload-artifact@v6 | |
| if: always() | |
| with: | |
| name: test-results-${{ matrix.configuration }} | |
| path: ./TestResults/ | |
| - name: Upload coverage reports | |
| uses: actions/upload-artifact@v6 | |
| if: always() | |
| with: | |
| name: coverage-${{ matrix.configuration }} | |
| path: ./TestResults/**/coverage.cobertura.xml | |
| code-quality: | |
| name: Code Quality | |
| runs-on: windows-latest | |
| needs: build-test | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup .NET 8 | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '8.x' | |
| - name: Restore dependencies | |
| run: dotnet restore --verbosity minimal | |
| - name: Format verification | |
| run: dotnet format --verify-no-changes --verbosity diagnostic | |
| - name: ADR policy check (Legacy PowerShell) | |
| shell: pwsh | |
| run: ./scripts/adr_policy_check.ps1 -RepoRoot . | |
| continue-on-error: true # Legacy script - NetArchTest is primary validation | |
| - name: ADR architecture tests (NetArchTest) | |
| id: adr-tests | |
| run: dotnet test tests/Spaarke.ArchTests/Spaarke.ArchTests.csproj --no-restore --logger "trx;LogFileName=adr-results.trx" --results-directory ./TestResults | |
| continue-on-error: true # Don't block PR - violations reported via comment | |
| - name: Upload ADR test results | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: adr-test-results | |
| path: ./TestResults/adr-results.trx | |
| - name: Plugin size validation | |
| shell: pwsh | |
| run: | | |
| # Validate plugin assemblies are under size limits per ADR-002 | |
| $pluginPath = "power-platform/plugins/Spaarke.Plugins/bin/Release/net48/Spaarke.Plugins.dll" | |
| if (Test-Path $pluginPath) { | |
| $size = (Get-Item $pluginPath).Length | |
| Write-Host "Plugin assembly size: $size bytes" | |
| if ($size -gt 1MB) { | |
| Write-Error "Plugin assembly exceeds 1MB size limit (ADR-002)" | |
| exit 1 | |
| } | |
| } | |
| - name: Dependencies audit | |
| shell: pwsh | |
| run: | | |
| # Check for vulnerable packages | |
| dotnet list package --vulnerable --include-transitive 2>&1 | Tee-Object -Variable output | |
| if ($output -match "has the following vulnerable packages") { | |
| Write-Error "Vulnerable packages detected" | |
| exit 1 | |
| } | |
| integration-readiness: | |
| name: Integration Readiness | |
| runs-on: windows-latest | |
| needs: [security-scan, build-test, code-quality] | |
| if: github.ref == 'refs/heads/master' || github.event_name == 'pull_request' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup .NET 8 | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '8.x' | |
| - name: Restore dependencies | |
| run: dotnet restore --verbosity minimal | |
| - name: Build for deployment | |
| run: | | |
| dotnet publish src/server/api/Sprk.Bff.Api/Sprk.Bff.Api.csproj -c Release -o ./publish/api --no-restore | |
| - name: Package artifacts | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: deployment-packages | |
| path: ./publish/** | |
| retention-days: 30 | |
| - name: Environment readiness check | |
| shell: pwsh | |
| run: | | |
| # Validate configuration templates and deployment readiness | |
| Write-Host "=== Deployment Readiness Check ===" | |
| Write-Host "✓ API package built successfully" | |
| Write-Host "✓ CI/CD pipeline validated" | |
| # Check for deployment configuration | |
| $hasDeployConfig = Test-Path ".github/workflows/deploy-*.yml" | |
| if (-not $hasDeployConfig) { | |
| Write-Warning "⚠ Deployment workflow not found. Add deployment pipelines for complete CI/CD." | |
| } | |
| adr-pr-comment: | |
| name: ADR Violations Report | |
| runs-on: ubuntu-latest | |
| needs: code-quality | |
| if: github.event_name == 'pull_request' | |
| permissions: | |
| pull-requests: write | |
| contents: read | |
| steps: | |
| - name: Download test results | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: adr-test-results | |
| path: ./test-results | |
| - name: Parse and comment on PR | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| // Read TRX file | |
| const trxPath = './test-results/adr-results.trx'; | |
| if (!fs.existsSync(trxPath)) { | |
| console.log('No test results found'); | |
| return; | |
| } | |
| const trxContent = fs.readFileSync(trxPath, 'utf8'); | |
| // Parse failures from TRX (simple regex parsing) | |
| const failureMatches = [...trxContent.matchAll(/<UnitTestResult.*?outcome="Failed".*?testName="(.*?)".*?<Message>(.*?)<\/Message>/gs)]; | |
| const totalTests = (trxContent.match(/<UnitTestResult/g) || []).length; | |
| const passedTests = (trxContent.match(/outcome="Passed"/g) || []).length; | |
| const failedTests = failureMatches.length; | |
| // Build comment | |
| let comment = '## 🏛️ ADR Architecture Validation Report\n\n'; | |
| comment += `**Summary:** ${passedTests}/${totalTests} tests passed`; | |
| if (failedTests === 0) { | |
| comment += ' ✅\n\n'; | |
| comment += '**Status:** All ADR validations passed! No architectural violations detected.\n\n'; | |
| comment += '---\n'; | |
| comment += '*This is a non-blocking validation. See [ADR Validation Process](../blob/master/docs/adr/ADR-VALIDATION-PROCESS.md) for details.*'; | |
| } else { | |
| comment += ` ⚠️ (${failedTests} violations)\n\n`; | |
| comment += '**Status:** ADR violations detected. These are **non-blocking warnings** but should be reviewed.\n\n'; | |
| comment += '### ⚠️ Violations Found\n\n'; | |
| failureMatches.forEach((match, index) => { | |
| const testName = match[1]; | |
| const message = match[2] | |
| .replace(/</g, '<') | |
| .replace(/>/g, '>') | |
| .replace(/"/g, '"') | |
| .replace(/&/g, '&') | |
| .trim(); | |
| // Extract ADR number from test name | |
| const adrMatch = testName.match(/ADR-(\d+)/); | |
| const adrNum = adrMatch ? adrMatch[1] : '???'; | |
| comment += `#### ${index + 1}. ${testName}\n\n`; | |
| comment += '```\n'; | |
| comment += message.substring(0, 500); // Limit message length | |
| if (message.length > 500) comment += '...\n(truncated)'; | |
| comment += '\n```\n\n'; | |
| comment += `📖 See [ADR-${adrNum}](../blob/master/docs/adr/ADR-${adrNum.padStart(3, '0')}-*.md) for guidance\n\n`; | |
| }); | |
| comment += '---\n\n'; | |
| comment += '### 🔧 How to Fix\n\n'; | |
| comment += '1. Run locally: `dotnet test tests/Spaarke.ArchTests/`\n'; | |
| comment += '2. Get guidance: `/adr-check` (Claude Code skill)\n'; | |
| comment += '3. Review [ADR Validation Process](../blob/master/docs/adr/ADR-VALIDATION-PROCESS.md)\n\n'; | |
| comment += '**Note:** These violations are **warnings only** and will not block this PR.\n'; | |
| } | |
| // Find existing comment | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| }); | |
| const botComment = comments.find(c => | |
| c.user.type === 'Bot' && | |
| c.body.includes('ADR Architecture Validation Report') | |
| ); | |
| // Create or update comment | |
| if (botComment) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: botComment.id, | |
| body: comment | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: comment | |
| }); | |
| } | |
| summary: | |
| name: CI Summary | |
| runs-on: ubuntu-latest | |
| needs: [security-scan, build-test, code-quality, integration-readiness] | |
| if: always() | |
| steps: | |
| - name: Summary | |
| run: | | |
| echo "## SDAP CI Pipeline Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "| Stage | Status |" >> $GITHUB_STEP_SUMMARY | |
| echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| Security Scan | ${{ needs.security-scan.result }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Build & Test | ${{ needs.build-test.result }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Code Quality | ${{ needs.code-quality.result }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Integration Readiness | ${{ needs.integration-readiness.result }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Next Steps" >> $GITHUB_STEP_SUMMARY | |
| echo "- [ ] Configure deployment environments" >> $GITHUB_STEP_SUMMARY | |
| echo "- [ ] Set up monitoring and alerting" >> $GITHUB_STEP_SUMMARY | |
| echo "- [ ] Complete service registrations for test coverage" >> $GITHUB_STEP_SUMMARY |