# .gitea/workflows/findings-ledger-ci.yml # Findings Ledger CI with RLS migration validation (DEVOPS-LEDGER-TEN-48-001-REL) name: Findings Ledger CI on: push: branches: [main] paths: - 'src/Findings/**' - '.gitea/workflows/findings-ledger-ci.yml' pull_request: branches: [main, develop] paths: - 'src/Findings/**' - '.gitea/workflows/findings-ledger-ci.yml' env: DOTNET_VERSION: '10.0.100' POSTGRES_IMAGE: postgres:16-alpine BUILD_CONFIGURATION: Release jobs: build-test: runs-on: ubuntu-22.04 env: TEST_RESULTS_DIR: ${{ github.workspace }}/artifacts/test-results steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup .NET ${{ env.DOTNET_VERSION }} uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} include-prerelease: true - name: Restore dependencies run: | dotnet restore src/Findings/StellaOps.Findings.Ledger/StellaOps.Findings.Ledger.csproj dotnet restore src/Findings/__Tests/StellaOps.Findings.Ledger.Tests/StellaOps.Findings.Ledger.Tests.csproj - name: Build run: | dotnet build src/Findings/StellaOps.Findings.Ledger/StellaOps.Findings.Ledger.csproj \ -c ${{ env.BUILD_CONFIGURATION }} \ /p:ContinuousIntegrationBuild=true - name: Run unit tests run: | mkdir -p $TEST_RESULTS_DIR dotnet test src/Findings/__Tests/StellaOps.Findings.Ledger.Tests/StellaOps.Findings.Ledger.Tests.csproj \ -c ${{ env.BUILD_CONFIGURATION }} \ --logger "trx;LogFileName=ledger-tests.trx" \ --results-directory $TEST_RESULTS_DIR - name: Upload test results uses: actions/upload-artifact@v4 if: always() with: name: ledger-test-results path: ${{ env.TEST_RESULTS_DIR }} migration-validation: runs-on: ubuntu-22.04 services: postgres: image: postgres:16-alpine env: POSTGRES_USER: ledgertest POSTGRES_PASSWORD: ledgertest POSTGRES_DB: ledger_test ports: - 5432:5432 options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 env: PGHOST: localhost PGPORT: 5432 PGUSER: ledgertest PGPASSWORD: ledgertest PGDATABASE: ledger_test steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup .NET ${{ env.DOTNET_VERSION }} uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} include-prerelease: true - name: Install PostgreSQL client run: | sudo apt-get update sudo apt-get install -y postgresql-client - name: Wait for PostgreSQL run: | until pg_isready -h $PGHOST -p $PGPORT -U $PGUSER; do echo "Waiting for PostgreSQL..." sleep 2 done - name: Apply prerequisite migrations (001-006) run: | set -euo pipefail MIGRATION_DIR="src/Findings/StellaOps.Findings.Ledger/migrations" for migration in 001_initial.sql 002_add_evidence_bundle_ref.sql 002_projection_offsets.sql \ 003_policy_rationale.sql 004_ledger_attestations.sql 004_risk_fields.sql \ 005_risk_fields.sql 006_orchestrator_airgap.sql; do if [ -f "$MIGRATION_DIR/$migration" ]; then echo "Applying migration: $migration" psql -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE -f "$MIGRATION_DIR/$migration" fi done - name: Apply RLS migration (007_enable_rls.sql) run: | set -euo pipefail echo "Applying RLS migration..." psql -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE \ -f src/Findings/StellaOps.Findings.Ledger/migrations/007_enable_rls.sql - name: Validate RLS configuration run: | set -euo pipefail echo "Validating RLS is enabled on all protected tables..." # Check RLS enabled TABLES_WITH_RLS=$(psql -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE -t -A -c " SELECT COUNT(*) FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = 'public' AND c.relrowsecurity = true AND c.relname IN ( 'ledger_events', 'ledger_merkle_roots', 'findings_projection', 'finding_history', 'triage_actions', 'ledger_attestations', 'orchestrator_exports', 'airgap_imports' ); ") if [ "$TABLES_WITH_RLS" -ne 8 ]; then echo "::error::Expected 8 tables with RLS enabled, found $TABLES_WITH_RLS" exit 1 fi echo "✓ All 8 tables have RLS enabled" # Check policies exist POLICIES=$(psql -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE -t -A -c " SELECT COUNT(DISTINCT tablename) FROM pg_policies WHERE schemaname = 'public' AND policyname LIKE '%_tenant_isolation'; ") if [ "$POLICIES" -ne 8 ]; then echo "::error::Expected 8 tenant isolation policies, found $POLICIES" exit 1 fi echo "✓ All 8 tenant isolation policies created" # Check tenant function exists FUNC_EXISTS=$(psql -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE -t -A -c " SELECT COUNT(*) FROM pg_proc p JOIN pg_namespace n ON p.pronamespace = n.oid WHERE p.proname = 'require_current_tenant' AND n.nspname = 'findings_ledger_app'; ") if [ "$FUNC_EXISTS" -ne 1 ]; then echo "::error::Tenant function 'require_current_tenant' not found" exit 1 fi echo "✓ Tenant function 'findings_ledger_app.require_current_tenant()' exists" echo "" echo "=== RLS Migration Validation PASSED ===" - name: Test rollback migration run: | set -euo pipefail echo "Testing rollback migration..." psql -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE \ -f src/Findings/StellaOps.Findings.Ledger/migrations/007_enable_rls_rollback.sql # Verify RLS is disabled TABLES_WITH_RLS=$(psql -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE -t -A -c " SELECT COUNT(*) FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = 'public' AND c.relrowsecurity = true AND c.relname IN ( 'ledger_events', 'ledger_merkle_roots', 'findings_projection', 'finding_history', 'triage_actions', 'ledger_attestations', 'orchestrator_exports', 'airgap_imports' ); ") if [ "$TABLES_WITH_RLS" -ne 0 ]; then echo "::error::Rollback failed - $TABLES_WITH_RLS tables still have RLS enabled" exit 1 fi echo "✓ Rollback successful - RLS disabled on all tables" - name: Re-apply RLS migration (idempotency check) run: | set -euo pipefail echo "Re-applying RLS migration to verify idempotency..." psql -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE \ -f src/Findings/StellaOps.Findings.Ledger/migrations/007_enable_rls.sql echo "✓ Migration is idempotent" generate-manifest: runs-on: ubuntu-22.04 needs: [build-test, migration-validation] steps: - name: Checkout repository uses: actions/checkout@v4 - name: Generate migration manifest run: | set -euo pipefail MIGRATION_FILE="src/Findings/StellaOps.Findings.Ledger/migrations/007_enable_rls.sql" ROLLBACK_FILE="src/Findings/StellaOps.Findings.Ledger/migrations/007_enable_rls_rollback.sql" MANIFEST_DIR="out/findings-ledger/migrations" mkdir -p "$MANIFEST_DIR" # Compute SHA256 hashes MIGRATION_SHA=$(sha256sum "$MIGRATION_FILE" | awk '{print $1}') ROLLBACK_SHA=$(sha256sum "$ROLLBACK_FILE" | awk '{print $1}') CREATED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ") cat > "$MANIFEST_DIR/007_enable_rls.manifest.json" <