name: CI # Pull-request gate. Required to pass before merging into protected branches # (SOC 2 CC8.1 — automated checks run on every change before review/merge). on: pull_request: branches: [main, prod] workflow_dispatch: concurrency: group: ci-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true permissions: contents: read jobs: # Detect which parts of the repo a PR touches so each build job runs only when # something in its dependency closure changed — no point typechecking the API # for a frontend-only PR, or rebuilding the desktop app for an API-only one. # # Each filter = the app's own dir + every workspace package it depends on (its # pnpm `--filter "X..."` closure) + the shared foundation files that can break # any pnpm build. The Bun-based sandbox agent has its own in-dir lockfile, so it # only shares tsconfig.base.json; desktop runs no TS typecheck. # # A job gated `if: needs.changes.outputs.* == 'true'` reports as "skipped" when # it doesn't run, and GitHub treats a skipped job as passing — safe even if # these are later made required checks. workflow_dispatch always runs the full # suite (the `|| workflow_dispatch` guard on each job). changes: name: Detect changes runs-on: ubuntu-latest timeout-minutes: 5 outputs: api: ${{ steps.filter.outputs.api }} frontend: ${{ steps.filter.outputs.frontend }} cli: ${{ steps.filter.outputs.cli }} sandbox_agent: ${{ steps.filter.outputs.sandbox_agent }} desktop: ${{ steps.filter.outputs.desktop }} steps: - uses: actions/checkout@v4 - uses: dorny/paths-filter@v4 id: filter with: filters: | # Shared foundation for the pnpm-installed jobs (api/frontend/cli): # a change here can break any of their installs/typechecks. pnpm: &pnpm - 'pnpm-lock.yaml' - 'pnpm-workspace.yaml' - 'package.json' - '.npmrc' - 'tsconfig.base.json' - '.github/workflows/ci.yml' api: - *pnpm - 'apps/api/**' - 'packages/agent-tunnel/**' - 'packages/db/**' - 'packages/manifest-schema/**' - 'packages/shared/**' - 'packages/starter/**' frontend: - *pnpm - 'apps/web/**' - 'packages/shared/**' cli: - *pnpm - 'apps/cli/**' - 'packages/manifest-schema/**' - 'packages/starter/**' sandbox_agent: - '.github/workflows/ci.yml' - 'tsconfig.base.json' - 'apps/kortix-sandbox-agent-server/**' desktop: - '.github/workflows/ci.yml' - 'apps/desktop/**' api-typecheck: name: API typecheck needs: changes if: needs.changes.outputs.api == 'true' || github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest timeout-minutes: 15 steps: - name: Checkout uses: actions/checkout@v4 - name: Install Node uses: actions/setup-node@v4 with: node-version: 22 - name: Install pnpm # The lockfile is pnpm 8 format (lockfileVersion 6.0), matching the # packageManager pin. corepack provides exactly that version. run: | corepack enable pnpm echo "pnpm: $(pnpm -v) node: $(node -v)" - name: Install dependencies (kortix-api + workspace deps) # engine-strict is relaxed for CI only: an out-of-scope web dep declares # engines pnpm>=10/node>=24, which conflicts with the repo's pnpm 8 # lockfile. This relaxes the version check only — supply-chain controls # (minimum-release-age, ignore-scripts) stay enforced. run: pnpm install --frozen-lockfile --filter "kortix-api..." env: npm_config_engine_strict: "false" - name: Typecheck run: pnpm --filter kortix-api typecheck frontend-build: name: Frontend build needs: changes if: needs.changes.outputs.frontend == 'true' || github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest timeout-minutes: 20 steps: - name: Checkout uses: actions/checkout@v4 - name: Install Node uses: actions/setup-node@v4 with: node-version: 22 - name: Install pnpm run: | corepack enable pnpm echo "pnpm: $(pnpm -v) node: $(node -v)" - name: Install dependencies (frontend + workspace deps) run: pnpm install --frozen-lockfile --filter "./apps/web..." env: npm_config_engine_strict: "false" - name: Build standalone frontend run: pnpm --filter ./apps/web build env: NEXT_PUBLIC_BACKEND_URL: http://localhost:8008/v1 NEXT_PUBLIC_SUPABASE_URL: https://placeholder.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY: local-build-placeholder-anon-key NEXT_PUBLIC_BILLING_ENABLED: "false" NEXT_OUTPUT: standalone sandbox-agent-build: name: Sandbox agent build needs: changes if: needs.changes.outputs.sandbox_agent == 'true' || github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest timeout-minutes: 15 steps: - name: Checkout uses: actions/checkout@v4 - name: Install Bun uses: oven-sh/setup-bun@v2 with: bun-version: latest - name: Install dependencies run: bun install --frozen-lockfile working-directory: apps/kortix-sandbox-agent-server - name: Typecheck run: bun run typecheck working-directory: apps/kortix-sandbox-agent-server - name: Build Linux sandbox agent daemon run: BUN_COMPILE_TARGET=bun-linux-x64 bun run build working-directory: apps/kortix-sandbox-agent-server - name: Verify binary exists run: | test -x apps/kortix-sandbox-agent-server/dist/kortix-agent ls -lh apps/kortix-sandbox-agent-server/dist/kortix-agent cli-binary-smoke: name: CLI binary smoke needs: changes if: needs.changes.outputs.cli == 'true' || github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest timeout-minutes: 15 steps: - name: Checkout uses: actions/checkout@v4 - name: Install Node uses: actions/setup-node@v4 with: node-version: 22 - name: Install Bun uses: oven-sh/setup-bun@v2 with: bun-version: latest - name: Install pnpm run: | corepack enable pnpm echo "pnpm: $(pnpm -v) node: $(node -v) bun: $(bun -v)" - name: Install dependencies (CLI + workspace deps) run: pnpm install --frozen-lockfile --filter "@kortix/cli..." env: npm_config_engine_strict: "false" - name: Build Linux CLI binary run: | bun build \ --compile \ --minify \ --target=bun-linux-x64 \ --outfile=/tmp/kortix-cli-smoke \ apps/cli/src/index.ts - name: Smoke test binary run: /tmp/kortix-cli-smoke version desktop-installer-smoke: name: Desktop installer smoke # Skip on PRs that don't touch desktop code — the Tauri/Rust build is the # slowest job in CI and the desktop shell changes rarely. Push-to-main builds # (desktop.yml) still cover release artifacts. needs: changes if: needs.changes.outputs.desktop == 'true' || github.event_name == 'workflow_dispatch' runs-on: ubuntu-22.04 timeout-minutes: 40 steps: - name: Checkout uses: actions/checkout@v4 - name: Install Linux Tauri dependencies run: | sudo apt-get update sudo apt-get install -y --no-install-recommends \ build-essential \ curl \ file \ libgtk-3-dev \ libssl-dev \ libwebkit2gtk-4.1-dev \ libappindicator3-dev \ librsvg2-dev \ patchelf \ wget - name: Install Node uses: actions/setup-node@v4 with: node-version: 22 - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Cache Rust build uses: Swatinem/rust-cache@v2 with: workspaces: apps/desktop/src-tauri - name: Install pnpm run: corepack enable pnpm - name: Install dependencies run: pnpm install --frozen-lockfile --filter @kortix/desktop env: npm_config_engine_strict: "false" - name: Prepare desktop shell assets run: pnpm --filter @kortix/desktop run prepare-assets - name: Build Linux desktop installer run: pnpm --filter @kortix/desktop tauri build --bundles appimage env: KORTIX_DESKTOP_DEFAULT_URL: https://dev.kortix.com/projects - name: Verify installer exists run: | shopt -s nullglob files=(apps/desktop/src-tauri/target/release/bundle/appimage/*.AppImage) test "${#files[@]}" -gt 0 ls -lh "${files[@]}" dependency-scan: name: Dependency + secret scan runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Trivy filesystem (fail on CRITICAL) uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0 with: scan-type: fs scan-ref: . scanners: vuln,secret severity: CRITICAL ignore-unfixed: true exit-code: "1" format: table