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:
@@ -7,8 +7,8 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from api.database import get_session
|
||||
from api.models.task import Task, TaskStatus
|
||||
from api.models.workplan_launch_request import WorkplanLaunchRequest
|
||||
from api.models.workstream import Workstream
|
||||
from api.models.workstream_dependency import WorkstreamDependency
|
||||
from api.models.workplan import Workplan
|
||||
from api.models.workplan_dependency import WorkplanDependency
|
||||
from api.schemas.execution import (
|
||||
ExecutionIntentRead,
|
||||
ExecutionIntentUpdate,
|
||||
@@ -25,10 +25,10 @@ from api.services.execution_queue import (
|
||||
STATE_HUB_RESPONSIBILITIES,
|
||||
execution_state_for_launch,
|
||||
queue_sort_key,
|
||||
workstream_blockers,
|
||||
workplan_blockers,
|
||||
)
|
||||
from api.routers.workstreams import _legacy_key, _meter_legacy_route
|
||||
from api.workplan_status import CLOSED_WORKSTREAM_STATUSES, normalize_workstream_status
|
||||
from api.workplan_status import CLOSED_WORKPLAN_STATUSES, normalize_workplan_status
|
||||
|
||||
router = APIRouter(prefix="/execution", tags=["execution"])
|
||||
|
||||
@@ -50,7 +50,7 @@ async def _update_execution_intent(
|
||||
body: ExecutionIntentUpdate,
|
||||
session: AsyncSession,
|
||||
) -> ExecutionIntentRead:
|
||||
ws = await session.get(Workstream, workstream_id)
|
||||
ws = await session.get(Workplan, workstream_id)
|
||||
if ws is None:
|
||||
raise HTTPException(status_code=404, detail="Workplan not found")
|
||||
|
||||
@@ -94,22 +94,22 @@ async def workplan_stack(
|
||||
include_blocked: bool = Query(True),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> list[WorkplanQueueItem]:
|
||||
result = await session.execute(select(Workstream))
|
||||
result = await session.execute(select(Workplan))
|
||||
workstreams = [
|
||||
ws for ws in result.scalars().all()
|
||||
if normalize_workstream_status(ws.status) not in CLOSED_WORKSTREAM_STATUSES
|
||||
if normalize_workplan_status(ws.status) not in CLOSED_WORKPLAN_STATUSES
|
||||
]
|
||||
ws_by_id = {ws.id: ws for ws in workstreams}
|
||||
ws_status = {ws.id: normalize_workstream_status(ws.status) for ws in workstreams}
|
||||
ws_status = {ws.id: normalize_workplan_status(ws.status) for ws in workstreams}
|
||||
|
||||
dep_result = await session.execute(select(WorkstreamDependency))
|
||||
dep_result = await session.execute(select(WorkplanDependency))
|
||||
ws_deps: dict[uuid.UUID, list[uuid.UUID]] = {}
|
||||
task_deps: dict[uuid.UUID, list[uuid.UUID]] = {}
|
||||
for dep in dep_result.scalars().all():
|
||||
if dep.to_workstream_id is not None:
|
||||
ws_deps.setdefault(dep.from_workstream_id, []).append(dep.to_workstream_id)
|
||||
if dep.to_workplan_id is not None:
|
||||
ws_deps.setdefault(dep.from_workplan_id, []).append(dep.to_workplan_id)
|
||||
if dep.to_task_id is not None:
|
||||
task_deps.setdefault(dep.from_workstream_id, []).append(dep.to_task_id)
|
||||
task_deps.setdefault(dep.from_workplan_id, []).append(dep.to_task_id)
|
||||
|
||||
task_ids = [task_id for ids in task_deps.values() for task_id in ids]
|
||||
task_status: dict[uuid.UUID, str] = {}
|
||||
@@ -121,9 +121,9 @@ async def workplan_stack(
|
||||
for ws in workstreams:
|
||||
if not include_manual and ws.execution_state == "manual":
|
||||
continue
|
||||
lifecycle_status = normalize_workstream_status(ws.status)
|
||||
lifecycle_status = normalize_workplan_status(ws.status)
|
||||
blocked_ws = [
|
||||
blocker for blocker in workstream_blockers(ws.id, ws_deps, ws_status)
|
||||
blocker for blocker in workplan_blockers(ws.id, ws_deps, ws_status)
|
||||
if blocker in ws_by_id or blocker in ws_status
|
||||
]
|
||||
blocked_tasks = [
|
||||
@@ -135,7 +135,7 @@ async def workplan_stack(
|
||||
continue
|
||||
sort_key = queue_sort_key(ws, eligible=eligible)
|
||||
items.append(WorkplanQueueItem(
|
||||
workstream_id=ws.id,
|
||||
workplan_id=ws.id,
|
||||
slug=ws.slug,
|
||||
title=ws.title,
|
||||
status=lifecycle_status,
|
||||
@@ -149,7 +149,7 @@ async def workplan_stack(
|
||||
execution_group=ws.execution_group,
|
||||
scheduled_for=ws.scheduled_for,
|
||||
eligible=eligible,
|
||||
blocked_by_workstream_ids=blocked_ws,
|
||||
blocked_by_workplan_ids=blocked_ws,
|
||||
blocked_by_task_ids=blocked_tasks,
|
||||
sort_key=sort_key,
|
||||
))
|
||||
@@ -165,12 +165,12 @@ async def create_launch_request(
|
||||
body: LaunchRequestCreate,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> WorkplanLaunchRequest:
|
||||
ws = await session.get(Workstream, body.workstream_id)
|
||||
ws = await session.get(Workplan, body.workplan_id)
|
||||
if ws is None:
|
||||
raise HTTPException(status_code=404, detail="Workstream not found")
|
||||
raise HTTPException(status_code=404, detail="Workplan not found")
|
||||
|
||||
launch_request = WorkplanLaunchRequest(
|
||||
workstream_id=ws.id,
|
||||
workplan_id=ws.id,
|
||||
requested_by=body.requested_by,
|
||||
requested_actor=body.requested_actor,
|
||||
launch_mode=body.launch_mode,
|
||||
@@ -199,16 +199,16 @@ async def list_launch_requests(
|
||||
) -> list[WorkplanLaunchRequest]:
|
||||
q = select(WorkplanLaunchRequest).order_by(WorkplanLaunchRequest.created_at.desc())
|
||||
if workstream_id:
|
||||
q = q.where(WorkplanLaunchRequest.workstream_id == workstream_id)
|
||||
q = q.where(WorkplanLaunchRequest.workplan_id == workstream_id)
|
||||
if request_status:
|
||||
q = q.where(WorkplanLaunchRequest.status == request_status)
|
||||
result = await session.execute(q)
|
||||
return list(result.scalars().all())
|
||||
|
||||
|
||||
def _intent_read(ws: Workstream) -> ExecutionIntentRead:
|
||||
def _intent_read(ws: Workplan) -> ExecutionIntentRead:
|
||||
return ExecutionIntentRead(
|
||||
workstream_id=ws.id,
|
||||
workplan_id=ws.id,
|
||||
execution_state=ws.execution_state,
|
||||
launch_mode=ws.launch_mode,
|
||||
concurrency_mode=ws.concurrency_mode,
|
||||
|
||||
Reference in New Issue
Block a user