Index workplan flavor and omit residuals from default views.
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Build and Publish Multi-Context Image / build-and-push (push) Successful in 32s

STATE-WP-0092: persist flavor from files, treat depends_on as the C-20
canonical edge, exclude flavor=residual from summary/next_steps/deps
unless include_residuals is set. Live primary still needs the alembic
revision applied.

Assistant: grok
Assistant-Session: 01a09dc1-b21e-77e1-919e-fcad2f82b267
This commit is contained in:
tegwick 2026-09-14 15:28:58 +02:00
parent ea11451e9c
commit ddc3338541
18 changed files with 852 additions and 25 deletions

View file

@ -5,6 +5,12 @@ from typing import Literal
from pydantic import BaseModel, ConfigDict, field_validator
from api.schemas.workplan_dependency import WorkplanDepStub
from api.work_record_flavor import (
FLAVOR_PROMOTION_REASONS,
WORK_RECORD_FLAVORS,
normalize_flavor,
normalize_promotion_reason,
)
from api.workplan_status import normalize_workplan_status
WorkplanStatus = Literal[
@ -28,7 +34,42 @@ class WorkplanStatusMixin(BaseModel):
return normalize_workplan_status(value)
class WorkplanCreate(WorkplanStatusMixin):
class WorkplanFlavorMixin(BaseModel):
@field_validator("flavor", mode="before", check_fields=False)
@classmethod
def _normalise_flavor(cls, value):
flavor = normalize_flavor(value)
if flavor is not None and flavor not in WORK_RECORD_FLAVORS:
raise ValueError(
f"Unknown work-record flavor {flavor!r}; "
f"expected one of {', '.join(WORK_RECORD_FLAVORS)}"
)
return flavor
@field_validator("flavor_promotion_reason", mode="before", check_fields=False)
@classmethod
def _normalise_promotion_reason(cls, value):
reason = normalize_promotion_reason(value)
if reason is not None and reason not in FLAVOR_PROMOTION_REASONS:
raise ValueError(
f"Unknown flavor_promotion_reason {reason!r}; "
f"expected one of {', '.join(FLAVOR_PROMOTION_REASONS)}"
)
return reason
@field_validator("flavor_promoted_from", mode="before", check_fields=False)
@classmethod
def _normalise_promoted_from(cls, value):
flavor = normalize_flavor(value)
if flavor is not None and flavor not in WORK_RECORD_FLAVORS:
raise ValueError(
f"Unknown flavor_promoted_from {flavor!r}; "
f"expected one of {', '.join(WORK_RECORD_FLAVORS)}"
)
return flavor
class WorkplanCreate(WorkplanStatusMixin, WorkplanFlavorMixin):
id: uuid.UUID | None = None
repo_id: uuid.UUID
topic_id: uuid.UUID | None = None
@ -40,6 +81,9 @@ class WorkplanCreate(WorkplanStatusMixin):
due_date: date | None = None
planning_priority: str | None = None
planning_order: int | None = None
flavor: str | None = None
flavor_promotion_reason: str | None = None
flavor_promoted_from: str | None = None
execution_state: ExecutionState = "manual"
launch_mode: LaunchMode = "manual"
concurrency_mode: ConcurrencyMode = "sequential"
@ -49,7 +93,7 @@ class WorkplanCreate(WorkplanStatusMixin):
repo_goal_id: uuid.UUID | None = None
class WorkplanUpdate(WorkplanStatusMixin):
class WorkplanUpdate(WorkplanStatusMixin, WorkplanFlavorMixin):
title: str | None = None
description: str | None = None
status: WorkplanStatus | None = None
@ -57,6 +101,9 @@ class WorkplanUpdate(WorkplanStatusMixin):
due_date: date | None = None
planning_priority: str | None = None
planning_order: int | None = None
flavor: str | None = None
flavor_promotion_reason: str | None = None
flavor_promoted_from: str | None = None
execution_state: ExecutionState | None = None
launch_mode: LaunchMode | None = None
concurrency_mode: ConcurrencyMode | None = None
@ -95,6 +142,9 @@ class WorkplanRead(WorkplanStatusMixin):
due_date: date | None = None
planning_priority: str | None = None
planning_order: int | None = None
flavor: str | None = None
flavor_promotion_reason: str | None = None
flavor_promoted_from: str | None = None
execution_state: ExecutionState = "manual"
launch_mode: LaunchMode = "manual"
concurrency_mode: ConcurrencyMode = "sequential"