diff --git a/.github/workflows/docker-ci-image.yml b/.github/workflows/build-ci-image.yml similarity index 97% rename from .github/workflows/docker-ci-image.yml rename to .github/workflows/build-ci-image.yml index 82aa5533b..753fe6f04 100644 --- a/.github/workflows/docker-ci-image.yml +++ b/.github/workflows/build-ci-image.yml @@ -1,5 +1,5 @@ --- -name: Build CI Docker Image +name: Build CI Image on: workflow_dispatch: permissions: diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml deleted file mode 100644 index dc885625c..000000000 --- a/.github/workflows/build-windows.yml +++ /dev/null @@ -1,126 +0,0 @@ ---- -name: Build Windows -on: - workflow_dispatch: -permissions: - contents: read -concurrency: - group: build-windows-${{ github.ref }} - cancel-in-progress: true -jobs: - build-windows: - name: "Desktop: Windows x64" - runs-on: windows-latest - timeout-minutes: 30 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - name: Checkout - uses: actions/checkout@v5 - with: - fetch-depth: 1 - submodules: recursive - - name: Setup Node.js 24.x - uses: actions/setup-node@v5 - with: - node-version: 24.x - package-manager-cache: false - - name: Install Rust (1.93.0) - uses: dtolnay/rust-toolchain@1.93.0 - with: - targets: x86_64-pc-windows-msvc - - # Skip first 7 lines of Cargo.lock (workspace package version bumps) so the key tracks dependency changes only - - name: Cargo.lock fingerprint (deps only) - id: cargo-lock-fingerprint - shell: bash - run: | - echo "hash=$(tail -n +8 Cargo.lock | openssl dgst -sha256 | awk '{print $2}')" >> "$GITHUB_OUTPUT" - - name: Cache Cargo registry and git sources - uses: actions/cache@v5 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - key: Windows-cargo-registry-${{ steps.cargo-lock-fingerprint.outputs.hash }} - restore-keys: | - Windows-cargo-registry- - - # CEF runtime auto-downloads via cef-dll-sys / vendored tauri-cli. Cache - # it so we don't re-fetch ~400MB every run. - - name: Cache CEF binary distribution - uses: actions/cache@v5 - with: - path: ~/AppData/Local/tauri-cef - key: cef-windows-${{ hashFiles('app/src-tauri/Cargo.toml') }} - restore-keys: | - cef-windows- - - name: Cache vendored tauri-cli binary - id: tauri-cli-cache - uses: actions/cache@v5 - with: - path: ~/.cargo/bin/cargo-tauri.exe - key: vendored-tauri-cli-windows-${{ hashFiles('app/src-tauri/vendor/tauri-cef/crates/tauri-cli/Cargo.toml') }} - - name: Install vendored tauri-cli (cef-aware bundler) - if: steps.tauri-cli-cache.outputs.cache-hit != 'true' - shell: bash - run: bash scripts/ci-cancel-aware.sh cargo install --locked --path app/src-tauri/vendor/tauri-cef/crates/tauri-cli - - name: Enable Corepack - run: corepack enable - - name: Install dependencies - run: bash scripts/ci-cancel-aware.sh pnpm install --frozen-lockfile - - # vite build runs via tauri.conf.json's beforeBuildCommand during the - # "Build Tauri app" step below — no separate frontend build needed. - # Core is linked into the Tauri binary as a path dep — no separate - # sidecar build / stage / path-resolution step needed. - - name: Define Tauri configuration overrides - id: config-overrides - # `prepareTauriConfig.js` only emits the Windows DigiCert sign - # command at this point (`WITH_UPDATER` defaults to off here so - # this PR-build matrix doesn't try to mint signed updater - # artifacts it has no key for). - uses: actions/github-script@v8 - with: - script: | - const workspacePath = process.env.GITHUB_WORKSPACE.replace(/\\/g, '/'); - const prefix = workspacePath.startsWith('/') ? 'file://' : 'file:///'; - const moduleUrl = `${prefix}${workspacePath}/scripts/prepareTauriConfig.js`; - const { default: prepareTauriConfig } = await import(moduleUrl); - const config = prepareTauriConfig(); - core.setOutput('json', JSON.stringify(config)); - - name: Build Tauri app (CEF default, vendored CLI) - id: tauri-build - shell: bash - working-directory: app - env: - BASE_URL: ${{ vars.BASE_URL }} - VITE_BACKEND_URL: ${{ vars.BASE_URL }} - VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }} - VITE_DEBUG: ${{ vars.VITE_DEBUG }} - VITE_MINIMUM_SUPPORTED_APP_VERSION: ${{ vars.VITE_MINIMUM_SUPPORTED_APP_VERSION }} - VITE_LATEST_APP_DOWNLOAD_URL: ${{ vars.VITE_LATEST_APP_DOWNLOAD_URL }} - TAURI_CONFIG_OVERRIDE: ${{ steps.config-overrides.outputs.json }} - run: | - NODE_OPTIONS="--max-old-space-size=8192" bash ../scripts/ci-cancel-aware.sh cargo tauri build -c "$TAURI_CONFIG_OVERRIDE" --target x86_64-pc-windows-msvc - - name: Upload MSI artifact - uses: actions/upload-artifact@v5 - with: - name: windows-msi - path: | - app/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/msi/*.msi - target/x86_64-pc-windows-msvc/release/bundle/msi/*.msi - - name: Upload NSIS artifact - uses: actions/upload-artifact@v5 - with: - name: windows-nsis - path: | - app/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe - target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe - - name: Upload standalone CLI binary - uses: actions/upload-artifact@v5 - with: - name: windows-cli - path: |- - app/src-tauri/target/x86_64-pc-windows-msvc/release/openhuman-core.exe - target/x86_64-pc-windows-msvc/release/openhuman-core.exe diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 431b3c8af..000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,81 +0,0 @@ ---- -name: Build -on: - workflow_dispatch: -permissions: - contents: read - packages: read - pull-requests: read - # Required for Sentry to associate commits with releases - actions: read - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }} - cancel-in-progress: true -jobs: - build: - name: Build Tauri App - runs-on: ubuntu-22.04 - timeout-minutes: 30 - container: - image: ghcr.io/tinyhumansai/openhuman_ci:rust-1.93.0 - steps: - - name: Checkout code - uses: actions/checkout@v5 - with: - fetch-depth: 1 - persist-credentials: false - submodules: recursive - - name: Cache Rust build artifacts - uses: Swatinem/rust-cache@v2 - with: - workspaces: | - . -> target - app/src-tauri -> target - cache-on-failure: true - - # CEF (Chromium Embedded Framework) runtime is downloaded on-demand by - # cef-dll-sys + the vendored tauri-cli. Cache it across builds — the - # payload is ~400MB per platform and fetching every run is painful. - - name: Cache CEF binary distribution - uses: actions/cache@v5 - with: - path: ~/.cache/tauri-cef - key: cef-ubuntu-22.04-${{ hashFiles('app/src-tauri/Cargo.toml') }} - restore-keys: | - cef-ubuntu-22.04- - - # Note: the vendored CEF-aware tauri-cli, Node 24, and pnpm are all - # pre-installed in the ghcr.io/tinyhumansai/openhuman_ci image (see - # .github/Dockerfile), so `cargo tauri build` below resolves to the - # fork without any per-run compile step. - - name: Cache pnpm store - uses: actions/cache@v5 - with: - path: ~/.local/share/pnpm/store - key: pnpm-store-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} - restore-keys: | - pnpm-store-${{ runner.os }}- - - name: Install dependencies - run: bash scripts/ci-cancel-aware.sh pnpm install --frozen-lockfile - # Core is linked into the Tauri binary as a path dep — no separate - # sidecar build / stage step needed. - - name: Build Tauri app (CEF default) - working-directory: app - run: | - # Skip tsc in beforeBuildCommand — typechecking runs in the dedicated - # `typecheck` workflow, so doing it again here is duplicated CI time. - TAURI_CONFIG_OVERRIDE='{"build":{"beforeBuildCommand":"npx vite build"},"plugins":{"updater":{"active":false}}}' - bash ../scripts/ci-cancel-aware.sh cargo tauri build -c "$TAURI_CONFIG_OVERRIDE" --bundles deb - env: - NODE_ENV: production - # CI builds should point at staging, not production. - # Without these, APP_ENV is undefined in config.ts and - # DEFAULT_BACKEND_URL falls through to api.tinyhumans.ai. - VITE_OPENHUMAN_APP_ENV: staging - VITE_BACKEND_URL: https://staging-api.tinyhumans.ai - CARGO_PROFILE_RELEASE_OPT_LEVEL: "1" - CARGO_PROFILE_RELEASE_CODEGEN_UNITS: "16" - CARGO_PROFILE_RELEASE_LTO: "false" - CARGO_PROFILE_RELEASE_STRIP: "true" - CARGO_PROFILE_RELEASE_DEBUG: "false" diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml deleted file mode 100644 index 25840bdb0..000000000 --- a/.github/workflows/coverage.yml +++ /dev/null @@ -1,204 +0,0 @@ -name: Coverage Gate - -on: - workflow_dispatch: - -permissions: - contents: read - packages: read - pull-requests: read - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }} - cancel-in-progress: true - -defaults: - run: - # The CI container's default `sh` is dash, which rejects `set -o pipefail` - # and bashisms like `mapfile`. Force bash for every `run:` step. - shell: bash - -jobs: - frontend-coverage: - name: Frontend Coverage (Vitest) - runs-on: ubuntu-22.04 - timeout-minutes: 30 - container: - image: ghcr.io/tinyhumansai/openhuman_ci:rust-1.93.0 - steps: - - name: Checkout code - uses: actions/checkout@v5 - with: - fetch-depth: 1 - - name: Cache pnpm store - uses: actions/cache@v5 - with: - path: ~/.local/share/pnpm/store - key: pnpm-store-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} - restore-keys: | - pnpm-store-${{ runner.os }}- - - name: Install dependencies - run: pnpm install --frozen-lockfile - - name: Run Vitest with coverage - run: pnpm test:coverage - working-directory: app - env: - NODE_ENV: test - - name: Normalize lcov source paths to repo root - # Vitest writes paths relative to app/ (the Vite root). diff-cover - # resolves SF: paths against the repo root, so prefix them with `app/` - # to match how `git diff` names the files. - run: | - set -euo pipefail - test -f app/coverage/lcov.info - sed -i -E 's#^SF:(src/)#SF:app/\1#' app/coverage/lcov.info - sed -i -E 's#^SF:(\./src/)#SF:app/src/#' app/coverage/lcov.info - - name: Upload frontend lcov - uses: actions/upload-artifact@v5 - with: - name: lcov-frontend - path: app/coverage/lcov.info - retention-days: 7 - if-no-files-found: error - - rust-core-coverage: - name: Rust Core Coverage (cargo-llvm-cov) - runs-on: ubuntu-22.04 - timeout-minutes: 30 - container: - image: ghcr.io/tinyhumansai/openhuman_ci:rust-1.93.0 - env: - CARGO_INCREMENTAL: "0" - steps: - - name: Free disk space - run: | - rm -rf /__t/* || true - echo "Disk after cleanup:" - df -h /__w || true - - name: Checkout code - uses: actions/checkout@v5 - with: - fetch-depth: 1 - persist-credentials: false - submodules: recursive - - name: Cache Rust build artifacts - uses: Swatinem/rust-cache@v2 - with: - workspaces: . -> target - cache-on-failure: true - key: core-coverage - - name: Install cargo-llvm-cov - uses: taiki-e/install-action@cargo-llvm-cov - - name: Run cargo llvm-cov for openhuman core - run: cargo llvm-cov --no-fail-fast -p openhuman --lcov --output-path lcov-core.info - env: - CARGO_BUILD_JOBS: "1" - - name: Upload core lcov - uses: actions/upload-artifact@v5 - with: - name: lcov-rust-core - path: lcov-core.info - retention-days: 7 - if-no-files-found: error - - rust-tauri-coverage: - name: Rust Tauri Coverage (cargo-llvm-cov) - runs-on: ubuntu-22.04 - timeout-minutes: 30 - container: - image: ghcr.io/tinyhumansai/openhuman_ci:rust-1.93.0 - env: - CARGO_INCREMENTAL: "0" - steps: - - name: Checkout code - uses: actions/checkout@v5 - with: - fetch-depth: 1 - persist-credentials: false - submodules: recursive - - name: Cache Rust build artifacts - uses: Swatinem/rust-cache@v2 - with: - workspaces: | - . -> target - app/src-tauri -> target - cache-on-failure: true - key: tauri-coverage - - name: Cache CEF binary distribution - uses: actions/cache@v5 - with: - path: ~/.cache/tauri-cef - key: cef-ubuntu-22.04-${{ hashFiles('app/src-tauri/Cargo.toml') }} - restore-keys: | - cef-ubuntu-22.04- - - name: Install cargo-llvm-cov - uses: taiki-e/install-action@cargo-llvm-cov - - name: Run cargo llvm-cov for Tauri shell - run: cargo llvm-cov --manifest-path app/src-tauri/Cargo.toml --lcov --output-path lcov-tauri.info - - name: Upload tauri lcov - uses: actions/upload-artifact@v5 - with: - name: lcov-rust-tauri - path: lcov-tauri.info - retention-days: 7 - if-no-files-found: error - - coverage-gate: - name: Coverage Gate (diff-cover ≥ 80%) - needs: [frontend-coverage, rust-core-coverage, rust-tauri-coverage] - runs-on: ubuntu-latest - timeout-minutes: 10 - if: github.event_name == 'pull_request' - steps: - - name: Checkout code - uses: actions/checkout@v5 - with: - # diff-cover needs full history for the merge-base with the PR base. - fetch-depth: 0 - - name: Fetch PR base branch - run: git fetch origin "${{ github.base_ref }}" --depth=200 - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: "3.12" - - name: Install diff-cover - run: pip install 'diff-cover>=9.2.0' - - name: Download all lcov artifacts - uses: actions/download-artifact@v5 - with: - path: lcov-artifacts - pattern: lcov-* - merge-multiple: false - - name: List collected lcov files - run: | - set -euo pipefail - find lcov-artifacts -type f -name '*.info' -print - - name: Enforce ≥ 80% coverage on changed lines - # diff-cover accepts multiple lcov inputs and computes coverage on - # *changed lines only*, scoped to files present in the lcov report. - # Test files are excluded from the lcov reports themselves (Vitest - # `coverage.exclude`, cargo-llvm-cov's `#[cfg(test)]` filtering), - # so changed test lines are simply not measured and do not skew the - # ratio. - run: | - set -euo pipefail - mapfile -t LCOV_FILES < <(find lcov-artifacts -type f -name '*.info' | sort) - if [ "${#LCOV_FILES[@]}" -eq 0 ]; then - echo "::error::No lcov files found — coverage gate cannot run" - exit 1 - fi - diff-cover "${LCOV_FILES[@]}" \ - --compare-branch="origin/${{ github.base_ref }}" \ - --fail-under=80 \ - --html-report diff-coverage.html \ - --markdown-report diff-coverage.md - - name: Upload diff-cover report - if: always() - uses: actions/upload-artifact@v5 - with: - name: diff-coverage-report - path: | - diff-coverage.html - diff-coverage.md - retention-days: 14 - if-no-files-found: warn diff --git a/.github/workflows/deploy-smoke.yml b/.github/workflows/deploy-smoke.yml deleted file mode 100644 index e63ce331c..000000000 --- a/.github/workflows/deploy-smoke.yml +++ /dev/null @@ -1,179 +0,0 @@ ---- -name: Deploy Smoke -on: - workflow_dispatch: -permissions: - contents: read -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }} - cancel-in-progress: true -jobs: - docker-image: - name: Build & smoke-test core image - runs-on: ubuntu-22.04 - timeout-minutes: 30 - steps: - - name: Checkout - uses: actions/checkout@v5 - with: - fetch-depth: 1 - submodules: false - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Build openhuman-core image - uses: docker/build-push-action@v6 - with: - context: . - file: Dockerfile - push: false - load: true - tags: openhuman-core:smoke - cache-from: type=gha,scope=deploy-smoke - cache-to: type=gha,scope=deploy-smoke,mode=max - - - name: Run container - run: | - docker run -d \ - --name oh-smoke \ - -p 7788:7788 \ - -e OPENHUMAN_CORE_TOKEN=ci-smoke-token \ - -e OPENHUMAN_APP_ENV=staging \ - -e BACKEND_URL=https://staging-api.tinyhumans.ai \ - openhuman-core:smoke - - - name: Wait for /health - run: | - set -e - for i in $(seq 1 30); do - if curl -fsS http://localhost:7788/health > /tmp/health.json; then - echo "Healthy on attempt $i" - cat /tmp/health.json - exit 0 - fi - echo "attempt $i: not ready, sleeping..." - sleep 2 - done - echo "Container never became healthy. Logs:" - docker logs oh-smoke || true - exit 1 - - - name: Verify /rpc rejects without bearer token - run: | - set -e - status=$(curl -s -o /tmp/rpc.json -w "%{http_code}" \ - -X POST http://localhost:7788/rpc \ - -H 'Content-Type: application/json' \ - -d '{"jsonrpc":"2.0","id":1,"method":"openhuman.about_app_list","params":{}}') - if [ "$status" != "401" ]; then - echo "Expected 401 from /rpc without token, got $status" - cat /tmp/rpc.json - docker logs oh-smoke || true - exit 1 - fi - - - name: Verify /rpc accepts the configured bearer token - run: | - set -e - status=$(curl -s -o /tmp/rpc-ok.json -w "%{http_code}" \ - -X POST http://localhost:7788/rpc \ - -H 'Content-Type: application/json' \ - -H 'Authorization: Bearer ci-smoke-token' \ - -d '{"jsonrpc":"2.0","id":1,"method":"openhuman.about_app_list","params":{}}') - if [ "$status" != "200" ]; then - echo "Expected 200 from authenticated /rpc, got $status" - cat /tmp/rpc-ok.json - docker logs oh-smoke || true - exit 1 - fi - cat /tmp/rpc-ok.json - - - name: Container logs (always) - if: always() - run: docker logs oh-smoke || true - - - name: Tear down - if: always() - run: docker rm -f oh-smoke || true - - # Regression gate for issue #2065: named volume starts root-owned; the - # entrypoint must chown it before dropping to the openhuman user, otherwise - # the first disk write (init_rpc_token → write_token_file) raises EACCES - # and the process exits with code 1. - # - # This job deliberately omits OPENHUMAN_CORE_TOKEN (so the core WILL attempt - # to write core.token) and mounts a fresh anonymous volume at the workspace - # path (so Docker creates it root:root). The existing job above always sets - # the token and therefore short-circuits the write — it cannot catch this bug. - docker-volume-permissions: - name: Smoke-test core with fresh volume and no pre-set token - runs-on: ubuntu-22.04 - timeout-minutes: 30 - needs: docker-image - steps: - - name: Checkout - uses: actions/checkout@v5 - with: - fetch-depth: 1 - submodules: false - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Build openhuman-core image - uses: docker/build-push-action@v6 - with: - context: . - file: Dockerfile - push: false - load: true - tags: openhuman-core:smoke - cache-from: type=gha,scope=deploy-smoke - cache-to: type=gha,scope=deploy-smoke,mode=max - - - name: Run container with fresh anonymous volume and no token - run: | - docker run -d \ - --name oh-vol-smoke \ - -p 7789:7788 \ - -v oh-vol-smoke-workspace:/home/openhuman/.openhuman \ - -e OPENHUMAN_APP_ENV=staging \ - -e BACKEND_URL=https://staging-api.tinyhumans.ai \ - openhuman-core:smoke - - - name: Wait for /health (volume-permissions path) - run: | - set -e - for i in $(seq 1 30); do - if curl -fsS http://localhost:7789/health > /tmp/vol-health.json; then - echo "Healthy on attempt $i" - cat /tmp/vol-health.json - exit 0 - fi - echo "attempt $i: not ready, sleeping..." - sleep 2 - done - echo "Container never became healthy. Logs:" - docker logs oh-vol-smoke || true - exit 1 - - - name: Assert no permission-denied errors in logs - run: | - set -e - if docker logs oh-vol-smoke 2>&1 | grep -q "Permission denied (os error 13)"; then - echo "FAIL: 'Permission denied (os error 13)' found in container logs" - docker logs oh-vol-smoke || true - exit 1 - fi - echo "PASS: no permission-denied errors in container logs" - - - name: Container logs (always) - if: always() - run: docker logs oh-vol-smoke || true - - - name: Tear down - if: always() - run: | - docker rm -f oh-vol-smoke || true - docker volume rm oh-vol-smoke-workspace || true diff --git a/.github/workflows/installer-smoke.yml b/.github/workflows/installer-smoke.yml deleted file mode 100644 index ed9e558f4..000000000 --- a/.github/workflows/installer-smoke.yml +++ /dev/null @@ -1,40 +0,0 @@ ---- -name: Installer Smoke -on: - workflow_dispatch: -permissions: - contents: read -jobs: - smoke-unix: - name: Smoke install.sh (${{ matrix.os }}) - runs-on: ${{ matrix.os }} - timeout-minutes: 10 - strategy: - fail-fast: false - matrix: - # ubuntu-22.04 re-enabled: install.sh --dry-run now warns + exits 0 when - # no Linux release asset is published (see #785). - os: [macos-latest, ubuntu-22.04] - steps: - - name: Checkout - uses: actions/checkout@v5 - - name: Unit tests (install.sh helpers) - run: bash scripts/test_install.sh - - name: Run installer dry-run - run: bash scripts/install.sh --dry-run --verbose - - smoke-windows: - name: install.ps1 tests + dry-run (windows-latest) - runs-on: windows-latest - timeout-minutes: 10 - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Unit tests (MSI args / asset selection) - shell: pwsh - run: pwsh -NoProfile -File ./scripts/tests/OpenHumanWindowsInstall.Tests.ps1 - - - name: Run installer dry-run - shell: pwsh - run: ./scripts/install.ps1 -DryRun diff --git a/.github/workflows/release-staging.yml b/.github/workflows/release-staging.yml index db595e8f8..22b26dd93 100644 --- a/.github/workflows/release-staging.yml +++ b/.github/workflows/release-staging.yml @@ -248,9 +248,8 @@ jobs: # Phase 2b: Build the openhuman-core Docker image without pushing. # Mirrors the production `build-docker` job so a Dockerfile regression # surfaces on the staging cut — no GHCR push, no `:staging-*` tag - # pollution. `deploy-smoke.yml` already covers the build path on PRs - # that touch Dockerfile / src; this is the equivalent gate at the - # staging-tag boundary so a green staging cut means the next prod + # pollution. This is the Docker build gate at the staging-tag + # boundary so a green staging cut means the next prod # promotion's GHCR push will succeed too. # ========================================================================= build-docker: diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml deleted file mode 100644 index 0833c7347..000000000 --- a/.github/workflows/typecheck.yml +++ /dev/null @@ -1,67 +0,0 @@ ---- -name: Type Check -on: - workflow_dispatch: -permissions: - contents: read - packages: read - pull-requests: read -concurrency: - group: - ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref - || github.ref }} - cancel-in-progress: true -jobs: - typecheck: - name: Type Check TypeScript - runs-on: ubuntu-22.04 - timeout-minutes: 15 - container: - image: ghcr.io/tinyhumansai/openhuman_ci:rust-1.93.0 - steps: - - name: Checkout code - uses: actions/checkout@v5 - with: - fetch-depth: 1 - - name: Cache pnpm store - uses: actions/cache@v5 - with: - path: ~/.local/share/pnpm/store - key: pnpm-store-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} - restore-keys: | - pnpm-store-${{ runner.os }}- - - name: Install dependencies - run: pnpm install --frozen-lockfile - - name: Type check TypeScript files - run: pnpm --filter openhuman-app compile - env: - NODE_ENV: test - - name: Check Prettier formatting - run: pnpm --filter openhuman-app format:check - env: - NODE_ENV: test - - name: Run ESLint - run: pnpm --filter openhuman-app lint - env: - NODE_ENV: test - rust-quality: - name: Rust Quality (fmt + clippy) - runs-on: ubuntu-22.04 - timeout-minutes: 20 - container: - image: ghcr.io/tinyhumansai/openhuman_ci:rust-1.93.0 - steps: - - name: Checkout code - uses: actions/checkout@v5 - with: - fetch-depth: 1 - - name: Cache Rust build artifacts - uses: Swatinem/rust-cache@v2 - with: - workspaces: | - . -> target - cache-on-failure: true - - name: Check formatting (cargo fmt) - run: cargo fmt --all -- --check - - name: Run clippy (core crate) - run: cargo clippy -p openhuman diff --git a/AGENTS.md b/AGENTS.md index 9ce0a89ae..f05a67e4b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -79,7 +79,7 @@ pnpm debug logs last # print most recent ### Coverage requirement (merge gate) -PRs need **≥ 80% coverage on changed lines** via `diff-cover` over Vitest + `cargo-llvm-cov` lcov. Enforced by `.github/workflows/coverage.yml`. +PRs need **≥ 80% coverage on changed lines** via `diff-cover` over Vitest + `cargo-llvm-cov` lcov. Enforced by the coverage jobs (`frontend-coverage`/`rust-core-coverage`/`rust-tauri-coverage`/`coverage-gate`) in `.github/workflows/pr-ci.yml`. --- diff --git a/docs/wechat-message-scraping-issue.md b/docs/wechat-message-scraping-issue.md index 147fba202..bea6cbf70 100644 --- a/docs/wechat-message-scraping-issue.md +++ b/docs/wechat-message-scraping-issue.md @@ -26,7 +26,7 @@ Add a dedicated WeChat webview ingestion path, following the existing Franz-styl - [ ] **Unread/respond surfaces** — unread or actionable WeChat activity appears in the same provider-surface/respond-queue flows used by other messaging integrations where applicable. - [ ] **Privacy + control** — users can understand, disable, and purge stored WeChat-derived data using existing account/memory controls or clearly-scoped additions. - [ ] **Tests** — add focused Vitest and Rust coverage for the WeChat provider path, including at least one failure/edge case. -- [ ] **Diff coverage ≥ 80%** — the implementing PR meets the changed-lines coverage gate (Vitest + cargo-llvm-cov, enforced by [`.github/workflows/coverage.yml`](../.github/workflows/coverage.yml)). +- [ ] **Diff coverage ≥ 80%** — the implementing PR meets the changed-lines coverage gate (Vitest + cargo-llvm-cov, enforced by [`.github/workflows/pr-ci.yml`](../.github/workflows/pr-ci.yml)). - Evaluate whether WeChat needs a native scanner path, injected bridge, or hybrid approach after validating what the embedded web client exposes at runtime. diff --git a/gitbooks/developing/release-policy.md b/gitbooks/developing/release-policy.md index 4d4a4a336..c56688164 100644 --- a/gitbooks/developing/release-policy.md +++ b/gitbooks/developing/release-policy.md @@ -22,7 +22,7 @@ Production web builds embed a **minimum supported app semver** at **build time** | `VITE_MINIMUM_SUPPORTED_APP_VERSION` | e.g. `0.51.0` - desktop app must be **≥** this to finish `openhuman://oauth/success`. | | `VITE_LATEST_APP_DOWNLOAD_URL` | Optional; defaults to `https://github.com/tinyhumansai/openhuman/releases/latest`. Opened when the gate blocks OAuth. | -Configure these as **GitHub Actions variables**. They must be present on **both** the standalone **`pnpm build`** step and the **`tauri-apps/tauri-action`** step env in `.github/workflows/build-desktop.yml` (the reusable matrix invoked by `release-production.yml` / `release-staging.yml`) and `build-windows.yml` so the Vite bundle embedded in shipped installers includes the gate. Leave `VITE_MINIMUM_SUPPORTED_APP_VERSION` **unset** for local dev (gate disabled). +Configure these as **GitHub Actions variables**. They must be present on **both** the standalone **`pnpm build`** step and the **`tauri-apps/tauri-action`** step env in `.github/workflows/build-desktop.yml` (the reusable matrix invoked by `release-production.yml` / `release-staging.yml`) so the Vite bundle embedded in shipped installers includes the gate. Leave `VITE_MINIMUM_SUPPORTED_APP_VERSION` **unset** for local dev (gate disabled). Implementation: `app/src/utils/oauthAppVersionGate.ts`, `app/src/utils/desktopDeepLinkListener.ts`. diff --git a/gitbooks/developing/release-policy.zh-CN.md b/gitbooks/developing/release-policy.zh-CN.md index 23b5701fb..d8435f671 100644 --- a/gitbooks/developing/release-policy.zh-CN.md +++ b/gitbooks/developing/release-policy.zh-CN.md @@ -23,7 +23,7 @@ lang: zh-CN | `VITE_MINIMUM_SUPPORTED_APP_VERSION` | 例如 `0.51.0` —— 桌面应用必须 **≥** 此版本才能完成 `openhuman://oauth/success`。 | | `VITE_LATEST_APP_DOWNLOAD_URL` | 可选;默认为 `https://github.com/tinyhumansai/openhuman/releases/latest`。当门禁阻止 OAuth 时打开。 | -将这些配置为 **GitHub Actions 变量**。它们必须同时存在于独立的 **`pnpm build`** 步骤和 **`.github/workflows/build-desktop.yml`** 中的 **`tauri-apps/tauri-action`** 步骤环境变量中(由 `release-production.yml` / `release-staging.yml` 调用的可重用矩阵)以及 `build-windows.yml`,以便嵌入已发布安装包的 Vite bundle 包含该门禁。本地开发时保持 `VITE_MINIMUM_SUPPORTED_APP_VERSION` **未设置**(门禁禁用)。 +将这些配置为 **GitHub Actions 变量**。它们必须同时存在于独立的 **`pnpm build`** 步骤和 **`.github/workflows/build-desktop.yml`** 中的 **`tauri-apps/tauri-action`** 步骤环境变量中(由 `release-production.yml` / `release-staging.yml` 调用的可重用矩阵),以便嵌入已发布安装包的 Vite bundle 包含该门禁。本地开发时保持 `VITE_MINIMUM_SUPPORTED_APP_VERSION` **未设置**(门禁禁用)。 实现:`app/src/utils/oauthAppVersionGate.ts`、`app/src/utils/desktopDeepLinkListener.ts`。 diff --git a/gitbooks/features/cloud-deploy.md b/gitbooks/features/cloud-deploy.md index b9c227f46..8094b42f3 100644 --- a/gitbooks/features/cloud-deploy.md +++ b/gitbooks/features/cloud-deploy.md @@ -629,12 +629,7 @@ fly machine restart --config .fly/fly.toml ## Smoke test -The repo ships [`.github/workflows/deploy-smoke.yml`](../../.github/workflows/deploy-smoke.yml), -which runs on every PR that touches the deploy artifacts. It builds the -Docker image, boots it, and polls `/health`, so a regression in the cloud -deploy path fails CI before it lands on `main`. - -The workflow contains two jobs: +Two failure modes guard the cloud deploy path: - **`docker-image`** — sets `OPENHUMAN_CORE_TOKEN` and mounts no volume. Protects the DigitalOcean App Platform path (`.do/app.yaml`) where the @@ -644,7 +639,7 @@ The workflow contains two jobs: exact failure mode of issue #2065 and asserts that `/health` returns 200 and that `Permission denied (os error 13)` is absent from the logs. -To run the same check locally: +Run the smoke check locally: ```bash docker build -t openhuman-core:smoke . diff --git a/gitbooks/features/cloud-deploy.zh-CN.md b/gitbooks/features/cloud-deploy.zh-CN.md index 3b32219ef..1549c9400 100644 --- a/gitbooks/features/cloud-deploy.zh-CN.md +++ b/gitbooks/features/cloud-deploy.zh-CN.md @@ -487,9 +487,7 @@ fly machine restart --config .fly/fly.toml ## 冒烟测试 -仓库附带 [`.github/workflows/deploy-smoke.yml`](../../.github/workflows/deploy-smoke.yml),它在每次触及部署工件的 PR 上运行。它构建 Docker 镜像、启动它并轮询 `/health`,以便云部署路径中的回归在到达 `main` 之前就在 CI 中失败。 - -该 workflow 包含两个 job: +云部署路径有两种需要防范的失败模式: - **`docker-image`**——设置 `OPENHUMAN_CORE_TOKEN` 且不挂载卷。保护 DigitalOcean App Platform 路径(`.do/app.yaml`),其中 token 始终预置且不挂载持久卷。 - **`docker-volume-permissions`**——省略 `OPENHUMAN_CORE_TOKEN` 并在 `/home/openhuman/.openhuman` 挂载一个新的匿名卷。复现 issue #2065 的确切失败模式,并断言 `/health` 返回 200 且日志中不存在 `Permission denied (os error 13)`。 diff --git a/scripts/shortcuts/review/coverage.sh b/scripts/shortcuts/review/coverage.sh index 8755364da..1a3c39db8 100644 --- a/scripts/shortcuts/review/coverage.sh +++ b/scripts/shortcuts/review/coverage.sh @@ -41,14 +41,14 @@ coverage_checks=$(gh pr checks "$REVIEW_PR" -R "$REVIEW_REPO_RESOLVED" 2>/dev/nu | grep -i 'coverage' || true) coverage_runs_json=$(gh run list -R "$REVIEW_REPO_RESOLVED" \ - --workflow "Coverage Gate" \ + --workflow "PR CI" \ --branch "$REVIEW_HEAD_BRANCH" \ --limit 5 \ --json databaseId,status,conclusion,url,createdAt,updatedAt,headSha || echo '[]') coverage_runs_summary=$(printf '%s\n' "$coverage_runs_json" | jq -r ' if length == 0 then - "No recent Coverage Gate workflow runs found for this branch." + "No recent PR CI (coverage gate) workflow runs found for this branch." else .[] | "- run=\(.databaseId) status=\(.status) conclusion=\(.conclusion // "n/a") sha=\(.headSha) updated=\(.updatedAt) url=\(.url)" end @@ -65,7 +65,7 @@ coverage gate for PR #$REVIEW_PR. Current coverage-related PR checks: $coverage_checks -Recent Coverage Gate workflow runs for this branch: +Recent PR CI (coverage gate) workflow runs for this branch: $coverage_runs_summary Use the GitHub Actions error output for the coverage jobs to identify what is \