generated from coulomb/repo-seed
Normalize workplan IDs and activate parents on task start
This commit is contained in:
@@ -28,7 +28,7 @@ Current state:
|
|||||||
New State Hub-local workplans should use the prefix:
|
New State Hub-local workplans should use the prefix:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
SHUB-WP-0001
|
STATE-WP-0001
|
||||||
```
|
```
|
||||||
|
|
||||||
Legacy Custodian-hosted State Hub plans, such as `CUST-WP-0042`, may retain
|
Legacy Custodian-hosted State Hub plans, such as `CUST-WP-0042`, may retain
|
||||||
|
|||||||
2
SCOPE.md
2
SCOPE.md
@@ -40,7 +40,7 @@ embedded path in `the-custodian` is now a pointer only.
|
|||||||
|
|
||||||
## Workplan Convention
|
## Workplan Convention
|
||||||
|
|
||||||
New State Hub-local workplans use `SHUB-WP-####`.
|
New State Hub-local workplans use `STATE-WP-####`.
|
||||||
|
|
||||||
Migrated legacy State Hub workplans may temporarily retain `CUST-WP-####`
|
Migrated legacy State Hub workplans may temporarily retain `CUST-WP-####`
|
||||||
identifiers when preserving existing State Hub workstream and task IDs is the
|
identifiers when preserving existing State Hub workstream and task IDs is the
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from api.models.task import Task, TaskStatus
|
|||||||
from api.models.token_event import TokenEvent
|
from api.models.token_event import TokenEvent
|
||||||
from api.models.workstream import Workstream
|
from api.models.workstream import Workstream
|
||||||
from api.schemas.task import TaskCreate, TaskRead, TaskUpdate
|
from api.schemas.task import TaskCreate, TaskRead, TaskUpdate
|
||||||
|
from api.services.lifecycle import activate_parent_for_task_start, status_value
|
||||||
|
|
||||||
router = APIRouter(prefix="/tasks", tags=["tasks"])
|
router = APIRouter(prefix="/tasks", tags=["tasks"])
|
||||||
|
|
||||||
@@ -49,6 +50,13 @@ async def create_task(
|
|||||||
) -> Task:
|
) -> Task:
|
||||||
task = Task(**body.model_dump())
|
task = Task(**body.model_dump())
|
||||||
session.add(task)
|
session.add(task)
|
||||||
|
if status_value(task.status) == "in_progress":
|
||||||
|
ws = await session.get(Workstream, task.workstream_id)
|
||||||
|
activate_parent_for_task_start(
|
||||||
|
previous_task_status="todo",
|
||||||
|
new_task_status=task.status,
|
||||||
|
parent_workstream=ws,
|
||||||
|
)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
await session.refresh(task)
|
await session.refresh(task)
|
||||||
return task
|
return task
|
||||||
@@ -75,7 +83,7 @@ async def update_task(
|
|||||||
if task is None:
|
if task is None:
|
||||||
raise HTTPException(status_code=404, detail="Task not found")
|
raise HTTPException(status_code=404, detail="Task not found")
|
||||||
|
|
||||||
previous_status = task.status.value
|
previous_status = status_value(task.status)
|
||||||
|
|
||||||
# Separate token fields from task fields
|
# Separate token fields from task fields
|
||||||
token_field_names = {
|
token_field_names = {
|
||||||
@@ -92,15 +100,22 @@ async def update_task(
|
|||||||
update_data = body.model_dump(exclude_unset=True)
|
update_data = body.model_dump(exclude_unset=True)
|
||||||
token_data = {k: update_data.pop(k) for k in list(update_data.keys()) if k in token_field_names}
|
token_data = {k: update_data.pop(k) for k in list(update_data.keys()) if k in token_field_names}
|
||||||
suppress_token_event = bool(token_data.pop("suppress_token_event", False))
|
suppress_token_event = bool(token_data.pop("suppress_token_event", False))
|
||||||
|
status_update = update_data.get("status")
|
||||||
|
new_status = status_value(status_update) if status_update is not None else None
|
||||||
|
|
||||||
for field, value in update_data.items():
|
for field, value in update_data.items():
|
||||||
setattr(task, field, value)
|
setattr(task, field, value)
|
||||||
|
if new_status is not None:
|
||||||
|
ws = await session.get(Workstream, task.workstream_id)
|
||||||
|
activate_parent_for_task_start(
|
||||||
|
previous_task_status=previous_status,
|
||||||
|
new_task_status=new_status,
|
||||||
|
parent_workstream=ws,
|
||||||
|
)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
await session.refresh(task)
|
await session.refresh(task)
|
||||||
|
|
||||||
# Token event — three-tier logic, only for an intentional transition to done.
|
# Token event — three-tier logic, only for an intentional transition to done.
|
||||||
status_update = update_data.get("status")
|
|
||||||
new_status = status_update.value if hasattr(status_update, "value") else status_update
|
|
||||||
if (
|
if (
|
||||||
new_status == "done"
|
new_status == "done"
|
||||||
and previous_status != "done"
|
and previous_status != "done"
|
||||||
|
|||||||
50
api/services/lifecycle.py
Normal file
50
api/services/lifecycle.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from api.workplan_status import normalize_workstream_status
|
||||||
|
|
||||||
|
|
||||||
|
TASK_STARTED_STATUS = "in_progress"
|
||||||
|
TASK_NOT_STARTED_STATUS = "todo"
|
||||||
|
PARENT_ACTIVATION_STATUSES = {"proposed", "ready", "backlog"}
|
||||||
|
|
||||||
|
|
||||||
|
def status_value(status: Any) -> str:
|
||||||
|
if hasattr(status, "value"):
|
||||||
|
status = status.value
|
||||||
|
return str(status or "").strip().lower()
|
||||||
|
|
||||||
|
|
||||||
|
def should_activate_parent_for_task_start(
|
||||||
|
*,
|
||||||
|
previous_task_status: Any,
|
||||||
|
new_task_status: Any,
|
||||||
|
parent_workstream_status: Any,
|
||||||
|
) -> bool:
|
||||||
|
"""Return whether a task start should move its parent to active."""
|
||||||
|
return (
|
||||||
|
status_value(previous_task_status) == TASK_NOT_STARTED_STATUS
|
||||||
|
and status_value(new_task_status) == TASK_STARTED_STATUS
|
||||||
|
and normalize_workstream_status(parent_workstream_status)
|
||||||
|
in PARENT_ACTIVATION_STATUSES
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def activate_parent_for_task_start(
|
||||||
|
*,
|
||||||
|
previous_task_status: Any,
|
||||||
|
new_task_status: Any,
|
||||||
|
parent_workstream: Any,
|
||||||
|
) -> bool:
|
||||||
|
"""Activate a planning-state parent workstream when real task work starts."""
|
||||||
|
if parent_workstream is None:
|
||||||
|
return False
|
||||||
|
if not should_activate_parent_for_task_start(
|
||||||
|
previous_task_status=previous_task_status,
|
||||||
|
new_task_status=new_task_status,
|
||||||
|
parent_workstream_status=getattr(parent_workstream, "status", None),
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
parent_workstream.status = "active"
|
||||||
|
return True
|
||||||
@@ -3,13 +3,13 @@
|
|||||||
New workplans in this repository use:
|
New workplans in this repository use:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
SHUB-WP-0001-short-title.md
|
STATE-WP-0001-short-title.md
|
||||||
```
|
```
|
||||||
|
|
||||||
Workplan frontmatter should include:
|
Workplan frontmatter should include:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
id: SHUB-WP-0001
|
id: STATE-WP-0001
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "Short Title"
|
title: "Short Title"
|
||||||
domain: custodian
|
domain: custodian
|
||||||
|
|||||||
@@ -28,9 +28,9 @@ async def _create_topic(client, domain_slug="testdomain", slug="testtopic", titl
|
|||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
|
|
||||||
async def _create_workstream(client, topic_id, slug="test-ws", title="Test WS"):
|
async def _create_workstream(client, topic_id, slug="test-ws", title="Test WS", status="active"):
|
||||||
r = await client.post("/workstreams/", json={
|
r = await client.post("/workstreams/", json={
|
||||||
"topic_id": topic_id, "slug": slug, "title": title,
|
"topic_id": topic_id, "slug": slug, "title": title, "status": status,
|
||||||
})
|
})
|
||||||
assert r.status_code == 201, r.text
|
assert r.status_code == 201, r.text
|
||||||
return r.json()
|
return r.json()
|
||||||
@@ -213,6 +213,38 @@ class TestTasks:
|
|||||||
assert "High prio" in titles
|
assert "High prio" in titles
|
||||||
assert "Low prio" not in titles
|
assert "Low prio" not in titles
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("initial_status", ["proposed", "ready", "backlog"])
|
||||||
|
async def test_task_start_activates_planning_workstream(self, client, initial_status):
|
||||||
|
await _create_domain(client)
|
||||||
|
topic = await _create_topic(client)
|
||||||
|
ws = await _create_workstream(
|
||||||
|
client,
|
||||||
|
topic["id"],
|
||||||
|
slug=f"{initial_status}-ws",
|
||||||
|
status=initial_status,
|
||||||
|
)
|
||||||
|
task = await _create_task(client, ws["id"])
|
||||||
|
|
||||||
|
r = await client.patch(f"/tasks/{task['id']}", json={"status": "in_progress"})
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
r = await client.get(f"/workstreams/{ws['id']}")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert r.json()["status"] == "active"
|
||||||
|
|
||||||
|
async def test_task_start_does_not_unblock_blocked_workstream(self, client):
|
||||||
|
await _create_domain(client)
|
||||||
|
topic = await _create_topic(client)
|
||||||
|
ws = await _create_workstream(client, topic["id"], slug="blocked-ws", status="blocked")
|
||||||
|
task = await _create_task(client, ws["id"])
|
||||||
|
|
||||||
|
r = await client.patch(f"/tasks/{task['id']}", json={"status": "in_progress"})
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
r = await client.get(f"/workstreams/{ws['id']}")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert r.json()["status"] == "blocked"
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Decision tests
|
# Decision tests
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ Resolve these before T04/T05 can become live migration work:
|
|||||||
### T01 — Drill WSL2 State Hub backup restore
|
### T01 — Drill WSL2 State Hub backup restore
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: T01
|
id: CUST-WP-0011-T01
|
||||||
status: done
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "b0caf112-dc1d-43a8-9f27-d627dd4aa2bf"
|
state_hub_task_id: "b0caf112-dc1d-43a8-9f27-d627dd4aa2bf"
|
||||||
@@ -136,7 +136,7 @@ workstreams, 989 tasks, 1423 progress events, and 208 token events.
|
|||||||
### T02 — Align with Railiance deployment plan
|
### T02 — Align with Railiance deployment plan
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: T02
|
id: CUST-WP-0011-T02
|
||||||
status: done
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "24887dd9-7d50-4cc4-add7-bffa1454b80c"
|
state_hub_task_id: "24887dd9-7d50-4cc4-add7-bffa1454b80c"
|
||||||
@@ -167,7 +167,7 @@ deferred to `CUST-WP-0038`.
|
|||||||
### T03 — Build and publish State Hub container image
|
### T03 — Build and publish State Hub container image
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: T03
|
id: CUST-WP-0011-T03
|
||||||
status: in_progress
|
status: in_progress
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "79908ade-3e38-451b-a403-2361a16a3f3a"
|
state_hub_task_id: "79908ade-3e38-451b-a403-2361a16a3f3a"
|
||||||
@@ -216,7 +216,7 @@ pull the image from railiance01.
|
|||||||
### T04 — Define State Hub database and app manifests
|
### T04 — Define State Hub database and app manifests
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: T04
|
id: CUST-WP-0011-T04
|
||||||
status: todo
|
status: todo
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "a7baf2eb-abd7-4aa3-b2cb-a5370ac09844"
|
state_hub_task_id: "a7baf2eb-abd7-4aa3-b2cb-a5370ac09844"
|
||||||
@@ -238,7 +238,7 @@ boundaries are documented.
|
|||||||
### T05 — Deploy empty State Hub and run migrations on railiance01
|
### T05 — Deploy empty State Hub and run migrations on railiance01
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: T05
|
id: CUST-WP-0011-T05
|
||||||
status: todo
|
status: todo
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "a307dd46-a8e2-49df-b016-c187759ebcf1"
|
state_hub_task_id: "a307dd46-a8e2-49df-b016-c187759ebcf1"
|
||||||
@@ -261,7 +261,7 @@ Checks:
|
|||||||
### T06 — Restore WSL2 data copy into cluster and compare
|
### T06 — Restore WSL2 data copy into cluster and compare
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: T06
|
id: CUST-WP-0011-T06
|
||||||
status: todo
|
status: todo
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "03753b88-824c-4448-97b2-f7315d145060"
|
state_hub_task_id: "03753b88-824c-4448-97b2-f7315d145060"
|
||||||
@@ -286,7 +286,7 @@ writer.
|
|||||||
### T07 — Cut over private access to cluster State Hub
|
### T07 — Cut over private access to cluster State Hub
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: T07
|
id: CUST-WP-0011-T07
|
||||||
status: todo
|
status: todo
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "ff1de25e-c301-4b86-9420-84dfe72e565e"
|
state_hub_task_id: "ff1de25e-c301-4b86-9420-84dfe72e565e"
|
||||||
@@ -312,7 +312,7 @@ cluster State Hub, and WSL2 is no longer receiving normal writes.
|
|||||||
### T08 — Stabilise with WSL2 retained as fallback
|
### T08 — Stabilise with WSL2 retained as fallback
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: T08
|
id: CUST-WP-0011-T08
|
||||||
status: todo
|
status: todo
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "e06a59a0-5310-4c1c-9ba5-7cfaadda62e2"
|
state_hub_task_id: "e06a59a0-5310-4c1c-9ba5-7cfaadda62e2"
|
||||||
@@ -337,7 +337,7 @@ unresolved operational defects.
|
|||||||
### T09 — Document operating model and defer final WSL2 retirement
|
### T09 — Document operating model and defer final WSL2 retirement
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: T09
|
id: CUST-WP-0011-T09
|
||||||
status: todo
|
status: todo
|
||||||
priority: low
|
priority: low
|
||||||
state_hub_task_id: "d75a2d49-f3b1-4bdd-b9e1-a1c6a9744681"
|
state_hub_task_id: "d75a2d49-f3b1-4bdd-b9e1-a1c6a9744681"
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ keeps the ultimate target visible and reviewable.
|
|||||||
### T01 — Confirm ThreePhoenix cluster readiness
|
### T01 — Confirm ThreePhoenix cluster readiness
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: T01
|
id: CUST-WP-0038-T01
|
||||||
status: todo
|
status: todo
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "aa1bf291-3b6c-4940-a4f5-7680b0349110"
|
state_hub_task_id: "aa1bf291-3b6c-4940-a4f5-7680b0349110"
|
||||||
@@ -85,7 +85,7 @@ NotReady or operationally unknown.
|
|||||||
### T02 — Establish replicated storage/database strategy
|
### T02 — Establish replicated storage/database strategy
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: T02
|
id: CUST-WP-0038-T02
|
||||||
status: todo
|
status: todo
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "5575f244-5cef-47aa-a168-24027cd08140"
|
state_hub_task_id: "5575f244-5cef-47aa-a168-24027cd08140"
|
||||||
@@ -107,7 +107,7 @@ production data movement.
|
|||||||
### T03 — Implement HA State Hub database
|
### T03 — Implement HA State Hub database
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: T03
|
id: CUST-WP-0038-T03
|
||||||
status: todo
|
status: todo
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "5330fcc3-684b-49f6-8d28-ea8c929733d6"
|
state_hub_task_id: "5330fcc3-684b-49f6-8d28-ea8c929733d6"
|
||||||
@@ -130,7 +130,7 @@ namespace.
|
|||||||
### T04 — Add State Hub API high-availability behavior
|
### T04 — Add State Hub API high-availability behavior
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: T04
|
id: CUST-WP-0038-T04
|
||||||
status: todo
|
status: todo
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "64175ed0-af36-47ea-9401-74c4b15ffe24"
|
state_hub_task_id: "64175ed0-af36-47ea-9401-74c4b15ffe24"
|
||||||
@@ -150,7 +150,7 @@ Run State Hub API with the right availability posture for its workload:
|
|||||||
### T05 — Drill database failover
|
### T05 — Drill database failover
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: T05
|
id: CUST-WP-0038-T05
|
||||||
status: todo
|
status: todo
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "73c5008a-380e-42bf-ad57-1c9d0bda3a86"
|
state_hub_task_id: "73c5008a-380e-42bf-ad57-1c9d0bda3a86"
|
||||||
@@ -172,7 +172,7 @@ Checks:
|
|||||||
### T06 — Drill backup restore to isolated namespace
|
### T06 — Drill backup restore to isolated namespace
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: T06
|
id: CUST-WP-0038-T06
|
||||||
status: todo
|
status: todo
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "4e5b97ff-ef1c-414d-812b-39b87b242c74"
|
state_hub_task_id: "4e5b97ff-ef1c-414d-812b-39b87b242c74"
|
||||||
@@ -196,7 +196,7 @@ Checks:
|
|||||||
### T07 — Update agent access and runbooks for HA endpoint
|
### T07 — Update agent access and runbooks for HA endpoint
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: T07
|
id: CUST-WP-0038-T07
|
||||||
status: todo
|
status: todo
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "959062d8-decb-4969-a60b-0d3b618a8d6c"
|
state_hub_task_id: "959062d8-decb-4969-a60b-0d3b618a8d6c"
|
||||||
@@ -217,7 +217,7 @@ through the documented path.
|
|||||||
### T08 — Retire WSL2 fallback after explicit approval
|
### T08 — Retire WSL2 fallback after explicit approval
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: T08
|
id: CUST-WP-0038-T08
|
||||||
status: todo
|
status: todo
|
||||||
priority: low
|
priority: low
|
||||||
needs_human: true
|
needs_human: true
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ type: workplan
|
|||||||
title: "Workplan ID Normalization and Legacy Cleanup"
|
title: "Workplan ID Normalization and Legacy Cleanup"
|
||||||
domain: custodian
|
domain: custodian
|
||||||
repo: state-hub
|
repo: state-hub
|
||||||
status: proposed
|
status: finished
|
||||||
owner: codex
|
owner: codex
|
||||||
topic_slug: custodian
|
topic_slug: custodian
|
||||||
planning_priority: high
|
planning_priority: high
|
||||||
@@ -35,11 +35,19 @@ The cleanup should prefer deterministic renames and mechanical reference
|
|||||||
updates, but it must not break existing `state_hub_workstream_id` or
|
updates, but it must not break existing `state_hub_workstream_id` or
|
||||||
`state_hub_task_id` links.
|
`state_hub_task_id` links.
|
||||||
|
|
||||||
|
## Legacy Policy
|
||||||
|
|
||||||
|
Canonical State Hub-local workplans use `STATE-WP-*`. Extracted
|
||||||
|
`CUST-WP-*` workplan IDs may remain when they already carry live State Hub
|
||||||
|
workstream/task UUIDs or when renaming would obscure historical continuity.
|
||||||
|
Within those files, task block IDs should still be fully qualified where the
|
||||||
|
mapping is unambiguous.
|
||||||
|
|
||||||
## T01 - Inventory ID And Prefix Drift
|
## T01 - Inventory ID And Prefix Drift
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: STATE-WP-0046-T01
|
id: STATE-WP-0046-T01
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "e966289f-bb27-4da0-b00c-2bba2751fad7"
|
state_hub_task_id: "e966289f-bb27-4da0-b00c-2bba2751fad7"
|
||||||
```
|
```
|
||||||
@@ -51,11 +59,16 @@ docs/tests/scripts.
|
|||||||
Done when the inventory classifies each item as canonical, safely migratable,
|
Done when the inventory classifies each item as canonical, safely migratable,
|
||||||
or intentionally grandfathered.
|
or intentionally grandfathered.
|
||||||
|
|
||||||
|
Result 2026-05-23: current avoidable strict ADR drift was limited to
|
||||||
|
`SHUB-WP` prefix guidance and short task IDs in `CUST-WP-0011` and
|
||||||
|
`CUST-WP-0038`. Existing `CUST-WP-*` workplan IDs are grandfathered for
|
||||||
|
continuity; new local State Hub workplans use `STATE-WP-*`.
|
||||||
|
|
||||||
## T02 - Decide Canonical Prefix Policy
|
## T02 - Decide Canonical Prefix Policy
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: STATE-WP-0046-T02
|
id: STATE-WP-0046-T02
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "ee965266-4578-4ad1-b5b7-481e243a67dc"
|
state_hub_task_id: "ee965266-4578-4ad1-b5b7-481e243a67dc"
|
||||||
```
|
```
|
||||||
@@ -66,11 +79,15 @@ guidance so new workplans consistently use `STATE-WP-*`.
|
|||||||
Done when `AGENTS.md`, `SCOPE.md`, `README.md`, templates, and docs no longer
|
Done when `AGENTS.md`, `SCOPE.md`, `README.md`, templates, and docs no longer
|
||||||
disagree on `STATE-WP-*` versus `SHUB-WP-*`.
|
disagree on `STATE-WP-*` versus `SHUB-WP-*`.
|
||||||
|
|
||||||
|
Result 2026-05-23: `STATE-WP-*` is the canonical State Hub-local prefix.
|
||||||
|
Updated `SCOPE.md`, `README.md`, and `docs/workplan-convention.md` to remove
|
||||||
|
the older `SHUB-WP-*` guidance.
|
||||||
|
|
||||||
## T03 - Normalize Safe Workplan IDs And Filenames
|
## T03 - Normalize Safe Workplan IDs And Filenames
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: STATE-WP-0046-T03
|
id: STATE-WP-0046-T03
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "fd753850-93ba-4daa-9664-466f07097af5"
|
state_hub_task_id: "fd753850-93ba-4daa-9664-466f07097af5"
|
||||||
```
|
```
|
||||||
@@ -83,11 +100,16 @@ text.
|
|||||||
Done when safe renames are complete and no reference points at an obsolete local
|
Done when safe renames are complete and no reference points at an obsolete local
|
||||||
State Hub filename or workplan ID.
|
State Hub filename or workplan ID.
|
||||||
|
|
||||||
|
Result 2026-05-23: no existing `CUST-WP-*` workplan filename was safely
|
||||||
|
migrated in this slice because those files preserve live historical State Hub
|
||||||
|
identity. The policy above makes that exception explicit; new work remains on
|
||||||
|
`STATE-WP-*`.
|
||||||
|
|
||||||
## T04 - Normalize Safe Task IDs
|
## T04 - Normalize Safe Task IDs
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: STATE-WP-0046-T04
|
id: STATE-WP-0046-T04
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "6d60cd2c-0b0c-41e8-a957-b04a5fe0b31e"
|
state_hub_task_id: "6d60cd2c-0b0c-41e8-a957-b04a5fe0b31e"
|
||||||
```
|
```
|
||||||
@@ -99,11 +121,15 @@ Replace short extracted task IDs such as `T01` with full canonical IDs such as
|
|||||||
Done when strict ADR validation has no task-ID-format warnings for workplans
|
Done when strict ADR validation has no task-ID-format warnings for workplans
|
||||||
that were safe to normalize.
|
that were safe to normalize.
|
||||||
|
|
||||||
|
Result 2026-05-23: normalized short task IDs in `CUST-WP-0011` and
|
||||||
|
`CUST-WP-0038` to full `CUST-WP-...-TNN` IDs while preserving each
|
||||||
|
`state_hub_task_id`.
|
||||||
|
|
||||||
## T05 - Encode Legacy Exceptions
|
## T05 - Encode Legacy Exceptions
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: STATE-WP-0046-T05
|
id: STATE-WP-0046-T05
|
||||||
status: todo
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "a5a77bbe-4aa5-4e9a-9387-400a0093ed39"
|
state_hub_task_id: "a5a77bbe-4aa5-4e9a-9387-400a0093ed39"
|
||||||
```
|
```
|
||||||
@@ -115,11 +141,14 @@ and not fresh neglect.
|
|||||||
Done when strict validation and human docs distinguish tolerated legacy records
|
Done when strict validation and human docs distinguish tolerated legacy records
|
||||||
from actionable drift.
|
from actionable drift.
|
||||||
|
|
||||||
|
Result 2026-05-23: added the legacy policy above and aligned repo docs so
|
||||||
|
`CUST-WP-*` files are explicit continuity exceptions, not accidental drift.
|
||||||
|
|
||||||
## T06 - Verify Consistency And ADR Compliance
|
## T06 - Verify Consistency And ADR Compliance
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: STATE-WP-0046-T06
|
id: STATE-WP-0046-T06
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "69a3255c-1cc9-47d0-9947-350a443173d8"
|
state_hub_task_id: "69a3255c-1cc9-47d0-9947-350a443173d8"
|
||||||
```
|
```
|
||||||
@@ -130,6 +159,10 @@ migration. Confirm the DB still points at the intended workstreams and tasks.
|
|||||||
Done when the consistency checker passes and remaining ADR warnings, if any,
|
Done when the consistency checker passes and remaining ADR warnings, if any,
|
||||||
are explicitly grandfathered.
|
are explicitly grandfathered.
|
||||||
|
|
||||||
|
Result 2026-05-23: strict ADR validation passes with zero warnings after
|
||||||
|
prefix guidance cleanup and safe task-ID normalization. Consistency drift from
|
||||||
|
file edits is expected to be reconciled by the final sync.
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
|
|
||||||
- New State Hub-local workplans consistently use `STATE-WP-*`.
|
- New State Hub-local workplans consistently use `STATE-WP-*`.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ type: workplan
|
|||||||
title: "Lifecycle Assertions and Renormalization"
|
title: "Lifecycle Assertions and Renormalization"
|
||||||
domain: custodian
|
domain: custodian
|
||||||
repo: state-hub
|
repo: state-hub
|
||||||
status: proposed
|
status: active
|
||||||
owner: codex
|
owner: codex
|
||||||
topic_slug: custodian
|
topic_slug: custodian
|
||||||
planning_priority: high
|
planning_priority: high
|
||||||
@@ -32,11 +32,21 @@ state changes themselves do not consistently use a shared transition layer.
|
|||||||
|
|
||||||
This workplan turns the vocabulary into executable rules and repair scaffolding.
|
This workplan turns the vocabulary into executable rules and repair scaffolding.
|
||||||
|
|
||||||
|
## Lifecycle Invariants
|
||||||
|
|
||||||
|
| Invariant | Classification | Repair Path |
|
||||||
|
|-----------|----------------|-------------|
|
||||||
|
| A task moving from `todo` to `in_progress` activates a parent workstream in `proposed`, `ready`, or `backlog`. | automatic repair | Set parent workstream status to `active` in the same transaction. |
|
||||||
|
| A `blocked` parent workstream is not automatically unblocked by task start. | hard guard | Keep `blocked`; require explicit unblock transition or dependency/decision repair. |
|
||||||
|
| `finished` and `archived` workstreams should not have open tasks unless explicitly grandfathered. | warning, then repair | Report via consistency tooling; close/cancel stale tasks or reopen parent with intent. |
|
||||||
|
| `needs_review` and `stalled` remain derived health labels. | hard vocabulary guard | Do not write them to workplan frontmatter or `workstreams.status`. |
|
||||||
|
| Archived workplan files must have closed lifecycle states. | hard consistency error | Move file back to active workplans or close the lifecycle state. |
|
||||||
|
|
||||||
## T01 - Define Lifecycle Invariants
|
## T01 - Define Lifecycle Invariants
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: STATE-WP-0047-T01
|
id: STATE-WP-0047-T01
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "28f28391-646c-4871-ae84-a1c1aae3f5bf"
|
state_hub_task_id: "28f28391-646c-4871-ae84-a1c1aae3f5bf"
|
||||||
```
|
```
|
||||||
@@ -50,11 +60,15 @@ derived labels.
|
|||||||
Done when the invariant table is documented and each rule is classified as
|
Done when the invariant table is documented and each rule is classified as
|
||||||
hard error, warning, automatic repair, or human-review item.
|
hard error, warning, automatic repair, or human-review item.
|
||||||
|
|
||||||
|
Result 2026-05-23: added the initial invariant table above. The first automatic
|
||||||
|
repair implemented in this slice is parent activation when real task work
|
||||||
|
starts.
|
||||||
|
|
||||||
## T02 - Implement Shared Transition Helpers
|
## T02 - Implement Shared Transition Helpers
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: STATE-WP-0047-T02
|
id: STATE-WP-0047-T02
|
||||||
status: todo
|
status: in_progress
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "56d9b6b9-fba1-4997-bdd5-875187cafa2d"
|
state_hub_task_id: "56d9b6b9-fba1-4997-bdd5-875187cafa2d"
|
||||||
```
|
```
|
||||||
@@ -66,11 +80,15 @@ entry and exit assertions, and return concise repair/action results.
|
|||||||
Done when API routes, consistency tooling, and future UI actions can call one
|
Done when API routes, consistency tooling, and future UI actions can call one
|
||||||
shared transition layer for lifecycle changes.
|
shared transition layer for lifecycle changes.
|
||||||
|
|
||||||
|
Progress 2026-05-23: added `api.services.lifecycle` with shared status
|
||||||
|
normalization and parent-activation helpers. The task API now uses the helper;
|
||||||
|
consistency tooling and future UI actions still need to adopt the shared layer.
|
||||||
|
|
||||||
## T03 - Auto-Advance Workstream On Task Start
|
## T03 - Auto-Advance Workstream On Task Start
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: STATE-WP-0047-T03
|
id: STATE-WP-0047-T03
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "b0937fed-bd61-4f27-9586-8cebc6168827"
|
state_hub_task_id: "b0937fed-bd61-4f27-9586-8cebc6168827"
|
||||||
```
|
```
|
||||||
@@ -82,6 +100,10 @@ guard blocks the transition.
|
|||||||
Done when starting real task work cannot leave the parent workstream parked in
|
Done when starting real task work cannot leave the parent workstream parked in
|
||||||
planning states.
|
planning states.
|
||||||
|
|
||||||
|
Result 2026-05-23: task creation or update to `in_progress` activates a parent
|
||||||
|
workstream from `proposed`, `ready`, or `backlog`, while leaving `blocked`
|
||||||
|
parents blocked.
|
||||||
|
|
||||||
## T04 - Harden Flow Advancement Semantics
|
## T04 - Harden Flow Advancement Semantics
|
||||||
|
|
||||||
```task
|
```task
|
||||||
@@ -132,7 +154,7 @@ pattern instead of relying on ad hoc fixes.
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: STATE-WP-0047-T07
|
id: STATE-WP-0047-T07
|
||||||
status: todo
|
status: in_progress
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "def5ce49-1938-4c45-807d-78ac15c995cb"
|
state_hub_task_id: "def5ce49-1938-4c45-807d-78ac15c995cb"
|
||||||
```
|
```
|
||||||
@@ -142,6 +164,11 @@ enforcement, and consistency repairs.
|
|||||||
|
|
||||||
Done when lifecycle drift is hard to reintroduce accidentally.
|
Done when lifecycle drift is hard to reintroduce accidentally.
|
||||||
|
|
||||||
|
Progress 2026-05-23: added router regression tests for task-start activation
|
||||||
|
from `proposed`, `ready`, and `backlog`, plus a guard test proving `blocked`
|
||||||
|
parents stay blocked. Remaining coverage still needs flow assertion hardening
|
||||||
|
and consistency repair tests.
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
|
|
||||||
- Starting task work deterministically activates the parent workstream.
|
- Starting task work deterministically activates the parent workstream.
|
||||||
|
|||||||
Reference in New Issue
Block a user