release(0.11.5): post-merge follow-up — Devin review fixes + authlib warning silenced (#74)
Cuts 0.11.5 with all the [Unreleased] bullets that landed on top of PR #73 between commit a899877 (the original "v0.11.4" tag in the chain) and the final merge commit on main. No new public-API surface; the user-visible payoff is that v8→v9-migrated installations work end-to-end (login flows, GET /api/users, admin nav, the new role-management REST API and its last-admin protection) and `make local-dev` startup is finally quiet. Bullets covered (full text in CHANGELOG.md [0.11.5]): - _hydrate_legacy_role re-resolves from grants on every request — fixes privilege-retention after grant revoke via the role-management API. - Dev-bypass + OAuth callback now pass user_id to resolve_internal_roles so direct grants land in the session cache (not the DB-fallback path). - GET /api/users hydrates user dicts before Pydantic validation (HTTP 500 on every migrated install) + same fix for update/delete paths so last-admin protection triggers on migrated admins. - Scheduler stopped spamming POST /auth/token 401 — the auto-fetch fallback was always broken; SCHEDULER_API_TOKEN is now the only path. - POST /auth/token / Google OAuth / password / email-magic-link all hydrate user["role"] before issuing the JWT (Pydantic 500 + wrong token payload). New TestAuthLoginFlowsPostMigration regression class. - docs/RBAC.md no longer documents the non-existent implies= keyword on register_internal_role. - _seed_core_roles now actually runs on every connect (the docstring was lying — only ran during fresh install + v8→v9). New TestSeedCoreRolesSafetyNet regression class. This commit also adds: - AuthlibDeprecationWarning suppression at app/main.py top — upstream- internal forward-compat note from authlib._joserfc_helpers, not actionable on our side. Filter is targeted by class (with a message-based fallback) so other DeprecationWarnings remain visible. - pyproject.toml version: 0.11.4 → 0.11.5. - CHANGELOG.md: [Unreleased] → [0.11.5] — 2026-04-27, new empty [Unreleased] skeleton appended for the next PR to land on. Tag v0.11.5 follows; keboola-deploy-v0.11.5 tag triggers the keboola-deploy.yml workflow for agnes-dev.keboola.com.
This commit is contained in:
parent
83ced81966
commit
2dfb246996
3 changed files with 27 additions and 1 deletions
|
|
@ -13,6 +13,10 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C
|
|||
<!-- Add bullets here. Group: Added / Changed / Fixed / Removed / Internal.
|
||||
Mark breaking changes with **BREAKING** at the start of the bullet. -->
|
||||
|
||||
## [0.11.5] — 2026-04-27
|
||||
|
||||
Follow-up release for PR #73: addresses four rounds of Devin AI review on the role-management-complete branch. No new public-API surface; the user-visible payoff is that v8→v9-migrated installations now work end-to-end (login flows, user list, admin nav, privilege revocation), and `make local-dev` startup is finally quiet.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Privilege retention after grant revocation via the new REST API** (Devin review #73). `_hydrate_legacy_role` previously short-circuited on a truthy `user.get("role")`. The role-management endpoints (`POST/DELETE /api/admin/users/{id}/role-grants`, plus the `changeCoreRole` UI flow) only mutate `user_role_grants` — they don't touch the legacy `users.role` column. After a downgrade-via-API, the stale legacy value would keep `user["role"] = "admin"` in memory; `_is_admin_user_dict` and the catalog/sync admin-bypass short-circuits then silently retained elevated table access even though `require_internal_role` correctly denied the API gates. Fix: always re-resolve from `user_role_grants` regardless of the legacy column, making the grants table the single source of truth on every authenticated request. Cost: one DB round-trip per request (same as the existing PAT-aware fallback).
|
||||
|
|
@ -22,6 +26,7 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C
|
|||
- **HTTP 500 on `POST /auth/token` for v8-migrated users** (Devin review #73 round 3). `TokenResponse.role` is a required `str` Pydantic field, but the v8→v9 migration NULL-s the legacy `users.role` column for every existing user. The login endpoint passed the raw NULL through to Pydantic, raising `ValidationError` → 500. Same root cause produced semantically wrong (but non-crashing) JWTs from Google OAuth, password, and email-magic-link flows — they wrote `role: null` into the issued token; downstream `_hydrate_legacy_role` in `get_current_user` would correct the per-request view, but the token payload itself stayed misleading. Fix: hydrate inline in each login flow before reading `user["role"]` — `app/auth/router.py` (`POST /auth/token`), `app/auth/providers/google.py` (OAuth callback), `app/auth/providers/password.py` (5 flows: JSON login, web login, JSON setup, web reset, web setup), and `app/auth/providers/email.py` (centralized in `_consume_token`, covers both magic-link `/verify` endpoints). New regression class `TestAuthLoginFlowsPostMigration` in `tests/test_schema_v9_migration.py` pins both the no-crash and the correct-role contracts for all four legacy levels (viewer/analyst/km_admin/admin).
|
||||
- **`docs/RBAC.md` documented an `implies=[…]` keyword on `register_internal_role()` that the function doesn't accept** (Devin review #73 round 3). A module author copying the example would hit `TypeError: got an unexpected keyword argument 'implies'` at import time. Reality: `implies` is currently seeded only for the `core.*` hierarchy via `_seed_core_roles` in `src/db.py` — the registry-side write path doesn't exist yet. Rewrote the *Implies hierarchy* and *Module-author workflow* sections to document what's actually supported in 0.11.4 and what a future change would need to add.
|
||||
- **`_seed_core_roles` was advertised as a per-connect safety net but only ran during fresh installs and the v8→v9 migration** (Devin review #73 round 4). The docstring promised "called from `_ensure_schema` on every connect" so an accidental `DELETE FROM internal_roles WHERE key = 'core.admin'` (or a doc-tweak release that updated `_CORE_ROLES_SEED` without bumping the schema version) would self-heal on the next process start. In reality both call sites lived inside `if current < SCHEMA_VERSION:` — once the DB was on v9, the seed function never ran again, leaving any deletion permanent and any in-code `display_name`/`description`/`implies` change requiring a manual SQL deploy. Fix: added an unconditional tail call to `_seed_core_roles(conn)` at the bottom of `_ensure_schema`, gated only by `current <= SCHEMA_VERSION` so the future-version-rollback contract still holds. New regression class `TestSeedCoreRolesSafetyNet` in `tests/test_schema_v9_migration.py` pins all three contracts (deleted row re-seeds, mutated `display_name` re-syncs from code, `applied_at` doesn't churn on already-current DBs).
|
||||
- **`make local-dev` startup spammed an `AuthlibDeprecationWarning` from upstream's own `_joserfc_helpers.py`** every time `app/auth/providers/google.py` triggered the `from authlib.integrations.starlette_client import OAuth` import chain. The warning is upstream-internal — authlib telling itself to migrate from `authlib.jose` to `joserfc` before its 2.0 cut — and isn't actionable on our side until either authlib ships the fix or we rewrite OAuth on top of `joserfc` directly. Filtered the specific warning class at the top of `app/main.py` (with a message-based fallback if the class moves in a future authlib release) so the warning no longer pollutes operator-facing stdout. Other `DeprecationWarning`s remain visible.
|
||||
|
||||
### Added
|
||||
|
||||
|
|
|
|||
21
app/main.py
21
app/main.py
|
|
@ -1,5 +1,26 @@
|
|||
"""FastAPI main application — unified server for web UI + API."""
|
||||
|
||||
# Silence authlib's internal forward-compat note. Authlib emits an
|
||||
# AuthlibDeprecationWarning from its own _joserfc_helpers when our
|
||||
# `from authlib.integrations.starlette_client import OAuth` import
|
||||
# touches `authlib.jose` paths. The warning is upstream-internal — it's
|
||||
# telling authlib to migrate to joserfc before its 2.0; it's not
|
||||
# actionable on our side until either authlib ships the fix or we
|
||||
# rewrite OAuth handling on top of joserfc directly. Filtering here
|
||||
# (before authlib gets imported transitively) keeps `make local-dev`
|
||||
# stdout clean without hiding warnings from any other package.
|
||||
import warnings as _warnings
|
||||
try:
|
||||
from authlib.deprecate import AuthlibDeprecationWarning as _AuthlibDepr
|
||||
_warnings.filterwarnings("ignore", category=_AuthlibDepr)
|
||||
except ImportError:
|
||||
# authlib too old / class moved — fall back to message-based match
|
||||
# so the filter still keeps startup clean.
|
||||
_warnings.filterwarnings(
|
||||
"ignore",
|
||||
message=r"authlib\.jose module is deprecated.*",
|
||||
)
|
||||
|
||||
import logging
|
||||
from contextlib import asynccontextmanager
|
||||
from importlib.metadata import PackageNotFoundError
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "agnes-the-ai-analyst"
|
||||
version = "0.11.4"
|
||||
version = "0.11.5"
|
||||
description = "Agnes — AI Data Analyst platform for AI analytical systems"
|
||||
requires-python = ">=3.11,<3.14"
|
||||
license = "MIT"
|
||||
|
|
|
|||
Loading…
Reference in a new issue