fix(cli-install): move kbcstorage to [server] extra so wheel installs cleanly (P0 onboarding hotfix → 0.53.4) (#272)

* fix(cli-install): move kbcstorage to [server] extra so wheel installs cleanly

The 0.53.3 wheel served at /cli/wheel/ is unsatisfiable on a clean machine:
analyst runs `uv tool install <wheel-url>` per the published /setup
instructions and the resolver immediately fails with

    Because kbcstorage<=0.9.5 depends on urllib3<2.0.0 and
    agnes-the-ai-analyst==0.53.3 depends on kbcstorage>=0.9.0 and
    urllib3>=2.7.0, we can conclude that agnes-the-ai-analyst==0.53.3
    cannot be used.

The `[tool.uv] override-dependencies = ["urllib3>=2.7.0"]` in pyproject.toml
masked the conflict in workspace contexts (Dockerfile + dev install) but
does NOT propagate to the wheel — wheel METADATA is plain PEP 621
Requires-Dist, and a fresh resolver context (uv tool install <wheel-url>)
never sees the override. Every existing test passed because the dev venv
already has kbcstorage 0.9.5 + urllib3 2.7.0 coexisting under workspace
overrides; the break only surfaces on the next analyst's first install.

Fix: kbcstorage moved out of [project] dependencies into
[project.optional-dependencies].server, since it is server-side only
(connectors/keboola/client.py is the sole import site, called from admin
endpoints, server connectors, and integration tests — never from the CLI
install path). Server install picks it up via Dockerfile's
`uv pip install --system --no-cache ".[server]"`. CI installs `.[dev,server]`
so workspace tests still cover the kbcstorage path. Analyst CLI wheel
METADATA now lists `kbcstorage>=0.9.0; extra == 'server'` (gated) and
`uv tool install <wheel>` resolves cleanly.

Verified end-to-end:
- Built wheel locally; inspected METADATA — kbcstorage line is now `; extra == 'server'`.
- `docker run --rm python:3.13-slim` + `uv tool install <wheel>`: agnes 0.53.4 installs, `agnes --version` works, `agnes catalog --help` renders, kbcstorage absent from CLI venv, urllib3 = 2.7.0.
- Same container with `.[server]` install path: kbcstorage present, urllib3 = 2.7.0 (override applies in workspace context).
- Full pytest suite green locally (4157 passed, 25 skipped).

* release: 0.53.4 — analyst CLI install hotfix (urllib3/kbcstorage resolver conflict)

Patch bump shipping the [server] extra split + new clean-install CI lane.
No DB migration; no API change; no operator-facing config change.
Operator side (Dockerfile path) auto-picks `.[server]` so the production
image gains kbcstorage transparently. Analyst onboarding (uv tool install
<wheel>) starts working again.
This commit is contained in:
ZdenekSrotyr 2026-05-12 19:09:44 +02:00 committed by GitHub
parent c8de0e0f64
commit 103669dafd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 89 additions and 12 deletions

View file

@ -23,13 +23,62 @@ jobs:
uses: astral-sh/setup-uv@v7 uses: astral-sh/setup-uv@v7
- name: Install dependencies - name: Install dependencies
run: uv pip install --system ".[dev]" run: uv pip install --system ".[dev,server]"
- name: Run tests (parallel) - name: Run tests (parallel)
run: pytest tests/ -v --tb=short -n auto run: pytest tests/ -v --tb=short -n auto
env: env:
TESTING: "1" 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: docker-build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -53,7 +102,7 @@ jobs:
uses: astral-sh/setup-uv@v7 uses: astral-sh/setup-uv@v7
- name: Install dependencies - name: Install dependencies
run: uv pip install --system ".[dev]" run: uv pip install --system ".[dev,server]"
- name: Start services - name: Start services
run: | run: |

View file

@ -19,7 +19,7 @@ jobs:
uses: astral-sh/setup-uv@v7 uses: astral-sh/setup-uv@v7
- name: Install dependencies - name: Install dependencies
run: uv pip install --system ".[dev]" run: uv pip install --system ".[dev,server]"
- name: Run tests - name: Run tests
run: pytest tests/ -v --tb=short run: pytest tests/ -v --tb=short

View file

@ -40,7 +40,7 @@ jobs:
uses: astral-sh/setup-uv@v7 uses: astral-sh/setup-uv@v7
- name: Install dependencies - name: Install dependencies
run: uv pip install --system ".[dev]" run: uv pip install --system ".[dev,server]"
- name: Lint with ruff - name: Lint with ruff
run: | run: |

View file

@ -59,7 +59,7 @@ jobs:
uses: astral-sh/setup-uv@v7 uses: astral-sh/setup-uv@v7
- name: Install dependencies - name: Install dependencies
run: uv pip install --system ".[dev]" run: uv pip install --system ".[dev,server]"
- name: Lint with ruff - name: Lint with ruff
run: | run: |

View file

@ -10,6 +10,16 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C
## [Unreleased] ## [Unreleased]
## [0.53.4] — 2026-05-12
### Fixed
- **Analyst CLI install (`uv tool install <wheel>`) 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 <wheel-url>`) 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 ## [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.) 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.)

View file

@ -56,8 +56,10 @@ RUN mkdir -p /opt/agnes-host && \
# Build wheel artifact (served at /cli/download) # Build wheel artifact (served at /cli/download)
RUN uv build --wheel --out-dir /app/dist RUN uv build --wheel --out-dir /app/dist
# Install production dependencies from pyproject.toml # Install production dependencies from pyproject.toml. The `[server]` extra
RUN uv pip install --system --no-cache . # 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). # 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 # uid/gid pinned to 999 so host-side chown in startup-script.sh.tpl can match

View file

@ -1,6 +1,6 @@
[project] [project]
name = "agnes-the-ai-analyst" name = "agnes-the-ai-analyst"
version = "0.53.3" version = "0.53.4"
description = "Agnes — AI Data Analyst platform for AI analytical systems" description = "Agnes — AI Data Analyst platform for AI analytical systems"
requires-python = ">=3.11,<3.14" requires-python = ">=3.11,<3.14"
license = "MIT" license = "MIT"
@ -78,7 +78,10 @@ dependencies = [
# module (export-async + signed-URL download) which talks to Storage API # module (export-async + signed-URL download) which talks to Storage API
# directly via `requests` — no SDK dependency on the data-path side. The # directly via `requests` — no SDK dependency on the data-path side. The
# SDK stays for the metadata reads. # 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", "sse-starlette>=2.0",
# Optional observability — pure-Python, no compilation. Lazily initialized # Optional observability — pure-Python, no compilation. Lazily initialized
# in src/observability/posthog_client.py and only emits events when # 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 # (4 high, 1 medium) flagged on urllib3<2.7.0: cross-origin sensitive
# header leak on proxied low-level redirects, decompression-bomb bypass # header leak on proxied low-level redirects, decompression-bomb bypass
# + unbounded decompression chain on the streaming API, redirects-when- # + unbounded decompression chain on the streaming API, redirects-when-
# retries-disabled. Forced via `[tool.uv] override-dependencies` below # retries-disabled. The `[server]` extra below adds kbcstorage which
# because kbcstorage<=0.9.5 still pins urllib3<2.0.0 even though # transitively caps urllib3<2.0.0; `[tool.uv] override-dependencies`
# botocore/requests/google-cloud-* all support 2.x on Python 3.10+. # forces 2.7+ in workspace installs (Dockerfile + dev). Wheel consumers
# who install only the CLI (`uv tool install <wheel>`) get no kbcstorage
# and no conflict.
"urllib3>=2.7.0", "urllib3>=2.7.0",
] ]
[project.optional-dependencies] [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 = [ observability = [
# Already in base dependencies — listed here so operators who want to # Already in base dependencies — listed here so operators who want to
# be explicit can `pip install -e ".[observability]"` and signal intent. # be explicit can `pip install -e ".[observability]"` and signal intent.