Keep a flow's runs when the flow goes
A run record is a record of what ran, so deleting a flow no longer sweeps its
history: `_forget_runs` is gone from the flow-delete path, and `DELETE /runs/{id}`
is the only thing that removes a run one at a time. The in-flight guard stays —
that is about work, not history.
`DELETE /runs?flow=` is the counterpart to the list's flow filter and what a
reseed needs, sharing the four statements with the single-run delete and
refusing the same way while a run of that flow is still going.
A run whose flow is gone reads as one: `useFlowInputs` reports the 404 rather
than an empty declaration set, so the run page says "flow deleted", explains it,
and disables Retry, which the route would refuse anyway.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CL9zvnnvcp1mvA8o7impxk
This commit is contained in:
@@ -16,7 +16,6 @@ from fastapi import (
|
||||
from fastapi.concurrency import run_in_threadpool
|
||||
from jwt.exceptions import InvalidTokenError
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy import delete
|
||||
from sqlmodel import Session, col, select
|
||||
|
||||
from fluksio.api.deps import (
|
||||
@@ -59,7 +58,7 @@ from fluksio.flow.store import (
|
||||
LibNotFound,
|
||||
StaleVersion,
|
||||
)
|
||||
from fluksio.models import Flavor, Message, Run, RunArtifact, RunMetric, RunNode
|
||||
from fluksio.models import Flavor, Message, Run
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/flows", tags=["flows"], dependencies=[Depends(get_current_user)]
|
||||
@@ -242,25 +241,6 @@ def _audit(action: str, flow: str, user: CurrentUser) -> None:
|
||||
)
|
||||
|
||||
|
||||
def _forget_runs(session: Session, flow: str) -> None:
|
||||
"""A deleted flow's runs, and everything hanging off them.
|
||||
|
||||
Here rather than in ``FlowController.forget_flow`` because renaming a flow
|
||||
calls that too, and a rename must keep its experiment history.
|
||||
|
||||
Only the run tables: ``flow_run``, ``metric_minute`` and ``engine_event``
|
||||
are the observability rollups, deliberately kept as a record of what ran
|
||||
and already pruned at OBS_RETENTION_DAYS.
|
||||
"""
|
||||
# A subquery, not a materialised list of ids: a demo can hold thousands.
|
||||
runs = select(col(Run.id)).where(col(Run.flow) == flow)
|
||||
session.execute(delete(RunNode).where(col(RunNode.run_id).in_(runs)))
|
||||
session.execute(delete(RunMetric).where(col(RunMetric.run_id).in_(runs)))
|
||||
session.execute(delete(RunArtifact).where(col(RunArtifact.run_id).in_(runs)))
|
||||
session.execute(delete(Run).where(col(Run.flow) == flow))
|
||||
session.commit()
|
||||
|
||||
|
||||
def _source_ref(definition: FlowDef, node_id: str) -> str | None:
|
||||
"""The library source this node runs, if it is a shared one."""
|
||||
node = next((n for n in definition.nodes if n.id == node_id), None)
|
||||
@@ -483,7 +463,7 @@ async def discard_draft(name: str, controller: FlowControllerDep) -> Any:
|
||||
async def delete_flow(
|
||||
name: str, controller: FlowControllerDep, user: CurrentUser, session: SessionDep
|
||||
) -> Any:
|
||||
"""Delete a flow and everything in it."""
|
||||
"""Delete a flow and everything in it, except the record of what it ran."""
|
||||
live = session.exec(
|
||||
select(Run.id).where(
|
||||
col(Run.flow) == name, col(Run.status).in_(("running", "queued"))
|
||||
@@ -503,8 +483,10 @@ async def delete_flow(
|
||||
raise HTTPException(status_code=404, detail=f"No flow named '{name}'")
|
||||
_audit("deleted", name, user)
|
||||
# Its files are gone; its values and queued work would otherwise linger.
|
||||
# Its runs stay: a run record is a record of what ran, and surviving the
|
||||
# flow it belonged to is the point of keeping one. `DELETE /runs?flow=`
|
||||
# is what clears them.
|
||||
await run_in_threadpool(controller.forget_flow, name)
|
||||
await run_in_threadpool(_forget_runs, session, name)
|
||||
await controller.reload_flow(name)
|
||||
return Message(message=f"Deleted flow '{name}'")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user