Turn two State Hub sync failures into clean outcomes.
An over-long blocked_on now returns a 422 instead of a 500, and decided decision records such as 'approved' map onto the hub's made type. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 57383@bnt-lap001 Assistant-Session: ef71febe-2a37-4c34-a979-2508ca973c91
This commit is contained in:
parent
2b5d8f90f8
commit
7d992821fb
5 changed files with 79 additions and 4 deletions
|
|
@ -2,7 +2,7 @@ import uuid
|
|||
from datetime import date, datetime
|
||||
from typing import Self
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, field_validator, model_validator
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
||||
|
||||
from api.models.task import TaskPriority, TaskStatus
|
||||
from api.schemas.compat import WorkplanIdCompatMixin, WorkplanIdCreateMixin
|
||||
|
|
@ -46,7 +46,8 @@ class TaskCreate(TaskStatusMixin, TaskFlavorMixin, WorkplanIdCreateMixin):
|
|||
needs_human: bool = False
|
||||
intervention_note: str | None = None
|
||||
decision_id: str | None = None
|
||||
blocked_on: str | None = None
|
||||
# Bounded by tasks.blocked_on String(200); longer text belongs in blocking_reason.
|
||||
blocked_on: str | None = Field(default=None, max_length=200)
|
||||
wait_until: date | None = None
|
||||
parent_task_id: uuid.UUID | None = None
|
||||
|
||||
|
|
@ -69,7 +70,8 @@ class TaskUpdate(TaskStatusMixin, TaskFlavorMixin):
|
|||
needs_human: bool | None = None
|
||||
intervention_note: str | None = None
|
||||
decision_id: str | None = None
|
||||
blocked_on: str | None = None
|
||||
# Bounded by tasks.blocked_on String(200); longer text belongs in blocking_reason.
|
||||
blocked_on: str | None = Field(default=None, max_length=200)
|
||||
wait_until: date | None = None
|
||||
parent_task_id: uuid.UUID | None = None
|
||||
# Token passthrough — three tiers (highest precision wins):
|
||||
|
|
|
|||
|
|
@ -247,6 +247,23 @@ FILE_TO_DB_WORKSTREAM_STATUS: dict[str, str] = dict(LEGACY_WORKSTREAM_STATUS_ALI
|
|||
STATUS_ORDER: dict[str, int] = dict(TASK_STATUS_ORDER)
|
||||
|
||||
|
||||
def normalise_decision_type(meta: dict) -> str:
|
||||
"""Map a file's decision_type onto the hub's two values, made and pending.
|
||||
|
||||
Repos record decided outcomes with words like ``approved``; the hub rejects
|
||||
anything else with a 422, which left those decisions unindexed.
|
||||
"""
|
||||
value = str(meta.get("decision_type") or "").strip().lower()
|
||||
if value in {"made", "pending"}:
|
||||
return value
|
||||
status = str(meta.get("status") or "").strip().lower()
|
||||
if status == "resolved" or value in {
|
||||
"approved", "accepted", "decided", "rejected", "declined", "resolved",
|
||||
}:
|
||||
return "made"
|
||||
return "pending"
|
||||
|
||||
|
||||
def historical_view_paths(repo_slug: str) -> set[str]:
|
||||
"""Repo-relative paths of bound historical views that share a parent row.
|
||||
|
||||
|
|
@ -3879,7 +3896,7 @@ def fix_repo(
|
|||
"id": desired_record_id,
|
||||
"title": title,
|
||||
"topic_id": topic_id,
|
||||
"decision_type": meta.get("decision_type", "pending"),
|
||||
"decision_type": normalise_decision_type(meta),
|
||||
"description": meta.get("description"),
|
||||
"rationale": meta.get("agent_recommendation") or meta.get("rationale"),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2598,3 +2598,20 @@ def test_historical_split_views_do_not_supply_parent_status():
|
|||
}
|
||||
assert "workplans/archived/260707-custodian-WP-0003-event-bridge.md" not in views
|
||||
assert historical_view_paths("the-custodian") == set()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("meta", "expected"),
|
||||
[
|
||||
({"decision_type": "approved", "status": "resolved"}, "made"),
|
||||
({"decision_type": "approved"}, "made"),
|
||||
({"decision_type": "made"}, "made"),
|
||||
({"decision_type": "pending", "status": "open"}, "pending"),
|
||||
({"status": "resolved"}, "made"),
|
||||
({}, "pending"),
|
||||
],
|
||||
)
|
||||
def test_decision_type_maps_onto_hub_values(meta, expected):
|
||||
from scripts.consistency_check import normalise_decision_type
|
||||
|
||||
assert normalise_decision_type(meta) == expected
|
||||
|
|
|
|||
10
tests/test_task_wait_qualifiers_schema.py
Normal file
10
tests/test_task_wait_qualifiers_schema.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from api.schemas.task import TaskUpdate
|
||||
|
||||
|
||||
def test_blocked_on_longer_than_column_is_rejected_not_a_500():
|
||||
TaskUpdate(blocked_on="x" * 200)
|
||||
with pytest.raises(ValidationError, match="blocked_on"):
|
||||
TaskUpdate(blocked_on="x" * 201)
|
||||
|
|
@ -9,6 +9,7 @@ owner: claude-code
|
|||
topic_slug: state-hub
|
||||
created: "2026-10-06"
|
||||
updated: "2026-10-06"
|
||||
state_hub_workstream_id: "b73dbcde-1e01-5ed0-a8ed-9f3feac1f15b"
|
||||
---
|
||||
|
||||
## Stop historical split views from writing their parent's status
|
||||
|
|
@ -17,6 +18,7 @@ updated: "2026-10-06"
|
|||
id: STATE-WP-ADHOC-2026-10-06-T01
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "09274552-5e91-5b2c-93bb-797704d52761"
|
||||
```
|
||||
|
||||
Activity's custodian-WP-0003a/b/c split views carry the archived parent's UUID
|
||||
|
|
@ -30,3 +32,30 @@ receiving, which takes the row from the parent only and gives historical
|
|||
workplans no edges. A direct `consistency_check.py --repo activity-core --fix`
|
||||
set the parent back to `archived`, and a re-run reports nothing to fix. UUIDs
|
||||
and tasks were untouched. Unblocks RMGR-WP-0019-T03 (see STATE-WP-0095).
|
||||
|
||||
## Reject over-long blocked_on with a 422 instead of a 500
|
||||
|
||||
```task
|
||||
id: STATE-WP-ADHOC-2026-10-06-T02
|
||||
status: done
|
||||
priority: low
|
||||
```
|
||||
|
||||
`tasks.blocked_on` is `String(200)`, but `TaskCreate`/`TaskUpdate` accepted any
|
||||
length, so a longer value failed in Postgres and returned HTTP 500 (hit by Repo
|
||||
Manager's C-41 sync of RMGR-WP-0019-T03). The schemas now cap it at 200 and
|
||||
return a 422 that names the field; long text belongs in `blocking_reason`
|
||||
(Text). Live after the next State Hub deploy.
|
||||
|
||||
## Map file decision_type values onto the hub's made/pending
|
||||
|
||||
```task
|
||||
id: STATE-WP-ADHOC-2026-10-06-T03
|
||||
status: done
|
||||
priority: low
|
||||
```
|
||||
|
||||
C-32 sent a file's `decision_type` verbatim; repo-manager's RMGR-DEC-2026-004
|
||||
says `approved`, which the hub rejects with a 422, so that decision was never
|
||||
indexed. `normalise_decision_type` maps resolved records and decided synonyms
|
||||
to `made`, and everything else to `pending`.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue