Skip to content

Merge AI Semantic Search Foundation R1 #417

Merge AI Semantic Search Foundation R1

Merge AI Semantic Search Foundation R1 #417

Workflow file for this run

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(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"')
.replace(/&amp;/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