sprints work
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
# .gitea/workflows/determinism-gate.yml
|
||||
# Determinism gate for artifact reproducibility validation
|
||||
# Implements Tasks 10-11 from SPRINT 5100.0007.0003
|
||||
# Updated: Task 13 from SPRINT 8200.0001.0003 - Add schema validation dependency
|
||||
|
||||
name: Determinism Gate
|
||||
|
||||
@@ -11,6 +12,8 @@ on:
|
||||
- 'src/**'
|
||||
- 'tests/integration/StellaOps.Integration.Determinism/**'
|
||||
- 'tests/baselines/determinism/**'
|
||||
- 'bench/golden-corpus/**'
|
||||
- 'docs/schemas/**'
|
||||
- '.gitea/workflows/determinism-gate.yml'
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
@@ -27,6 +30,11 @@ on:
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
skip_schema_validation:
|
||||
description: 'Skip schema validation step'
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
DOTNET_VERSION: '10.0.100'
|
||||
@@ -35,10 +43,90 @@ env:
|
||||
BASELINE_DIR: tests/baselines/determinism
|
||||
|
||||
jobs:
|
||||
# ===========================================================================
|
||||
# Schema Validation Gate (runs before determinism checks)
|
||||
# ===========================================================================
|
||||
schema-validation:
|
||||
name: Schema Validation
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.event.inputs.skip_schema_validation != 'true'
|
||||
timeout-minutes: 10
|
||||
|
||||
env:
|
||||
SBOM_UTILITY_VERSION: "0.16.0"
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install sbom-utility
|
||||
run: |
|
||||
curl -sSfL "https://github.com/CycloneDX/sbom-utility/releases/download/v${SBOM_UTILITY_VERSION}/sbom-utility-v${SBOM_UTILITY_VERSION}-linux-amd64.tar.gz" | tar xz
|
||||
sudo mv sbom-utility /usr/local/bin/
|
||||
sbom-utility --version
|
||||
|
||||
- name: Validate CycloneDX fixtures
|
||||
run: |
|
||||
set -e
|
||||
SCHEMA="docs/schemas/cyclonedx-bom-1.6.schema.json"
|
||||
FIXTURE_DIRS=(
|
||||
"bench/golden-corpus"
|
||||
"tests/fixtures"
|
||||
"seed-data"
|
||||
)
|
||||
|
||||
FOUND=0
|
||||
PASSED=0
|
||||
FAILED=0
|
||||
|
||||
for dir in "${FIXTURE_DIRS[@]}"; do
|
||||
if [ -d "$dir" ]; then
|
||||
# Skip invalid fixtures directory (used for negative testing)
|
||||
while IFS= read -r -d '' file; do
|
||||
if [[ "$file" == *"/invalid/"* ]]; then
|
||||
continue
|
||||
fi
|
||||
if grep -q '"bomFormat".*"CycloneDX"' "$file" 2>/dev/null; then
|
||||
FOUND=$((FOUND + 1))
|
||||
echo "::group::Validating: $file"
|
||||
if sbom-utility validate --input-file "$file" --schema "$SCHEMA" 2>&1; then
|
||||
echo "✅ PASS: $file"
|
||||
PASSED=$((PASSED + 1))
|
||||
else
|
||||
echo "❌ FAIL: $file"
|
||||
FAILED=$((FAILED + 1))
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
fi
|
||||
done < <(find "$dir" -name '*.json' -type f -print0 2>/dev/null || true)
|
||||
fi
|
||||
done
|
||||
|
||||
echo "================================================"
|
||||
echo "CycloneDX Validation Summary"
|
||||
echo "================================================"
|
||||
echo "Found: $FOUND fixtures"
|
||||
echo "Passed: $PASSED"
|
||||
echo "Failed: $FAILED"
|
||||
echo "================================================"
|
||||
|
||||
if [ "$FAILED" -gt 0 ]; then
|
||||
echo "::error::$FAILED CycloneDX fixtures failed validation"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Schema validation summary
|
||||
run: |
|
||||
echo "## Schema Validation" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "✅ All SBOM fixtures passed schema validation" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# ===========================================================================
|
||||
# Determinism Validation Gate
|
||||
# ===========================================================================
|
||||
determinism-gate:
|
||||
needs: [schema-validation]
|
||||
if: always() && (needs.schema-validation.result == 'success' || needs.schema-validation.result == 'skipped')
|
||||
name: Determinism Validation
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 30
|
||||
@@ -156,7 +244,7 @@ jobs:
|
||||
update-baselines:
|
||||
name: Update Baselines
|
||||
runs-on: ubuntu-22.04
|
||||
needs: determinism-gate
|
||||
needs: [schema-validation, determinism-gate]
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.update_baselines == 'true'
|
||||
|
||||
steps:
|
||||
@@ -206,18 +294,26 @@ jobs:
|
||||
drift-check:
|
||||
name: Drift Detection Gate
|
||||
runs-on: ubuntu-22.04
|
||||
needs: determinism-gate
|
||||
needs: [schema-validation, determinism-gate]
|
||||
if: always()
|
||||
|
||||
steps:
|
||||
- name: Check for drift
|
||||
run: |
|
||||
SCHEMA_STATUS="${{ needs.schema-validation.result || 'skipped' }}"
|
||||
DRIFTED="${{ needs.determinism-gate.outputs.drifted || '0' }}"
|
||||
STATUS="${{ needs.determinism-gate.outputs.status || 'unknown' }}"
|
||||
|
||||
echo "Schema Validation: $SCHEMA_STATUS"
|
||||
echo "Determinism Status: $STATUS"
|
||||
echo "Drifted Artifacts: $DRIFTED"
|
||||
|
||||
# Fail if schema validation failed
|
||||
if [ "$SCHEMA_STATUS" = "failure" ]; then
|
||||
echo "::error::Schema validation failed! Fix SBOM schema issues before determinism check."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$STATUS" = "fail" ] || [ "$DRIFTED" != "0" ]; then
|
||||
echo "::error::Determinism drift detected! $DRIFTED artifact(s) have changed."
|
||||
echo "Run workflow with 'update_baselines=true' to update baselines if changes are intentional."
|
||||
@@ -230,4 +326,5 @@ jobs:
|
||||
run: |
|
||||
echo "## Drift Detection Gate" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Status: ${{ needs.determinism-gate.outputs.status || 'pass' }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Schema Validation: ${{ needs.schema-validation.result || 'skipped' }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Determinism Status: ${{ needs.determinism-gate.outputs.status || 'pass' }}" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
473
.gitea/workflows/e2e-reproducibility.yml
Normal file
473
.gitea/workflows/e2e-reproducibility.yml
Normal file
@@ -0,0 +1,473 @@
|
||||
# =============================================================================
|
||||
# e2e-reproducibility.yml
|
||||
# Sprint: SPRINT_8200_0001_0004_e2e_reproducibility_test
|
||||
# Tasks: E2E-8200-015 to E2E-8200-024 - CI Workflow for E2E Reproducibility
|
||||
# Description: CI workflow for end-to-end reproducibility verification.
|
||||
# Runs tests across multiple platforms and compares results.
|
||||
# =============================================================================
|
||||
|
||||
name: E2E Reproducibility
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'tests/integration/StellaOps.Integration.E2E/**'
|
||||
- 'tests/fixtures/**'
|
||||
- '.gitea/workflows/e2e-reproducibility.yml'
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'tests/integration/StellaOps.Integration.E2E/**'
|
||||
schedule:
|
||||
# Nightly at 2am UTC
|
||||
- cron: '0 2 * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
run_cross_platform:
|
||||
description: 'Run cross-platform tests'
|
||||
type: boolean
|
||||
default: false
|
||||
update_baseline:
|
||||
description: 'Update golden baseline (requires approval)'
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
env:
|
||||
DOTNET_VERSION: '10.0.x'
|
||||
DOTNET_NOLOGO: true
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
||||
|
||||
jobs:
|
||||
# =============================================================================
|
||||
# Job: Run E2E reproducibility tests on primary platform
|
||||
# =============================================================================
|
||||
reproducibility-ubuntu:
|
||||
name: E2E Reproducibility (Ubuntu)
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
verdict_hash: ${{ steps.run-tests.outputs.verdict_hash }}
|
||||
manifest_hash: ${{ steps.run-tests.outputs.manifest_hash }}
|
||||
envelope_hash: ${{ steps.run-tests.outputs.envelope_hash }}
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
env:
|
||||
POSTGRES_USER: test_user
|
||||
POSTGRES_PASSWORD: test_password
|
||||
POSTGRES_DB: stellaops_e2e_test
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: ${{ env.DOTNET_VERSION }}
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore tests/integration/StellaOps.Integration.E2E/StellaOps.Integration.E2E.csproj
|
||||
|
||||
- name: Build E2E tests
|
||||
run: dotnet build tests/integration/StellaOps.Integration.E2E/StellaOps.Integration.E2E.csproj --no-restore -c Release
|
||||
|
||||
- name: Run E2E reproducibility tests
|
||||
id: run-tests
|
||||
run: |
|
||||
dotnet test tests/integration/StellaOps.Integration.E2E/StellaOps.Integration.E2E.csproj \
|
||||
--no-build \
|
||||
-c Release \
|
||||
--logger "trx;LogFileName=e2e-results.trx" \
|
||||
--logger "console;verbosity=detailed" \
|
||||
--results-directory ./TestResults \
|
||||
-- RunConfiguration.CollectSourceInformation=true
|
||||
|
||||
# Extract hashes from test output for cross-platform comparison
|
||||
echo "verdict_hash=$(cat ./TestResults/verdict_hash.txt 2>/dev/null || echo 'NOT_FOUND')" >> $GITHUB_OUTPUT
|
||||
echo "manifest_hash=$(cat ./TestResults/manifest_hash.txt 2>/dev/null || echo 'NOT_FOUND')" >> $GITHUB_OUTPUT
|
||||
echo "envelope_hash=$(cat ./TestResults/envelope_hash.txt 2>/dev/null || echo 'NOT_FOUND')" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
ConnectionStrings__ScannerDb: "Host=localhost;Port=5432;Database=stellaops_e2e_test;Username=test_user;Password=test_password"
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: e2e-results-ubuntu
|
||||
path: ./TestResults/
|
||||
retention-days: 14
|
||||
|
||||
- name: Upload hash artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: hashes-ubuntu
|
||||
path: |
|
||||
./TestResults/verdict_hash.txt
|
||||
./TestResults/manifest_hash.txt
|
||||
./TestResults/envelope_hash.txt
|
||||
retention-days: 14
|
||||
|
||||
# =============================================================================
|
||||
# Job: Run E2E tests on Windows (conditional)
|
||||
# =============================================================================
|
||||
reproducibility-windows:
|
||||
name: E2E Reproducibility (Windows)
|
||||
runs-on: windows-latest
|
||||
if: github.event_name == 'schedule' || github.event.inputs.run_cross_platform == 'true'
|
||||
outputs:
|
||||
verdict_hash: ${{ steps.run-tests.outputs.verdict_hash }}
|
||||
manifest_hash: ${{ steps.run-tests.outputs.manifest_hash }}
|
||||
envelope_hash: ${{ steps.run-tests.outputs.envelope_hash }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: ${{ env.DOTNET_VERSION }}
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore tests/integration/StellaOps.Integration.E2E/StellaOps.Integration.E2E.csproj
|
||||
|
||||
- name: Build E2E tests
|
||||
run: dotnet build tests/integration/StellaOps.Integration.E2E/StellaOps.Integration.E2E.csproj --no-restore -c Release
|
||||
|
||||
- name: Run E2E reproducibility tests
|
||||
id: run-tests
|
||||
run: |
|
||||
dotnet test tests/integration/StellaOps.Integration.E2E/StellaOps.Integration.E2E.csproj `
|
||||
--no-build `
|
||||
-c Release `
|
||||
--logger "trx;LogFileName=e2e-results.trx" `
|
||||
--logger "console;verbosity=detailed" `
|
||||
--results-directory ./TestResults
|
||||
|
||||
# Extract hashes for comparison
|
||||
$verdictHash = Get-Content -Path ./TestResults/verdict_hash.txt -ErrorAction SilentlyContinue
|
||||
$manifestHash = Get-Content -Path ./TestResults/manifest_hash.txt -ErrorAction SilentlyContinue
|
||||
$envelopeHash = Get-Content -Path ./TestResults/envelope_hash.txt -ErrorAction SilentlyContinue
|
||||
|
||||
"verdict_hash=$($verdictHash ?? 'NOT_FOUND')" >> $env:GITHUB_OUTPUT
|
||||
"manifest_hash=$($manifestHash ?? 'NOT_FOUND')" >> $env:GITHUB_OUTPUT
|
||||
"envelope_hash=$($envelopeHash ?? 'NOT_FOUND')" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: e2e-results-windows
|
||||
path: ./TestResults/
|
||||
retention-days: 14
|
||||
|
||||
- name: Upload hash artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: hashes-windows
|
||||
path: |
|
||||
./TestResults/verdict_hash.txt
|
||||
./TestResults/manifest_hash.txt
|
||||
./TestResults/envelope_hash.txt
|
||||
retention-days: 14
|
||||
|
||||
# =============================================================================
|
||||
# Job: Run E2E tests on macOS (conditional)
|
||||
# =============================================================================
|
||||
reproducibility-macos:
|
||||
name: E2E Reproducibility (macOS)
|
||||
runs-on: macos-latest
|
||||
if: github.event_name == 'schedule' || github.event.inputs.run_cross_platform == 'true'
|
||||
outputs:
|
||||
verdict_hash: ${{ steps.run-tests.outputs.verdict_hash }}
|
||||
manifest_hash: ${{ steps.run-tests.outputs.manifest_hash }}
|
||||
envelope_hash: ${{ steps.run-tests.outputs.envelope_hash }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: ${{ env.DOTNET_VERSION }}
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore tests/integration/StellaOps.Integration.E2E/StellaOps.Integration.E2E.csproj
|
||||
|
||||
- name: Build E2E tests
|
||||
run: dotnet build tests/integration/StellaOps.Integration.E2E/StellaOps.Integration.E2E.csproj --no-restore -c Release
|
||||
|
||||
- name: Run E2E reproducibility tests
|
||||
id: run-tests
|
||||
run: |
|
||||
dotnet test tests/integration/StellaOps.Integration.E2E/StellaOps.Integration.E2E.csproj \
|
||||
--no-build \
|
||||
-c Release \
|
||||
--logger "trx;LogFileName=e2e-results.trx" \
|
||||
--logger "console;verbosity=detailed" \
|
||||
--results-directory ./TestResults
|
||||
|
||||
# Extract hashes for comparison
|
||||
echo "verdict_hash=$(cat ./TestResults/verdict_hash.txt 2>/dev/null || echo 'NOT_FOUND')" >> $GITHUB_OUTPUT
|
||||
echo "manifest_hash=$(cat ./TestResults/manifest_hash.txt 2>/dev/null || echo 'NOT_FOUND')" >> $GITHUB_OUTPUT
|
||||
echo "envelope_hash=$(cat ./TestResults/envelope_hash.txt 2>/dev/null || echo 'NOT_FOUND')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: e2e-results-macos
|
||||
path: ./TestResults/
|
||||
retention-days: 14
|
||||
|
||||
- name: Upload hash artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: hashes-macos
|
||||
path: |
|
||||
./TestResults/verdict_hash.txt
|
||||
./TestResults/manifest_hash.txt
|
||||
./TestResults/envelope_hash.txt
|
||||
retention-days: 14
|
||||
|
||||
# =============================================================================
|
||||
# Job: Cross-platform hash comparison
|
||||
# =============================================================================
|
||||
cross-platform-compare:
|
||||
name: Cross-Platform Hash Comparison
|
||||
runs-on: ubuntu-latest
|
||||
needs: [reproducibility-ubuntu, reproducibility-windows, reproducibility-macos]
|
||||
if: always() && (github.event_name == 'schedule' || github.event.inputs.run_cross_platform == 'true')
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download Ubuntu hashes
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: hashes-ubuntu
|
||||
path: ./hashes/ubuntu
|
||||
|
||||
- name: Download Windows hashes
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: hashes-windows
|
||||
path: ./hashes/windows
|
||||
continue-on-error: true
|
||||
|
||||
- name: Download macOS hashes
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: hashes-macos
|
||||
path: ./hashes/macos
|
||||
continue-on-error: true
|
||||
|
||||
- name: Compare hashes across platforms
|
||||
run: |
|
||||
echo "=== Cross-Platform Hash Comparison ==="
|
||||
echo ""
|
||||
|
||||
ubuntu_verdict=$(cat ./hashes/ubuntu/verdict_hash.txt 2>/dev/null || echo "NOT_AVAILABLE")
|
||||
windows_verdict=$(cat ./hashes/windows/verdict_hash.txt 2>/dev/null || echo "NOT_AVAILABLE")
|
||||
macos_verdict=$(cat ./hashes/macos/verdict_hash.txt 2>/dev/null || echo "NOT_AVAILABLE")
|
||||
|
||||
echo "Verdict Hashes:"
|
||||
echo " Ubuntu: $ubuntu_verdict"
|
||||
echo " Windows: $windows_verdict"
|
||||
echo " macOS: $macos_verdict"
|
||||
echo ""
|
||||
|
||||
ubuntu_manifest=$(cat ./hashes/ubuntu/manifest_hash.txt 2>/dev/null || echo "NOT_AVAILABLE")
|
||||
windows_manifest=$(cat ./hashes/windows/manifest_hash.txt 2>/dev/null || echo "NOT_AVAILABLE")
|
||||
macos_manifest=$(cat ./hashes/macos/manifest_hash.txt 2>/dev/null || echo "NOT_AVAILABLE")
|
||||
|
||||
echo "Manifest Hashes:"
|
||||
echo " Ubuntu: $ubuntu_manifest"
|
||||
echo " Windows: $windows_manifest"
|
||||
echo " macOS: $macos_manifest"
|
||||
echo ""
|
||||
|
||||
# Check if all available hashes match
|
||||
all_match=true
|
||||
|
||||
if [ "$ubuntu_verdict" != "NOT_AVAILABLE" ] && [ "$windows_verdict" != "NOT_AVAILABLE" ]; then
|
||||
if [ "$ubuntu_verdict" != "$windows_verdict" ]; then
|
||||
echo "❌ FAIL: Ubuntu and Windows verdict hashes differ!"
|
||||
all_match=false
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$ubuntu_verdict" != "NOT_AVAILABLE" ] && [ "$macos_verdict" != "NOT_AVAILABLE" ]; then
|
||||
if [ "$ubuntu_verdict" != "$macos_verdict" ]; then
|
||||
echo "❌ FAIL: Ubuntu and macOS verdict hashes differ!"
|
||||
all_match=false
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$all_match" = true ]; then
|
||||
echo "✅ All available platform hashes match!"
|
||||
else
|
||||
echo ""
|
||||
echo "Cross-platform reproducibility verification FAILED."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Create comparison report
|
||||
run: |
|
||||
cat > ./cross-platform-report.md << 'EOF'
|
||||
# Cross-Platform Reproducibility Report
|
||||
|
||||
## Test Run Information
|
||||
- **Workflow Run:** ${{ github.run_id }}
|
||||
- **Trigger:** ${{ github.event_name }}
|
||||
- **Commit:** ${{ github.sha }}
|
||||
- **Branch:** ${{ github.ref_name }}
|
||||
|
||||
## Hash Comparison
|
||||
|
||||
| Platform | Verdict Hash | Manifest Hash | Status |
|
||||
|----------|--------------|---------------|--------|
|
||||
| Ubuntu | ${{ needs.reproducibility-ubuntu.outputs.verdict_hash }} | ${{ needs.reproducibility-ubuntu.outputs.manifest_hash }} | ✅ |
|
||||
| Windows | ${{ needs.reproducibility-windows.outputs.verdict_hash }} | ${{ needs.reproducibility-windows.outputs.manifest_hash }} | ${{ needs.reproducibility-windows.result == 'success' && '✅' || '⚠️' }} |
|
||||
| macOS | ${{ needs.reproducibility-macos.outputs.verdict_hash }} | ${{ needs.reproducibility-macos.outputs.manifest_hash }} | ${{ needs.reproducibility-macos.result == 'success' && '✅' || '⚠️' }} |
|
||||
|
||||
## Conclusion
|
||||
|
||||
Cross-platform reproducibility: **${{ job.status == 'success' && 'VERIFIED' || 'NEEDS REVIEW' }}**
|
||||
EOF
|
||||
|
||||
cat ./cross-platform-report.md
|
||||
|
||||
- name: Upload comparison report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cross-platform-report
|
||||
path: ./cross-platform-report.md
|
||||
retention-days: 30
|
||||
|
||||
# =============================================================================
|
||||
# Job: Golden baseline comparison
|
||||
# =============================================================================
|
||||
golden-baseline:
|
||||
name: Golden Baseline Verification
|
||||
runs-on: ubuntu-latest
|
||||
needs: [reproducibility-ubuntu]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download current hashes
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: hashes-ubuntu
|
||||
path: ./current
|
||||
|
||||
- name: Compare with golden baseline
|
||||
run: |
|
||||
echo "=== Golden Baseline Comparison ==="
|
||||
|
||||
baseline_file="./bench/determinism/golden-baseline/e2e-hashes.json"
|
||||
|
||||
if [ ! -f "$baseline_file" ]; then
|
||||
echo "⚠️ Golden baseline not found. Skipping comparison."
|
||||
echo "To create baseline, run with update_baseline=true"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
current_verdict=$(cat ./current/verdict_hash.txt 2>/dev/null || echo "NOT_FOUND")
|
||||
baseline_verdict=$(jq -r '.verdict_hash' "$baseline_file" 2>/dev/null || echo "NOT_FOUND")
|
||||
|
||||
echo "Current verdict hash: $current_verdict"
|
||||
echo "Baseline verdict hash: $baseline_verdict"
|
||||
|
||||
if [ "$current_verdict" != "$baseline_verdict" ]; then
|
||||
echo ""
|
||||
echo "❌ FAIL: Current run does not match golden baseline!"
|
||||
echo ""
|
||||
echo "This may indicate:"
|
||||
echo " 1. An intentional change requiring baseline update"
|
||||
echo " 2. An unintentional regression in reproducibility"
|
||||
echo ""
|
||||
echo "To update baseline, run workflow with update_baseline=true"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ Current run matches golden baseline!"
|
||||
|
||||
- name: Update golden baseline (if requested)
|
||||
if: github.event.inputs.update_baseline == 'true'
|
||||
run: |
|
||||
mkdir -p ./bench/determinism/golden-baseline
|
||||
|
||||
cat > ./bench/determinism/golden-baseline/e2e-hashes.json << EOF
|
||||
{
|
||||
"verdict_hash": "$(cat ./current/verdict_hash.txt 2>/dev/null || echo 'NOT_SET')",
|
||||
"manifest_hash": "$(cat ./current/manifest_hash.txt 2>/dev/null || echo 'NOT_SET')",
|
||||
"envelope_hash": "$(cat ./current/envelope_hash.txt 2>/dev/null || echo 'NOT_SET')",
|
||||
"updated_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
||||
"updated_by": "${{ github.actor }}",
|
||||
"commit": "${{ github.sha }}"
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "Golden baseline updated:"
|
||||
cat ./bench/determinism/golden-baseline/e2e-hashes.json
|
||||
|
||||
- name: Commit baseline update
|
||||
if: github.event.inputs.update_baseline == 'true'
|
||||
uses: stefanzweifel/git-auto-commit-action@v5
|
||||
with:
|
||||
commit_message: "chore: Update E2E reproducibility golden baseline"
|
||||
file_pattern: bench/determinism/golden-baseline/e2e-hashes.json
|
||||
|
||||
# =============================================================================
|
||||
# Job: Status check gate
|
||||
# =============================================================================
|
||||
reproducibility-gate:
|
||||
name: Reproducibility Gate
|
||||
runs-on: ubuntu-latest
|
||||
needs: [reproducibility-ubuntu, golden-baseline]
|
||||
if: always()
|
||||
|
||||
steps:
|
||||
- name: Check reproducibility status
|
||||
run: |
|
||||
ubuntu_status="${{ needs.reproducibility-ubuntu.result }}"
|
||||
baseline_status="${{ needs.golden-baseline.result }}"
|
||||
|
||||
echo "Ubuntu E2E tests: $ubuntu_status"
|
||||
echo "Golden baseline: $baseline_status"
|
||||
|
||||
if [ "$ubuntu_status" != "success" ]; then
|
||||
echo "❌ E2E reproducibility tests failed!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$baseline_status" == "failure" ]; then
|
||||
echo "⚠️ Golden baseline comparison failed (may require review)"
|
||||
# Don't fail the gate for baseline mismatch - it may be intentional
|
||||
fi
|
||||
|
||||
echo "✅ Reproducibility gate passed!"
|
||||
@@ -231,10 +231,75 @@ jobs:
|
||||
echo "::warning::No OpenVEX fixtures found to validate"
|
||||
fi
|
||||
|
||||
# Negative testing: verify that invalid fixtures are correctly rejected
|
||||
validate-negative:
|
||||
name: Validate Negative Test Cases
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install sbom-utility
|
||||
run: |
|
||||
curl -sSfL "https://github.com/CycloneDX/sbom-utility/releases/download/v${SBOM_UTILITY_VERSION}/sbom-utility-v${SBOM_UTILITY_VERSION}-linux-amd64.tar.gz" | tar xz
|
||||
sudo mv sbom-utility /usr/local/bin/
|
||||
sbom-utility --version
|
||||
|
||||
- name: Verify invalid fixtures fail validation
|
||||
run: |
|
||||
set -e
|
||||
SCHEMA="docs/schemas/cyclonedx-bom-1.6.schema.json"
|
||||
INVALID_DIR="tests/fixtures/invalid"
|
||||
|
||||
if [ ! -d "$INVALID_DIR" ]; then
|
||||
echo "::warning::No invalid fixtures directory found at $INVALID_DIR"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
EXPECTED_FAILURES=0
|
||||
ACTUAL_FAILURES=0
|
||||
UNEXPECTED_PASSES=0
|
||||
|
||||
while IFS= read -r -d '' file; do
|
||||
if grep -q '"bomFormat".*"CycloneDX"' "$file" 2>/dev/null; then
|
||||
EXPECTED_FAILURES=$((EXPECTED_FAILURES + 1))
|
||||
echo "::group::Testing invalid fixture: $file"
|
||||
|
||||
# This SHOULD fail - if it passes, that's an error
|
||||
if sbom-utility validate --input-file "$file" --schema "$SCHEMA" 2>&1; then
|
||||
echo "❌ UNEXPECTED PASS: $file (should have failed validation)"
|
||||
UNEXPECTED_PASSES=$((UNEXPECTED_PASSES + 1))
|
||||
else
|
||||
echo "✅ EXPECTED FAILURE: $file (correctly rejected)"
|
||||
ACTUAL_FAILURES=$((ACTUAL_FAILURES + 1))
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
fi
|
||||
done < <(find "$INVALID_DIR" -name '*.json' -type f -print0 2>/dev/null || true)
|
||||
|
||||
echo "================================================"
|
||||
echo "Negative Test Summary"
|
||||
echo "================================================"
|
||||
echo "Expected failures: $EXPECTED_FAILURES"
|
||||
echo "Actual failures: $ACTUAL_FAILURES"
|
||||
echo "Unexpected passes: $UNEXPECTED_PASSES"
|
||||
echo "================================================"
|
||||
|
||||
if [ "$UNEXPECTED_PASSES" -gt 0 ]; then
|
||||
echo "::error::$UNEXPECTED_PASSES invalid fixtures passed validation unexpectedly"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$EXPECTED_FAILURES" -eq 0 ]; then
|
||||
echo "::warning::No invalid CycloneDX fixtures found for negative testing"
|
||||
fi
|
||||
|
||||
echo "✅ All invalid fixtures correctly rejected by schema validation"
|
||||
|
||||
summary:
|
||||
name: Validation Summary
|
||||
runs-on: ubuntu-latest
|
||||
needs: [validate-cyclonedx, validate-spdx, validate-vex]
|
||||
needs: [validate-cyclonedx, validate-spdx, validate-vex, validate-negative]
|
||||
if: always()
|
||||
steps:
|
||||
- name: Check results
|
||||
@@ -244,12 +309,14 @@ jobs:
|
||||
echo "CycloneDX: ${{ needs.validate-cyclonedx.result }}"
|
||||
echo "SPDX: ${{ needs.validate-spdx.result }}"
|
||||
echo "OpenVEX: ${{ needs.validate-vex.result }}"
|
||||
|
||||
echo "Negative Tests: ${{ needs.validate-negative.result }}"
|
||||
|
||||
if [ "${{ needs.validate-cyclonedx.result }}" = "failure" ] || \
|
||||
[ "${{ needs.validate-spdx.result }}" = "failure" ] || \
|
||||
[ "${{ needs.validate-vex.result }}" = "failure" ]; then
|
||||
[ "${{ needs.validate-vex.result }}" = "failure" ] || \
|
||||
[ "${{ needs.validate-negative.result }}" = "failure" ]; then
|
||||
echo "::error::One or more schema validations failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
echo "✅ All schema validations passed or skipped"
|
||||
|
||||
Reference in New Issue
Block a user