# Schema Validation CI Workflow # Sprint: SPRINT_8200_0001_0003_sbom_schema_validation_ci # Tasks: SCHEMA-8200-007 through SCHEMA-8200-011 # # Purpose: Validate SBOM fixtures against official JSON schemas to detect # schema drift before runtime. Fails CI if any fixture is invalid. name: Schema Validation on: pull_request: paths: - 'bench/golden-corpus/**' - 'src/Scanner/**' - 'docs/schemas/**' - 'scripts/validate-*.sh' - '.gitea/workflows/schema-validation.yml' push: branches: [main] paths: - 'bench/golden-corpus/**' - 'src/Scanner/**' - 'docs/schemas/**' - 'scripts/validate-*.sh' env: SBOM_UTILITY_VERSION: "0.16.0" jobs: validate-cyclonedx: name: Validate CycloneDX Fixtures 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: 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 while IFS= read -r -d '' file; do 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 if [ "$FOUND" -eq 0 ]; then echo "::warning::No CycloneDX fixtures found to validate" fi validate-spdx: name: Validate SPDX Fixtures runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.12' - name: Install SPDX tools run: | pip install spdx-tools pip install check-jsonschema - name: Validate SPDX fixtures run: | set -e SCHEMA="docs/schemas/spdx-jsonld-3.0.1.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 while IFS= read -r -d '' file; do # Check for SPDX markers if grep -qE '"spdxVersion"|"@context".*spdx' "$file" 2>/dev/null; then FOUND=$((FOUND + 1)) echo "::group::Validating: $file" # Try pyspdxtools first (semantic validation) if pyspdxtools validate "$file" 2>&1; then echo "✅ PASS (semantic): $file" PASSED=$((PASSED + 1)) # Fall back to JSON schema validation elif check-jsonschema --schemafile "$SCHEMA" "$file" 2>&1; then echo "✅ PASS (schema): $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 "SPDX Validation Summary" echo "================================================" echo "Found: $FOUND fixtures" echo "Passed: $PASSED" echo "Failed: $FAILED" echo "================================================" if [ "$FAILED" -gt 0 ]; then echo "::error::$FAILED SPDX fixtures failed validation" exit 1 fi if [ "$FOUND" -eq 0 ]; then echo "::warning::No SPDX fixtures found to validate" fi validate-vex: name: Validate OpenVEX Fixtures runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: '20' - name: Install ajv-cli run: npm install -g ajv-cli ajv-formats - name: Validate OpenVEX fixtures run: | set -e SCHEMA="docs/schemas/openvex-0.2.0.schema.json" FIXTURE_DIRS=( "bench/golden-corpus" "bench/vex-lattice" "tests/fixtures" "seed-data" ) FOUND=0 PASSED=0 FAILED=0 for dir in "${FIXTURE_DIRS[@]}"; do if [ -d "$dir" ]; then while IFS= read -r -d '' file; do # Check for OpenVEX markers if grep -qE '"@context".*openvex|"@type".*"https://openvex' "$file" 2>/dev/null; then FOUND=$((FOUND + 1)) echo "::group::Validating: $file" if ajv validate -s "$SCHEMA" -d "$file" --strict=false -c ajv-formats 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 "OpenVEX Validation Summary" echo "================================================" echo "Found: $FOUND fixtures" echo "Passed: $PASSED" echo "Failed: $FAILED" echo "================================================" if [ "$FAILED" -gt 0 ]; then echo "::error::$FAILED OpenVEX fixtures failed validation" exit 1 fi if [ "$FOUND" -eq 0 ]; then echo "::warning::No OpenVEX fixtures found to validate" fi summary: name: Validation Summary runs-on: ubuntu-latest needs: [validate-cyclonedx, validate-spdx, validate-vex] if: always() steps: - name: Check results run: | echo "Schema Validation Results" echo "=========================" echo "CycloneDX: ${{ needs.validate-cyclonedx.result }}" echo "SPDX: ${{ needs.validate-spdx.result }}" echo "OpenVEX: ${{ needs.validate-vex.result }}" if [ "${{ needs.validate-cyclonedx.result }}" = "failure" ] || \ [ "${{ needs.validate-spdx.result }}" = "failure" ] || \ [ "${{ needs.validate-vex.result }}" = "failure" ]; then echo "::error::One or more schema validations failed" exit 1 fi echo "✅ All schema validations passed or skipped"