oxios 1.6.0

Oxios Agent OS — Agent Operating System powered by oxi-sdk
name: CI

on:
  push:
    branches: [main, develop]
  pull_request:

# Restrict default token permissions; jobs that need more (e.g. release) override.
permissions:
  contents: read

env:
  CARGO_TERM_COLOR: always

jobs:
  # ─── Formatting & Linting ────────────────────────────────────────
  fmt:
    name: fmt
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

      - name: Read rust-toolchain channel
        id: rust-channel
        run: |
          if grep -q 'channel = "' rust-toolchain.toml 2>/dev/null; then
            CHANNEL=$(grep 'channel = "' rust-toolchain.toml | sed 's/.*= "\([^"]*\)".*/\1/')
            echo "channel=$CHANNEL" >> $GITHUB_OUTPUT
          else
            echo "channel=stable" >> $GITHUB_OUTPUT
          fi

      - uses: dtolnay/rust-toolchain@master
        with:
          toolchain: ${{ steps.rust-channel.outputs.channel }}
          components: rustfmt, clippy

      - name: Check formatting
        run: cargo fmt --all -- --check

      - name: Clippy (core crates)
        run: |
          cargo clippy \
            -p oxios-kernel --features "sqlite-memory" \
            -p oxios-ouroboros -p oxios-gateway -p oxios-markdown \
            -p oxios-mcp -p oxios-memory -p oxios-calendar \
            -- \
            -D warnings

  # ─── Frontend ─────────────────────────────────────────────────────
  # Verifies web app: install deps, typecheck, run unit tests, build.
  # E2E (Playwright) is intentionally not run in CI — slow and flaky in container.
  frontend:
    name: frontend
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

      - uses: oven-sh/setup-bun@v2

      - name: Install deps
        working-directory: web
        run: bun install --frozen-lockfile

      - name: Type check
        working-directory: web
        run: bun run typecheck

      - name: Unit tests
        working-directory: web
        run: bun run test

      - name: Lint
        working-directory: web
        run: bun run lint

      - name: Build
        working-directory: web
        run: bun run build

  # ─── Test Suite ────────────────────────────────────────────────────
  test:
    name: test
    runs-on: ubuntu-latest
    needs: [fmt, frontend]
    strategy:
      fail-fast: false
      matrix:
        partition:
          - [1, 4]
          - [2, 4]
          - [3, 4]
          - [4, 4]
    steps:
      - uses: actions/checkout@v5

      - name: Read rust-toolchain channel
        id: rust-channel
        run: |
          if grep -q 'channel = "' rust-toolchain.toml 2>/dev/null; then
            CHANNEL=$(grep 'channel = "' rust-toolchain.toml | sed 's/.*= "\([^"]*\)".*/\1/')
            echo "channel=$CHANNEL" >> $GITHUB_OUTPUT
          else
            echo "channel=stable" >> $GITHUB_OUTPUT
          fi

      - uses: dtolnay/rust-toolchain@master
        with:
          toolchain: ${{ steps.rust-channel.outputs.channel }}

      - name: Install nextest
        uses: taiki-e/install-action@nextest

      - name: Cache cargo registry
        uses: actions/cache@v4
        with:
          path: ~/.cargo/registry
          key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}

      - name: Cache cargo index
        uses: actions/cache@v4
        with:
          path: ~/.cargo/git
          key: ${{ runner.os }}-cargo-git-${{ hashFiles('**/Cargo.lock') }}

      - name: Cache target directory
        uses: actions/cache@v4
        with:
          # Include github.sha so identical Cargo.lock state across commits
          # still produces a primary hit; restore-keys fall back to shared state.
          path: target
          key: ${{ runner.os }}-target-${{ hashFiles('**/Cargo.lock') }}-${{ github.sha }}
          restore-keys: |
            ${{ runner.os }}-target-${{ hashFiles('**/Cargo.lock') }}-

      # Frontend assets must exist before Rust compilation because
      # rust_embed::Embed generates code from web/dist/ at compile time.
      - uses: oven-sh/setup-bun@v2

      - name: Build web assets
        working-directory: web
        run: bun install --frozen-lockfile && bun run build

      - name: Run unit tests (partitioned)
        run: |
          cargo nextest run --workspace \
            --partition count:${{ matrix.partition[0] }}/${{ matrix.partition[1] }} \
            --no-fail-fast

      - name: Run doc tests
        run: cargo test --workspace --doc

  # ─── Security Audit ────────────────────────────────────────────────
  audit:
    name: audit
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

      - name: Read rust-toolchain channel
        id: rust-channel
        run: |
          if grep -q 'channel = "' rust-toolchain.toml 2>/dev/null; then
            CHANNEL=$(grep 'channel = "' rust-toolchain.toml | sed 's/.*= "\([^"]*\)".*/\1/')
            echo "channel=$CHANNEL" >> $GITHUB_OUTPUT
          else
            echo "channel=stable" >> $GITHUB_OUTPUT
          fi

      - uses: dtolnay/rust-toolchain@master
        with:
          toolchain: ${{ steps.rust-channel.outputs.channel }}

      - name: Install cargo-audit
        uses: taiki-e/install-action@cargo-audit

      - name: Security audit
        # Audit failures are non-blocking by policy — see audit.toml for ignored
        # advisories. Review the run log and open an issue for any new RUSTSEC.
        continue-on-error: true
        run: cargo audit

  # ─── Summary ───────────────────────────────────────────────────────
  summary:
    name: summary
    needs: [fmt, frontend, test, audit]
    runs-on: ubuntu-latest
    if: always()
    steps:
      - name: All checks passed
        run: echo "✅ All CI checks passed"