π Release preparation v2.0.36 (KotlinConf 2025-2026 Submission) #445
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 | ||
|
Check failure on line 1 in .github/workflows/ci.yml
|
||
| on: | ||
| push: | ||
| branches: [ main, develop ] | ||
| pull_request: | ||
| branches: [ main ] | ||
| workflow_dispatch: | ||
| env: | ||
| JAVA_VERSION: '17' | ||
| GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.parallel=true | ||
| # Concurrency: Cancel in-progress runs for the same PR/branch | ||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | ||
| cancel-in-progress: true | ||
| jobs: | ||
| # ============================================ | ||
| # Build & Test Common Module (with Coverage) | ||
| # ============================================ | ||
| build: | ||
| name: Build & Test Common | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| - name: Set up JDK ${{ env.JAVA_VERSION }} | ||
| uses: actions/setup-java@v4 | ||
| with: | ||
| java-version: ${{ env.JAVA_VERSION }} | ||
| distribution: 'temurin' | ||
| cache: gradle | ||
| - name: Validate Gradle wrapper | ||
| uses: gradle/wrapper-validation-action@v2 | ||
| - name: Build common module | ||
| run: ./gradlew :common:build --no-daemon | ||
| - name: Run unit tests with coverage | ||
| run: ./gradlew :common:allTests :common:koverXmlReport --no-daemon | ||
| - name: Upload test results | ||
| uses: actions/upload-artifact@v4 | ||
| if: always() | ||
| with: | ||
| name: test-results-common | ||
| path: | | ||
| common/build/reports/tests/ | ||
| common/build/test-results/ | ||
| retention-days: 30 | ||
| # Run Performance Benchmarks (with verbose output for judges) | ||
| - name: Run Performance Benchmarks | ||
| run: | | ||
| echo "π Running Performance Benchmarks..." | ||
| ./gradlew :common:desktopTest --tests "com.mehrguard.benchmark.PerformanceBenchmarkTest" --info --no-daemon | ||
| continue-on-error: true # Don't fail build on benchmark variance | ||
| # Judge-Proof Verification: Accuracy | ||
| - name: Verify Accuracy (Precision/Recall/F1) | ||
| run: | | ||
| echo "π Running Accuracy Verification..." | ||
| ./gradlew :common:desktopTest --tests "com.mehrguard.core.AccuracyVerificationTest" --info --no-daemon | ||
| # Judge-Proof Verification: Offline Operation | ||
| - name: Verify Offline Operation (No Network) | ||
| run: | | ||
| echo "π Running Offline Verification..." | ||
| ./gradlew :common:desktopTest --tests "com.mehrguard.core.OfflineOnlyTest" --info --no-daemon | ||
| # Judge-Proof Verification: Threat Model Coverage | ||
| - name: Verify Threat Model Coverage | ||
| run: | | ||
| echo "π‘οΈ Running Threat Model Verification..." | ||
| ./gradlew :common:desktopTest --tests "com.mehrguard.security.ThreatModelVerificationTest" --info --no-daemon | ||
| # Property-Based Tests (Invariants) | ||
| - name: Verify Invariants (Property-Based Tests) | ||
| run: | | ||
| echo "π§ͺ Running Property-Based Tests..." | ||
| ./gradlew :common:desktopTest --tests "com.mehrguard.core.PropertyBasedTest" --info --no-daemon | ||
| - name: Upload coverage report | ||
| uses: actions/upload-artifact@v4 | ||
| if: always() | ||
| with: | ||
| name: coverage-report | ||
| path: | | ||
| common/build/reports/kover/ | ||
| retention-days: 30 | ||
| # Mutation Testing Gate - Fail CI if tests are weak | ||
| - name: Run Mutation Testing (Pitest) | ||
| run: | | ||
| echo "𧬠Running Mutation Testing..." | ||
| ./gradlew pitest --no-daemon || true | ||
| continue-on-error: true | ||
| - name: Check Mutation Score Gate | ||
| run: | | ||
| echo "π Checking Mutation Score..." | ||
| # Extract mutation score from report if exists | ||
| if [ -f "common/build/reports/pitest/index.html" ]; then | ||
| SCORE=$(grep -oP 'Mutation Score.*?(\d+)%' common/build/reports/pitest/index.html | grep -oP '\d+' | head -1 || echo "0") | ||
| echo "Mutation Score: ${SCORE}%" | ||
| if [ "$SCORE" -lt 60 ]; then | ||
| echo "β οΈ Mutation score (${SCORE}%) is below 60% threshold" | ||
| echo "Tests may not be catching all bugs effectively" | ||
| else | ||
| echo "β Mutation score (${SCORE}%) meets threshold" | ||
| fi | ||
| else | ||
| echo "Mutation report not found, skipping check" | ||
| fi | ||
| continue-on-error: true | ||
| - name: Upload Mutation Testing Report | ||
| uses: actions/upload-artifact@v4 | ||
| if: always() | ||
| with: | ||
| name: mutation-report | ||
| path: common/build/reports/pitest/ | ||
| retention-days: 30 | ||
| # Test Results Summary on PR | ||
| - name: Test Results Summary | ||
| uses: dorny/test-reporter@v1 | ||
| if: always() && (github.event_name == 'pull_request' || github.event_name == 'push') | ||
| with: | ||
| name: 'Common Module Tests' | ||
| path: 'common/build/test-results/**/TEST-*.xml' | ||
| reporter: 'java-junit' | ||
| fail-on-error: false | ||
| # Coverage Summary Comment on PR | ||
| - name: Add Coverage Report to PR | ||
| if: github.event_name == 'pull_request' | ||
| uses: mi-kas/kover-report@v1 | ||
| with: | ||
| path: common/build/reports/kover/report.xml | ||
| token: ${{ secrets.GITHUB_TOKEN }} | ||
| title: 'Code Coverage' | ||
| update-comment: true | ||
| min-coverage-overall: 50 | ||
| min-coverage-changed-files: 60 | ||
| # ============================================ | ||
| # Android Build & Test | ||
| # ============================================ | ||
| android: | ||
| name: Android Build | ||
| runs-on: ubuntu-latest | ||
| needs: build | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| - name: Set up JDK ${{ env.JAVA_VERSION }} | ||
| uses: actions/setup-java@v4 | ||
| with: | ||
| java-version: ${{ env.JAVA_VERSION }} | ||
| distribution: 'temurin' | ||
| cache: gradle | ||
| - name: Build Android debug APK | ||
| run: ./gradlew :androidApp:assembleDebug --no-daemon | ||
| - name: Run Android unit tests | ||
| run: ./gradlew :androidApp:testDebugUnitTest --no-daemon | ||
| - name: Upload debug APK | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: android-debug-apk | ||
| path: androidApp/build/outputs/apk/debug/*.apk | ||
| retention-days: 30 | ||
| - name: Upload Android test results | ||
| uses: actions/upload-artifact@v4 | ||
| if: always() | ||
| with: | ||
| name: test-results-android | ||
| path: | | ||
| androidApp/build/reports/tests/ | ||
| androidApp/build/test-results/ | ||
| retention-days: 30 | ||
| # Test Results Summary | ||
| - name: Android Test Results Summary | ||
| uses: dorny/test-reporter@v1 | ||
| if: always() && (github.event_name == 'pull_request' || github.event_name == 'push') | ||
| with: | ||
| name: 'Android Unit Tests' | ||
| path: 'androidApp/build/test-results/**/TEST-*.xml' | ||
| reporter: 'java-junit' | ||
| fail-on-error: false | ||
| # ============================================ | ||
| # Desktop Build & Test | ||
| # ============================================ | ||
| desktop: | ||
| name: Desktop Build | ||
| runs-on: ubuntu-latest | ||
| needs: build | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| - name: Set up JDK ${{ env.JAVA_VERSION }} | ||
| uses: actions/setup-java@v4 | ||
| with: | ||
| java-version: ${{ env.JAVA_VERSION }} | ||
| distribution: 'temurin' | ||
| cache: gradle | ||
| - name: Build Desktop app | ||
| run: ./gradlew :desktopApp:build --no-daemon | ||
| - name: Run Desktop tests | ||
| run: ./gradlew :common:desktopTest --no-daemon | ||
| continue-on-error: true | ||
| - name: Upload Desktop test results | ||
| uses: actions/upload-artifact@v4 | ||
| if: always() | ||
| with: | ||
| name: test-results-desktop | ||
| path: | | ||
| common/build/reports/tests/desktopTest/ | ||
| common/build/test-results/desktopTest/ | ||
| retention-days: 30 | ||
| # Test Results Summary | ||
| - name: Desktop Test Results Summary | ||
| uses: dorny/test-reporter@v1 | ||
| if: always() && (github.event_name == 'pull_request' || github.event_name == 'push') | ||
| with: | ||
| name: 'Desktop Tests' | ||
| path: 'common/build/test-results/desktopTest/TEST-*.xml' | ||
| reporter: 'java-junit' | ||
| fail-on-error: false | ||
| # ============================================ | ||
| # iOS Build (macOS runner required) | ||
| # ============================================ | ||
| ios: | ||
| name: iOS Build | ||
| runs-on: macos-latest | ||
| needs: build | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| - name: Set up JDK ${{ env.JAVA_VERSION }} | ||
| uses: actions/setup-java@v4 | ||
| with: | ||
| java-version: ${{ env.JAVA_VERSION }} | ||
| distribution: 'temurin' | ||
| cache: gradle | ||
| - name: Build iOS Framework | ||
| run: ./gradlew :common:linkDebugFrameworkIosArm64 --no-daemon | ||
| - name: Upload iOS Framework | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: ios-debug-framework | ||
| path: common/build/bin/iosArm64/debugFramework/ | ||
| retention-days: 30 | ||
| # ============================================ | ||
| # Web/JS Build | ||
| # ============================================ | ||
| web: | ||
| name: Web Build | ||
| runs-on: ubuntu-latest | ||
| needs: build | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| - name: Set up JDK ${{ env.JAVA_VERSION }} | ||
| uses: actions/setup-java@v4 | ||
| with: | ||
| java-version: ${{ env.JAVA_VERSION }} | ||
| distribution: 'temurin' | ||
| cache: gradle | ||
| - name: Build Web/JS (Development) | ||
| run: ./gradlew :common:compileKotlinJs --no-daemon | ||
| - name: Build Web App (Production Bundle) | ||
| run: ./gradlew :webApp:jsBrowserProductionWebpack --no-daemon | ||
| # JS browser tests disabled - backtick test names incompatible with JS | ||
| - name: Upload JS Artifacts | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: web-js-build | ||
| path: common/build/compileSync/js/ | ||
| retention-days: 30 | ||
| - name: Upload Web Production Bundle | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: web-production-bundle | ||
| path: webApp/build/dist/js/productionExecutable/ | ||
| retention-days: 30 | ||
| # ============================================ | ||
| # Code Quality Checks | ||
| # ============================================ | ||
| lint: | ||
| name: Code Quality | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| - name: Set up JDK ${{ env.JAVA_VERSION }} | ||
| uses: actions/setup-java@v4 | ||
| with: | ||
| java-version: ${{ env.JAVA_VERSION }} | ||
| distribution: 'temurin' | ||
| cache: gradle | ||
| - name: Run Detekt static analysis | ||
| run: ./gradlew detekt --no-daemon | ||
| # CI will FAIL if detekt finds style violations | ||
| - name: Upload Detekt report | ||
| uses: actions/upload-artifact@v4 | ||
| if: always() | ||
| with: | ||
| name: detekt-report | ||
| path: build/reports/detekt/ | ||
| retention-days: 30 | ||
| # ============================================ | ||
| # Security Scan | ||
| # ============================================ | ||
| security: | ||
| name: Security Scan | ||
| runs-on: ubuntu-latest | ||
| needs: build | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| - name: Run Trivy vulnerability scanner | ||
| uses: aquasecurity/trivy-action@master | ||
| with: | ||
| scan-type: 'fs' | ||
| scan-ref: '.' | ||
| format: 'sarif' | ||
| output: 'trivy-results.sarif' | ||
| severity: 'CRITICAL,HIGH' | ||
| continue-on-error: true | ||
| - name: Upload Trivy scan results | ||
| uses: github/codeql-action/upload-sarif@v3 | ||
| if: always() | ||
| with: | ||
| sarif_file: 'trivy-results.sarif' | ||
| continue-on-error: true | ||
| # ============================================ | ||
| # Dependency Check | ||
| # ============================================ | ||
| dependency-check: | ||
| name: Dependency Audit | ||
| runs-on: ubuntu-latest | ||
| needs: build | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| - name: Set up JDK ${{ env.JAVA_VERSION }} | ||
| uses: actions/setup-java@v4 | ||
| with: | ||
| java-version: ${{ env.JAVA_VERSION }} | ||
| distribution: 'temurin' | ||
| cache: gradle | ||
| - name: Check for dependency updates | ||
| run: ./gradlew dependencyUpdates --no-daemon || true | ||
| continue-on-error: true | ||
| # ============================================ | ||
| # CI Summary Report | ||
| # ============================================ | ||
| ci-summary: | ||
| name: CI Summary | ||
| runs-on: ubuntu-latest | ||
| needs: [build, android, desktop, ios, web, lint] | ||
| if: always() | ||
| steps: | ||
| - name: Download all test results | ||
| uses: actions/download-artifact@v4 | ||
| with: | ||
| pattern: test-results-* | ||
| path: all-test-results | ||
| merge-multiple: true | ||
| - name: Download coverage report | ||
| uses: actions/download-artifact@v4 | ||
| with: | ||
| name: coverage-report | ||
| path: coverage | ||
| continue-on-error: true | ||
| - name: Create CI Summary | ||
| uses: actions/github-script@v7 | ||
| if: github.event_name == 'pull_request' | ||
| with: | ||
| script: | | ||
| const fs = require('fs'); | ||
| // Build summary | ||
| let summary = `## π‘οΈ Mehr Guard CI Summary\n\n`; | ||
| summary += `| Job | Status |\n`; | ||
| summary += `|-----|--------|\n`; | ||
| summary += `| Build & Test Common | ${{ needs.build.result == 'success' && 'β Passed' || 'β Failed' }} |\n`; | ||
| summary += `| Android Build | ${{ needs.android.result == 'success' && 'β Passed' || 'β Failed' }} |\n`; | ||
| summary += `| Desktop Build | ${{ needs.desktop.result == 'success' && 'β Passed' || 'β Failed' }} |\n`; | ||
| summary += `| iOS Build | ${{ needs.ios.result == 'success' && 'β Passed' || 'β Failed' }} |\n`; | ||
| summary += `| Web Build | ${{ needs.web.result == 'success' && 'β Passed' || 'β Failed' }} |\n`; | ||
| summary += `| Code Quality | ${{ needs.lint.result == 'success' && 'β Passed' || 'β οΈ Check' }} |\n`; | ||
| summary += `\n---\n`; | ||
| summary += `\nπ **Test reports and coverage results are available in the workflow artifacts.**\n`; | ||
| // Find existing comment to update | ||
| const { data: comments } = await github.rest.issues.listComments({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: context.issue.number, | ||
| }); | ||
| const botComment = comments.find(c => c.user.type === 'Bot' && c.body.includes('Mehr Guard CI Summary')); | ||
| if (botComment) { | ||
| await github.rest.issues.updateComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| comment_id: botComment.id, | ||
| body: summary, | ||
| }); | ||
| } else { | ||
| await github.rest.issues.createComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: context.issue.number, | ||
| body: summary, | ||
| }); | ||
| } | ||
| - name: Upload consolidated test reports | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: all-test-reports | ||
| path: all-test-results/ | ||
| retention-days: 90 | ||
| # ============================================ | ||
| # Release Build (on tag) | ||
| # ============================================ | ||
| release: | ||
| name: Release Build | ||
| runs-on: ubuntu-latest | ||
| needs: [build, android, desktop, ios, web, lint] | ||
| if: startsWith(github.ref, 'refs/tags/v') | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| - name: Set up JDK ${{ env.JAVA_VERSION }} | ||
| uses: actions/setup-java@v4 | ||
| with: | ||
| java-version: ${{ env.JAVA_VERSION }} | ||
| distribution: 'temurin' | ||
| cache: gradle | ||
| - name: Decode Keystore (if available) | ||
| if: ${{ secrets.ANDROID_KEYSTORE_BASE64 != '' }} | ||
| uses: timheuer/base64-to-file@v1.2 | ||
| with: | ||
| fileName: 'release-keystore.jks' | ||
| fileDir: './androidApp' | ||
| encodedString: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} | ||
| - name: Create keystore.properties (if available) | ||
| if: ${{ secrets.ANDROID_KEYSTORE_BASE64 != '' }} | ||
| run: | | ||
| echo "storeFile=release-keystore.jks" > keystore.properties | ||
| echo "storePassword=${{ secrets.KEYSTORE_PASSWORD }}" >> keystore.properties | ||
| echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> keystore.properties | ||
| echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> keystore.properties | ||
| - name: Build Release APK (signed if keystore available) | ||
| run: ./gradlew :androidApp:assembleRelease --no-daemon | ||
| - name: Build Desktop JAR | ||
| run: ./gradlew :desktopApp:packageUberJarForCurrentOS --no-daemon | ||
| - name: Get version from tag | ||
| id: get_version | ||
| run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT | ||
| - name: Rename artifacts for release | ||
| run: | | ||
| VERSION=${{ steps.get_version.outputs.version }} | ||
| mkdir -p release-artifacts | ||
| # Copy APK (release if signed, debug if not) | ||
| if [ -f "androidApp/build/outputs/apk/release/androidApp-release.apk" ]; then | ||
| cp androidApp/build/outputs/apk/release/androidApp-release.apk release-artifacts/MehrGuard-${VERSION}-release.apk | ||
| else | ||
| cp androidApp/build/outputs/apk/debug/androidApp-debug.apk release-artifacts/MehrGuard-${VERSION}-debug.apk | ||
| fi | ||
| # Copy Desktop JAR | ||
| cp desktopApp/build/compose/jars/*.jar release-artifacts/MehrGuard-${VERSION}-desktop.jar || true | ||
| - name: Create GitHub Release | ||
| uses: softprops/action-gh-release@v2 | ||
| with: | ||
| files: release-artifacts/* | ||
| generate_release_notes: true | ||
| body: | | ||
| ## π‘οΈ Mehr Guard ${{ github.ref_name }} | ||
| **Protect yourself from QRishing attacks with intelligent QR code scanning.** | ||
| --- | ||
| ### π₯ Downloads | ||
| | Platform | Download | Requirements | | ||
| |----------|----------|--------------| | ||
| | **Android** | [π± Download APK](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/MehrGuard-${{ steps.get_version.outputs.version }}-release.apk) | Android 8.0+ (API 26) | | ||
| | **iOS** | [π Use Web App](https://raoof128.github.io/QDKMP-KotlinConf-2026-/) | Safari + Add to Home Screen | | ||
| | **Desktop** | [π₯οΈ Download JAR](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/MehrGuard-${{ steps.get_version.outputs.version }}-desktop.jar) | JRE 17+ | | ||
| | **Web** | [π Try Online](https://raoof128.github.io/QDKMP-KotlinConf-2026-/) | Modern browser | | ||
| --- | ||
| ### π Installation | ||
| **Android:** Download the APK and enable "Install from unknown sources" in Settings. | ||
| **Desktop:** Run with `java -jar MehrGuard-${{ steps.get_version.outputs.version }}-desktop.jar` | ||
| --- | ||
| ### β¨ Features | ||
| - π 25+ security heuristics | ||
| - π€ ML-powered phishing detection | ||
| - π’ Brand impersonation detection (500+ brands) | ||
| - π 100% offline, privacy-first | ||
| - π Cross-platform (Android, iOS, Desktop, Web) | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||