vetto 0.2.9

Daemon-less sandbox + security layer for AI coding agents (Landlock/Seatbelt, TUI statusline, post-session audit reports)
Documentation
name: ci

on:
  push:
    branches: [main, 'tier-*', 'feat/*']
  pull_request:
  workflow_dispatch:

env:
  CARGO_TERM_COLOR: always

jobs:
  check:
    name: fmt + clippy + test (ubuntu)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

      - uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy, rustfmt

      - uses: Swatinem/rust-cache@v2

      - name: Install cargo-llvm-cov
        uses: taiki-e/install-action@v2
        with:
          tool: cargo-llvm-cov

      - name: Format
        run: cargo fmt --check

      - name: Clippy (deny warnings)
        run: cargo clippy --all-targets --all-features -- -D warnings

      - name: Tests + coverage
        # Runs the full suite under llvm-cov so one job produces both test
        # results and an lcov artifact. Enforcement tests self-skip when the
        # runner lacks a tier; the GitHub-hosted ubuntu kernel normally
        # provides both landlock and unprivileged userns. The --fail-under
        # threshold is intentionally unset until a real baseline number
        # exists; do not invent one.
        run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info

      - name: Report total coverage percentage and enforce the floor
        # Baseline measured 2026-08-30: 39.7% (13,514/34,032 lines). The
        # floor sits below it to absorb runner variance; raise it
        # deliberately, never lower it silently.
        run: |
          python3 - <<'PY'
          import sys

          FLOOR = 38.0
          hits = misses = 0
          with open("lcov.info", encoding="utf-8") as fh:
              for line in fh:
                  if line.startswith("LF:"):
                      misses += int(line[3:])
                  elif line.startswith("LH:"):
                      hits += int(line[3:])
          total = hits + misses
          pct = (hits * 100.0 / total) if total else 0.0
          print(f"coverage: {hits}/{total} lines = {pct:.1f}% (floor {FLOOR}%)")
          if pct < FLOOR:
              print(f"coverage {pct:.1f}% fell below the {FLOOR}% floor")
              sys.exit(1)
          PY

      - name: Upload coverage artifact
        uses: actions/upload-artifact@v4
        with:
          name: coverage-lcov
          path: lcov.info

      - name: Release build
        run: cargo build --release --all-features

  check-aarch64:
    name: compile + syscall ABI tests (aarch64 via QEMU)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

      # Register binfmt_misc so the aarch64 test binary executes under QEMU.
      - uses: docker/setup-qemu-action@v3
        with:
          platforms: arm64

      - uses: taiki-e/install-action@v2
        with:
          tool: cross

      # Compile the complete test suite for ARM64, then execute the pure BPF
      # syscall/architecture tests under QEMU. Full filesystem and /proc tests
      # run natively in the Ubuntu job: qemu-user does not preserve every
      # openat/procfs behavior those enforcement tests intentionally inspect.
      - name: Compile complete test suite (aarch64)
        run: cross test --target aarch64-unknown-linux-gnu --all-features --no-run

      - name: Execute syscall ABI tests (aarch64 via QEMU)
        run: >-
          cross test --target aarch64-unknown-linux-gnu --all-features --lib
          sandbox::linux::seccomp_netblock::tests --quiet

  build-macos:
    name: build + test (macOS arm64 and x86_64 check)
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v5
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy
          targets: x86_64-apple-darwin
      - uses: Swatinem/rust-cache@v2
      - run: cargo build --release --all-features
      - run: cargo test --all-features --quiet
      - run: cargo clippy --all-targets --all-features -- -D warnings
      - name: Intel macOS cross-check
        run: cargo check --target x86_64-apple-darwin --all-features

  build-windows:
    name: build + test (Windows x86_64)
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@v5
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy
      - uses: Swatinem/rust-cache@v2
      - run: cargo build --release --all-features
      - run: cargo test --all-features --quiet
      - run: cargo clippy --all-targets --all-features -- -D warnings

  supply-chain:
    name: cargo-deny (advisories + licenses)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: EmbarkStudios/cargo-deny-action@v2

  perf:
    name: e2e spawn overhead + baseline gate
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

      - uses: dtolnay/rust-toolchain@stable

      - uses: Swatinem/rust-cache@v2

      - name: Build release binary
        run: cargo build --release --all-features

      # The bench resolves the vetto binary itself (CARGO_BIN_EXE_vetto) and
      # writes its 30-sample median/p95 summary to VETTO_PERF_OUT. It exits 0
      # without recording anything when the host has no usable sandbox tier,
      # so an incapable runner must not fail this job.
      - name: Run e2e spawn benchmark
        env:
          VETTO_PERF_OUT: ${{ runner.temp }}/vetto-perf-latest.json
        run: cargo bench --bench e2e_spawn --all-features

      # Gate: the committed baseline stays empty until this job has produced
      # one real run; only then do 3.0x median regressions fail the job.
      - name: Compare against perf baseline
        env:
          VETTO_PERF_LATEST: ${{ runner.temp }}/vetto-perf-latest.json
          VETTO_PERF_BASELINE: ${{ github.workspace }}/benches/baseline/perf-baseline.json
        run: |
          python3 - <<'PY' "$VETTO_PERF_LATEST" "$VETTO_PERF_BASELINE"
          import json
          import sys

          latest_path, baseline_path = sys.argv[1], sys.argv[2]

          with open(latest_path, encoding="utf-8") as fh:
              latest = json.load(fh)
          with open(baseline_path, encoding="utf-8") as fh:
              baseline = json.load(fh)

          base = baseline.get("variants") or {}
          if not base:
              print("no baseline yet; recording only")
              sys.exit(0)

          current = latest.get("variants") or {}
          failures = []
          for name in sorted(base):
              reference = base[name].get("median_ms")
              if reference is None or reference <= 0:
                  print(f"baseline variant '{name}' has no usable median_ms")
                  sys.exit(1)
              if name not in current:
                  print(f"variant '{name}' missing from latest perf summary")
                  failures.append(name)
                  continue
              median = current[name].get("median_ms")
              if median is None:
                  print(f"latest variant '{name}' has no median_ms")
                  failures.append(name)
                  continue
              if median > 3.0 * reference:
                  print(
                      f"variant '{name}': median {median} ms exceeds "
                      f"3.0x baseline {reference} ms"
                  )
                  failures.append(name)

          if failures:
              print(f"perf gate failed: {', '.join(failures)}")
              sys.exit(1)
          print("perf gate passed")
          PY

      - name: Upload perf summary
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: perf-summary
          path: ${{ runner.temp }}/vetto-perf-latest.json

  micro-tier-fallback:
    name: micro-tier downgrade & redteam (seccomp fallback)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - name: Test downgrade matrix and redteam
        run: |
          cargo test --test integration linux_downgrade --all-features
          cargo test --test integration linux_redteam --all-features
          cargo run --all-features -- redteam --json
          VETTO_FORCE_TIER=seccomp cargo run --all-features -- doctor