Normalize workplan IDs and activate parents on task start

This commit is contained in:
2026-05-23 16:31:28 +02:00
parent 9f3561a254
commit 90c9f8e7a7
10 changed files with 195 additions and 38 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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-*`.

View File

@@ -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.