Keep historical split views from writing their parent's row.
C-04 ran once per file, so the three custodian-WP-0003 views and their archived parent flipped the shared row's status on every run. 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
b81c084dc3
commit
2b5d8f90f8
3 changed files with 66 additions and 1 deletions
|
|
@ -119,6 +119,7 @@ from api.classification import ( # noqa: E402
|
||||||
resolve_topic_domain_slug,
|
resolve_topic_domain_slug,
|
||||||
)
|
)
|
||||||
from api.services.lifecycle import should_activate_parent_for_active_tasks # noqa: E402
|
from api.services.lifecycle import should_activate_parent_for_active_tasks # noqa: E402
|
||||||
|
from api.services.historical_projection import BINDINGS as _HISTORICAL_BINDINGS # noqa: E402
|
||||||
from api.task_status import ( # noqa: E402
|
from api.task_status import ( # noqa: E402
|
||||||
CANONICAL_TASK_STATUSES,
|
CANONICAL_TASK_STATUSES,
|
||||||
OPEN_TASK_STATUSES,
|
OPEN_TASK_STATUSES,
|
||||||
|
|
@ -246,6 +247,20 @@ FILE_TO_DB_WORKSTREAM_STATUS: dict[str, str] = dict(LEGACY_WORKSTREAM_STATUS_ALI
|
||||||
STATUS_ORDER: dict[str, int] = dict(TASK_STATUS_ORDER)
|
STATUS_ORDER: dict[str, int] = dict(TASK_STATUS_ORDER)
|
||||||
|
|
||||||
|
|
||||||
|
def historical_view_paths(repo_slug: str) -> set[str]:
|
||||||
|
"""Repo-relative paths of bound historical views that share a parent row.
|
||||||
|
|
||||||
|
A split view carries its parent's UUID but does not supply the parent's
|
||||||
|
fields; only the bound parent file does (see historical_projection). Letting
|
||||||
|
each view write the shared row made its status flip on every run.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
entry["path"]
|
||||||
|
for entry in _HISTORICAL_BINDINGS
|
||||||
|
if entry["repo"] == repo_slug and entry["record_id"] != entry["parent_record_id"]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def normalise_workstream_status(status: str, *, has_started: bool | None = None) -> str:
|
def normalise_workstream_status(status: str, *, has_started: bool | None = None) -> str:
|
||||||
"""Translate a workplan file status value to its DB-canonical equivalent."""
|
"""Translate a workplan file status value to its DB-canonical equivalent."""
|
||||||
return _normalize_workstream_status(status, has_started=has_started)
|
return _normalize_workstream_status(status, has_started=has_started)
|
||||||
|
|
@ -1770,6 +1785,10 @@ def check_repo(
|
||||||
fixable=False,
|
fixable=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# A bound historical split view shares its parent's row but supplies
|
||||||
|
# none of its fields or edges; only the bound parent file does.
|
||||||
|
historical_view = workplan_display_path(repo_dir, wp_file) in historical_view_paths(repo_slug)
|
||||||
|
|
||||||
# C-04: status drift — normalise file value before comparing so that
|
# C-04: status drift — normalise file value before comparing so that
|
||||||
# legacy file/API aliases are not treated as drift. Lifecycle repairs
|
# legacy file/API aliases are not treated as drift. Lifecycle repairs
|
||||||
# take precedence so a stale planning file cannot regress active work.
|
# take precedence so a stale planning file cannot regress active work.
|
||||||
|
|
@ -1780,6 +1799,7 @@ def check_repo(
|
||||||
and file_status
|
and file_status
|
||||||
and db_status
|
and db_status
|
||||||
and normalised_file_status != normalised_db_status
|
and normalised_file_status != normalised_db_status
|
||||||
|
and not historical_view
|
||||||
):
|
):
|
||||||
report.add(
|
report.add(
|
||||||
severity="WARN", check_id="C-04",
|
severity="WARN", check_id="C-04",
|
||||||
|
|
@ -1995,7 +2015,7 @@ def check_repo(
|
||||||
if dep.get("to_task_id"):
|
if dep.get("to_task_id"):
|
||||||
existing_dep_keys.add((from_task_id, "task", dep["to_task_id"], rel))
|
existing_dep_keys.add((from_task_id, "task", dep["to_task_id"], rel))
|
||||||
|
|
||||||
for target_wp_id in _frontmatter_depends_on_workplans(meta):
|
for target_wp_id in ([] if historical_view else _frontmatter_depends_on_workplans(meta)):
|
||||||
target_ws_id = workplan_id_to_ws_id.get(target_wp_id)
|
target_ws_id = workplan_id_to_ws_id.get(target_wp_id)
|
||||||
if not target_ws_id:
|
if not target_ws_id:
|
||||||
report.add(
|
report.add(
|
||||||
|
|
|
||||||
|
|
@ -2585,3 +2585,16 @@ def test_task_owner_return_sweep(tmp_path, monkeypatch, read_at, date, flavor, s
|
||||||
if expected:
|
if expected:
|
||||||
assert report.issues[0]._fix_context == {"task_record_id": "DEMO-WP-0001-T01", "message_ids": ["reply-1"]}
|
assert report.issues[0]._fix_context == {"task_record_id": "DEMO-WP-0001-T01", "message_ids": ["reply-1"]}
|
||||||
assert queries[0]["unread_only"] is False
|
assert queries[0]["unread_only"] is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_historical_split_views_do_not_supply_parent_status():
|
||||||
|
from scripts.consistency_check import historical_view_paths
|
||||||
|
|
||||||
|
views = historical_view_paths("activity-core")
|
||||||
|
assert views == {
|
||||||
|
"workplans/custodian-WP-0003a-event-bridge-model-rules-registry.md",
|
||||||
|
"workplans/custodian-WP-0003b-event-bridge-parser-workflow-triggers.md",
|
||||||
|
"workplans/custodian-WP-0003c-event-bridge-context-integration.md",
|
||||||
|
}
|
||||||
|
assert "workplans/archived/260707-custodian-WP-0003-event-bridge.md" not in views
|
||||||
|
assert historical_view_paths("the-custodian") == set()
|
||||||
|
|
|
||||||
32
workplans/ADHOC-2026-10-06.md
Normal file
32
workplans/ADHOC-2026-10-06.md
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
---
|
||||||
|
id: STATE-WP-ADHOC-2026-10-06
|
||||||
|
type: workplan
|
||||||
|
title: "Ad hoc fixes — 2026-10-06"
|
||||||
|
domain: infotech
|
||||||
|
repo: state-hub
|
||||||
|
status: finished
|
||||||
|
owner: claude-code
|
||||||
|
topic_slug: state-hub
|
||||||
|
created: "2026-10-06"
|
||||||
|
updated: "2026-10-06"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Stop historical split views from writing their parent's status
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: STATE-WP-ADHOC-2026-10-06-T01
|
||||||
|
status: done
|
||||||
|
priority: medium
|
||||||
|
```
|
||||||
|
|
||||||
|
Activity's custodian-WP-0003a/b/c split views carry the archived parent's UUID
|
||||||
|
`b4eb45a9-69e3-4ab0-b00c-67a53c3117c5`. C-04 ran per file, so each
|
||||||
|
`fix-consistency` run set the shared row to whichever file it processed last.
|
||||||
|
On 2026-10-06 it flipped `finished` → `archived` → `finished`. The parent file
|
||||||
|
says `archived`; the views say `done`. C-04 and the frontmatter C-20 now skip
|
||||||
|
bound historical views (`historical_view_paths` from `historical_projection`
|
||||||
|
BINDINGS), so only the parent file supplies the row. This matches Forge
|
||||||
|
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).
|
||||||
Loading…
Add table
Add a link
Reference in a new issue