Skip to content

feat: Add SysML v2 integration requirements (FR-20 to FR-25) #4

feat: Add SysML v2 integration requirements (FR-20 to FR-25)

feat: Add SysML v2 integration requirements (FR-20 to FR-25) #4

Workflow file for this run

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)

Check failure on line 759 in .github/workflows/ci.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/ci.yml

Invalid workflow file

You have an error in your yaml syntax on line 759
`;
// 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
});
}