diff --git a/scripts/consistency_check.py b/scripts/consistency_check.py index ee080e7..3299a91 100644 --- a/scripts/consistency_check.py +++ b/scripts/consistency_check.py @@ -119,6 +119,7 @@ from api.classification import ( # noqa: E402 resolve_topic_domain_slug, ) 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 CANONICAL_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) +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: """Translate a workplan file status value to its DB-canonical equivalent.""" return _normalize_workstream_status(status, has_started=has_started) @@ -1770,6 +1785,10 @@ def check_repo( 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 # legacy file/API aliases are not treated as drift. Lifecycle repairs # take precedence so a stale planning file cannot regress active work. @@ -1780,6 +1799,7 @@ def check_repo( and file_status and db_status and normalised_file_status != normalised_db_status + and not historical_view ): report.add( severity="WARN", check_id="C-04", @@ -1995,7 +2015,7 @@ def check_repo( if dep.get("to_task_id"): 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) if not target_ws_id: report.add( diff --git a/tests/test_consistency_check.py b/tests/test_consistency_check.py index 9540b26..9b7b75e 100644 --- a/tests/test_consistency_check.py +++ b/tests/test_consistency_check.py @@ -2585,3 +2585,16 @@ def test_task_owner_return_sweep(tmp_path, monkeypatch, read_at, date, flavor, s if expected: assert report.issues[0]._fix_context == {"task_record_id": "DEMO-WP-0001-T01", "message_ids": ["reply-1"]} 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() diff --git a/workplans/ADHOC-2026-10-06.md b/workplans/ADHOC-2026-10-06.md new file mode 100644 index 0000000..90f30d9 --- /dev/null +++ b/workplans/ADHOC-2026-10-06.md @@ -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).