feat: approval decisions, tenant scoping, error paths with tests
- POST /api/approvals/{id}/decision (approve starts continuation flow
with task facts; reject closes; 404/409/422 guards)
- Tenant scoping: list filters + cross-tenant reads 404
- Error mapping covered: LFX down 503, LFX drift 502, corrupt flow 422
- 34 tests green
This commit is contained in:
parent
1034206870
commit
6c195bc2e7
4 changed files with 389 additions and 12 deletions
|
|
@ -174,16 +174,19 @@ def get_run(session: Session, run_id: UUID) -> FlowRun:
|
|||
return run
|
||||
|
||||
|
||||
def list_runs(session: Session, flow_id: UUID) -> list[FlowRun]:
|
||||
def list_runs(
|
||||
session: Session, flow_id: UUID, tenant_id: str | None = None
|
||||
) -> list[FlowRun]:
|
||||
get_definition(session, flow_id)
|
||||
return list(
|
||||
session.exec(
|
||||
select(FlowRun)
|
||||
.join(FlowVersion, FlowVersion.id == FlowRun.version_id)
|
||||
.where(FlowVersion.flow_id == flow_id)
|
||||
.order_by(FlowRun.created_at.desc())
|
||||
).all()
|
||||
query = (
|
||||
select(FlowRun)
|
||||
.join(FlowVersion, FlowVersion.id == FlowRun.version_id)
|
||||
.where(FlowVersion.flow_id == flow_id)
|
||||
.order_by(FlowRun.created_at.desc())
|
||||
)
|
||||
if tenant_id is not None:
|
||||
query = query.where(FlowRun.tenant_id == tenant_id)
|
||||
return list(session.exec(query).all())
|
||||
|
||||
|
||||
def create_approval_task(session: Session, data: ApprovalCreate) -> ApprovalTask:
|
||||
|
|
@ -223,3 +226,33 @@ def list_approval_tasks(session: Session, tenant_id: str | None = None) -> list[
|
|||
if tenant_id is not None:
|
||||
query = query.where(ApprovalTask.tenant_id == tenant_id)
|
||||
return list(session.exec(query).all())
|
||||
|
||||
|
||||
def get_task(session: Session, task_id: UUID) -> ApprovalTask:
|
||||
task = session.get(ApprovalTask, task_id)
|
||||
if task is None:
|
||||
raise NotFoundError(f"approval task {task_id} not found")
|
||||
return task
|
||||
|
||||
|
||||
def decide_task(
|
||||
session: Session,
|
||||
task: ApprovalTask,
|
||||
*,
|
||||
decision: str,
|
||||
approver: str = "",
|
||||
note: str = "",
|
||||
) -> ApprovalTask:
|
||||
"""Approve or reject an open task. Decided tasks are immutable (409)."""
|
||||
if task.status != "open":
|
||||
raise ConflictError(f"task {task.id} is already {task.status}")
|
||||
if decision not in ("approved", "rejected"):
|
||||
raise InvalidError(f"unknown decision {decision!r}")
|
||||
task.status = decision
|
||||
task.approver = approver
|
||||
task.decision_note = note
|
||||
task.decided_at = datetime.now(timezone.utc)
|
||||
session.add(task)
|
||||
session.commit()
|
||||
session.refresh(task)
|
||||
return task
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue