state-hub/api/schemas/workplan_dependency.py

65 lines
2 KiB
Python
Raw Normal View History

import uuid
from datetime import datetime
from pydantic import AliasChoices, BaseModel, ConfigDict, Field, computed_field
class WorkplanDependencyCreate(BaseModel):
to_workplan_id: uuid.UUID | None = Field(
default=None,
validation_alias=AliasChoices("to_workplan_id", "to_workstream_id"),
)
to_task_id: uuid.UUID | None = None
CUST-WP-0074-T02: task wait qualifiers in the hub read model Adds tasks.decision_id and workplan_dependencies.from_task_id (migration e8f9a0b1c2d3). A from_task_id column is needed rather than from_workplan_id plus description: the partial unique indexes key on (from_workplan_id, target, relationship_type), so two tasks in one workplan waiting on the same target could not both be indexed, and the derived wait_kind needs per-task attribution. The two existing unique indexes are narrowed to frontmatter edges (from_task_id IS NULL) and two task-origin counterparts are added; downgrade deletes task-origin rows before restoring the old indexes. POST /workplans/{id}/dependencies/ accepts from_task_id (must belong to the from workplan; a task cannot depend on itself). TaskCreate/Update/Read carry decision_id. Derived read-model fields, no new write routes (rule 5): - TaskRead.wait_kind: external | human | both | unqualified, null unless status is wait (dependency rows from this task / needs_human). - WorkplanRead.blocked_kind: human | external | none, null unless status is blocked. Human wins over external. Both come from api/services/wait_kind.py, applied on GET /tasks/, GET /tasks/{id}, GET /workplans/ and GET /workplans/{id}. The identifier-migration reference-count proof now includes from_task_id (FK count guard 22 -> 23). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: sonnet Assistant-Process: 237582@bnt-lap001 Assistant-Session: f2b3d9f1-8fb9-4b9c-bc2b-837ec5dfc826
2026-09-28 22:39:18 +02:00
from_task_id: uuid.UUID | None = None
relationship_type: str = "blocks"
description: str | None = None
class WorkplanDependencyRead(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: uuid.UUID
from_workplan_id: uuid.UUID
CUST-WP-0074-T02: task wait qualifiers in the hub read model Adds tasks.decision_id and workplan_dependencies.from_task_id (migration e8f9a0b1c2d3). A from_task_id column is needed rather than from_workplan_id plus description: the partial unique indexes key on (from_workplan_id, target, relationship_type), so two tasks in one workplan waiting on the same target could not both be indexed, and the derived wait_kind needs per-task attribution. The two existing unique indexes are narrowed to frontmatter edges (from_task_id IS NULL) and two task-origin counterparts are added; downgrade deletes task-origin rows before restoring the old indexes. POST /workplans/{id}/dependencies/ accepts from_task_id (must belong to the from workplan; a task cannot depend on itself). TaskCreate/Update/Read carry decision_id. Derived read-model fields, no new write routes (rule 5): - TaskRead.wait_kind: external | human | both | unqualified, null unless status is wait (dependency rows from this task / needs_human). - WorkplanRead.blocked_kind: human | external | none, null unless status is blocked. Human wins over external. Both come from api/services/wait_kind.py, applied on GET /tasks/, GET /tasks/{id}, GET /workplans/ and GET /workplans/{id}. The identifier-migration reference-count proof now includes from_task_id (FK count guard 22 -> 23). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: sonnet Assistant-Process: 237582@bnt-lap001 Assistant-Session: f2b3d9f1-8fb9-4b9c-bc2b-837ec5dfc826
2026-09-28 22:39:18 +02:00
from_task_id: uuid.UUID | None = None
to_workplan_id: uuid.UUID | None = None
to_task_id: uuid.UUID | None = None
relationship_type: str
description: str | None = None
created_at: datetime
updated_at: datetime
class WorkplanDepStub(BaseModel):
"""Minimal projection of the other end of a dependency edge."""
dep_id: uuid.UUID
target_type: str = "workplan"
relationship_type: str = "blocks"
workplan_id: uuid.UUID | None = Field(
default=None,
validation_alias=AliasChoices("workplan_id", "workstream_id"),
)
workplan_slug: str | None = Field(
default=None,
validation_alias=AliasChoices("workplan_slug", "workstream_slug"),
)
workplan_title: str | None = Field(
default=None,
validation_alias=AliasChoices("workplan_title", "workstream_title"),
)
task_id: uuid.UUID | None = None
task_title: str | None = None
description: str | None = None
@computed_field # type: ignore[prop-decorator]
@property
def workstream_id(self) -> uuid.UUID | None:
return self.workplan_id
@computed_field # type: ignore[prop-decorator]
@property
def workstream_slug(self) -> str | None:
return self.workplan_slug
@computed_field # type: ignore[prop-decorator]
@property
def workstream_title(self) -> str | None:
return self.workplan_title