feat: Add SysML v2 integration requirements (FR-20 to FR-25) #4
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: CI/CD Pipeline | ||
| on: | ||
| push: | ||
| branches: [ main, alpha, beta, release/** ] | ||
| pull_request: | ||
| branches: [ main, release/** ] | ||
| release: | ||
| types: [ published ] | ||
| env: | ||
| DOTNET_VERSION: '10.0.x' | ||
| DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 | ||
| DOTNET_NOLOGO: true | ||
| DOTNET_CLI_TELEMETRY_OPTOUT: 1 | ||
| jobs: | ||
| sanity-checks: | ||
| name: Sanity Checks | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
| - name: Check branch naming convention | ||
| run: | | ||
| BRANCH_NAME="${GITHUB_REF#refs/heads/}" | ||
| echo "🔍 Checking branch: $BRANCH_NAME" | ||
| # Valid branch patterns: main, alpha, beta, release/*, hotfix/*, feature/* | ||
| if [[ "$BRANCH_NAME" =~ ^(main|alpha|beta|release\/|hotfix\/|feature\/).*$ ]]; then | ||
| echo "✅ Branch name follows convention" | ||
| else | ||
| echo "❌ Branch name '$BRANCH_NAME' does not follow convention" | ||
| echo " Expected: main, alpha, beta, release/*, hotfix/*, feature/*" | ||
| exit 1 | ||
| fi | ||
| - name: Check for required files | ||
| run: | | ||
| echo "🔍 Checking for required project files..." | ||
| REQUIRED_FILES=( | ||
| "README.md" | ||
| "LICENSE" | ||
| "project/REQUIREMENTS.md" | ||
| "project/REQUIREMENTS_MATRIX.md" | ||
| "project/TRACEABILITY.md" | ||
| "project/CONTRIBUTING.md" | ||
| "src/SysDocs.sln" | ||
| ".github/workflows/ci.yml" | ||
| ".github/workflows/release.yml" | ||
| "Dockerfile" | ||
| "Makefile" | ||
| ) | ||
| MISSING_FILES=() | ||
| for file in "${REQUIRED_FILES[@]}"; do | ||
| if [ ! -f "$file" ]; then | ||
| MISSING_FILES+=("$file") | ||
| fi | ||
| done | ||
| if [ ${#MISSING_FILES[@]} -eq 0 ]; then | ||
| echo "✅ All required files present" | ||
| else | ||
| echo "❌ Missing required files:" | ||
| printf ' - %s\n' "${MISSING_FILES[@]}" | ||
| exit 1 | ||
| fi | ||
| - name: Check for sensitive data | ||
| run: | | ||
| echo "🔍 Scanning for potential sensitive data..." | ||
| # Check for common secrets patterns (excluding test files and docs) | ||
| PATTERNS=( | ||
| "password.*=.*['\"].*['\"]" | ||
| "api[_-]?key.*=.*['\"].*['\"]" | ||
| "secret.*=.*['\"].*['\"]" | ||
| "token.*=.*['\"].*['\"]" | ||
| "-----BEGIN (RSA|DSA|EC) PRIVATE KEY-----" | ||
| ) | ||
| FOUND=0 | ||
| for pattern in "${PATTERNS[@]}"; do | ||
| if git grep -iE "$pattern" -- ':!*.md' ':!docs/' ':!examples/' ':!tests/' > /dev/null 2>&1; then | ||
| echo "⚠️ Found potential secret pattern: $pattern" | ||
| FOUND=1 | ||
| fi | ||
| done | ||
| if [ $FOUND -eq 0 ]; then | ||
| echo "✅ No obvious secrets found in code" | ||
| else | ||
| echo "⚠️ Review findings above - they may be false positives" | ||
| fi | ||
| - name: Check file sizes | ||
| run: | | ||
| echo "🔍 Checking for large files..." | ||
| # Find files larger than 1MB (excluding build artifacts) | ||
| LARGE_FILES=$(find . -type f -size +1M \ | ||
| ! -path "*/bin/*" \ | ||
| ! -path "*/obj/*" \ | ||
| ! -path "*/.git/*" \ | ||
| ! -path "*/node_modules/*" \ | ||
| ! -path "*/TestResults/*" \ | ||
| -exec ls -lh {} \; 2>/dev/null) | ||
| if [ -z "$LARGE_FILES" ]; then | ||
| echo "✅ No large files found" | ||
| else | ||
| echo "⚠️ Large files detected (>1MB):" | ||
| echo "$LARGE_FILES" | ||
| echo "" | ||
| echo "Consider using Git LFS for large binary files" | ||
| fi | ||
| - name: Setup .NET | ||
| uses: actions/setup-dotnet@v4 | ||
| with: | ||
| dotnet-version: ${{ env.DOTNET_VERSION }} | ||
| - name: Check code formatting | ||
| run: | | ||
| echo "🔍 Checking code formatting..." | ||
| dotnet format src/SysDocs.sln --verify-no-changes --verbosity diagnostic | ||
| - name: Check for TODO/FIXME comments | ||
| run: | | ||
| echo "🔍 Checking for TODO/FIXME comments..." | ||
| TODO_COUNT=$(git grep -iE "(TODO|FIXME|HACK|XXX)" -- '*.cs' '*.md' | wc -l || echo 0) | ||
| if [ "$TODO_COUNT" -gt 0 ]; then | ||
| echo "ℹ️ Found $TODO_COUNT TODO/FIXME comments:" | ||
| git grep -iE "(TODO|FIXME|HACK|XXX)" -- '*.cs' '*.md' | head -20 | ||
| echo "" | ||
| echo "⚠️ Consider addressing these before stable release" | ||
| else | ||
| echo "✅ No TODO/FIXME comments found" | ||
| fi | ||
| - name: Verify Dockerfile | ||
| run: | | ||
| echo "🔍 Checking Dockerfile..." | ||
| if [ -f "Dockerfile" ]; then | ||
| # Basic Dockerfile linting | ||
| if ! grep -q "FROM" Dockerfile; then | ||
| echo "❌ Dockerfile missing FROM instruction" | ||
| exit 1 | ||
| fi | ||
| if ! grep -q "ENTRYPOINT\|CMD" Dockerfile; then | ||
| echo "⚠️ Dockerfile missing ENTRYPOINT or CMD" | ||
| fi | ||
| echo "✅ Dockerfile appears valid" | ||
| else | ||
| echo "❌ Dockerfile not found" | ||
| exit 1 | ||
| fi | ||
| - name: Check dependency versions | ||
| run: | | ||
| echo "🔍 Checking for outdated dependencies..." | ||
| dotnet list src/SysDocs.sln package --outdated || true | ||
| - name: Security scan | ||
| run: | | ||
| echo "🔍 Running security vulnerability scan..." | ||
| dotnet list src/SysDocs.sln package --vulnerable || true | ||
| build: | ||
| name: Build & Test | ||
| runs-on: ${{ matrix.os }} | ||
| needs: sanity-checks | ||
| strategy: | ||
| matrix: | ||
| os: [ubuntu-latest, windows-latest, macos-latest] | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 # Full history for deterministic builds | ||
| - name: Setup .NET | ||
| uses: actions/setup-dotnet@v4 | ||
| with: | ||
| dotnet-version: ${{ env.DOTNET_VERSION }} | ||
| - name: Restore dependencies | ||
| run: dotnet restore src/SysDocs.sln | ||
| - name: Build | ||
| run: | | ||
| dotnet build src/SysDocs.sln \ | ||
| --configuration Release \ | ||
| --no-restore \ | ||
| /p:TreatWarningsAsErrors=true \ | ||
| /p:Deterministic=true \ | ||
| /p:ContinuousIntegrationBuild=true | ||
| - name: Test | ||
| run: | | ||
| dotnet test src/SysDocs.sln \ | ||
| --configuration Release \ | ||
| --no-build \ | ||
| --verbosity normal \ | ||
| --logger "trx;LogFileName=test-results.trx" \ | ||
| --collect:"XPlat Code Coverage" | ||
| - name: Test FR-01 Multi-Format Import | ||
| run: | | ||
| dotnet test tests/SysDocs.Tests/SysDocs.Tests.csproj \ | ||
| --configuration Release \ | ||
| --no-build \ | ||
| --filter "Category=FileFormat" \ | ||
| --verbosity normal | ||
| - name: Test FR-06/NFR-01 Determinism | ||
| run: | | ||
| dotnet test tests/SysDocs.Tests/SysDocs.Tests.csproj \ | ||
| --configuration Release \ | ||
| --no-build \ | ||
| --filter "Category=Determinism" \ | ||
| --verbosity normal | ||
| - name: Test FR-16-19 Manifest-Based Assembly | ||
| run: | | ||
| dotnet test tests/SysDocs.Tests/SysDocs.Tests.csproj \ | ||
| --configuration Release \ | ||
| --no-build \ | ||
| --filter "Category=Manifest" \ | ||
| --verbosity normal | ||
| - name: Test FR-20-25 SysML v2 Integration | ||
| run: | | ||
| dotnet test tests/SysDocs.Tests/SysDocs.Tests.csproj \ | ||
| --configuration Release \ | ||
| --no-build \ | ||
| --filter "Category=SysML" \ | ||
| --verbosity normal | ||
| - name: Upload test results | ||
| uses: actions/upload-artifact@v4 | ||
| if: always() | ||
| with: | ||
| name: test-results-${{ matrix.os }} | ||
| path: '**/TestResults/**' | ||
| - name: Publish (Linux only) | ||
| if: matrix.os == 'ubuntu-latest' | ||
| run: | | ||
| dotnet publish src/SysDocs.Cli/SysDocs.Cli.csproj \ | ||
| --configuration Release \ | ||
| --output publish \ | ||
| /p:Deterministic=true \ | ||
| /p:ContinuousIntegrationBuild=true | ||
| - name: Upload artifacts (Linux only) | ||
| if: matrix.os == 'ubuntu-latest' | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: sysdocs-linux | ||
| path: publish/ | ||
| docker: | ||
| name: Build Docker Image | ||
| runs-on: ubuntu-latest | ||
| needs: build | ||
| if: github.event_name == 'push' | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| - name: Set up Docker Buildx | ||
| uses: docker/setup-buildx-action@v3 | ||
| - name: Login to GitHub Container Registry | ||
| if: github.event_name != 'pull_request' | ||
| uses: docker/login-action@v3 | ||
| with: | ||
| registry: ghcr.io | ||
| username: ${{ github.actor }} | ||
| password: ${{ secrets.GITHUB_TOKEN }} | ||
| - name: Extract metadata | ||
| id: meta | ||
| uses: docker/metadata-action@v5 | ||
| with: | ||
| images: ghcr.io/${{ github.repository }} | ||
| tags: | | ||
| type=ref,event=branch | ||
| type=semver,pattern={{version}} | ||
| type=semver,pattern={{major}}.{{minor}} | ||
| type=sha | ||
| - name: Build and push | ||
| uses: docker/build-push-action@v5 | ||
| with: | ||
| context: . | ||
| push: ${{ github.event_name != 'pull_request' }} | ||
| tags: ${{ steps.meta.outputs.tags }} | ||
| labels: ${{ steps.meta.outputs.labels }} | ||
| cache-from: type=gha | ||
| cache-to: type=gha,mode=max | ||
| nix-build: | ||
| name: Nix Build (Deterministic) | ||
| runs-on: ${{ matrix.os }} | ||
| strategy: | ||
| matrix: | ||
| os: [ubuntu-latest, macos-latest] | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
| - name: Install Nix | ||
| uses: cachix/install-nix-action@v26 | ||
| with: | ||
| extra_nix_config: | | ||
| experimental-features = nix-command flakes | ||
| - name: Build with Nix | ||
| run: nix build -L --show-trace | ||
| - name: Calculate build hash | ||
| id: build_hash | ||
| run: | | ||
| HASH=$(nix-hash --type sha256 --base32 result) | ||
| echo "hash=$HASH" >> $GITHUB_OUTPUT | ||
| echo "Build hash (${{ matrix.os }}): $HASH" | ||
| - name: Upload build artifact | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: nix-build-${{ matrix.os }} | ||
| path: result/ | ||
| nix-cross-distro: | ||
| name: Cross-Distro Verification (BR-02) | ||
| runs-on: ubuntu-latest | ||
| strategy: | ||
| matrix: | ||
| distro: [fedora, debian] | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
| - name: Build on ${{ matrix.distro }} (Docker + Nix) | ||
| run: | | ||
| docker run --rm \ | ||
| -v $PWD:/workspace \ | ||
| -w /workspace \ | ||
| ${{ matrix.distro == 'fedora' && 'fedora:latest' || 'debian:stable' }} \ | ||
| bash -c " | ||
| # Install curl and basic tools | ||
| if [ '${{ matrix.distro }}' = 'fedora' ]; then | ||
| dnf install -y curl xz tar git | ||
| else | ||
| apt-get update && apt-get install -y curl xz-utils git | ||
| fi | ||
| # Install Nix | ||
| curl -L https://nixos.org/nix/install | sh -s -- --no-daemon | ||
| . /root/.nix-profile/etc/profile.d/nix.sh | ||
| # Enable flakes | ||
| mkdir -p /root/.config/nix | ||
| echo 'experimental-features = nix-command flakes' > /root/.config/nix/nix.conf | ||
| # Build | ||
| nix build -L --show-trace | ||
| # Calculate hash | ||
| nix-hash --type sha256 --base32 result | ||
| " > build_${{ matrix.distro }}.log 2>&1 | ||
| # Extract hash from log | ||
| HASH=$(grep -oP 'nix-hash.*\K[a-z0-9]{52}' build_${{ matrix.distro }}.log || tail -1 build_${{ matrix.distro }}.log) | ||
| echo "HASH_${{ matrix.distro }}=$HASH" >> $GITHUB_ENV | ||
| echo "Build hash (${{ matrix.distro }}): $HASH" | ||
| - name: Upload build log | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: cross-distro-${{ matrix.distro }} | ||
| path: build_${{ matrix.distro }}.log | ||
| determinism-check: | ||
| name: .NET Build Determinism Verification | ||
| runs-on: ubuntu-latest | ||
| needs: build | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| - name: Setup .NET | ||
| uses: actions/setup-dotnet@v4 | ||
| with: | ||
| dotnet-version: ${{ env.DOTNET_VERSION }} | ||
| - name: Build twice and compare | ||
| run: | | ||
| # First build | ||
| dotnet publish src/SysDocs.Cli/SysDocs.Cli.csproj \ | ||
| -c Release -o build1 \ | ||
| /p:Deterministic=true /p:ContinuousIntegrationBuild=true | ||
| # Wait a moment | ||
| sleep 2 | ||
| # Second build | ||
| dotnet publish src/SysDocs.Cli/SysDocs.Cli.csproj \ | ||
| -c Release -o build2 \ | ||
| /p:Deterministic=true /p:ContinuousIntegrationBuild=true | ||
| # Compare DLLs | ||
| if diff build1/SysDocs.dll build2/SysDocs.dll; then | ||
| echo "✅ .NET builds are deterministic" | ||
| else | ||
| echo "❌ .NET builds are NOT deterministic" | ||
| exit 1 | ||
| fi | ||
| output-determinism-check: | ||
| name: Output Determinism Tests (FR-06, NFR-01) | ||
| runs-on: ${{ matrix.os }} | ||
| needs: build | ||
| strategy: | ||
| matrix: | ||
| os: [ubuntu-latest, windows-latest, macos-latest] | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| - name: Setup .NET | ||
| uses: actions/setup-dotnet@v4 | ||
| with: | ||
| dotnet-version: ${{ env.DOTNET_VERSION }} | ||
| - name: Restore and build | ||
| run: | | ||
| dotnet restore src/SysDocs.sln | ||
| dotnet build src/SysDocs.sln --configuration Release --no-restore | ||
| - name: Run Determinism Tests (FR-06, NFR-01) | ||
| run: | | ||
| dotnet test tests/SysDocs.Tests/SysDocs.Tests.csproj \ | ||
| --configuration Release \ | ||
| --no-build \ | ||
| --filter "Category=Determinism" \ | ||
| --verbosity detailed \ | ||
| --logger "trx;LogFileName=determinism-test-results-${{ matrix.os }}.trx" | ||
| - name: Upload determinism test results | ||
| uses: actions/upload-artifact@v4 | ||
| if: always() | ||
| with: | ||
| name: determinism-test-results-${{ matrix.os }} | ||
| path: '**/determinism-test-results-*.trx' | ||
| - name: Generate platform hash file for expected results | ||
| shell: pwsh | ||
| run: | | ||
| # If expected results exist, compute their hashes for cross-platform comparison | ||
| $expectedResultsPath = "examples/expected-results/individual" | ||
| if (Test-Path $expectedResultsPath) { | ||
| $hashes = @{} | ||
| Get-ChildItem -Path $expectedResultsPath -Filter "*.pdf" | ForEach-Object { | ||
| $hash = (Get-FileHash -Path $_.FullName -Algorithm SHA256).Hash | ||
| $hashes[$_.Name] = $hash | ||
| Write-Output "$($_.Name): $hash" | ||
| } | ||
| # Save hashes to JSON file | ||
| $hashes | ConvertTo-Json | Out-File -FilePath "determinism-hashes-${{ matrix.os }}.json" | ||
| Write-Output "✅ Generated hash file for ${{ matrix.os }}" | ||
| } else { | ||
| Write-Output "⚠️ Expected results not found - skipping hash generation" | ||
| Write-Output "This is expected if expected results haven't been generated yet" | ||
| } | ||
| - name: Upload hash file for cross-platform comparison | ||
| uses: actions/upload-artifact@v4 | ||
| if: success() | ||
| with: | ||
| name: determinism-hashes-${{ matrix.os }} | ||
| path: determinism-hashes-${{ matrix.os }}.json | ||
| fr14-cross-platform-tests: | ||
| name: FR-14 Cross-Platform Tests | ||
| runs-on: ${{ matrix.os }} | ||
| needs: output-determinism-check | ||
| strategy: | ||
| matrix: | ||
| os: [ubuntu-latest, windows-latest, macos-latest] | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
| - name: Setup .NET | ||
| uses: actions/setup-dotnet@v4 | ||
| with: | ||
| dotnet-version: ${{ env.DOTNET_VERSION }} | ||
| - name: Restore and build | ||
| run: | | ||
| dotnet restore src/SysDocs.sln | ||
| dotnet build src/SysDocs.sln --configuration Release --no-restore | ||
| - name: Run FR-14 Cross-Platform Tests | ||
| run: | | ||
| dotnet test tests/SysDocs.Tests/SysDocs.Tests.csproj \ | ||
| --configuration Release \ | ||
| --no-build \ | ||
| --filter "Category=CrossPlatform" \ | ||
| --verbosity detailed \ | ||
| --logger "trx;LogFileName=fr14-test-results-${{ matrix.os }}.trx" | ||
| - name: Upload FR-14 test results | ||
| uses: actions/upload-artifact@v4 | ||
| if: always() | ||
| with: | ||
| name: fr14-test-results-${{ matrix.os }} | ||
| path: '**/fr14-test-results-*.trx' | ||
| generate-expected-results: | ||
| name: Generate Expected Test Results (if needed) | ||
| runs-on: ubuntu-latest | ||
| needs: build | ||
| if: github.event_name == 'push' && github.ref == 'refs/heads/alpha' | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| - name: Setup .NET | ||
| uses: actions/setup-dotnet@v4 | ||
| with: | ||
| dotnet-version: ${{ env.DOTNET_VERSION }} | ||
| - name: Check if expected results exist | ||
| id: check_expected | ||
| run: | | ||
| if [ -f "examples/expected-results/individual/01_markdown.pdf" ]; then | ||
| echo "exists=true" >> $GITHUB_OUTPUT | ||
| echo "✅ Expected results already exist" | ||
| else | ||
| echo "exists=false" >> $GITHUB_OUTPUT | ||
| echo "⚠️ Expected results do not exist - need to generate" | ||
| fi | ||
| - name: Build SysDocs | ||
| if: steps.check_expected.outputs.exists == 'false' | ||
| run: | | ||
| dotnet restore src/SysDocs.sln | ||
| dotnet build src/SysDocs.sln --configuration Release --no-restore | ||
| - name: Generate expected results | ||
| if: steps.check_expected.outputs.exists == 'false' | ||
| run: | | ||
| # TODO: Generate expected results when implementation is ready | ||
| # dotnet run --project src/SysDocs.Cli --configuration Release -- \ | ||
| # --input examples/adns-project/12_SRS_ADNS.md \ | ||
| # --output examples/expected-results/individual/01_markdown.pdf | ||
| echo "⚠️ Expected results generation not yet implemented" | ||
| echo "Run manually: make generate-expected-results" | ||
| - name: Commit expected results (if generated) | ||
| if: steps.check_expected.outputs.exists == 'false' | ||
| run: | | ||
| git config user.name "github-actions[bot]" | ||
| git config user.email "github-actions[bot]@users.noreply.github.com" | ||
| if [ -n "$(git status --porcelain examples/expected-results/)" ]; then | ||
| git add examples/expected-results/ | ||
| git commit -m "chore: Generate expected test results for FR-01, FR-06, FR-14" | ||
| git push | ||
| else | ||
| echo "No new expected results to commit" | ||
| fi | ||
| cross-platform-hash-verification: | ||
| name: Verify Cross-Platform Determinism (FR-14, NFR-01) | ||
| runs-on: ubuntu-latest | ||
| needs: [output-determinism-check, fr14-cross-platform-tests] | ||
| steps: | ||
| - name: Download all determinism hash files | ||
| uses: actions/download-artifact@v4 | ||
| with: | ||
| pattern: determinism-hashes-* | ||
| merge-multiple: false | ||
| - name: Compare hashes across platforms | ||
| shell: pwsh | ||
| run: | | ||
| Write-Output "=== Cross-Platform Determinism Verification (FR-14, NFR-01) ===" | ||
| Write-Output "" | ||
| Write-Output "Verifying that all platforms produce byte-for-byte identical output" | ||
| Write-Output "when processing the same inputs (examples/expected-results/)." | ||
| Write-Output "" | ||
| $platforms = @("ubuntu-latest", "windows-latest", "macos-latest") | ||
| $allHashes = @{} | ||
| $foundAny = $false | ||
| foreach ($platform in $platforms) { | ||
| $hashFile = "determinism-hashes-$platform/determinism-hashes-$platform.json" | ||
| if (Test-Path $hashFile) { | ||
| $platformHashes = Get-Content $hashFile | ConvertFrom-Json | ||
| $allHashes[$platform] = $platformHashes | ||
| $foundAny = $true | ||
| Write-Output "✅ Loaded hashes for $platform" | ||
| } else { | ||
| Write-Output "⚠️ No hash file found for $platform" | ||
| } | ||
| } | ||
| if (-not $foundAny) { | ||
| Write-Output "" | ||
| Write-Output "⚠️ No platform hashes available for comparison" | ||
| Write-Output "This is expected if examples/expected-results/ PDFs haven't been generated yet." | ||
| Write-Output "" | ||
| Write-Output "To generate expected results:" | ||
| Write-Output " 1. Implement SysDocs PDF generation" | ||
| Write-Output " 2. Run: make generate-expected-results" | ||
| Write-Output " 3. Commit the generated PDFs to examples/expected-results/" | ||
| Write-Output "" | ||
| Write-Output "Skipping cross-platform verification for now." | ||
| exit 0 | ||
| } | ||
| # Compare hashes across platforms | ||
| Write-Output "" | ||
| Write-Output "Comparing hashes across platforms..." | ||
| Write-Output "" | ||
| $allMatch = $true | ||
| $linuxHashes = $allHashes["ubuntu-latest"] | ||
| if ($linuxHashes) { | ||
| foreach ($file in $linuxHashes.PSObject.Properties.Name) { | ||
| $linuxHash = $linuxHashes.$file | ||
| Write-Output "File: $file" | ||
| Write-Output " Linux: $linuxHash" | ||
| if ($allHashes["windows-latest"]) { | ||
| $windowsHash = $allHashes["windows-latest"].$file | ||
| Write-Output " Windows: $windowsHash" | ||
| if ($windowsHash -ne $linuxHash) { | ||
| Write-Output " ❌ MISMATCH: Windows hash differs from Linux" | ||
| $allMatch = $false | ||
| } | ||
| } | ||
| if ($allHashes["macos-latest"]) { | ||
| $macosHash = $allHashes["macos-latest"].$file | ||
| Write-Output " macOS: $macosHash" | ||
| if ($macosHash -ne $linuxHash) { | ||
| Write-Output " ❌ MISMATCH: macOS hash differs from Linux" | ||
| $allMatch = $false | ||
| } | ||
| } | ||
| if ($allMatch) { | ||
| Write-Output " ✅ All platforms match (byte-for-byte identical)" | ||
| } | ||
| Write-Output "" | ||
| } | ||
| } | ||
| Write-Output "=== Verification Summary ===" | ||
| if ($allMatch) { | ||
| Write-Output "✅ FR-14 PASSED: All platforms produce identical output" | ||
| Write-Output "✅ NFR-01 PASSED: Output is 100% reproducible (byte-for-byte)" | ||
| Write-Output "✅ FR-06 PASSED: Output is deterministic" | ||
| } else { | ||
| Write-Error "❌ FAILED: Platform outputs differ" | ||
| Write-Error "❌ FR-14 VIOLATION: Cross-platform determinism failed" | ||
| Write-Error "❌ NFR-01 VIOLATION: Output is not reproducible" | ||
| exit 1 | ||
| } | ||
| traceability-check: | ||
| name: V&V Traceability Check | ||
| runs-on: ubuntu-latest | ||
| needs: build | ||
| steps: | ||
| - name: Placeholder for traceability check | ||
| run: echo "Traceability check placeholder" | ||
| manual-verification-reminder: | ||
| name: Manual Verification Reminder | ||
| runs-on: ubuntu-latest | ||
| needs: build | ||
| if: github.event_name == 'pull_request' | ||
| steps: | ||
| - name: Check for manual verification requirements | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| const pr = context.payload.pull_request; | ||
| const body = pr.body || ''; | ||
| // Check if manual V&V checklist is present | ||
| const hasManualChecklist = body.includes('Manual Requirements Verification'); | ||
| if (!hasManualChecklist) { | ||
| core.setFailed('❌ PR description missing Manual V&V Checklist. Please use the PR template.'); | ||
| return; | ||
| } | ||
| // List of manual requirements that need verification | ||
| const manualRequirements = ['FR-08', 'FR-09', 'FR-10', 'FR-11', 'NFR-03', 'NFR-06']; | ||
| // Create a comment with reminder | ||
| const comment = `## 🔍 Manual V&V Verification Required | ||
| This PR requires reviewer verification of manual requirements. | ||
| **Manual Requirements** (cannot be automatically tested): | ||
| - **FR-08**: Change and trace report generation | ||
| - **FR-09**: Tool qualification support | ||
| - **FR-10**: INCOSE SE Handbook alignment | ||
| - **FR-11**: V-Model phase support | ||
| - **NFR-03**: Performance for large docs (>250 pages) | ||
| - **NFR-06**: Secure Git credentials handling | ||
| **Reviewer Actions**: | ||
| 1. Check which requirements are affected by this PR | ||
| 2. For each affected requirement, perform manual verification per [REQUIREMENTS_MATRIX.md](https://github.com/${context.repo.owner}/${context.repo.repo}/blob/main/REQUIREMENTS_MATRIX.md) | ||
| 3. Check off items in the PR description's "Manual V&V Checklist" | ||
| 4. Document verification results in review comments | ||
| **See**: [BRANCH_STRATEGY.md - Reviewer Guidelines](https://github.com/${context.repo.owner}/${context.repo.repo}/blob/main/BRANCH_STRATEGY.md#reviewer-guidelines) | ||
| `; | ||
| // Only post if this is a new PR or the comment doesn't exist | ||
| const comments = await github.rest.issues.listComments({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: pr.number | ||
| }); | ||
| const botComment = comments.data.find(c => | ||
| c.user.type === 'Bot' && c.body.includes('Manual V&V Verification Required') | ||
| ); | ||
| if (!botComment) { | ||
| await github.rest.issues.createComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: pr.number, | ||
| body: comment | ||
| }); | ||
| } | ||