fix(ci): regenerate uv.lock and enforce it with --locked
CI & Build / Python lint (push) Successful in 3s
CI & Build / integration (push) Successful in 18s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 50s
CI & Build / Build & push image (push) Successful in 17s

Completes the previous two commits. `--frozen` failed run 3007 with
`ModuleNotFoundError: No module named 'pgvector'` — the lock wasn't merely
stale in its recorded metadata, it was missing a real dependency. pgvector
was added to pyproject for the vector-search work and the lock was never
regenerated, and nothing noticed because CI resolved from pyproject and never
read the lock. The lock has been dead weight for some time.

Regenerated with `uv lock` inside a throwaway `ci-python:3.14` container —
this workstation has no uv and no pip, and the CI image already carries the
right toolchain, so nothing was installed to do it. uv was conservative as
promised: pgvector 0.5.0 added, and NOT ONE existing pin moved (verified by
diffing name=version pairs across all 106 packages).

Both lanes now run `uv sync --locked`, so a dependency edit without a re-lock
fails loudly at install rather than resolving around the lock. The check paid
for itself on its first run by surfacing the missing pgvector.

Also added uv.lock to the workflow's `paths:` filter. It was absent, so a
lock-only change — exactly what a dependency bump looks like now — would not
have triggered CI at all.

Closes the CI half of #2194. The Dockerfile still resolves independently and
is tracked there.
This commit is contained in:
2026-07-28 12:58:18 -04:00
parent 8bab0c762b
commit a47e1b9c4e
2 changed files with 70 additions and 59 deletions
+11 -11
View File
@@ -40,6 +40,9 @@ on:
- "frontend/**" - "frontend/**"
- "tests/**" - "tests/**"
- "pyproject.toml" - "pyproject.toml"
# The lock now determines what gets installed, so a lock-only change has
# to trigger a run — otherwise a dependency bump lands untested.
- "uv.lock"
- "alembic/**" - "alembic/**"
- "alembic.ini" - "alembic.ini"
- "Dockerfile" - "Dockerfile"
@@ -140,19 +143,16 @@ jobs:
# red with no repo change (issue #2194). Green CI has to mean "these exact # red with no repo change (issue #2194). Green CI has to mean "these exact
# versions passed", or it isn't evidence of anything. # versions passed", or it isn't evidence of anything.
# #
# `--frozen`, not `--locked`, and that difference is a known gap: --locked # `--locked` also FAILS when uv.lock is stale against pyproject, so a
# additionally fails when the lock is STALE against pyproject, which is the # dependency edit has to go through a deliberate `uv lock` — it can't
# guard that would catch someone editing a dependency without re-locking. # arrive on its own. That check earned its place immediately: it caught
# It can't be turned on until uv.lock is regenerated with `uv lock` — the # that the lock had been missing `pgvector` entirely (added to pyproject,
# mcp cap edited pyproject, so the lock no longer matches, and this # never re-locked), which the old install path had been silently papering
# workstation has no uv (or pip) to regenerate it with. --frozen still # over by resolving from pyproject instead.
# delivers the reproducibility this fix is for; a forgotten re-lock shows
# up as a loud ImportError rather than a silent version drift. Flip to
# --locked in the same change that regenerates the lock.
- name: Install locked dependencies - name: Install locked dependencies
env: env:
UV_PROJECT_ENVIRONMENT: /opt/venv UV_PROJECT_ENVIRONMENT: /opt/venv
run: uv sync --frozen --extra dev run: uv sync --locked --extra dev
- name: Run tests - name: Run tests
# Integration tests (real Postgres) run in the `integration` job below. # Integration tests (real Postgres) run in the `integration` job below.
@@ -197,7 +197,7 @@ jobs:
- name: Install locked dependencies - name: Install locked dependencies
env: env:
UV_PROJECT_ENVIRONMENT: /opt/venv UV_PROJECT_ENVIRONMENT: /opt/venv
run: uv sync --frozen --extra dev run: uv sync --locked --extra dev
- name: Integration suite (resolve service IP, migrate, test) - name: Integration suite (resolve service IP, migrate, test)
run: | run: |
set -eux set -eux
Generated
+59 -48
View File
@@ -354,54 +354,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" },
] ]
[[package]]
name = "scribe"
version = "0.1.0"
source = { editable = "." }
dependencies = [
{ name = "aiosmtplib" },
{ name = "alembic" },
{ name = "apscheduler" },
{ name = "asyncpg" },
{ name = "bcrypt" },
{ name = "caldav" },
{ name = "fastembed" },
{ name = "httpx" },
{ name = "hypercorn" },
{ name = "icalendar" },
{ name = "mcp", extra = ["cli"] },
{ name = "quart" },
{ name = "sqlalchemy", extra = ["asyncio"] },
]
[package.optional-dependencies]
dev = [
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "ruff" },
]
[package.metadata]
requires-dist = [
{ name = "aiosmtplib", specifier = ">=3.0" },
{ name = "alembic", specifier = ">=1.13" },
{ name = "apscheduler", specifier = ">=3.10,<4.0" },
{ name = "asyncpg", specifier = ">=0.29" },
{ name = "bcrypt", specifier = ">=4.0" },
{ name = "caldav", specifier = ">=1.3" },
{ name = "fastembed", specifier = ">=0.4" },
{ name = "httpx", specifier = ">=0.27" },
{ name = "hypercorn", specifier = ">=0.17" },
{ name = "icalendar", specifier = ">=5.0" },
{ name = "mcp", extras = ["cli"], specifier = ">=1.0,<2" },
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0" },
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.23" },
{ name = "quart", specifier = ">=0.19" },
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.6" },
{ name = "sqlalchemy", extras = ["asyncio"], specifier = ">=2.0" },
]
provides-extras = ["dev"]
[[package]] [[package]]
name = "fastembed" name = "fastembed"
version = "0.8.0" version = "0.8.0"
@@ -1038,6 +990,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" },
] ]
[[package]]
name = "pgvector"
version = "0.5.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a7/ec/6eb80aebc728200f95229219882994c1b0585b956ca47da5edb9d062627a/pgvector-0.5.0.tar.gz", hash = "sha256:07a9dcf735696879406983afc6eba9a787cef7c0cf6c367ca1a5779f036dee74", size = 35170, upload-time = "2026-07-06T18:27:27.767Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5c/e4/a5573f2c579ca9ad133293bfb624148ba0893674ca4a6eeec85ced9a6a09/pgvector-0.5.0-py3-none-any.whl", hash = "sha256:fedc9800894e6da2be51358d7b7c574bf34f247ca741a5a09513622135f5964f", size = 30958, upload-time = "2026-07-06T18:27:26.797Z" },
]
[[package]] [[package]]
name = "pillow" name = "pillow"
version = "12.2.0" version = "12.2.0"
@@ -1496,6 +1457,56 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/58/ed/dea90a65b7d9e69888890fb14c90d7f51bf0c1e82ad800aeb0160e4bacfd/ruff-0.15.10-py3-none-win_arm64.whl", hash = "sha256:601d1610a9e1f1c2165a4f561eeaa2e2ea1e97f3287c5aa258d3dab8b57c6188", size = 11035607, upload-time = "2026-04-09T14:05:47.593Z" }, { url = "https://files.pythonhosted.org/packages/58/ed/dea90a65b7d9e69888890fb14c90d7f51bf0c1e82ad800aeb0160e4bacfd/ruff-0.15.10-py3-none-win_arm64.whl", hash = "sha256:601d1610a9e1f1c2165a4f561eeaa2e2ea1e97f3287c5aa258d3dab8b57c6188", size = 11035607, upload-time = "2026-04-09T14:05:47.593Z" },
] ]
[[package]]
name = "scribe"
version = "0.1.0"
source = { editable = "." }
dependencies = [
{ name = "aiosmtplib" },
{ name = "alembic" },
{ name = "apscheduler" },
{ name = "asyncpg" },
{ name = "bcrypt" },
{ name = "caldav" },
{ name = "fastembed" },
{ name = "httpx" },
{ name = "hypercorn" },
{ name = "icalendar" },
{ name = "mcp", extra = ["cli"] },
{ name = "pgvector" },
{ name = "quart" },
{ name = "sqlalchemy", extra = ["asyncio"] },
]
[package.optional-dependencies]
dev = [
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "ruff" },
]
[package.metadata]
requires-dist = [
{ name = "aiosmtplib", specifier = ">=3.0" },
{ name = "alembic", specifier = ">=1.13" },
{ name = "apscheduler", specifier = ">=3.10,<4.0" },
{ name = "asyncpg", specifier = ">=0.29" },
{ name = "bcrypt", specifier = ">=4.0" },
{ name = "caldav", specifier = ">=1.3" },
{ name = "fastembed", specifier = ">=0.4" },
{ name = "httpx", specifier = ">=0.27" },
{ name = "hypercorn", specifier = ">=0.17" },
{ name = "icalendar", specifier = ">=5.0" },
{ name = "mcp", extras = ["cli"], specifier = ">=1.0,<2" },
{ name = "pgvector", specifier = ">=0.3" },
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0" },
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.23" },
{ name = "quart", specifier = ">=0.19" },
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.6" },
{ name = "sqlalchemy", extras = ["asyncio"], specifier = ">=2.0" },
]
provides-extras = ["dev"]
[[package]] [[package]]
name = "shellingham" name = "shellingham"
version = "1.5.4" version = "1.5.4"