fix(storage): unify SQLite + graph writes behind one process-level lock #141
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| lint: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| cache: pip | |
| cache-dependency-path: pyproject.toml | |
| - name: Install dependencies | |
| run: pip install -e ".[dev]" | |
| - name: Lint (ruff) | |
| run: ruff check src/ | |
| - name: Format check (ruff) | |
| run: ruff format --check src/ | |
| - name: Type check (mypy) | |
| run: mypy src/hebb/ | |
| # Fast, deterministic gate: unit + integration + eval-harness tests on every | |
| # OS/Python cell. No model download — model-dependent tests are `-m slow` and | |
| # run only in the `model` job below. | |
| test: | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| python-version: ["3.10", "3.11", "3.12", "3.13"] | |
| fail-fast: false | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| cache: pip | |
| cache-dependency-path: pyproject.toml | |
| - name: Install dependencies | |
| run: pip install -e ".[dev]" | |
| - name: Unit + integration + eval (no model) | |
| run: >- | |
| pytest tests/unit tests/integration tests/eval | |
| -m "not slow" --tb=short | |
| --cov=hebb --cov-report=xml --cov-report=term | |
| - name: Upload coverage artifact | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: coverage-${{ matrix.os }}-py${{ matrix.python-version }} | |
| path: coverage.xml | |
| if-no-files-found: ignore | |
| # Black-box tests that spawn the installed `hebb` / `hebb-mcp` entrypoints. | |
| # POSIX-only surfaces (stdio guard, service managers) — Windows is gated out; | |
| # individual tests carry their own skipif as a second guard. | |
| e2e: | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| matrix: | |
| os: [ubuntu-latest, macos-latest] | |
| fail-fast: false | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| cache: pip | |
| cache-dependency-path: pyproject.toml | |
| - name: Install dependencies | |
| run: pip install -e ".[dev]" | |
| - name: E2E (subprocess hebb / hebb-mcp) | |
| run: pytest tests/e2e -m "not slow" --tb=short | |
| # Exercises the REAL embedding + cross-encoder rerank + vector path that the | |
| # offline matrix can't — the retrieval core that's most actively tuned. | |
| # HF weights are cached across runs and keyed on the model ids. | |
| model: | |
| runs-on: ubuntu-latest | |
| env: | |
| HF_HOME: ${{ github.workspace }}/.hf | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| cache: pip | |
| cache-dependency-path: pyproject.toml | |
| - name: Install dependencies | |
| run: pip install -e ".[dev]" | |
| - name: Cache HuggingFace models | |
| uses: actions/cache@v5 | |
| with: | |
| path: ${{ env.HF_HOME }} | |
| key: hf-${{ runner.os }}-minilm-msmarco-reranker | |
| restore-keys: hf-${{ runner.os }}- | |
| # On a cache miss the warm step hits HF anonymously from a shared CI IP | |
| # and routinely gets 429 Too Many Requests — snapshot_download's repo_info | |
| # call does not retry on 429, so one rate-limit killed the whole job. | |
| # Retry with backoff (honoring Retry-After) and authenticate when an | |
| # optional HF_TOKEN secret is configured (far higher per-token limit). | |
| - name: Warm model cache | |
| env: | |
| HF_TOKEN: ${{ secrets.HF_TOKEN }} | |
| HF_HUB_ETAG_TIMEOUT: "60" | |
| run: | | |
| python - <<'PY' | |
| import os | |
| import sys | |
| import time | |
| from huggingface_hub import snapshot_download | |
| try: # moved to huggingface_hub.errors in newer releases | |
| from huggingface_hub.errors import HfHubHTTPError | |
| except ImportError: # pragma: no cover - older huggingface_hub | |
| from huggingface_hub.utils import HfHubHTTPError | |
| REPOS = ( | |
| "sentence-transformers/all-MiniLM-L6-v2", | |
| "cross-encoder/ms-marco-MiniLM-L-6-v2", | |
| ) | |
| TOKEN = os.environ.get("HF_TOKEN") or None | |
| MAX_ATTEMPTS = 6 | |
| # Back off on rate-limit / transient server errors only; a 401/404 is | |
| # a real config error and should fail fast. | |
| RETRY_STATUS = {429, 500, 502, 503, 504} | |
| def warm(repo: str) -> None: | |
| delay = 10.0 | |
| for attempt in range(1, MAX_ATTEMPTS + 1): | |
| try: | |
| snapshot_download(repo, token=TOKEN, etag_timeout=60) | |
| print(f"[warm] {repo}: ok (attempt {attempt})", flush=True) | |
| return | |
| except HfHubHTTPError as err: | |
| resp = getattr(err, "response", None) | |
| status = getattr(resp, "status_code", None) | |
| if status not in RETRY_STATUS or attempt == MAX_ATTEMPTS: | |
| raise | |
| retry_after = resp.headers.get("Retry-After") if resp is not None else None | |
| wait = float(retry_after) if retry_after and retry_after.isdigit() else delay | |
| wait += (attempt * 1.7) % 5 # deterministic jitter, no RNG | |
| print( | |
| f"[warm] {repo}: HTTP {status}, retry {attempt}/{MAX_ATTEMPTS} in {wait:.0f}s", | |
| file=sys.stderr, | |
| flush=True, | |
| ) | |
| time.sleep(wait) | |
| delay = min(delay * 2, 120) | |
| for repo in REPOS: | |
| warm(repo) | |
| PY | |
| - name: Model-dependent tests (real vector path, offline read from cache) | |
| env: | |
| HF_HUB_OFFLINE: "1" | |
| TRANSFORMERS_OFFLINE: "1" | |
| run: pytest -m slow --tb=short | |
| docker: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Build Docker image | |
| run: docker build -f docker/Dockerfile -t hebb:test . |