Files
govoplan-workflow/src/govoplan_workflow/backend/schemas.py

228 lines
6.5 KiB
Python

from __future__ import annotations
import math
from datetime import datetime
from typing import Any, Literal
from pydantic import BaseModel, Field, field_validator
WorkflowDefinitionStatus = Literal["draft", "active", "archived"]
DefinitionScopeType = Literal["system", "tenant", "group", "user"]
DefinitionKind = Literal["flow", "template"]
class WorkflowPosition(BaseModel):
x: float = 0
y: float = 0
@field_validator("x", "y")
@classmethod
def finite_coordinate(cls, value: float) -> float:
if not math.isfinite(value):
raise ValueError("Graph coordinates must be finite.")
return value
class WorkflowNode(BaseModel):
id: str = Field(min_length=1, max_length=120)
type: str = Field(min_length=1, max_length=120)
label: str = Field(default="", max_length=300)
position: WorkflowPosition = Field(default_factory=WorkflowPosition)
config: dict[str, Any] = Field(default_factory=dict)
class WorkflowEdge(BaseModel):
id: str = Field(min_length=1, max_length=120)
source: str = Field(min_length=1, max_length=120)
target: str = Field(min_length=1, max_length=120)
source_port: str = Field(default="output", min_length=1, max_length=120)
target_port: str = Field(default="input", min_length=1, max_length=120)
class WorkflowGraph(BaseModel):
schema_version: Literal[1] = 1
nodes: list[WorkflowNode] = Field(default_factory=list, max_length=150)
edges: list[WorkflowEdge] = Field(default_factory=list, max_length=300)
class WorkflowGraphValidationRequest(BaseModel):
graph: WorkflowGraph
class WorkflowDiagnosticResponse(BaseModel):
severity: Literal["error", "warning"]
code: str
message: str
node_id: str | None = None
field: str | None = None
class WorkflowGraphValidationResponse(BaseModel):
valid: bool
diagnostics: list[WorkflowDiagnosticResponse]
class WorkflowPortResponse(BaseModel):
id: str
label: str
required: bool
multiple: bool
minimum_connections: int
class WorkflowConfigFieldResponse(BaseModel):
id: str
label: str
kind: str
required: bool
description: str | None
options: list[tuple[str, str]]
class WorkflowNodeTypeResponse(BaseModel):
type: str
category: str
category_label: str
label: str
description: str
icon: str
input_ports: list[WorkflowPortResponse]
output_ports: list[WorkflowPortResponse]
config_fields: list[WorkflowConfigFieldResponse]
default_config: dict[str, Any]
class WorkflowNodeLibraryResponse(BaseModel):
id: str
version: str
allows_cycles: bool
nodes: list[WorkflowNodeTypeResponse]
class WorkflowDefinitionRevisionResponse(BaseModel):
id: str
revision: int
schema_version: int
graph: WorkflowGraph
content_hash: str
library_id: str
library_version: str
created_by: str | None
created_at: datetime
class WorkflowActionDecisionResponse(BaseModel):
allowed: bool
reason: str | None = None
source_path: list[dict[str, Any]] = Field(default_factory=list)
requirements: list[str] = Field(default_factory=list)
details: dict[str, Any] = Field(default_factory=dict)
class WorkflowGovernanceResponse(BaseModel):
scope_type: DefinitionScopeType
scope_id: str | None
definition_kind: DefinitionKind
inherit_to_lower_scopes: bool
allow_start: bool
allow_reuse: bool
allow_automation: bool
derived_from_definition_id: str | None
derived_from_revision: int | None
derived_from_hash: str | None
derivation_provenance: dict[str, Any] = Field(default_factory=dict)
actions: dict[str, WorkflowActionDecisionResponse]
automation_runtime_available: bool = False
automation_runtime_reason: str | None = None
class WorkflowDefinitionResponse(BaseModel):
id: str
tenant_id: str | None
key: str
name: str
description: str | None
status: WorkflowDefinitionStatus
current_revision: int
active_revision: int | None
metadata: dict[str, Any]
created_by: str | None
updated_by: str | None
created_at: datetime
updated_at: datetime
revision: WorkflowDefinitionRevisionResponse
governance: WorkflowGovernanceResponse
class WorkflowDefinitionListResponse(BaseModel):
definitions: list[WorkflowDefinitionResponse]
class WorkflowDefinitionRevisionListResponse(BaseModel):
revisions: list[WorkflowDefinitionRevisionResponse]
class WorkflowDefinitionCreateRequest(BaseModel):
key: str | None = Field(
default=None,
min_length=1,
max_length=120,
pattern=r"^[A-Za-z0-9][A-Za-z0-9_-]*$",
)
name: str = Field(min_length=1, max_length=300)
description: str | None = Field(default=None, max_length=4_000)
graph: WorkflowGraph
metadata: dict[str, Any] = Field(default_factory=dict)
scope_type: DefinitionScopeType = "tenant"
scope_id: str | None = Field(default=None, max_length=36)
definition_kind: DefinitionKind = "flow"
inherit_to_lower_scopes: bool = False
allow_start: bool = True
allow_reuse: bool = False
allow_automation: bool = False
class WorkflowDefinitionUpdateRequest(BaseModel):
name: str = Field(min_length=1, max_length=300)
description: str | None = Field(default=None, max_length=4_000)
graph: WorkflowGraph
metadata: dict[str, Any] = Field(default_factory=dict)
expected_revision: int = Field(ge=1)
scope_type: DefinitionScopeType = "tenant"
scope_id: str | None = Field(default=None, max_length=36)
definition_kind: DefinitionKind = "flow"
inherit_to_lower_scopes: bool = False
allow_start: bool = True
allow_reuse: bool = False
allow_automation: bool = False
class WorkflowDefinitionDeriveRequest(BaseModel):
key: str | None = Field(
default=None,
min_length=1,
max_length=120,
pattern=r"^[A-Za-z0-9][A-Za-z0-9_-]*$",
)
name: str = Field(min_length=1, max_length=300)
description: str | None = Field(default=None, max_length=4_000)
source_revision: int | None = Field(default=None, ge=1)
metadata: dict[str, Any] = Field(default_factory=dict)
scope_type: DefinitionScopeType = "tenant"
scope_id: str | None = Field(default=None, max_length=36)
definition_kind: DefinitionKind = "flow"
inherit_to_lower_scopes: bool = False
allow_start: bool = True
allow_reuse: bool = False
allow_automation: bool = False
class WorkflowDefinitionActivateRequest(BaseModel):
revision: int | None = Field(default=None, ge=1)
class WorkflowDefinitionDeleteResponse(BaseModel):
deleted: bool
definition_id: str