Skip to content

refactor: delete the dead v1 backend layers (#968) - #1161

Open
frankbria wants to merge 2 commits into
mainfrom
feature/issue-968-delete-dead-v1-backend-layers
Open

refactor: delete the dead v1 backend layers (#968)#1161
frankbria wants to merge 2 commits into
mainfrom
feature/issue-968-delete-dead-v1-backend-layers

Conversation

@frankbria

Copy link
Copy Markdown
Owner

Closes #968.

Deletes ~3,400 lines of v1-era server code that shipped in the installed wheel and the public import surface, describing endpoints that no longer exist. Dead auth-adjacent code is worse than absent code, because it implies controls that are not in force.

Net: +399 / −3,879 across 35 files.

Three corrections to the issue's own claims

The issue was written from a static scan; three of its claims did not survive verification, and one of them would have shipped a broken fresh install.

Issue said Reality
response_models.py "prescribes an envelope no endpoint uses" The file is live — 16 modules import api_error / ErrorCodes / internal_error. Only 8 of its 12 public symbols were dead.
"streaming_v2 is deleted and unmounted" The module cannot be deleted: tasks_v2.py:894,1271 imports its helpers for the live SSE route. Only the never-decorated router object was dead.
"accounts/sessions/verification dropped from schema init" Deleting the DDL alone breaks every fresh install, and orphans the tables forever on existing ones. See below.

Two smaller ones: the v1 config block is at config.py:607, not :495, and is not uniformly dead (GlobalConfig / load_environment are load-bearing); python-jose is at pyproject.toml:63, not :58.

The schema trap

create_schema() is idempotent CREATE TABLE IF NOT EXISTS against a long-lived state.db — it is never rebuilt. So removing the DDL leaves the three tables, and their REFERENCES users(id) ON DELETE CASCADE foreign keys, in every deployed database forever.

Worse: _create_indexes built idx_sessions_user_id / idx_sessions_expires_at outside the DDL block. Removing only the CREATE TABLE statements would have made every fresh Database.initialize() raise sqlite3.OperationalError: no such table: sessions.

This ships instead as a real MIGRATIONS entry — (2, _migration_002_drop_dead_auth_tables) with SCHEMA_VERSION 1 → 2 — following the recipe the class docstring has carried since #953.

What was removed

  • ui/models.py — 29 of 42 symbols (999 → 107 lines). 21 classes had zero references; 6 more only looked referenced because the live v2 routers define identically-named classes locally (TaskResponse in tasks_v2.py:162, BlockerResponse in blockers_v2.py:37, …). Only the settings/API-key block settings_v2 imports survives.
  • ui/response_models.py — the success envelope (ApiResponse, ApiError, PaginatedResponse, api_response) and four *_message helpers. No endpoint ever adopted them, while the module docstring advertised them as the house style.
  • ConnectionManager + WebSocketSubscriptionManager and POST /test/broadcast with its CODEFRAME_ENABLE_TEST_ENDPOINTS gate. connect() was never called anywhere, so the subscriber list was permanently empty and every broadcast a no-op. SessionChatManager stays — session_chat_ws depends on it.
  • The v1 JSON config layerProjectConfig and its five member models, the Config manager, and its core/__init__ re-export.
  • accounts / sessions / verification — vestiges of a BetterAuth migration that never happened. Auth is stateless JWT; this is why logout cannot revoke.
  • python-jose (zero imports — JWT is fastapi-users → PyJWT) and passlib (also zero imports). Drops the unmaintained ecdsa (CVE-2024-23342, wontfix), plus rsa and pyasn1. bcrypt is now declared explicitly: auth/api_keys.py:115 imports it directly but was getting it only transitively.
  • rate_limit_auth / rate_limit_websocket decorators — never applied, and unapplicable by construction (the fastapi-users routes are mounted by library factories a decorator cannot reach; slowapi needs an HTTP Request). Auth throttling is enforce_auth_rate_limit, untouched.
  • The unreachable app.state.default_workspace_path branch — nothing ever set that attribute, so the default always resolved to cwd.

streaming_v2.py moved to ui/streaming_utils.py — it is a utility module, and out of routers/ it cannot be re-mounted.

uv.lock regenerated. Not optional: Dockerfile:33,36 build with uv sync --frozen, so a stale lock passes every CI job and then fails the image build.

Verification

  • Backend: 6461 passed, 0 failed (uv run pytest tests/ --ignore=tests/e2e -m "not lifecycle"). web-ui: 1280 passed. ruff clean.
  • The route table is byte-identical to main — 144 routes, same paths, methods and resolved dependency functions — and so is app.openapi(). For a deletion of this size that is the load-bearing evidence: no endpoint and no auth dependency changed.
  • The migration was exercised against a real pre-[P3.1] Delete the dead v1 backend layers #968 database built by main's own SchemaManager, seeded with rows in all three tables: after upgrade all three tables and all their indexes are gone, user_version = 2, and a second run is a no-op. Downgrade is safe (old code recreates them via CREATE IF NOT EXISTS and does not crash).
  • Password hashing verified in a clean uv sync --frozen --no-dev environment — argon2-cffi and bcrypt survive via fastapi-users → pwdlib[argon2,bcrypt], so removing passlib stripped nothing.

Reviewed pre-PR by codex review (no findings) and an internal reviewer (no blocking findings; five nits, all fixed in 251f356).

Deleted tests

tests/ui/test_models.py, tests/ui/test_websocket_subscriptions.py, tests/ui/test_websocket_integration.py (already 100% skipped since the v1 /ws protocol was removed), and the sprint-validation classes in tests/config/test_config.py / tests/core/test_config_credentials.py. Each deleted test's only subject was deleted production code — test_load_environment was salvaged and moved, because load_environment is live.

Known limitations

  • CODEFRAME_ENABLE_TEST_ENDPOINTS is gone as a supported variable. /test/broadcast was its only consumer. Anything setting it in CI is now a no-op, not an error. CHANGELOG.md still mentions it, correctly, as history.
  • No uv lock --check in CI. The absence of that guard is what let python-jose sit unused; adding it is new scope, so it is not in this PR. Worth a follow-up.
  • SCHEMA_VERSION has no downgrade path. Consistent with the existing migration framework ([P2.3] Route all platform_store writes through the shared lock and add schema versioning #953), not a regression introduced here.

Roughly 3,400 lines of v1-era server code shipped in the installed wheel and
the public import surface, describing endpoints that no longer exist. Dead
auth-adjacent code is worse than absent code, because it implies controls that
are not in force.

Removed:

* ui/models.py — 29 of 42 symbols (999 -> 107 lines). 21 classes had zero
  references; 6 more only *looked* referenced because the live v2 routers
  define their own identically-named classes locally. Only the settings/
  API-key block that settings_v2 imports survives.
* ui/response_models.py — the success envelope (ApiResponse, ApiError,
  PaginatedResponse, api_response) and four *_message helpers. No endpoint
  ever adopted them, while the module docstring advertised them as the house
  style. api_error/ErrorCodes/internal_error are live and stay.
* ConnectionManager + WebSocketSubscriptionManager and POST /test/broadcast
  (with its CODEFRAME_ENABLE_TEST_ENDPOINTS gate). connect() was never called,
  so the subscriber list was permanently empty and every broadcast a no-op.
  SessionChatManager stays — session_chat_ws depends on it.
* The v1 JSON config layer: ProjectConfig and its five member models plus the
  `Config` manager and its core/__init__ re-export. GlobalConfig,
  load_environment and the singleton accessors are load-bearing and stay.
* accounts / sessions / verification tables — vestiges of a BetterAuth
  migration that never happened; no query has ever touched them.
* python-jose (zero imports; JWT is fastapi-users -> PyJWT) and passlib (also
  zero imports). This drops the unmaintained `ecdsa` (CVE-2024-23342, wontfix)
  from the graph. bcrypt is now declared explicitly — api_keys.py imports it
  directly but was getting it only transitively.
* rate_limit_auth / rate_limit_websocket decorators, never applied to a route
  and unapplicable by construction, plus the now-dead RATE_LIMIT_WEBSOCKET
  setting.
* The unreachable app.state.default_workspace_path branch — nothing ever set
  that attribute, so the default always resolved to cwd.

Three corrections to the issue's own claims:

1. response_models.py is NOT dead — 16 modules import api_error/ErrorCodes/
   internal_error. Only 8 of its 12 public symbols went.
2. streaming_v2.py could not be deleted: tasks_v2 imports its helpers for the
   live SSE route. Only the never-decorated `router` object was dead. Moved to
   ui/streaming_utils.py so it cannot be re-mounted; server.py's include_router
   for it was adding zero routes.
3. Dropping the three tables needed a real migration, not a DDL delete.
   create_schema is idempotent CREATE-IF-NOT-EXISTS against a long-lived
   state.db, so removing the DDL alone would orphan them in every deployed
   database — and _create_indexes built idx_sessions_* outside the DDL block,
   so a bare delete would have broken every fresh install with
   "no such table: sessions". Ships as MIGRATIONS entry (2, ...) with
   SCHEMA_VERSION 1 -> 2.

uv.lock regenerated: the Dockerfile builds with `uv sync --frozen`, so a stale
lock passes every CI job and then fails the image build.

Tests: 6461 passed. Deleted the suites whose only subject was deleted code
(test_models.py, test_websocket_subscriptions.py, the already-fully-skipped
test_websocket_integration.py, and the sprint-validation classes).
web-ui: 1280 passed.
Pre-PR review found four dead things that this PR made dead, plus a missing
assertion. All same-class as what the issue asks for.

* pyasn1 is no longer a direct dependency. Its only parent chain was
  python-jose -> rsa -> pyasn1, both now gone, so the "security floor" pin was
  flooring a package with no consumer — and the comment above it named
  python-jose as a parent.
* GlobalConfig.cors_origins (alias CORS_ORIGINS) removed. Its sole reader was
  get_cors_origins_list(), deleted here. Live CORS is parsed independently from
  CORS_ALLOWED_ORIGINS in server.py, so this was a knob an operator could set
  with no effect. `extra="ignore"` on the settings model means a leftover
  CORS_ORIGINS in someone's .env is inert, not an error.
* .env.example kept the "Rate limit for WebSocket connections" comment after
  its RATE_LIMIT_WEBSOCKET line went, leaving it to read as documentation for
  the storage-backend block below it.
* AGENT_SYSTEM_REFERENCE.md said "16 v2 router modules" over a list this PR cut
  to 15. The count was already wrong (there are 22 v2 REST routers), so it is
  now stated without a number.

The added assertion pins the failure mode the migration exists to avoid: a
surviving `CREATE INDEX ... ON sessions` would break every fresh install with
"no such table: sessions". The fresh-database and legacy-upgrade tests now
assert on sqlite_master indexes, by exact name — `interactive_sessions` is a
live table whose own indexes must survive a substring match.

Tests: 6461 passed.
@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error after 7m 23s —— View job


Bug-hunt review — PR #1161 (delete dead v1 backend layers)

Reviewing for concrete defects only — logic errors, crash paths, security issues, data loss/races, breaking runtime contract changes. (Style, naming, architecture, and coverage are handled by a separate review.)

  • Gather full diff and check existing review threads for duplicates
  • Audit the schema migration (drop tables/indexes; fresh vs. existing DBs)
  • Verify no live references to deleted symbols (ui/models.py, core/config.py, ui/shared.py, streaming_v2)
  • Audit dependency removals (python-jose, passlib, bcrypt) vs. actual imports
  • Audit rate-limiter / auth-manager / dependencies.py / server.py edits
  • Post inline findings + summary

@claude

claude Bot commented Aug 17, 2026

Copy link
Copy Markdown

Review

Reviewed the full diff (35 files, +418/−3,879). This is a large deletion PR and it's unusually well-executed — I checked the highest-risk pieces by hand rather than trusting the PR description's own claims.

What I verified independently

  • The SchemaManager migration (platform_store/schema_manager.py): _migration_002_drop_dead_auth_tables is correctly registered in MIGRATIONS with SCHEMA_VERSION bumped 1→2, and it drops accounts/sessions/verification with DROP TABLE IF EXISTS (idempotent, safe against create_schema's CREATE IF NOT EXISTS pattern). The idx_sessions_* indexes that used to live in _create_indexes outside the table-creation block are also gone — confirmed there's no leftover CREATE INDEX ... ON sessions that would break a fresh install. tests/core/test_dead_v1_layers_968.py backs this with both a fresh-DB test and a legacy-DB-with-seeded-rows migration test (plus a re-run-is-a-no-op check), which is the right coverage for a migration like this.
  • No dangling references to deleted symbols. Grepped the whole codeframe/ tree for streaming_v2, ConnectionManager, WebSocketSubscriptionManager, rate_limit_websocket, ProjectConfig, and core.config.Config — nothing left outside of comments/docstrings that intentionally reference the old names for historical context.
  • Import surface for the two kept modules matches what's live. ui/models.py's surviving symbols are exactly what settings_v2.py imports (the only remaining importer). ui/response_models.py's api_error/ErrorCodes/internal_error are imported by 16 router modules — matches the PR description's count.
  • streaming_v2.pyui/streaming_utils.py rename is clean: both call sites in tasks_v2.py (worker spawn + SSE route) were updated, server.py no longer imports or mounts it, and the never-decorated router object (the actual dead part) is gone rather than moved.
  • GlobalConfig has extra="ignore" (core/config.py:668), so removing cors_origins/rate_limit_websocket as declared fields is safe — a stray CORS_ORIGINS/RATE_LIMIT_WEBSOCKET in an operator's .env becomes inert rather than a pydantic-settings validation error at startup. This is exactly the kind of thing that silently breaks a deploy if unchecked, and it's fine here.
  • tests/ui/conftest.py: create_test_session_token() was correctly deleted along with its INSERT INTO sessions — it would have failed loudly post-migration. No other test file still references it.
  • pyproject.toml: python-jose/passlib removed, bcrypt added as a direct dependency (was previously only transitive via fastapi-users), pyasn1 security-floor pin dropped since its only parent chain (python-jose → rsa → pyasn1) is gone. test_dead_dependencies_are_removed_from_pyproject parses pyproject.toml with tomllib rather than grepping, which correctly avoids false-passing on the explanatory comment.

Minor observations (not blocking)

  • The PR body is unusually candid about its own scope limits — no uv lock --check in CI, no downgrade path for SCHEMA_VERSION — and correctly frames both as pre-existing gaps rather than regressions introduced here. Worth actually filing the uv lock --check follow-up given this PR's own root cause (python-jose sitting unused) is precisely the failure mode that check would catch.
  • _migration_002_drop_dead_auth_tables's docstring notes FK ON DELETE CASCADE is "still enforced" — worth double-checking SQLite's PRAGMA foreign_keys is actually on wherever these connections are opened, since a dropped-table-with-CASCADE-FK is irrelevant if enforcement was never on, but this doesn't affect correctness of the migration itself (dropping the table is safe either way).
  • Good catch fixing the .env.example orphaned "Rate limit for WebSocket connections" comment in the follow-up commit — that's the kind of doc drift that's easy to miss in a deletion this size.

Security

No concerns. If anything this reduces attack surface: removes an unauthenticated-by-construction-looking broadcast primitive (/test/broadcast, dead by disuse but still a class of thing worth not shipping), drops python-jose's transitive ecdsa (CVE-2024-23342), and removes tables that implied session-revocation capabilities that were never actually wired up (the JWT-only, no-revocation reality is now honestly reflected in the schema rather than contradicted by unused tables).

Test coverage

Strong. The new test_dead_v1_layers_968.py is structured well for a deletion PR — mostly "does this symbol come back" assertions (correct, since that's the actual regression risk for dead-code removal), plus real behavioral coverage of the one stateful change (the migration). Route-table/OpenAPI byte-identical claim against main is a good sanity check for a PR this size.

Nothing here blocks merge from my read of the diff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P3.1] Delete the dead v1 backend layers

1 participant