fix(sync): release the remote fence even when the cycle was aborted, … #3072
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 | |
| on: | |
| push: | |
| branches: [main, master] | |
| pull_request: | |
| branches: [main, master] | |
| permissions: | |
| contents: read | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| jobs: | |
| # Core package tests | |
| core: | |
| runs-on: ubuntu-latest | |
| name: Core Package | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 | |
| with: | |
| bun-version-file: ".bun-version" | |
| - name: Cache Bun dependencies | |
| uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 | |
| with: | |
| path: ~/.bun/install/cache | |
| key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-bun- | |
| - name: Install dependencies | |
| run: | | |
| set -e | |
| bun install --frozen-lockfile || (echo "bun install failed, clearing cache and retrying..." && (bun pm cache rm || bun pm cache clean || true) && bun install --frozen-lockfile) | |
| - name: TypeScript check | |
| run: cd packages/core && bun run tsc --noEmit | |
| - name: Large-store performance budgets | |
| run: bun run test:perf | |
| - name: Locale parity check | |
| run: bun run i18n:check | |
| - name: Schema and release workflow governance checks | |
| run: bun run test:governance | |
| - name: README heading parity check | |
| run: bun run docs:check-readme | |
| - name: Store image asset check | |
| run: bash scripts/ci/validate-store-image-assets.sh | |
| - name: AppStream metainfo locale check | |
| run: python3 scripts/ci/validate-appstream-locales.py | |
| - name: Workflow JSON newline check | |
| run: node scripts/ci/validate-workflow-json-newlines.js | |
| - name: Package-lock override sync check | |
| run: | | |
| node scripts/ci/check-package-lock-sync.js packages/core/package.json packages/core/package-lock.json --check-overrides | |
| node scripts/ci/check-package-lock-sync.js apps/desktop/package.json apps/desktop/package-lock.json --check-overrides | |
| node scripts/ci/check-mobile-foss-lock-sync.js | |
| - name: EAS CLI lock install check | |
| run: npm ci --prefix tools/eas-cli --ignore-scripts --legacy-peer-deps --dry-run | |
| - name: Google Play listing metadata check | |
| run: | | |
| python3 scripts/ci/prepare-google-play-listing-metadata.test.py | |
| python3 scripts/ci/prepare-google-play-listing-metadata.py \ | |
| metadata \ | |
| /tmp/mindwtr-play-listing-metadata.json | |
| - name: Store changelog length check | |
| run: node scripts/ci/check-changelog-length.js | |
| - name: App Store listing metadata check | |
| run: | | |
| python3 scripts/ci/validate-fastlane-metadata.test.py | |
| FASTLANE_METADATA_DIR=/tmp/mindwtr-fastlane-metadata scripts/ci/prepare-fastlane-metadata.sh | |
| - name: iOS App Intents availability check | |
| run: node scripts/ci/validate-ios-app-intents-availability.js | |
| - name: Apple property list syntax check | |
| run: python3 scripts/ci/validate-apple-plists.py | |
| - name: Run unit tests | |
| run: bun run --filter @mindwtr/core test -- --coverage --coverage.thresholds.lines=70 --coverage.thresholds.statements=70 --coverage.thresholds.functions=50 --coverage.thresholds.branches=50 | |
| - name: Core coverage summary | |
| if: always() | |
| run: node scripts/ci/write-coverage-summary.js "Core Coverage" packages/core/coverage/coverage-summary.json --min-lines=70 --min-statements=70 --min-functions=50 --min-branches=50 | |
| - name: Upload core coverage artifact | |
| if: always() | |
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| with: | |
| name: core-coverage | |
| path: packages/core/coverage | |
| if-no-files-found: ignore | |
| # Compile and execute the actual Swift/Objective-C CloudKit Task mappers. | |
| native-schema: | |
| runs-on: macos-15 | |
| name: Native Task Schema Contracts | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 | |
| with: | |
| bun-version-file: ".bun-version" | |
| - name: Native synced field parity check | |
| run: bun run schema:check | |
| # Desktop app build and tests (Tauri) | |
| desktop: | |
| runs-on: ubuntu-latest | |
| name: Desktop App (Tauri) | |
| timeout-minutes: 45 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| - name: Install system dependencies (Tauri) | |
| run: | | |
| set -euo pipefail | |
| # GitHub-hosted Ubuntu runners sometimes point apt at a flaky Azure | |
| # mirror, but the canonical Ubuntu archive can also stall. Try the | |
| # runner's default mirror first, then alternate with the archive | |
| # fallback on retry. | |
| APT_MIRROR_BACKUP="" | |
| if [ -f /etc/apt/apt-mirrors.txt ]; then | |
| APT_MIRROR_BACKUP="$(mktemp)" | |
| sudo cp /etc/apt/apt-mirrors.txt "$APT_MIRROR_BACKUP" | |
| fi | |
| # The runner image also carries optional Microsoft apt feeds that are | |
| # unrelated to Tauri. If those feeds return 403, apt-get update fails | |
| # before the Ubuntu packages below can install. | |
| mapfile -t MICROSOFT_APT_SOURCES < <(grep -rl 'packages.microsoft.com' /etc/apt/sources.list /etc/apt/sources.list.d 2>/dev/null || true) | |
| for source_file in "${MICROSOFT_APT_SOURCES[@]}"; do | |
| sudo mv "$source_file" "$source_file.disabled" | |
| done | |
| APT_OPTIONS=( | |
| -o Acquire::Retries=3 | |
| -o Acquire::http::Timeout=30 | |
| -o Acquire::https::Timeout=30 | |
| ) | |
| APT_PACKAGES=( | |
| libwebkit2gtk-4.1-dev | |
| libappindicator3-dev | |
| librsvg2-dev | |
| patchelf | |
| libasound2-dev | |
| libssl-dev | |
| ) | |
| for attempt in 1 2 3; do | |
| if [ -n "$APT_MIRROR_BACKUP" ]; then | |
| sudo cp "$APT_MIRROR_BACKUP" /etc/apt/apt-mirrors.txt | |
| if [ "$attempt" -eq 2 ]; then | |
| sudo sed -i 's|http://azure.archive.ubuntu.com/ubuntu|https://archive.ubuntu.com/ubuntu|g' /etc/apt/apt-mirrors.txt | |
| fi | |
| fi | |
| if timeout 8m sudo apt-get "${APT_OPTIONS[@]}" update && \ | |
| timeout 8m sudo apt-get "${APT_OPTIONS[@]}" install -y "${APT_PACKAGES[@]}"; then | |
| exit 0 | |
| fi | |
| if [ "$attempt" -eq 3 ]; then | |
| echo "Tauri system dependency install failed after $attempt attempts" >&2 | |
| exit 1 | |
| fi | |
| echo "Tauri system dependency install failed on attempt $attempt, retrying..." >&2 | |
| sudo apt-get clean | |
| sudo rm -rf /var/lib/apt/lists/* | |
| sleep 10 | |
| done | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable | |
| - name: Ensure Rust toolchain (retry) | |
| shell: bash | |
| run: | | |
| for attempt in 1 2 3; do | |
| if rustup toolchain install stable --profile minimal --no-self-update && rustup default stable; then | |
| exit 0 | |
| fi | |
| if [ "$attempt" -eq 3 ]; then | |
| echo "Rust toolchain setup failed after $attempt attempts" >&2 | |
| exit 1 | |
| fi | |
| echo "Rust toolchain setup failed on attempt $attempt, retrying..." >&2 | |
| sleep 10 | |
| done | |
| - name: Cache Rust artifacts | |
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 | |
| with: | |
| workspaces: apps/desktop/src-tauri | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 | |
| with: | |
| bun-version-file: ".bun-version" | |
| - name: Cache Bun dependencies | |
| uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 | |
| with: | |
| path: ~/.bun/install/cache | |
| key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-bun- | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: TypeScript check | |
| run: cd apps/desktop && bun run tsc --noEmit | |
| - name: Run tests | |
| run: bun run --filter mindwtr test -- --coverage --coverage.thresholds.lines=50 --coverage.thresholds.statements=50 --coverage.thresholds.functions=45 --coverage.thresholds.branches=50 | |
| - name: Desktop coverage summary | |
| if: always() | |
| run: node scripts/ci/write-coverage-summary.js "Desktop Coverage" apps/desktop/coverage/coverage-summary.json --min-lines=50 --min-statements=50 --min-functions=45 --min-branches=50 | |
| - name: Upload desktop coverage artifact | |
| if: always() | |
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| with: | |
| name: desktop-coverage | |
| path: apps/desktop/coverage | |
| if-no-files-found: ignore | |
| - name: Run Rust tests | |
| timeout-minutes: 20 | |
| run: bun run native:test | |
| - name: Build Tauri app (debug, no bundle) | |
| timeout-minutes: 20 | |
| run: cd apps/desktop && bunx tauri build --debug --no-bundle | |
| # Exercise cfg(windows) persistence and recovery branches with native tests. | |
| desktop-native-windows: | |
| runs-on: windows-2022 | |
| name: Desktop Native (Windows) | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| - name: Ensure Rust toolchain | |
| shell: pwsh | |
| run: | | |
| rustup toolchain install stable --profile minimal --no-self-update | |
| rustup default stable | |
| - name: Cache Rust artifacts | |
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 | |
| with: | |
| workspaces: apps/desktop/src-tauri | |
| - name: Run Windows native tests | |
| env: | |
| # Packaged Windows builds keep the repo-wide static CRT setting, but | |
| # cargo test also links whisper-rs-sys into a test DLL. Its C/C++ | |
| # objects use the dynamic CRT, so forcing +crt-static here leaves | |
| # unresolved __imp_* CRT symbols at link time. | |
| RUSTFLAGS: -C target-feature=-crt-static | |
| run: cargo test --locked --manifest-path apps/desktop/src-tauri/Cargo.toml | |
| # Mobile app validation | |
| mobile: | |
| runs-on: ubuntu-latest | |
| name: Mobile App | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 | |
| with: | |
| bun-version-file: ".bun-version" | |
| - name: Cache Bun dependencies | |
| uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 | |
| with: | |
| path: ~/.bun/install/cache | |
| key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-bun- | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: TypeScript check | |
| run: cd apps/mobile && bunx tsc --noEmit | |
| - name: Expo Doctor check | |
| run: cd apps/mobile && bunx expo-doctor | |
| - name: Mobile tests | |
| run: bun run --filter mobile test:coverage | |
| - name: Mobile coverage summary | |
| if: always() | |
| run: node scripts/ci/write-coverage-summary.js "Mobile Coverage" apps/mobile/coverage/coverage-summary.json --min-lines=38 --min-statements=38 --min-functions=50 --min-branches=50 | |
| - name: Upload mobile coverage artifact | |
| if: always() | |
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| with: | |
| name: mobile-coverage | |
| path: apps/mobile/coverage | |
| if-no-files-found: ignore | |
| cloud-mcp: | |
| runs-on: ubuntu-latest | |
| name: Cloud and MCP Servers | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| - name: Test owner-only Docker secret handoff | |
| run: scripts/ci/validate-docker-cloud-secret-runtime.sh | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 | |
| with: | |
| bun-version-file: ".bun-version" | |
| - name: Cache Bun dependencies | |
| uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 | |
| with: | |
| path: ~/.bun/install/cache | |
| key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-bun- | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Cloud TypeScript check | |
| run: bun run --filter mindwtr-cloud typecheck | |
| - name: MCP version metadata check | |
| run: | | |
| set -euo pipefail | |
| node - <<'NODE' | |
| const fs = require('fs'); | |
| const packageJson = JSON.parse(fs.readFileSync('apps/mcp-server/package.json', 'utf8')); | |
| const serverJson = JSON.parse(fs.readFileSync('apps/mcp-server/server.json', 'utf8')); | |
| const version = packageJson.version; | |
| const failures = []; | |
| if (!version) { | |
| failures.push('apps/mcp-server/package.json: missing version'); | |
| } | |
| if (serverJson.version !== version) { | |
| failures.push(`apps/mcp-server/server.json: expected ${version}, got ${serverJson.version || '<missing>'}`); | |
| } | |
| if (!Array.isArray(serverJson.packages)) { | |
| failures.push('apps/mcp-server/server.json: missing packages array'); | |
| } else { | |
| for (const entry of serverJson.packages) { | |
| if (entry.version !== version) { | |
| failures.push( | |
| `apps/mcp-server/server.json package ${entry.identifier || entry.name || '<unknown>'}: expected ${version}, got ${entry.version || '<missing>'}` | |
| ); | |
| } | |
| } | |
| } | |
| if (failures.length) { | |
| console.error('MCP npm package and registry metadata versions must match.'); | |
| console.error(failures.join('\n')); | |
| process.exit(1); | |
| } | |
| console.log(`Verified MCP package and registry metadata use ${version}.`); | |
| NODE | |
| - name: MCP TypeScript check | |
| run: bun run --filter mindwtr-mcp typecheck | |
| - name: Cloud tests | |
| run: bun run --filter mindwtr-cloud test:coverage | |
| - name: Cloud coverage summary | |
| if: always() | |
| # Bun's LCOV reporter for cloud/mcp currently emits line/function data but no BRF/BRH branch records. | |
| # Keep statement gates as an explicit LCOV line proxy and add a real branch gate when the reporter exposes it. | |
| run: node scripts/ci/write-lcov-summary.js "Cloud Coverage" apps/cloud/coverage/lcov.info --include-prefix=src/ --min-lines=70 --min-statements=70 --min-functions=50 | |
| - name: Upload cloud coverage artifact | |
| if: always() | |
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| with: | |
| name: cloud-coverage | |
| path: apps/cloud/coverage | |
| if-no-files-found: ignore | |
| - name: MCP tests | |
| run: bun run --filter mindwtr-mcp test:coverage | |
| - name: MCP coverage summary | |
| if: always() | |
| # Bun's LCOV reporter for cloud/mcp currently emits line/function data but no BRF/BRH branch records. | |
| # Keep statement gates as an explicit LCOV line proxy and add a real branch gate when the reporter exposes it. | |
| run: node scripts/ci/write-lcov-summary.js "MCP Coverage" apps/mcp-server/coverage/lcov.info --include-prefix=src/ --min-lines=70 --min-statements=70 --min-functions=50 | |
| - name: MCP core adapter coverage floor | |
| if: always() | |
| run: node scripts/ci/write-lcov-summary.js "MCP core-adapter.ts Coverage" apps/mcp-server/coverage/lcov.info --include-prefix=src/core-adapter.ts --min-lines=53 --min-statements=53 --min-functions=60 | |
| - name: MCP paths coverage floor | |
| if: always() | |
| run: node scripts/ci/write-lcov-summary.js "MCP paths.ts Coverage" apps/mcp-server/coverage/lcov.info --include-prefix=src/paths.ts --min-lines=29 --min-statements=29 --min-functions=17 | |
| - name: Upload MCP coverage artifact | |
| if: always() | |
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| with: | |
| name: mcp-coverage | |
| path: apps/mcp-server/coverage | |
| if-no-files-found: ignore | |
| e2e: | |
| runs-on: ubuntu-latest | |
| name: E2E (Web) | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 | |
| with: | |
| bun-version-file: ".bun-version" | |
| - name: Cache Bun dependencies | |
| uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 | |
| with: | |
| path: ~/.bun/install/cache | |
| key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-bun- | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Drop unused Microsoft apt repos | |
| # packages.microsoft.com intermittently serves invalid clearsigned | |
| # indexes, which fails the apt update inside `playwright install | |
| # --with-deps`. The E2E job never installs from these repos; same | |
| # workaround as the desktop build job. | |
| run: | | |
| mapfile -t MICROSOFT_APT_SOURCES < <(grep -rl 'packages.microsoft.com' /etc/apt/sources.list /etc/apt/sources.list.d 2>/dev/null || true) | |
| for source_file in "${MICROSOFT_APT_SOURCES[@]}"; do | |
| sudo mv "$source_file" "$source_file.disabled" | |
| done | |
| - name: Install Playwright browsers | |
| run: bunx playwright install --with-deps chromium | |
| - name: Run E2E tests | |
| run: bun run test:e2e | |
| - name: Upload Playwright HTML report | |
| if: failure() | |
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| with: | |
| name: playwright-report | |
| path: playwright-report | |
| if-no-files-found: ignore | |
| - name: Upload Playwright test results | |
| if: failure() | |
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| with: | |
| name: playwright-test-results | |
| path: test-results | |
| if-no-files-found: ignore | |
| # Lint check | |
| lint: | |
| runs-on: ubuntu-latest | |
| name: Code Quality | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 | |
| with: | |
| bun-version-file: ".bun-version" | |
| - name: Cache Bun dependencies | |
| uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 | |
| with: | |
| path: ~/.bun/install/cache | |
| key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-bun- | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Check for lint errors (core) | |
| run: bun run --filter @mindwtr/core lint | |
| - name: Check for lint errors (desktop) | |
| run: cd apps/desktop && bun run lint | |
| - name: Check for lint errors (mobile) | |
| run: cd apps/mobile && bun run lint | |
| - name: Check for lint errors (cloud) | |
| run: bun run --filter mindwtr-cloud lint | |
| - name: Check for lint errors (mcp) | |
| run: bun run --filter mindwtr-mcp lint |