diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 93a30e2..ab7aa1e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,13 +23,62 @@ jobs: uses: astral-sh/setup-uv@v7 - name: Install dependencies - run: uv pip install --system ".[dev]" + run: uv pip install --system ".[dev,server]" - name: Run tests (parallel) run: pytest tests/ -v --tb=short -n auto env: TESTING: "1" + cli-wheel-clean-install: + # Catches the "wheel METADATA conflicts with transitive deps under fresh + # resolver" class — exactly what the workspace-only `[tool.uv] + # override-dependencies` does NOT protect against. Builds the wheel the + # way `release.yml` ships it to analysts (`uv build --wheel`), then + # installs it into a fresh `python:3.13-slim` container with `uv tool + # install` (the path the `/setup` page advertises) and asserts the + # `agnes` binary actually launches. Without this, a regression like + # 0.53.3's `kbcstorage>=0.9.0 → urllib3<2.0.0` cap silently caps the + # wheel METADATA, every existing test passes (workspace overrides the + # cap), and the break only surfaces on the next analyst's first install. + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-python@v6 + with: + python-version: "3.13" + + - name: Install uv + uses: astral-sh/setup-uv@v7 + + - name: Build wheel + run: uv build --wheel --out-dir dist + + - name: Smoke install in fresh python:3.13-slim + run: | + docker run --rm -v "$PWD/dist:/wheels:ro" python:3.13-slim bash -c ' + set -euo pipefail + apt-get update -qq && apt-get install -y -qq --no-install-recommends curl ca-certificates >/dev/null + curl -LsSf https://astral.sh/uv/install.sh | sh > /dev/null 2>&1 + export PATH="$HOME/.local/bin:$PATH" + WHEEL=$(ls /wheels/agnes_the_ai_analyst-*-py3-none-any.whl | head -1) + uv tool install --force "$WHEEL" + agnes --version + agnes --help > /dev/null + agnes catalog --help > /dev/null + python3 -c " + try: + import kbcstorage + raise SystemExit(\"REGRESSION: kbcstorage leaked into the CLI wheel — should be in [server] extra only\") + except ImportError: + pass + import urllib3 + assert tuple(int(x) for x in urllib3.__version__.split(\".\")[:2]) >= (2, 7), urllib3.__version__ + print(\"OK: kbcstorage absent, urllib3\", urllib3.__version__) + " + ' + docker-build: runs-on: ubuntu-latest steps: @@ -53,7 +102,7 @@ jobs: uses: astral-sh/setup-uv@v7 - name: Install dependencies - run: uv pip install --system ".[dev]" + run: uv pip install --system ".[dev,server]" - name: Start services run: | diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 35e6177..ed1828c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -19,7 +19,7 @@ jobs: uses: astral-sh/setup-uv@v7 - name: Install dependencies - run: uv pip install --system ".[dev]" + run: uv pip install --system ".[dev,server]" - name: Run tests run: pytest tests/ -v --tb=short diff --git a/.github/workflows/keboola-deploy.yml b/.github/workflows/keboola-deploy.yml index 3e22d10..bd5f7cb 100644 --- a/.github/workflows/keboola-deploy.yml +++ b/.github/workflows/keboola-deploy.yml @@ -40,7 +40,7 @@ jobs: uses: astral-sh/setup-uv@v7 - name: Install dependencies - run: uv pip install --system ".[dev]" + run: uv pip install --system ".[dev,server]" - name: Lint with ruff run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3749f99..64346b5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -59,7 +59,7 @@ jobs: uses: astral-sh/setup-uv@v7 - name: Install dependencies - run: uv pip install --system ".[dev]" + run: uv pip install --system ".[dev,server]" - name: Lint with ruff run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 35c778f..9b926e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,16 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C ## [Unreleased] +## [0.53.4] — 2026-05-12 + +### Fixed + +- **Analyst CLI install (`uv tool install `) no longer fails with `urllib3 / kbcstorage` resolver conflict on a clean machine.** From 0.53.3, every fresh `/setup` walkthrough hit `kbcstorage<=0.9.5 → urllib3<2.0.0` vs the wheel METADATA's `urllib3>=2.7.0` security pin and resolved to `unsatisfiable`. The `[tool.uv] override-dependencies = ["urllib3>=2.7.0"]` workaround that masked the conflict in workspace installs (Dockerfile, dev) does NOT propagate to the wheel — wheel METADATA is plain PEP 621 `Requires-Dist`, and a fresh resolver context (`uv tool install `) never sees the override. Fix: `kbcstorage` moved out of `[project] dependencies` into `[project.optional-dependencies] server`, since it is server-side-only (`connectors/keboola/client.py` callers — admin endpoints, server connectors, integration tests; no CLI import path). Server install picks it up via the Dockerfile's `uv pip install --system --no-cache ".[server]"`; CI installs `.[dev,server]` so the workspace tests still cover the kbcstorage path. Analyst CLI wheel METADATA now lists `kbcstorage>=0.9.0; extra == 'server'` (gated) — `uv tool install` resolves cleanly. + +### Internal + +- **New CI lane `cli-wheel-clean-install` in `.github/workflows/ci.yml`** builds the wheel via `uv build` and installs it into a fresh `python:3.13-slim` container with `uv tool install`, asserting `agnes --version` works AND that `kbcstorage` is absent from the CLI venv. Catches the "wheel METADATA conflicts with transitive deps under fresh resolver" regression class — exactly what `[tool.uv] override-dependencies` does NOT protect against. Without this lane, the previous regression slipped through every existing test (workspace overrides masked the conflict in pytest) and only surfaced on the next analyst's first install. + ## [0.53.3] — 2026-05-12 Hygiene round closing #244 + #252 + clearing 5 Dependabot urllib3 advisories. (Originally cut as 0.53.2 — bumped to 0.53.3 after #264 / #268 landed as 0.53.2 in parallel.) diff --git a/Dockerfile b/Dockerfile index 8710690..b4ca405 100644 --- a/Dockerfile +++ b/Dockerfile @@ -56,8 +56,10 @@ RUN mkdir -p /opt/agnes-host && \ # Build wheel artifact (served at /cli/download) RUN uv build --wheel --out-dir /app/dist -# Install production dependencies from pyproject.toml -RUN uv pip install --system --no-cache . +# Install production dependencies from pyproject.toml. The `[server]` extra +# pulls in connectors-only deps (kbcstorage) that the CLI wheel deliberately +# omits — see [project.optional-dependencies].server in pyproject.toml. +RUN uv pip install --system --no-cache ".[server]" # Run as non-root user for container hardening (C13). # uid/gid pinned to 999 so host-side chown in startup-script.sh.tpl can match diff --git a/pyproject.toml b/pyproject.toml index 6ebfd17..d673474 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "agnes-the-ai-analyst" -version = "0.53.3" +version = "0.53.4" description = "Agnes — AI Data Analyst platform for AI analytical systems" requires-python = ">=3.11,<3.14" license = "MIT" @@ -78,7 +78,10 @@ dependencies = [ # module (export-async + signed-URL download) which talks to Storage API # directly via `requests` — no SDK dependency on the data-path side. The # SDK stays for the metadata reads. - "kbcstorage>=0.9.0", + # + # NOTE: kbcstorage moved to the [server] extra below — see the rationale + # in [project.optional-dependencies].server. CLI wheels installed via + # `uv tool install` deliberately ship without it. "sse-starlette>=2.0", # Optional observability — pure-Python, no compilation. Lazily initialized # in src/observability/posthog_client.py and only emits events when @@ -105,13 +108,26 @@ dependencies = [ # (4 high, 1 medium) flagged on urllib3<2.7.0: cross-origin sensitive # header leak on proxied low-level redirects, decompression-bomb bypass # + unbounded decompression chain on the streaming API, redirects-when- - # retries-disabled. Forced via `[tool.uv] override-dependencies` below - # because kbcstorage<=0.9.5 still pins urllib3<2.0.0 even though - # botocore/requests/google-cloud-* all support 2.x on Python 3.10+. + # retries-disabled. The `[server]` extra below adds kbcstorage which + # transitively caps urllib3<2.0.0; `[tool.uv] override-dependencies` + # forces 2.7+ in workspace installs (Dockerfile + dev). Wheel consumers + # who install only the CLI (`uv tool install `) get no kbcstorage + # and no conflict. "urllib3>=2.7.0", ] [project.optional-dependencies] +# Server-side connectors. The CLI wheel does NOT need these — analysts who +# `uv tool install` the wheel never reach a kbcstorage import. Splitting it +# out keeps the wheel's METADATA `Requires-Dist` set free of the +# `kbcstorage<=0.9.5 → urllib3<2.0.0` cap that conflicts with our +# `urllib3>=2.7.0` security pin under any fresh resolver context (where +# `[tool.uv] override-dependencies` does NOT apply — see comment on +# [tool.uv] below). Server install pulls it in via Dockerfile's +# `uv pip install --system --no-cache .[server]`. +server = [ + "kbcstorage>=0.9.0", +] observability = [ # Already in base dependencies — listed here so operators who want to # be explicit can `pip install -e ".[observability]"` and signal intent.