infino 0.1.0

A fast retrieval engine that stores data on object storage and runs SQL, full-text search, and vector search over it from a single system — search-on-Parquet.
Documentation
name: CI

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

env:
  CARGO_TERM_COLOR: always
  RUSTFLAGS: -D warnings

jobs:
  check:
    name: Format, Build & Test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      # The default github-hosted ubuntu runner has ~14 GB free at job
      # start; infino's dep graph (datafusion + lance + tantivy + s3s)
      # plus `cargo llvm-cov`'s separate `target/llvm-cov-target/`
      # directory (it instruments a second full build of everything)
      # overflows that, so the coverage step fails with ENOSPC. Free
      # ~30 GB by removing toolchains the build doesn't need (Android
      # SDK, .NET, Haskell, ...) before any compile work starts.
      - uses: jlumbroso/free-disk-space@main
        with:
          tool-cache: false
          android: true
          dotnet: true
          haskell: true
          large-packages: true
          docker-images: true
          swap-storage: true
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt
      - uses: Swatinem/rust-cache@v2
        with:
          cache-directories: |
            ~/.cargo/bin
      - name: Install cargo-llvm-cov
        run: |
          if ! command -v cargo-llvm-cov &> /dev/null; then
            cargo install cargo-llvm-cov --locked
          fi
      - run: make ci

  public-api:
    name: Public API surface
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      # cargo-public-api builds rustdoc JSON, which requires nightly.
      - uses: dtolnay/rust-toolchain@nightly
      - uses: Swatinem/rust-cache@v2
        with:
          cache-directories: |
            ~/.cargo/bin
      - name: Install cargo-public-api
        run: |
          if ! command -v cargo-public-api &> /dev/null; then
            cargo install cargo-public-api --locked
          fi
      # Fails if the curated public surface drifts from the committed
      # `public-api.txt` snapshot. The snapshot is taken without
      # `test-helpers`, so internal modules stay off the contract.
      - run: make public-api

  python:
    name: Python bindings (${{ matrix.os }})
    runs-on: ${{ matrix.os }}
    # The bindings build `infino` as a normal dependency; don't fail the
    # build on a transitive-dependency deprecation warning. infino's own
    # warnings are gated by the `check` job (against infino's lockfile).
    env:
      RUSTFLAGS: ""
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-14]
    steps:
      - uses: actions/checkout@v4
      - name: Free disk space
        if: runner.os == 'Linux'
        uses: jlumbroso/free-disk-space@main
        with:
          tool-cache: false
          android: true
          dotnet: true
          haskell: true
          large-packages: true
          docker-images: true
          swap-storage: true
      - uses: dtolnay/rust-toolchain@stable
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - uses: Swatinem/rust-cache@v2
        with:
          workspaces: infino-python
      # Builds the extension + runs the smoke tests (self-contained venv).
      - run: make python-test
      # Type-check the shipped stubs against a sample consumer (mypy --strict).
      - run: make python-typecheck

  python-examples:
    name: Python examples (end to end)
    runs-on: ubuntu-latest
    timeout-minutes: 45
    # Build infino as a normal dep — don't fail on a transitive deprecation warning.
    env:
      RUSTFLAGS: ""
      # Azure LLM creds; absent on fork PRs, where notebooks print context instead.
      AZURE_AI_ENDPOINT: ${{ secrets.AZURE_AI_ENDPOINT }}
      AZURE_AI_API_KEY: ${{ secrets.AZURE_AI_API_KEY }}
      DEFAULT_AZURE_MODEL: gpt-5.4
    steps:
      - uses: actions/checkout@v4
      - name: Free disk space
        uses: jlumbroso/free-disk-space@main
        with:
          tool-cache: false
          android: true
          dotnet: true
          haskell: true
          large-packages: true
          docker-images: true
          swap-storage: true
      - uses: dtolnay/rust-toolchain@stable
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - uses: Swatinem/rust-cache@v2
        with:
          workspaces: infino-python
      # Cache the model + dataset samples the notebooks download.
      - name: Cache HuggingFace assets
        uses: actions/cache@v4
        with:
          path: ~/.cache/huggingface
          key: hf-${{ runner.os }}-${{ hashFiles('infino-python/examples/_shared/loaders.py', 'infino-python/examples/_shared/embedding.py') }}
          restore-keys: hf-${{ runner.os }}-
      # Run every notebook; the LLM answer step runs when the secrets are present.
      - run: make python-examples-test

  python-wheels:
    name: Wheel (${{ matrix.os }})
    runs-on: ${{ matrix.os }}
    env:
      RUSTFLAGS: ""
    strategy:
      fail-fast: false
      # Native runner per platform — `make python-wheel` builds for the
      # host, so no cross-compilation. One abi3 wheel each (CPython >= 3.9).
      matrix:
        os: [ubuntu-latest, ubuntu-24.04-arm, macos-14]
    steps:
      - uses: actions/checkout@v4
      - name: Free disk space
        if: runner.os == 'Linux'
        uses: jlumbroso/free-disk-space@main
        with:
          tool-cache: false
          android: true
          dotnet: true
          haskell: true
          large-packages: true
          docker-images: true
          swap-storage: true
      - uses: dtolnay/rust-toolchain@stable
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - uses: Swatinem/rust-cache@v2
        with:
          workspaces: infino-python
      - run: make python-wheel
      # The README advertises `uv pip install infino`; verify that path
      # against the freshly built wheel, not just pip.
      - name: Smoke-test wheel with uv
        shell: bash
        run: |
          python3 -m pip install --upgrade uv
          uv venv /tmp/uv-smoke
          uv pip install --python /tmp/uv-smoke infino-python/dist/*.whl pytest pyarrow pandas
          /tmp/uv-smoke/bin/python -m pytest infino-python/tests/ -v
      - uses: actions/upload-artifact@v4
        with:
          name: wheel-${{ matrix.os }}
          path: infino-python/dist

  node:
    name: Node bindings (${{ matrix.os }})
    runs-on: ${{ matrix.os }}
    # Same rationale as the Python job: the bindings build `infino` as a
    # normal dependency, so don't fail on a transitive deprecation warning.
    env:
      RUSTFLAGS: ""
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-14]
    steps:
      - uses: actions/checkout@v4
      - name: Free disk space
        if: runner.os == 'Linux'
        uses: jlumbroso/free-disk-space@main
        with:
          tool-cache: false
          android: true
          dotnet: true
          haskell: true
          large-packages: true
          docker-images: true
          swap-storage: true
      - uses: dtolnay/rust-toolchain@stable
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
      - uses: Swatinem/rust-cache@v2
        with:
          workspaces: infino-node
      # Builds the addon (debug) + runs the node:test smoke suite.
      - run: make node-test

  # End-to-end: run the Node examples against a freshly built addon (mirrors the
  # python-examples job). They fetch real public datasets and download a model,
  # so this is its own parallel job — it never slows the `node` smoke job — and is
  # meant to stay NON-required: a Hub outage shouldn't block a merge. The
  # deterministic gate is the `node` job's `make node-test`.
  node-examples:
    name: Node examples (end to end)
    runs-on: ubuntu-latest
    timeout-minutes: 30
    # Build infino as a normal dep — don't fail on a transitive deprecation warning.
    env:
      RUSTFLAGS: ""
    steps:
      - uses: actions/checkout@v4
      - name: Free disk space
        uses: jlumbroso/free-disk-space@main
        with:
          tool-cache: false
          android: true
          dotnet: true
          haskell: true
          large-packages: true
          docker-images: true
          swap-storage: true
      - uses: dtolnay/rust-toolchain@stable
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
      - uses: Swatinem/rust-cache@v2
        with:
          workspaces: infino-node
      # Build the addon (debug); the examples' `file:../..` dependency links it.
      - run: cd infino-node && npm install && npm run build:debug
      # Run each example end to end (self-asserting; non-zero exit fails the job).
      - run: make node-example

  # LOCOMO recall tripwire — runs the locomo-recall example against the committed
  # fixture (frozen vectors → only the engine varies) and reports recall@k on real
  # long-term-memory queries. Complements the brute-force recall gate in `check`
  # (which proves the ANN index returns the true neighbours); this proves
  # ranking/fusion quality holds up on real questions. Cheap — no embedder or
  # network. Keep it out of branch protection's required set so a recall wobble
  # never blocks a merge; tighten --fail-under as the baseline firms up.
  locomo-recall:
    name: LOCOMO recall test
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v4
      - name: Free disk space
        uses: jlumbroso/free-disk-space@main
        with:
          tool-cache: false
          android: true
          dotnet: true
          haskell: true
          large-packages: true
          docker-images: true
          swap-storage: true
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      # Frozen fixture → no embedder, key, or network. Reports recall@k + every
      # hybrid miss in the log, and fails only below the tolerance floor.
      - run: cargo run --example locomo-recall -- --fail-under=0.68

  # Informational benchmark — superfile + supertable tiers, one in-region
  # Azure VM each, in parallel. Never blocks merge. On a PR: diff vs main's baseline.
  # On merge to main: publish the run as that baseline + a per-commit history
  # point. Same-repo only (fork PRs can't use the Azure OIDC secrets).
  bench:
    if: >-
      github.event_name == 'push' ||
      github.event.pull_request.head.repo.full_name == github.repository
    strategy:
      fail-fast: false # one member failing must not cancel the other
      matrix:
        # `suffix` makes each leg's Azure resources unique — both legs
        # share one run_id, so without it they'd collide / cross-delete.
        include:
          - { bench: superfile, docs: "1000000", suffix: sf }
          - { bench: supertable, docs: "1000000", suffix: st }
    # PR: a new push cancels the in-flight run. main: serialize per bench so
    # concurrent merges don't race the baseline blob (never cancel).
    concurrency:
      group: bench-${{ github.event_name == 'push' && 'main' || github.event.pull_request.number }}-${{ matrix.bench }}
      cancel-in-progress: ${{ github.event_name == 'pull_request' }}
    permissions:
      id-token: write
      contents: read
      pull-requests: write # post the bench-results comment on the PR
    # Informational: keep these checks out of branch-protection's required
    # set so a red bench never blocks merge.
    uses: ./.github/workflows/supertable-bench-azure.yml
    with:
      bench: ${{ matrix.bench }}
      docs: ${{ matrix.docs }}
      ref: ${{ github.event_name == 'push' && github.sha || github.event.pull_request.head.sha }}
      name_suffix: -${{ matrix.suffix }}
      update_baseline: ${{ github.event_name == 'push' }}
      pr_number: ${{ github.event_name == 'pull_request' && github.event.pull_request.number || '' }}
    secrets: inherit