feat(memory): injection receipts + blame path (T1+T2+T3, decision 4255039) #713
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] | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| test: | |
| name: Test (Python ${{ matrix.python-version }}) | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python-version: ["3.10", "3.11", "3.12", "3.13"] | |
| env: | |
| DATABASE_URL: postgresql://cortex:cortex@localhost:5432/cortex | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python ${{ matrix.python-version }} | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| # Provision PostgreSQL + pgvector on the runner itself, instead of a | |
| # Docker-Hub service container. Anonymous `pgvector/pgvector:pg17` pulls | |
| # from registry-1.docker.io are rate-limited and outage-prone (observed: | |
| # "context deadline exceeded" failing container init before any test ran, | |
| # CI run 27190427877). The runner ships PostgreSQL preinstalled; pgvector | |
| # comes from the PGDG apt repo (apt.postgresql.org) — no registry, no pull | |
| # rate limit. source: runner image (actions/runner-images) + PGDG. | |
| - name: Set up PostgreSQL + pgvector (runner-local, no registry pull) | |
| run: | | |
| set -euxo pipefail | |
| sudo systemctl start postgresql | |
| PG_VER="$(pg_lsclusters -h | awk 'NR==1 {print $1}')" | |
| # Ensure the PGDG apt repo (canonical pgvector source); idempotent. | |
| sudo install -d /usr/share/postgresql-common/pgdg | |
| sudo curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc \ | |
| -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc | |
| echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" \ | |
| | sudo tee /etc/apt/sources.list.d/pgdg.list | |
| for i in 1 2 3; do sudo apt-get update && break || sleep 5; done | |
| for i in 1 2 3; do sudo apt-get install -y "postgresql-${PG_VER}-pgvector" && break || sleep 5; done | |
| # Role + DB expected by DATABASE_URL. | |
| sudo -u postgres psql -v ON_ERROR_STOP=1 \ | |
| -c "CREATE ROLE cortex LOGIN SUPERUSER PASSWORD 'cortex';" | |
| sudo -u postgres createdb -O cortex cortex | |
| sudo -u postgres psql -v ON_ERROR_STOP=1 -d cortex \ | |
| -c "CREATE EXTENSION IF NOT EXISTS vector; CREATE EXTENSION IF NOT EXISTS pg_trgm;" | |
| # Wait until reachable over TCP with password auth. | |
| for i in $(seq 1 30); do | |
| PGPASSWORD=cortex pg_isready -h localhost -U cortex -d cortex && break || sleep 1 | |
| done | |
| PGPASSWORD=cortex psql -h localhost -U cortex -d cortex -c "SELECT version();" | |
| - name: Cache pip | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/pip | |
| key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pip-${{ matrix.python-version }}- | |
| - name: Cache HuggingFace models | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/huggingface | |
| key: ${{ runner.os }}-hf-all-MiniLM-L6-v2 | |
| - name: Install dependencies | |
| run: pip install -e ".[dev,postgresql,codebase]" | |
| # Populate the HuggingFace cache before the (offline) test run. A transient | |
| # huggingface.co blip must not leave the cache empty — that surfaced as 58 | |
| # spurious "couldn't connect to huggingface.co" test failures on a single | |
| # matrix leg (CI run 28495801728, Python 3.10, 2026-07-01) while every other | |
| # leg was fine. Retry with backoff so a blip self-heals; fail loudly here (no | |
| # continue-on-error) instead of cascading into a misleading test failure. | |
| - name: Pre-download embedding model | |
| run: | | |
| for attempt in 1 2 3 4 5; do | |
| python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('all-MiniLM-L6-v2', device='cpu')" && exit 0 | |
| echo "HF pre-download attempt ${attempt} failed; retrying in $((attempt * 10))s" >&2 | |
| sleep $((attempt * 10)) | |
| done | |
| echo "HF pre-download failed after 5 attempts" >&2 | |
| exit 1 | |
| # Offline: the model is already cached by the step above, so tests must never | |
| # reach out to huggingface.co mid-suite — deterministic and flake-free. | |
| - name: Run tests | |
| env: | |
| HF_HUB_OFFLINE: "1" | |
| TRANSFORMERS_OFFLINE: "1" | |
| run: pytest --tb=short -q | |
| - name: Run tests with coverage | |
| if: matrix.python-version == '3.12' | |
| env: | |
| HF_HUB_OFFLINE: "1" | |
| TRANSFORMERS_OFFLINE: "1" | |
| run: pytest --cov=mcp_server --cov-report=xml --cov-report=term-missing | |
| - name: Upload coverage | |
| if: matrix.python-version == '3.12' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-report | |
| path: coverage.xml | |
| test-sqlite: | |
| name: Test (SQLite backend) | |
| runs-on: ubuntu-latest | |
| # Force the SQLite fallback path — no PostgreSQL installed or started. | |
| # The conftest detects PG-unreachable and sets CORTEX_MEMORY_STORE_BACKEND=sqlite | |
| # automatically; this env var makes the selection explicit and observable in logs. | |
| # source: mcp_server/infrastructure/memory_store.py _construct_store() — the | |
| # 'sqlite' backend branch is always reachable without PG; conftest.py line 99 | |
| # mirrors this override when _USE_PG is False. | |
| env: | |
| CORTEX_MEMORY_STORE_BACKEND: sqlite | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Cache pip | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/pip | |
| key: ${{ runner.os }}-pip-3.12-sqlite-${{ hashFiles('pyproject.toml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pip-3.12-sqlite- | |
| - name: Cache HuggingFace models | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/huggingface | |
| key: ${{ runner.os }}-hf-all-MiniLM-L6-v2 | |
| - name: Install dependencies (no postgresql extra) | |
| run: pip install -e ".[dev,sqlite]" | |
| # Retry-with-backoff, fail-loudly: see the `test` job's pre-download step | |
| # for the root-cause rationale (CI run 28495801728, 2026-07-01). | |
| - name: Pre-download embedding model | |
| run: | | |
| for attempt in 1 2 3 4 5; do | |
| python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('all-MiniLM-L6-v2', device='cpu')" && exit 0 | |
| echo "HF pre-download attempt ${attempt} failed; retrying in $((attempt * 10))s" >&2 | |
| sleep $((attempt * 10)) | |
| done | |
| echo "HF pre-download failed after 5 attempts" >&2 | |
| exit 1 | |
| - name: Run SQLite backend tests | |
| env: | |
| HF_HUB_OFFLINE: "1" | |
| TRANSFORMERS_OFFLINE: "1" | |
| # Scope: the SQLite fallback is intentionally NOT at full feature parity | |
| # with the mandatory PostgreSQL backend (some SqliteMemoryStore methods | |
| # and PG-specific tests do not apply). Run the dedicated SQLite backend | |
| # suite, which exercises CRUD, heat_base columns/indexes, FTS, and | |
| # backend selection on the fallback path. Broadening to the full suite | |
| # on SQLite is tracked separately (full-parity effort). | |
| run: pytest tests_py/infrastructure/test_sqlite_backend.py --tb=short -q | |
| test-windows: | |
| name: Test (Windows, SQLite backend) | |
| runs-on: windows-latest | |
| # Real NT proof for the cross-platform fixes (fcntl→msvcrt, sys.executable, | |
| # NTFS exec bits, $HOME override, path separators). We use the SQLite | |
| # fallback so no PostgreSQL has to be provisioned on the Windows runner — | |
| # the conftest selects it when PG is unreachable; the env var makes it | |
| # explicit. source: RAPPORT_INSTALLATION_CORTEX_WINDOWS.md §10 (CI Windows) | |
| env: | |
| CORTEX_MEMORY_STORE_BACKEND: sqlite | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Cache HuggingFace models | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/huggingface | |
| key: ${{ runner.os }}-hf-all-MiniLM-L6-v2 | |
| - name: Install dependencies (no postgresql extra) | |
| run: pip install -e ".[dev,sqlite]" | |
| # Import smoke: the modules that previously crashed at load on Windows | |
| # (fcntl import) or silently misbehaved. If any fails to import, the | |
| # platform branches are wrong — fail fast before the suite. | |
| - name: Import smoke (formerly Windows-broken modules) | |
| run: >- | |
| python -c "import mcp_server.shared.platform, | |
| mcp_server.infrastructure.pipeline_install_lock, | |
| mcp_server.infrastructure.pipeline_discovery, | |
| mcp_server.core.staleness, mcp_server.doctor, mcp_server.doctor_mcp; | |
| print('windows import smoke OK')" | |
| # Retry-with-backoff, fail-loudly: see the `test` job's pre-download step | |
| # for the root-cause rationale (CI run 28495801728, 2026-07-01). shell: bash | |
| # so the retry loop runs under Git Bash rather than the Windows default pwsh. | |
| - name: Pre-download embedding model | |
| shell: bash | |
| run: | | |
| for attempt in 1 2 3 4 5; do | |
| python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('all-MiniLM-L6-v2', device='cpu')" && exit 0 | |
| echo "HF pre-download attempt ${attempt} failed; retrying in $((attempt * 10))s" >&2 | |
| sleep $((attempt * 10)) | |
| done | |
| echo "HF pre-download failed after 5 attempts" >&2 | |
| exit 1 | |
| # Scope (explicit, not silent): the portability tests plus the modules | |
| # carrying Windows-specific branches and the SQLite backend suite. The | |
| # full PG suite is not run here — it is covered by the ubuntu `test` job. | |
| - name: Run portability + backend tests | |
| env: | |
| HF_HUB_OFFLINE: "1" | |
| TRANSFORMERS_OFFLINE: "1" | |
| run: >- | |
| pytest --tb=short -q | |
| tests_py/shared/test_platform.py | |
| tests_py/infrastructure/test_pipeline_discovery.py | |
| tests_py/infrastructure/test_pipeline_install_lock.py | |
| tests_py/core/test_staleness.py | |
| tests_py/infrastructure/test_sqlite_backend.py | |
| lint: | |
| name: Lint | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Install ruff | |
| run: pip install ruff | |
| - name: Check formatting | |
| run: ruff format --check . | |
| - name: Check linting | |
| run: ruff check . | |
| typecheck: | |
| name: Type Check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.13" | |
| # Pyright resolves third-party imports from ./.venv (pinned in | |
| # pyrightconfig.json: venvPath="."/venv=".venv"). The full stub set MUST | |
| # be installed or every import collapses to Unknown — and Unknown | |
| # SUPPRESSES downstream type errors, making the baseline meaningless. | |
| # source: measured 2026-06-18 — an unresolved env reports 566 errors, a | |
| # fully-resolved env reports 593 (Unknown was masking 27+ real errors). | |
| # flashrank (core reranker) + sqlite-vec live outside dev/postgresql/codebase. | |
| - name: Create .venv with the full type-check environment | |
| run: | | |
| python -m venv .venv | |
| .venv/bin/pip install -U pip | |
| .venv/bin/pip install -e ".[dev,postgresql,sqlite,codebase]" flashrank | |
| # Pin pyright — diagnostic output drifts between releases, so the | |
| # committed baseline is only comparable against the pinned version. | |
| .venv/bin/pip install pyright==1.1.410 | |
| # The ratchet IS the gate. pyright always exits non-zero while the | |
| # 568-error backlog stands, so its own exit is ignored (|| true); the | |
| # build fails only when a --blocking rule regresses past its committed | |
| # baseline (0). Add rules to --blocking as each batch drives them to | |
| # their floor — see docs/provenance/pyright-remediation-plan.md. | |
| - name: Run pyright + per-rule ratchet gate | |
| run: | | |
| .venv/bin/python -m pyright --outputjson mcp_server/ > pyright-current.json || true | |
| .venv/bin/python scripts/check_pyright_ratchet.py \ | |
| pyright-current.json typecheck-baseline.json \ | |
| --blocking reportOptionalMemberAccess reportOptionalSubscript | |
| build: | |
| name: Build Package | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Install build tools | |
| run: pip install build | |
| - name: Build sdist and wheel | |
| run: python -m build | |
| - name: Upload build artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: dist | |
| path: dist/ |