generated from coulomb/repo-seed
feat(classification-spine): implement STATE-WP-0065 repo-anchored model
Replace the ad-hoc coordination-domain spine with the Repo Classification Standard: 14 market domains, classification columns on managed_repos, and workplans anchored by repo_id (topic_id optional). - Add Alembic migration d8e9f0a1b2c3 with data backfill and workstream→workplan rename - Add api/classification.py validation and register-from-classification tooling - Expose workplan-first REST/MCP surface with legacy workstream aliases - Add C-24 consistency rule and legacy domain frontmatter mapping - Update dashboard repos page with category/capability/stake filters - Update orientation docs; mark STATE-WP-0065 finished
This commit is contained in:
@@ -4,6 +4,8 @@ from api.models.domain_goal import DomainGoal, DomainGoalStatus
|
||||
from api.models.topic import Topic, TopicStatus
|
||||
from api.models.managed_repo import ManagedRepo
|
||||
from api.models.repo_goal import RepoGoal, RepoGoalStatus
|
||||
from api.models.workplan import Workplan
|
||||
from api.models.workplan_dependency import WorkplanDependency
|
||||
from api.models.workstream import Workstream
|
||||
from api.models.workstream_dependency import WorkstreamDependency
|
||||
from api.models.task import Task, TaskStatus, TaskPriority
|
||||
@@ -39,6 +41,8 @@ __all__ = [
|
||||
"Topic", "TopicStatus",
|
||||
"ManagedRepo",
|
||||
"RepoGoal", "RepoGoalStatus",
|
||||
"Workplan",
|
||||
"WorkplanDependency",
|
||||
"Workstream",
|
||||
"WorkstreamDependency",
|
||||
"Task", "TaskStatus", "TaskPriority",
|
||||
@@ -61,4 +65,4 @@ __all__ = [
|
||||
"WorkplanLaunchRequest",
|
||||
"FabricGraphImport", "FabricGraphNode", "FabricGraphEdge",
|
||||
"LegacyInterface", "LegacyInterfaceUsageBucket",
|
||||
]
|
||||
]
|
||||
@@ -31,9 +31,9 @@ class CapabilityRequest(Base, TimestampMixin):
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
requesting_workstream_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
requesting_workplan_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("workstreams.id", ondelete="SET NULL"),
|
||||
ForeignKey("workplans.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
)
|
||||
requesting_agent: Mapped[str] = mapped_column(String(100), nullable=False)
|
||||
@@ -45,9 +45,9 @@ class CapabilityRequest(Base, TimestampMixin):
|
||||
nullable=True,
|
||||
index=True,
|
||||
)
|
||||
fulfilling_workstream_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
fulfilling_workplan_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("workstreams.id", ondelete="SET NULL"),
|
||||
ForeignKey("workplans.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
)
|
||||
fulfilling_agent: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
|
||||
@@ -47,8 +47,8 @@ class Contribution(Base, TimestampMixin):
|
||||
related_topic_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("topics.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
related_workstream_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("workstreams.id", ondelete="SET NULL"), nullable=True
|
||||
related_workplan_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("workplans.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
repo_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("managed_repos.id", ondelete="SET NULL"), nullable=True
|
||||
@@ -62,5 +62,5 @@ class Contribution(Base, TimestampMixin):
|
||||
notes: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
|
||||
topic: Mapped["Topic"] = relationship("Topic", lazy="selectin") # noqa: F821
|
||||
workstream: Mapped["Workstream"] = relationship("Workstream", lazy="selectin") # noqa: F821
|
||||
workplan: Mapped["Workplan"] = relationship("Workplan", lazy="selectin") # noqa: F821
|
||||
repo: Mapped["ManagedRepo"] = relationship("ManagedRepo", lazy="selectin") # noqa: F821
|
||||
|
||||
@@ -25,8 +25,8 @@ class Decision(Base, TimestampMixin):
|
||||
__tablename__ = "decisions"
|
||||
__table_args__ = (
|
||||
CheckConstraint(
|
||||
"topic_id IS NOT NULL OR workstream_id IS NOT NULL",
|
||||
name="ck_decisions_topic_or_workstream",
|
||||
"topic_id IS NOT NULL OR workplan_id IS NOT NULL",
|
||||
name="ck_decisions_topic_or_workplan",
|
||||
),
|
||||
)
|
||||
|
||||
@@ -36,8 +36,8 @@ class Decision(Base, TimestampMixin):
|
||||
topic_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("topics.id", ondelete="RESTRICT"), nullable=True, index=True
|
||||
)
|
||||
workstream_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("workstreams.id", ondelete="RESTRICT"), nullable=True, index=True
|
||||
workplan_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("workplans.id", ondelete="RESTRICT"), nullable=True, index=True
|
||||
)
|
||||
title: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
@@ -57,7 +57,7 @@ class Decision(Base, TimestampMixin):
|
||||
)
|
||||
|
||||
topic: Mapped["Topic | None"] = relationship("Topic", back_populates="decisions") # noqa: F821
|
||||
workstream: Mapped["Workstream | None"] = relationship("Workstream", back_populates="decisions") # noqa: F821
|
||||
workplan: Mapped["Workplan | None"] = relationship("Workplan", back_populates="decisions") # noqa: F821
|
||||
progress_events: Mapped[list["ProgressEvent"]] = relationship( # noqa: F821
|
||||
"ProgressEvent", back_populates="decision", lazy="selectin"
|
||||
)
|
||||
|
||||
@@ -44,13 +44,13 @@ class ExtensionPoint(Base, TimestampMixin):
|
||||
topic_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("topics.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
workstream_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("workstreams.id", ondelete="SET NULL"), nullable=True
|
||||
workplan_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("workplans.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
|
||||
domain: Mapped["Domain"] = relationship("Domain", lazy="selectin") # noqa: F821
|
||||
topic: Mapped["Topic"] = relationship("Topic", lazy="selectin") # noqa: F821
|
||||
workstream: Mapped["Workstream"] = relationship("Workstream", lazy="selectin") # noqa: F821
|
||||
workplan: Mapped["Workplan"] = relationship("Workplan", lazy="selectin") # noqa: F821
|
||||
|
||||
@property
|
||||
def domain_slug(self) -> str:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from datetime import date, datetime
|
||||
|
||||
from sqlalchemy import DateTime, ForeignKey, String, Text
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
from sqlalchemy import Date, DateTime, ForeignKey, String, Text
|
||||
from sqlalchemy.dialects.postgresql import ARRAY, JSONB, UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from api.models.base import Base, TimestampMixin, new_uuid
|
||||
@@ -36,6 +36,15 @@ class ManagedRepo(Base, TimestampMixin):
|
||||
DateTime(timezone=True), nullable=True
|
||||
)
|
||||
|
||||
category: Mapped[str | None] = mapped_column(String(50), nullable=True)
|
||||
secondary_domains: Mapped[list[str] | None] = mapped_column(ARRAY(Text), nullable=True)
|
||||
capability_tags: Mapped[list[str] | None] = mapped_column(ARRAY(Text), nullable=True)
|
||||
business_stake: Mapped[list[str] | None] = mapped_column(ARRAY(Text), nullable=True)
|
||||
business_mechanics: Mapped[list[str] | None] = mapped_column(ARRAY(Text), nullable=True)
|
||||
classified_at: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
classified_by: Mapped[str | None] = mapped_column(String(50), nullable=True)
|
||||
standard_version: Mapped[str | None] = mapped_column(String(20), nullable=True)
|
||||
|
||||
domain: Mapped["Domain"] = relationship( # noqa: F821
|
||||
"Domain", back_populates="repos", lazy="selectin"
|
||||
)
|
||||
|
||||
@@ -19,8 +19,8 @@ class ProgressEvent(Base):
|
||||
topic_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("topics.id", ondelete="RESTRICT"), nullable=True, index=True
|
||||
)
|
||||
workstream_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("workstreams.id", ondelete="RESTRICT"), nullable=True, index=True
|
||||
workplan_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("workplans.id", ondelete="RESTRICT"), nullable=True, index=True
|
||||
)
|
||||
task_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("tasks.id", ondelete="RESTRICT"), nullable=True, index=True
|
||||
@@ -38,6 +38,6 @@ class ProgressEvent(Base):
|
||||
)
|
||||
|
||||
topic: Mapped["Topic | None"] = relationship("Topic", back_populates="progress_events") # noqa: F821
|
||||
workstream: Mapped["Workstream | None"] = relationship("Workstream", back_populates="progress_events") # noqa: F821
|
||||
workplan: Mapped["Workplan | None"] = relationship("Workplan", back_populates="progress_events") # noqa: F821
|
||||
task: Mapped["Task | None"] = relationship("Task", back_populates="progress_events") # noqa: F821
|
||||
decision: Mapped["Decision | None"] = relationship("Decision", back_populates="progress_events") # noqa: F821
|
||||
|
||||
@@ -40,8 +40,8 @@ class RepoGoal(Base, TimestampMixin):
|
||||
domain_goal: Mapped["DomainGoal"] = relationship( # noqa: F821
|
||||
"DomainGoal", back_populates="repo_goals", lazy="selectin"
|
||||
)
|
||||
workstreams: Mapped[list["Workstream"]] = relationship( # noqa: F821
|
||||
"Workstream", back_populates="repo_goal", lazy="selectin"
|
||||
workplans: Mapped[list["Workplan"]] = relationship( # noqa: F821
|
||||
"Workplan", back_populates="repo_goal", lazy="selectin"
|
||||
)
|
||||
|
||||
@property
|
||||
|
||||
@@ -30,8 +30,8 @@ class Task(Base, TimestampMixin):
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True), primary_key=True, default=new_uuid
|
||||
)
|
||||
workstream_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("workstreams.id", ondelete="RESTRICT"), nullable=False, index=True
|
||||
workplan_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("workplans.id", ondelete="RESTRICT"), nullable=False, index=True
|
||||
)
|
||||
title: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
@@ -50,7 +50,7 @@ class Task(Base, TimestampMixin):
|
||||
UUID(as_uuid=True), ForeignKey("tasks.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
|
||||
workstream: Mapped["Workstream"] = relationship("Workstream", back_populates="tasks") # noqa: F821
|
||||
workplan: Mapped["Workplan"] = relationship("Workplan", back_populates="tasks") # noqa: F821
|
||||
subtasks: Mapped[list["Task"]] = relationship(
|
||||
"Task", foreign_keys=[parent_task_id], lazy="selectin"
|
||||
)
|
||||
|
||||
@@ -76,13 +76,13 @@ class TechnicalDebt(Base, TimestampMixin):
|
||||
topic_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("topics.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
workstream_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("workstreams.id", ondelete="SET NULL"), nullable=True
|
||||
workplan_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("workplans.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
|
||||
domain: Mapped["Domain"] = relationship("Domain", lazy="selectin") # noqa: F821
|
||||
topic: Mapped["Topic"] = relationship("Topic", lazy="selectin") # noqa: F821
|
||||
workstream: Mapped["Workstream"] = relationship("Workstream", lazy="selectin") # noqa: F821
|
||||
workplan: Mapped["Workplan"] = relationship("Workplan", lazy="selectin") # noqa: F821
|
||||
notes: Mapped[list["TDNote"]] = relationship(
|
||||
"TDNote", back_populates="td", lazy="selectin",
|
||||
order_by="TDNote.created_at",
|
||||
|
||||
@@ -27,8 +27,8 @@ class TokenEvent(Base):
|
||||
task_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("tasks.id", ondelete="SET NULL"), nullable=True, index=True
|
||||
)
|
||||
workstream_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("workstreams.id", ondelete="SET NULL"), nullable=True, index=True
|
||||
workplan_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("workplans.id", ondelete="SET NULL"), nullable=True, index=True
|
||||
)
|
||||
repo_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("managed_repos.id", ondelete="SET NULL"), nullable=True, index=True
|
||||
@@ -75,5 +75,5 @@ class TokenEvent(Base):
|
||||
)
|
||||
|
||||
task: Mapped["Task | None"] = relationship("Task", lazy="selectin") # noqa: F821
|
||||
workstream: Mapped["Workstream | None"] = relationship("Workstream", lazy="selectin") # noqa: F821
|
||||
workplan: Mapped["Workplan | None"] = relationship("Workplan", lazy="selectin") # noqa: F821
|
||||
repo: Mapped["ManagedRepo | None"] = relationship("ManagedRepo", lazy="selectin") # noqa: F821
|
||||
|
||||
@@ -36,8 +36,8 @@ class Topic(Base, TimestampMixin):
|
||||
domain: Mapped["Domain"] = relationship( # noqa: F821
|
||||
"Domain", back_populates="topics", lazy="selectin"
|
||||
)
|
||||
workstreams: Mapped[list["Workstream"]] = relationship( # noqa: F821
|
||||
"Workstream", back_populates="topic", lazy="selectin"
|
||||
workplans: Mapped[list["Workplan"]] = relationship( # noqa: F821
|
||||
"Workplan", back_populates="topic", lazy="selectin"
|
||||
)
|
||||
decisions: Mapped[list["Decision"]] = relationship( # noqa: F821
|
||||
"Decision", back_populates="topic", lazy="selectin"
|
||||
|
||||
70
api/models/workplan.py
Normal file
70
api/models/workplan.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import uuid
|
||||
from datetime import date, datetime
|
||||
|
||||
from sqlalchemy import Date, DateTime, ForeignKey, Integer, String, Text
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from api.models.base import Base, TimestampMixin, new_uuid
|
||||
|
||||
|
||||
class Workplan(Base, TimestampMixin):
|
||||
__tablename__ = "workplans"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True), primary_key=True, default=new_uuid
|
||||
)
|
||||
topic_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("topics.id", ondelete="RESTRICT"), nullable=True, index=True
|
||||
)
|
||||
slug: Mapped[str] = mapped_column(String(100), unique=True, nullable=False, index=True)
|
||||
title: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
status: Mapped[str] = mapped_column(
|
||||
String(20), nullable=False, default="active", server_default="active"
|
||||
)
|
||||
owner: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
due_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
planning_priority: Mapped[str | None] = mapped_column(String(20), nullable=True, index=True)
|
||||
planning_order: Mapped[int | None] = mapped_column(Integer, nullable=True, index=True)
|
||||
execution_state: Mapped[str] = mapped_column(
|
||||
String(20), nullable=False, default="manual", server_default="manual", index=True
|
||||
)
|
||||
launch_mode: Mapped[str] = mapped_column(
|
||||
String(20), nullable=False, default="manual", server_default="manual", index=True
|
||||
)
|
||||
concurrency_mode: Mapped[str] = mapped_column(
|
||||
String(20), nullable=False, default="sequential", server_default="sequential", index=True
|
||||
)
|
||||
queue_rank: Mapped[int | None] = mapped_column(Integer, nullable=True, index=True)
|
||||
execution_group: Mapped[str | None] = mapped_column(String(100), nullable=True, index=True)
|
||||
scheduled_for: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, index=True)
|
||||
|
||||
repo_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("managed_repos.id", ondelete="RESTRICT"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
repo_goal_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("repo_goals.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
index=True,
|
||||
)
|
||||
|
||||
topic: Mapped["Topic | None"] = relationship("Topic", back_populates="workplans") # noqa: F821
|
||||
repo: Mapped["ManagedRepo"] = relationship("ManagedRepo", lazy="selectin") # noqa: F821
|
||||
repo_goal: Mapped["RepoGoal"] = relationship("RepoGoal", back_populates="workplans", lazy="selectin") # noqa: F821
|
||||
tasks: Mapped[list["Task"]] = relationship( # noqa: F821
|
||||
"Task", back_populates="workplan", lazy="selectin"
|
||||
)
|
||||
decisions: Mapped[list["Decision"]] = relationship( # noqa: F821
|
||||
"Decision", back_populates="workplan", lazy="selectin"
|
||||
)
|
||||
progress_events: Mapped[list["ProgressEvent"]] = relationship( # noqa: F821
|
||||
"ProgressEvent", back_populates="workplan", lazy="selectin"
|
||||
)
|
||||
launch_requests: Mapped[list["WorkplanLaunchRequest"]] = relationship( # noqa: F821
|
||||
"WorkplanLaunchRequest", back_populates="workplan", lazy="selectin"
|
||||
)
|
||||
75
api/models/workplan_dependency.py
Normal file
75
api/models/workplan_dependency.py
Normal file
@@ -0,0 +1,75 @@
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import CheckConstraint, ForeignKey, Index, String, Text, text
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from api.models.base import Base, TimestampMixin, new_uuid
|
||||
|
||||
|
||||
class WorkplanDependency(Base, TimestampMixin):
|
||||
"""Directed dependency edge: `from_workplan` depends on a workplan or task.
|
||||
|
||||
Semantics: the target must reach a satisfactory state before `from_workplan`
|
||||
can fully proceed. Hard deletes are intentional —
|
||||
removing an edge removes a constraint, not information.
|
||||
"""
|
||||
|
||||
__tablename__ = "workplan_dependencies"
|
||||
__table_args__ = (
|
||||
CheckConstraint(
|
||||
"(to_workplan_id IS NOT NULL AND to_task_id IS NULL) "
|
||||
"OR (to_workplan_id IS NULL AND to_task_id IS NOT NULL)",
|
||||
name="ck_wp_dep_exactly_one_target",
|
||||
),
|
||||
Index(
|
||||
"uq_wp_dep_workplan_target",
|
||||
"from_workplan_id",
|
||||
"to_workplan_id",
|
||||
"relationship_type",
|
||||
unique=True,
|
||||
postgresql_where=text("to_workplan_id IS NOT NULL"),
|
||||
),
|
||||
Index(
|
||||
"uq_wp_dep_task_target",
|
||||
"from_workplan_id",
|
||||
"to_task_id",
|
||||
"relationship_type",
|
||||
unique=True,
|
||||
postgresql_where=text("to_task_id IS NOT NULL"),
|
||||
),
|
||||
)
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True), primary_key=True, default=new_uuid
|
||||
)
|
||||
from_workplan_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("workplans.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
to_workplan_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("workplans.id", ondelete="CASCADE"),
|
||||
nullable=True,
|
||||
index=True,
|
||||
)
|
||||
to_task_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("tasks.id", ondelete="CASCADE"),
|
||||
nullable=True,
|
||||
index=True,
|
||||
)
|
||||
relationship_type: Mapped[str] = mapped_column(
|
||||
String(40), nullable=False, default="blocks", server_default="blocks", index=True
|
||||
)
|
||||
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
|
||||
from_workplan: Mapped["Workplan"] = relationship( # noqa: F821
|
||||
"Workplan", foreign_keys=[from_workplan_id]
|
||||
)
|
||||
to_workplan: Mapped["Workplan | None"] = relationship( # noqa: F821
|
||||
"Workplan", foreign_keys=[to_workplan_id]
|
||||
)
|
||||
to_task: Mapped["Task | None"] = relationship("Task", foreign_keys=[to_task_id]) # noqa: F821
|
||||
@@ -13,9 +13,9 @@ class WorkplanLaunchRequest(Base, TimestampMixin):
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True), primary_key=True, default=new_uuid
|
||||
)
|
||||
workstream_id: Mapped[uuid.UUID] = mapped_column(
|
||||
workplan_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("workstreams.id", ondelete="CASCADE"),
|
||||
ForeignKey("workplans.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
@@ -36,4 +36,4 @@ class WorkplanLaunchRequest(Base, TimestampMixin):
|
||||
notes: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
request_metadata: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict, server_default="{}")
|
||||
|
||||
workstream: Mapped["Workstream"] = relationship("Workstream", back_populates="launch_requests") # noqa: F821
|
||||
workplan: Mapped["Workplan"] = relationship("Workplan", back_populates="launch_requests") # noqa: F821
|
||||
|
||||
@@ -1,70 +1,6 @@
|
||||
import uuid
|
||||
from datetime import date, datetime
|
||||
"""Backward-compatibility shim — prefer ``api.models.workplan``."""
|
||||
from api.models.workplan import Workplan
|
||||
|
||||
from sqlalchemy import Date, DateTime, ForeignKey, Integer, String, Text
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
Workstream = Workplan
|
||||
|
||||
from api.models.base import Base, TimestampMixin, new_uuid
|
||||
|
||||
|
||||
class Workstream(Base, TimestampMixin):
|
||||
__tablename__ = "workstreams"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True), primary_key=True, default=new_uuid
|
||||
)
|
||||
topic_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("topics.id", ondelete="RESTRICT"), nullable=False, index=True
|
||||
)
|
||||
slug: Mapped[str] = mapped_column(String(100), unique=True, nullable=False, index=True)
|
||||
title: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
status: Mapped[str] = mapped_column(
|
||||
String(20), nullable=False, default="active", server_default="active"
|
||||
)
|
||||
owner: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
due_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
planning_priority: Mapped[str | None] = mapped_column(String(20), nullable=True, index=True)
|
||||
planning_order: Mapped[int | None] = mapped_column(Integer, nullable=True, index=True)
|
||||
execution_state: Mapped[str] = mapped_column(
|
||||
String(20), nullable=False, default="manual", server_default="manual", index=True
|
||||
)
|
||||
launch_mode: Mapped[str] = mapped_column(
|
||||
String(20), nullable=False, default="manual", server_default="manual", index=True
|
||||
)
|
||||
concurrency_mode: Mapped[str] = mapped_column(
|
||||
String(20), nullable=False, default="sequential", server_default="sequential", index=True
|
||||
)
|
||||
queue_rank: Mapped[int | None] = mapped_column(Integer, nullable=True, index=True)
|
||||
execution_group: Mapped[str | None] = mapped_column(String(100), nullable=True, index=True)
|
||||
scheduled_for: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, index=True)
|
||||
|
||||
repo_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("managed_repos.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
index=True,
|
||||
)
|
||||
repo_goal_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("repo_goals.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
index=True,
|
||||
)
|
||||
|
||||
topic: Mapped["Topic"] = relationship("Topic", back_populates="workstreams") # noqa: F821
|
||||
repo: Mapped["ManagedRepo"] = relationship("ManagedRepo", lazy="selectin") # noqa: F821
|
||||
repo_goal: Mapped["RepoGoal"] = relationship("RepoGoal", back_populates="workstreams", lazy="selectin") # noqa: F821
|
||||
tasks: Mapped[list["Task"]] = relationship( # noqa: F821
|
||||
"Task", back_populates="workstream", lazy="selectin"
|
||||
)
|
||||
decisions: Mapped[list["Decision"]] = relationship( # noqa: F821
|
||||
"Decision", back_populates="workstream", lazy="selectin"
|
||||
)
|
||||
progress_events: Mapped[list["ProgressEvent"]] = relationship( # noqa: F821
|
||||
"ProgressEvent", back_populates="workstream", lazy="selectin"
|
||||
)
|
||||
launch_requests: Mapped[list["WorkplanLaunchRequest"]] = relationship( # noqa: F821
|
||||
"WorkplanLaunchRequest", back_populates="workstream", lazy="selectin"
|
||||
)
|
||||
__all__ = ["Workstream", "Workplan"]
|
||||
@@ -1,75 +1,6 @@
|
||||
import uuid
|
||||
"""Backward-compatibility shim — prefer ``api.models.workplan_dependency``."""
|
||||
from api.models.workplan_dependency import WorkplanDependency
|
||||
|
||||
from sqlalchemy import CheckConstraint, ForeignKey, Index, String, Text, text
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
WorkstreamDependency = WorkplanDependency
|
||||
|
||||
from api.models.base import Base, TimestampMixin, new_uuid
|
||||
|
||||
|
||||
class WorkstreamDependency(Base, TimestampMixin):
|
||||
"""Directed dependency edge: `from_workstream` depends on a workstream or task.
|
||||
|
||||
Semantics: the target must reach a satisfactory state before `from_workstream`
|
||||
can fully proceed. Hard deletes are intentional —
|
||||
removing an edge removes a constraint, not information.
|
||||
"""
|
||||
|
||||
__tablename__ = "workstream_dependencies"
|
||||
__table_args__ = (
|
||||
CheckConstraint(
|
||||
"(to_workstream_id IS NOT NULL AND to_task_id IS NULL) "
|
||||
"OR (to_workstream_id IS NULL AND to_task_id IS NOT NULL)",
|
||||
name="ck_ws_dep_exactly_one_target",
|
||||
),
|
||||
Index(
|
||||
"uq_ws_dep_workstream_target",
|
||||
"from_workstream_id",
|
||||
"to_workstream_id",
|
||||
"relationship_type",
|
||||
unique=True,
|
||||
postgresql_where=text("to_workstream_id IS NOT NULL"),
|
||||
),
|
||||
Index(
|
||||
"uq_ws_dep_task_target",
|
||||
"from_workstream_id",
|
||||
"to_task_id",
|
||||
"relationship_type",
|
||||
unique=True,
|
||||
postgresql_where=text("to_task_id IS NOT NULL"),
|
||||
),
|
||||
)
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True), primary_key=True, default=new_uuid
|
||||
)
|
||||
from_workstream_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("workstreams.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
to_workstream_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("workstreams.id", ondelete="CASCADE"),
|
||||
nullable=True,
|
||||
index=True,
|
||||
)
|
||||
to_task_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("tasks.id", ondelete="CASCADE"),
|
||||
nullable=True,
|
||||
index=True,
|
||||
)
|
||||
relationship_type: Mapped[str] = mapped_column(
|
||||
String(40), nullable=False, default="blocks", server_default="blocks", index=True
|
||||
)
|
||||
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
|
||||
from_workstream: Mapped["Workstream"] = relationship( # noqa: F821
|
||||
"Workstream", foreign_keys=[from_workstream_id]
|
||||
)
|
||||
to_workstream: Mapped["Workstream | None"] = relationship( # noqa: F821
|
||||
"Workstream", foreign_keys=[to_workstream_id]
|
||||
)
|
||||
to_task: Mapped["Task | None"] = relationship("Task", foreign_keys=[to_task_id]) # noqa: F821
|
||||
__all__ = ["WorkstreamDependency", "WorkplanDependency"]
|
||||
Reference in New Issue
Block a user