Files
kite/.planning/phases/08-stack-upgrade-backend-decomposition/08-02-PLAN.md
T
curo1305andClaude Sonnet 4.6 25e568973f docs(08): create phase 8 plan — 8 plans, 3 waves (stack upgrade + backend decomposition)
Wave 0: CR-01/02/03 test stubs + api/schemas.py (CloudConnectionOut migration)
Wave 1: useToastStore stub + Phase 7.1 frontend completion
Wave 2 (parallel): api/admin/, api/documents/, api/auth/ package splits + client.js decomposition + PERF-01 deps

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 14:59:27 +02:00

173 lines
10 KiB
Markdown

---
phase: 08-stack-upgrade-backend-decomposition
plan: 02
type: execute
wave: 0
depends_on: []
files_modified:
- backend/api/schemas.py
- backend/api/cloud.py
autonomous: true
requirements: [CODE-08]
tags: [shared-schemas, cross-package, prerequisite, wave-0]
must_haves:
truths:
- "backend/api/schemas.py exists and defines CloudConnectionOut with field_validator coerce_id_to_str"
- "backend/api/cloud.py imports CloudConnectionOut from api.schemas (not api.admin)"
- "All cloud endpoints continue to return the same JSON shape as before"
- "CloudConnectionOut definition appears exactly once across the entire backend tree"
artifacts:
- path: "backend/api/schemas.py"
provides: "Cross-package Pydantic response models (D-10 destination for shared schemas)"
contains: "class CloudConnectionOut"
- path: "backend/api/cloud.py"
provides: "Cloud endpoint router updated to import from api.schemas"
contains: "from api.schemas import CloudConnectionOut"
key_links:
- from: "backend/api/cloud.py"
to: "backend/api/schemas.py"
via: "import statement at module top"
pattern: "from api.schemas import CloudConnectionOut"
- from: "backend/api/admin.py"
to: "backend/api/schemas.py"
via: "import statement to be added in plan 08-04 admin split"
pattern: "from api.schemas import CloudConnectionOut"
---
<objective>
Create the new `backend/api/schemas.py` module and move `CloudConnectionOut` into it. Update `backend/api/cloud.py` to import from the new location. This MUST happen BEFORE plan 08-04 splits `api/admin.py` — otherwise the admin-split plan would break `cloud.py`'s `from api.admin import CloudConnectionOut` import.
Purpose: Eliminate the cross-package coupling between `api/cloud.py` and `api/admin.py` (RESEARCH.md §"Pitfall 3"). Establishes the `api/schemas.py` module that plan 08-04 will continue populating with any models discovered to be shared.
Output: New `backend/api/schemas.py` with `CloudConnectionOut`; one updated import in `backend/api/cloud.py`.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/08-stack-upgrade-backend-decomposition/08-CONTEXT.md
@.planning/phases/08-stack-upgrade-backend-decomposition/08-RESEARCH.md
@.planning/phases/08-stack-upgrade-backend-decomposition/08-PATTERNS.md
@backend/api/admin.py
@backend/api/cloud.py
<interfaces>
<!-- Key definition to move (from backend/api/admin.py lines ~198-223). -->
<!-- After this plan, this class lives ONLY in backend/api/schemas.py. -->
class CloudConnectionOut(BaseModel):
id: str
provider: str
display_name: str
status: str
connected_at: datetime
server_url: Optional[str] = None
connection_username: Optional[str] = None
model_config = {"from_attributes": True}
@field_validator("id", mode="before")
@classmethod
def coerce_id_to_str(cls, v) -> str:
return str(v)
<!-- The import currently in backend/api/cloud.py line 35: -->
from api.admin import CloudConnectionOut
<!-- becomes: -->
from api.schemas import CloudConnectionOut
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Create backend/api/schemas.py with CloudConnectionOut</name>
<files>backend/api/schemas.py</files>
<read_first>
- backend/api/admin.py lines 198-223 (current CloudConnectionOut definition — must be copied verbatim including the field_validator)
- backend/api/__init__.py (confirm it exists as a package marker; do not modify)
- .planning/phases/08-stack-upgrade-backend-decomposition/08-PATTERNS.md section "backend/api/schemas.py" (exact pattern template)
</read_first>
<action>
Create `backend/api/schemas.py` as a new file. Add a `from __future__ import annotations` header. Add a module docstring: `"""Cross-package Pydantic response models. Models here are used by 2+ API packages and cannot live in a single package without creating circular imports (D-10, RESEARCH.md Pitfall 3)."""`. Add imports: `from datetime import datetime`, `from typing import Optional`, `from pydantic import BaseModel, field_validator`. Then define `class CloudConnectionOut(BaseModel)` with the exact field set from `backend/api/admin.py` lines ~198-223 (per D-10): `id: str`, `provider: str`, `display_name: str`, `status: str`, `connected_at: datetime`, `server_url: Optional[str] = None`, `connection_username: Optional[str] = None`, `model_config = {"from_attributes": True}`, and the `@field_validator("id", mode="before") coerce_id_to_str` classmethod that returns `str(v)`. Add a class-level docstring noting SEC-08: `credentials_enc` deliberately excluded, moved from `api/admin.py`, used by `api/cloud.py` and `api/admin/`. Do NOT remove the class from `api/admin.py` in this task — task 2 handles the import switch in cloud.py, and plan 08-04 will delete the original definition during the admin split.
</action>
<verify>
<automated>cd backend && python -c "from api.schemas import CloudConnectionOut; obj = CloudConnectionOut.model_validate({'id': 'abc-123', 'provider': 'google_drive', 'display_name': 'Test', 'status': 'ACTIVE', 'connected_at': '2026-06-07T00:00:00'}); assert obj.id == 'abc-123' and obj.provider == 'google_drive'"</automated>
</verify>
<acceptance_criteria>
- File `backend/api/schemas.py` exists
- `grep -c "^class CloudConnectionOut" backend/api/schemas.py` returns 1
- `grep -c "coerce_id_to_str" backend/api/schemas.py` returns 1
- `grep -c "from_attributes" backend/api/schemas.py` returns 1
- `python -c "from api.schemas import CloudConnectionOut"` from inside `backend/` exits 0 with no output
- All seven fields (id, provider, display_name, status, connected_at, server_url, connection_username) are present per `grep -c " [a-z_]*: " backend/api/schemas.py` ≥ 7
</acceptance_criteria>
<done>schemas.py exists, importable, CloudConnectionOut validates a sample dict, original admin.py class is still in place (deletion deferred to plan 08-04).</done>
</task>
<task type="auto">
<name>Task 2: Switch backend/api/cloud.py import to api.schemas</name>
<files>backend/api/cloud.py</files>
<read_first>
- backend/api/cloud.py line 35 (current `from api.admin import CloudConnectionOut`)
- backend/api/cloud.py top imports block (confirm no other re-exports from `api.admin` exist)
</read_first>
<action>
In `backend/api/cloud.py`, replace the single line `from api.admin import CloudConnectionOut` with `from api.schemas import CloudConnectionOut`. Do not change any other imports or any code below. After this edit, run the full test suite to confirm cloud endpoints still respond with the identical JSON shape (existing `tests/test_cloud.py` covers admin response surface; if not, the broader `pytest -v` smoke covers the admin list-cloud-connections endpoint too).
</action>
<verify>
<automated>cd backend && grep -c "from api.admin import CloudConnectionOut" api/cloud.py; cd backend && grep -c "from api.schemas import CloudConnectionOut" api/cloud.py; cd backend && pytest tests/test_cloud.py -x -v 2>&1 | tail -20</automated>
</verify>
<acceptance_criteria>
- `grep -c "from api.admin import CloudConnectionOut" backend/api/cloud.py` returns 0
- `grep -c "from api.schemas import CloudConnectionOut" backend/api/cloud.py` returns 1
- `cd backend && pytest tests/test_cloud.py -x` exits 0
- `cd backend && pytest tests/test_admin.py -x -k "cloud"` exits 0 (admin endpoints that serialize CloudConnectionOut continue to work because the original definition in admin.py is still untouched at this point)
</acceptance_criteria>
<done>cloud.py imports CloudConnectionOut from api.schemas; tests for cloud endpoints and any admin endpoints that surface cloud connections continue to pass.</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| api/cloud.py → api/schemas.py | Module-load-time import; no runtime data crosses this boundary other than a class reference |
| Admin list-cloud-connections endpoint → CloudConnectionOut serialization | SEC-08 requires `credentials_enc` to be deliberately excluded from this model |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-08-02-01 | Information Disclosure | CloudConnectionOut schema | mitigate | Field list must NOT include `credentials_enc`; field set is restricted to the 7 fields documented in PATTERNS.md |
| T-08-02-02 | Tampering | id coercion | mitigate | Preserve `@field_validator("id", mode="before") coerce_id_to_str` so UUID→str conversion behavior is byte-identical to the old definition |
| T-08-02-03 | Denial of Service | Duplicate class definitions | mitigate | Plan 08-04 removes the old definition from api/admin.py during the admin split; until then both definitions coexist but only the schemas.py version is imported by cloud.py |
| T-08-02-SC | Supply Chain | No new packages | accept | This plan installs zero new packages |
</threat_model>
<verification>
- `cd backend && pytest tests/test_cloud.py tests/test_admin.py -x` — zero failures
- `grep -rn "from api.admin import CloudConnectionOut" backend/` returns no matches (cloud.py was the only consumer)
- `grep -rn "from api.schemas import CloudConnectionOut" backend/` returns exactly one match (cloud.py)
- `backend/api/admin.py` still contains the original `class CloudConnectionOut` definition (deletion deferred to plan 08-04)
</verification>
<success_criteria>
- `backend/api/schemas.py` exists with `CloudConnectionOut` defined correctly
- `backend/api/cloud.py` imports from `api.schemas`
- Cloud and admin tests continue to pass
- No other file is modified
</success_criteria>
<output>
Create `.planning/phases/08-stack-upgrade-backend-decomposition/08-02-SUMMARY.md` when done. Include: confirmation that the original class in admin.py is left untouched, the exact `pytest tests/test_cloud.py tests/test_admin.py -v` summary line, and a note that plan 08-04 owns the deletion of the old admin.py definition.
</output>