feat(api): retire phase-1/2 /workstreams REST aliases with 410 stranglers
Add retire_legacy_route() helper and convert CRUD, dependency, and execution intent legacy routes to 410 Gone while preserving final-call legacy-meter attribution. Update tests, retirement backlog, and close STATE-WP-0075 with handoff to STATE-WP-0070 T02 and STATE-WP-0073 T04.
This commit is contained in:
parent
c4ac853e35
commit
7036e2aa6b
11 changed files with 320 additions and 139 deletions
|
|
@ -23,8 +23,7 @@ from api.schemas.workplan import (
|
|||
WorkplanUpdate,
|
||||
)
|
||||
from api.services.lifecycle import transition_workplan_status
|
||||
from api.services.legacy_compat import legacy_response_headers, mark_legacy_response
|
||||
from api.services.legacy_meter import identity_from_request, record_legacy_usage
|
||||
from api.services.legacy_compat import retire_legacy_route
|
||||
from api.workplan_status import (
|
||||
is_supported_workplan_status,
|
||||
normalize_workplan_status,
|
||||
|
|
@ -42,7 +41,6 @@ _INDEX_TTL = 30.0
|
|||
_INDEX_REFRESH_TASK: asyncio.Task | None = None
|
||||
_INDEX_LAST_ERROR: str | None = None
|
||||
|
||||
_LEGACY_OWNER = "state-hub.api"
|
||||
_COMPLETED_WORKPLAN_EVENT = "org.statehub.workplan.completed"
|
||||
|
||||
|
||||
|
|
@ -82,30 +80,6 @@ def _legacy_key(method: str, route: str) -> str:
|
|||
return f"rest_api:{method} {route}"
|
||||
|
||||
|
||||
async def _meter_legacy_route(
|
||||
*,
|
||||
session: AsyncSession,
|
||||
request: Request | None,
|
||||
response: Response | None,
|
||||
interface_key: str,
|
||||
replacement_ref: str,
|
||||
) -> None:
|
||||
mark_legacy_response(response, replacement_ref)
|
||||
try:
|
||||
await record_legacy_usage(
|
||||
session,
|
||||
interface_key=interface_key,
|
||||
interface_kind="rest_api",
|
||||
replacement_ref=replacement_ref,
|
||||
owner_component=_LEGACY_OWNER,
|
||||
replacement_verified=True,
|
||||
identity=identity_from_request(request),
|
||||
)
|
||||
except Exception:
|
||||
await session.rollback()
|
||||
logger.warning("legacy-meter failed to record %s", interface_key, exc_info=True)
|
||||
|
||||
|
||||
async def _list_workplans(
|
||||
*,
|
||||
topic_id: uuid.UUID | None,
|
||||
|
|
@ -361,7 +335,7 @@ async def _publish_completion_events(wp: Workplan, session: AsyncSession) -> Non
|
|||
asyncio.create_task(publish_event(_COMPLETED_WORKPLAN_EVENT, workplan_envelope))
|
||||
|
||||
|
||||
@router.get("/", response_model=list[WorkplanRead])
|
||||
@router.get("/", status_code=status.HTTP_410_GONE)
|
||||
async def list_workstreams(
|
||||
request: Request,
|
||||
response: Response,
|
||||
|
|
@ -372,22 +346,14 @@ async def list_workstreams(
|
|||
owner: str | None = None,
|
||||
slug: str | None = None,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> list[Workplan]:
|
||||
await _meter_legacy_route(
|
||||
) -> None:
|
||||
await retire_legacy_route(
|
||||
session=session,
|
||||
request=request,
|
||||
response=response,
|
||||
interface_key=_legacy_key("GET", "/workstreams/"),
|
||||
replacement_ref="/workplans/",
|
||||
)
|
||||
return await _list_workplans(
|
||||
topic_id=topic_id,
|
||||
repo_id=repo_id,
|
||||
repo_goal_id=repo_goal_id,
|
||||
status_filter=status,
|
||||
owner=owner,
|
||||
slug=slug,
|
||||
session=session,
|
||||
detail="Legacy GET /workstreams/ retired; use GET /workplans/",
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -419,18 +385,13 @@ async def workplan_index(
|
|||
refresh: bool = Query(False, description="Force cache invalidation"),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> None:
|
||||
replacement_ref = "/workplans/index"
|
||||
await _meter_legacy_route(
|
||||
await retire_legacy_route(
|
||||
session=session,
|
||||
request=request,
|
||||
response=response,
|
||||
interface_key=_legacy_key("GET", "/workstreams/workplan-index"),
|
||||
replacement_ref=replacement_ref,
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_410_GONE,
|
||||
replacement_ref="/workplans/index",
|
||||
detail="Legacy GET /workstreams/workplan-index retired; use GET /workplans/index",
|
||||
headers=legacy_response_headers(replacement_ref),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -464,21 +425,21 @@ async def sync_workplan_bindings(
|
|||
return {"updated": updated, "received": len(body.bindings)}
|
||||
|
||||
|
||||
@router.post("/", response_model=WorkplanRead, status_code=status.HTTP_201_CREATED)
|
||||
@router.post("/", status_code=status.HTTP_410_GONE)
|
||||
async def create_workstream(
|
||||
request: Request,
|
||||
response: Response,
|
||||
body: WorkplanCreate,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> Workplan:
|
||||
await _meter_legacy_route(
|
||||
) -> None:
|
||||
await retire_legacy_route(
|
||||
session=session,
|
||||
request=request,
|
||||
response=response,
|
||||
interface_key=_legacy_key("POST", "/workstreams/"),
|
||||
replacement_ref="/workplans/",
|
||||
detail="Legacy POST /workstreams/ retired; use POST /workplans/",
|
||||
)
|
||||
return await _create_workplan(body=body, session=session)
|
||||
|
||||
|
||||
@workplan_router.post("/", response_model=WorkplanRead, status_code=status.HTTP_201_CREATED)
|
||||
|
|
@ -489,21 +450,21 @@ async def create_workplan(
|
|||
return await _create_workplan(body=body, session=session)
|
||||
|
||||
|
||||
@router.get("/{workstream_id}", response_model=WorkplanRead)
|
||||
@router.get("/{workstream_id}", status_code=status.HTTP_410_GONE)
|
||||
async def get_workstream(
|
||||
request: Request,
|
||||
response: Response,
|
||||
workstream_id: uuid.UUID,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> Workplan:
|
||||
await _meter_legacy_route(
|
||||
) -> None:
|
||||
await retire_legacy_route(
|
||||
session=session,
|
||||
request=request,
|
||||
response=response,
|
||||
interface_key=_legacy_key("GET", "/workstreams/{workstream_id}"),
|
||||
replacement_ref="/workplans/{workplan_id}",
|
||||
detail="Legacy GET /workstreams/{workstream_id} retired; use GET /workplans/{workplan_id}",
|
||||
)
|
||||
return await _get_workplan(workplan_id=workstream_id, session=session)
|
||||
|
||||
|
||||
@workplan_router.get("/{workplan_id}", response_model=WorkplanRead)
|
||||
|
|
@ -514,22 +475,22 @@ async def get_workplan(
|
|||
return await _get_workplan(workplan_id=workplan_id, session=session)
|
||||
|
||||
|
||||
@router.patch("/{workstream_id}", response_model=WorkplanRead)
|
||||
@router.patch("/{workstream_id}", status_code=status.HTTP_410_GONE)
|
||||
async def update_workstream(
|
||||
request: Request,
|
||||
response: Response,
|
||||
workstream_id: uuid.UUID,
|
||||
body: WorkplanUpdate,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> Workplan:
|
||||
await _meter_legacy_route(
|
||||
) -> None:
|
||||
await retire_legacy_route(
|
||||
session=session,
|
||||
request=request,
|
||||
response=response,
|
||||
interface_key=_legacy_key("PATCH", "/workstreams/{workstream_id}"),
|
||||
replacement_ref="/workplans/{workplan_id}",
|
||||
detail="Legacy PATCH /workstreams/{workstream_id} retired; use PATCH /workplans/{workplan_id}",
|
||||
)
|
||||
return await _update_workplan(workplan_id=workstream_id, body=body, session=session)
|
||||
|
||||
|
||||
@workplan_router.patch("/{workplan_id}", response_model=WorkplanRead)
|
||||
|
|
@ -548,18 +509,13 @@ async def archive_workstream(
|
|||
workstream_id: uuid.UUID,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> None:
|
||||
replacement_ref = "DELETE /workplans/{workplan_id}"
|
||||
await _meter_legacy_route(
|
||||
await retire_legacy_route(
|
||||
session=session,
|
||||
request=request,
|
||||
response=response,
|
||||
interface_key=_legacy_key("DELETE", "/workstreams/{workstream_id}"),
|
||||
replacement_ref=replacement_ref,
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_410_GONE,
|
||||
replacement_ref="DELETE /workplans/{workplan_id}",
|
||||
detail="Legacy DELETE /workstreams/{workstream_id} retired; use DELETE /workplans/{workplan_id}",
|
||||
headers=legacy_response_headers(replacement_ref),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue