Measure a worktree against where it lands, not the repo default (SPEC-51) #558
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: Flutter Security + Lint CI | |
| on: | |
| push: | |
| branches: [ main ] | |
| paths: | |
| - 'app/**' | |
| - '.github/workflows/flutter-ci.yml' | |
| pull_request: | |
| branches: [ main ] | |
| paths: | |
| - 'app/**' | |
| - '.github/workflows/flutter-ci.yml' | |
| jobs: | |
| lint-and-analyze: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v5 | |
| - name: Setup Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| flutter-version: '3.44.9' | |
| channel: 'stable' | |
| - name: Cache Flutter dependencies | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ${{ runner.tool_cache }}/flutter/ | |
| ~/.pub-cache/ | |
| key: ${{ runner.os }}-flutter-3.44.9-${{ hashFiles('app/pubspec.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-flutter-3.44.9- | |
| - name: Install Flutter dependencies (lockfile-locked) | |
| run: | | |
| cd app | |
| flutter pub get --enforce-lockfile | |
| - name: Run analysis (strict linting) | |
| run: | | |
| cd app | |
| flutter analyze --no-pub --fatal-infos | |
| - name: Check code formatting | |
| run: | | |
| cd app | |
| dart format --output=none --set-exit-if-changed lib test | |
| - name: Check for vulnerable packages | |
| run: | | |
| cd app | |
| flutter pub outdated || true | |
| continue-on-error: true # Advisory only, doesn't block CI | |
| - name: Verify pubspec.lock integrity | |
| run: | | |
| cd app | |
| if ! git diff --exit-code pubspec.lock > /dev/null; then | |
| echo "ERROR: pubspec.lock would be modified by pub get" | |
| echo "This indicates a lockfile inconsistency or manual edit." | |
| echo "Run: flutter pub get && git add pubspec.lock" | |
| exit 1 | |
| fi | |
| test: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v5 | |
| - name: Setup Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| flutter-version: '3.44.9' | |
| channel: 'stable' | |
| - name: Cache Flutter dependencies | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ${{ runner.tool_cache }}/flutter/ | |
| ~/.pub-cache/ | |
| key: ${{ runner.os }}-flutter-3.44.9-${{ hashFiles('app/pubspec.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-flutter-3.44.9- | |
| - name: Install Flutter dependencies | |
| run: | | |
| cd app | |
| flutter pub get --enforce-lockfile | |
| - name: Run tests | |
| run: | | |
| cd app | |
| flutter test --coverage | |
| - name: Upload coverage to Codecov | |
| uses: codecov/codecov-action@v5 | |
| with: | |
| files: ./app/coverage/lcov.info | |
| flags: flutter | |
| continue-on-error: true # Coverage upload is advisory; test failures block above | |
| security-audit: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v5 | |
| with: | |
| # Full history: the cooldown gate diffs pubspec.lock against the | |
| # PR base / previous commit. | |
| fetch-depth: 0 | |
| - name: Setup Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| flutter-version: '3.44.9' | |
| channel: 'stable' | |
| - name: Cache Flutter dependencies | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ${{ runner.tool_cache }}/flutter/ | |
| ~/.pub-cache/ | |
| key: ${{ runner.os }}-flutter-3.44.9-${{ hashFiles('app/pubspec.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-flutter-3.44.9- | |
| - name: Install Flutter dependencies | |
| run: | | |
| cd app | |
| flutter pub get --enforce-lockfile | |
| - name: Enforce pub release cooldown (SECURITY.md §8) | |
| env: | |
| # pub has no `minimumReleaseAge`; block lockfile versions published | |
| # less than 3 days ago (SECURITY.md §8). Baseline = PR base, or the | |
| # previous commit on a push. Empty on other event types (e.g. a | |
| # future `schedule`/`workflow_dispatch`), and all-zero on branch | |
| # creation — both are resolved to a usable commit below, because an | |
| # empty ref would make git read the *index* and pass silently. | |
| BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }} | |
| run: | | |
| cd app | |
| base="$BASE_SHA" | |
| if [ -z "$base" ] || [ "$base" = "0000000000000000000000000000000000000000" ] \ | |
| || ! git rev-parse --verify --quiet "$base^{commit}" > /dev/null; then | |
| echo "base '$base' unusable — falling back to HEAD~1" | |
| base="HEAD~1" | |
| fi | |
| git rev-parse --verify --quiet "$base^{commit}" > /dev/null || { | |
| echo "no usable baseline commit — cannot verify cooldown"; exit 1; } | |
| COOLDOWN_BASE_REF="$base" dart run tool/pub_cooldown.dart | |
| - name: Scan dependencies for known advisories (OSV) | |
| run: | | |
| cd app | |
| # Pinned binary + SHA256 verify — no third-party action in the | |
| # trust boundary. Bump OSV_VERSION/OSV_SHA256 together; get the | |
| # hash from the release's osv-scanner_SHA256SUMS. | |
| OSV_VERSION="v2.3.8" | |
| OSV_SHA256="bc98e15319ed0d515e3f9235287ba53cdc5535d576d24fd573978ecfe9ab92dc" | |
| curl -sSfL -o /tmp/osv-scanner \ | |
| "https://github.com/google/osv-scanner/releases/download/${OSV_VERSION}/osv-scanner_linux_amd64" | |
| echo "${OSV_SHA256} /tmp/osv-scanner" | sha256sum -c - | |
| chmod +x /tmp/osv-scanner | |
| # Exits non-zero if any package in the lockfile has a known advisory. | |
| /tmp/osv-scanner scan source --lockfile=pubspec.lock |