fix: stop C-23 promoting proposed and C-15 overwriting files
ADR-007 / RMGR-WP-0005-T06: proposed stays a human gate. Task status follows the file (ADR-001); wait is not "ahead" of progress or todo.
This commit is contained in:
parent
aa45c9b6b0
commit
6d038fd050
2 changed files with 66 additions and 34 deletions
|
|
@ -19,7 +19,7 @@ Checks:
|
|||
C-12 orphan-db-task WARN Yes DB task in workstream has no file backing unless terminal in a closed workstream
|
||||
C-13 workstream-auto-complete WARN Yes All DB tasks done but workstream still active
|
||||
C-14 ghost-duplicate WARN No Active topic workstream with no repo_id matches a file-backed title — probable ghost from premature create_workstream() call
|
||||
C-15 task-db-ahead WARN Yes DB task status is ahead of file — regression prevented; writeback syncs file
|
||||
C-15 task-db-ahead WARN No DB task is terminal and file is not — report only; file wins (ADR-001)
|
||||
C-16 repo-behind-remote WARN No Local repo is behind remote tracking branch — --fix skipped to avoid clobbering remote progress
|
||||
C-17 repo-ahead-push-failed WARN No Local repo has unpushed commits and push failed — writes skipped to prevent runaway divergence
|
||||
C-19 workstream-planning-drift WARN Yes planning_priority/planning_order differs between file and DB
|
||||
|
|
@ -281,7 +281,10 @@ RENORMALIZATION_RULES: tuple[RenormalizationRule, ...] = (
|
|||
"Workplan status is proposed, ready, or backlog while a linked "
|
||||
"task is progress or wait."
|
||||
),
|
||||
repair="Patch the DB workstream and workplan frontmatter to status=active.",
|
||||
repair=(
|
||||
"ready/backlog: patch DB and file to active. "
|
||||
"proposed: report only (ADR-007 decision 3)."
|
||||
),
|
||||
test_anchor="tests/test_consistency_check.py::TestLifecycleRenormalization",
|
||||
),
|
||||
)
|
||||
|
|
@ -1302,18 +1305,27 @@ def check_repo(api_base: str, repo_slug: str, repo_path_override: str | None = N
|
|||
task_statuses=[*file_task_statuses, *db_task_statuses],
|
||||
)
|
||||
if active_task_requires_activation:
|
||||
# ADR-007 decision 3 / RMGR-WP-0005-T06: automation may report
|
||||
# that a proposed workplan has started work, but must not promote
|
||||
# proposed → active. proposed is a human review gate.
|
||||
proposed_gate = file_status == "proposed"
|
||||
report.add(
|
||||
severity="WARN",
|
||||
check_id="C-23",
|
||||
message=(
|
||||
f"Lifecycle drift in '{ws.get('slug')}': workplan status "
|
||||
f"{file_status!r} has an active task — repair to 'active'"
|
||||
f"{file_status!r} has an active task"
|
||||
+ (
|
||||
" — report only (ADR-007: do not auto-promote proposed)"
|
||||
if proposed_gate
|
||||
else " — repair to 'active'"
|
||||
)
|
||||
),
|
||||
file_path=fname,
|
||||
db_id=ws_id,
|
||||
file_value=file_status,
|
||||
db_value=ws.get("status", ""),
|
||||
fixable=True,
|
||||
fixable=not proposed_gate,
|
||||
_fix_context={
|
||||
"ws_id": ws_id,
|
||||
"wp_file": str(wp_file),
|
||||
|
|
@ -1544,48 +1556,45 @@ def check_repo(api_base: str, repo_slug: str, repo_path_override: str | None = N
|
|||
fixable=False,
|
||||
)
|
||||
continue
|
||||
# C-10 / C-15: task status drift
|
||||
# C-10 / C-15: task status drift. ADR-001: the file originates
|
||||
# work. Same-rank wait/progress is not "DB ahead" — writing
|
||||
# wait over progress/todo is the CFED/RMGR-WP-0004-T02 bug.
|
||||
# C-15 remains a report-only review signal when the DB is
|
||||
# terminal and the file is not.
|
||||
db_t_status = normalise_task_status(db_task.get("status", "todo"))
|
||||
if t_status and db_t_status and t_status != db_t_status:
|
||||
db_rank = STATUS_ORDER.get(db_t_status, 0)
|
||||
file_rank = STATUS_ORDER.get(t_status, 0)
|
||||
if db_rank >= file_rank:
|
||||
# C-15: DB is already at the same rank or ahead — prevent
|
||||
# regression. Writeback syncs the file to match DB.
|
||||
db_terminal = db_t_status in {"done", "cancel"}
|
||||
file_terminal = t_status in {"done", "cancel"}
|
||||
if db_terminal and not file_terminal:
|
||||
report.add(
|
||||
severity="WARN", check_id="C-15",
|
||||
message=(
|
||||
f"DB task '{t_id}' is ahead of file "
|
||||
f"DB task '{t_id}' is terminal "
|
||||
f"(db={db_t_status!r}, file={t_status!r}) "
|
||||
f"— regression prevented; writeback will sync file"
|
||||
f"— report only; file wins (ADR-001 / RMGR-WP-0005-T06)"
|
||||
),
|
||||
file_path=f"{fname}#{t_id}",
|
||||
db_id=t_sh_id,
|
||||
file_value=t_status,
|
||||
db_value=db_t_status,
|
||||
fixable=True,
|
||||
_fix_context={
|
||||
"task_id": t_sh_id,
|
||||
"wp_file": str(wp_file),
|
||||
"task_block_id": t_id,
|
||||
"db_status": db_t_status,
|
||||
},
|
||||
)
|
||||
else:
|
||||
# C-10: file is ahead — apply file→DB sync (normal drift)
|
||||
report.add(
|
||||
severity="WARN", check_id="C-10",
|
||||
message=(
|
||||
f"Task status drift '{t_id}': "
|
||||
f"file={t_status!r} db={db_t_status!r} (file wins)"
|
||||
),
|
||||
file_path=f"{fname}#{t_id}",
|
||||
db_id=t_sh_id,
|
||||
file_value=t_status,
|
||||
db_value=db_t_status,
|
||||
fixable=True,
|
||||
_fix_context={"task_id": t_sh_id, "status": t_status},
|
||||
fixable=False,
|
||||
)
|
||||
# File always wins, including same-rank wait ↔ progress.
|
||||
report.add(
|
||||
severity="WARN", check_id="C-10",
|
||||
message=(
|
||||
f"Task status drift '{t_id}': "
|
||||
f"file={t_status!r} db={db_t_status!r} (file wins)"
|
||||
),
|
||||
file_path=f"{fname}#{t_id}",
|
||||
db_id=t_sh_id,
|
||||
file_value=t_status,
|
||||
db_value=db_t_status,
|
||||
fixable=True,
|
||||
_fix_context={"task_id": t_sh_id, "status": t_status},
|
||||
)
|
||||
file_description = task.get("description")
|
||||
if isinstance(file_description, str):
|
||||
file_description = file_description.strip() or None
|
||||
|
|
|
|||
|
|
@ -993,10 +993,10 @@ class TestLifecycleRenormalization:
|
|||
assert "C-23" in check_ids
|
||||
assert "C-04" not in check_ids
|
||||
issue = next(issue for issue in report.issues if issue.check_id == "C-23")
|
||||
assert issue.fixable is True
|
||||
assert issue.fixable is False
|
||||
assert issue.file_value == "proposed"
|
||||
|
||||
def test_fix_repo_repairs_planning_workplan_with_active_task(self, tmp_path, monkeypatch):
|
||||
def test_fix_repo_does_not_promote_proposed(self, tmp_path, monkeypatch):
|
||||
repo = self._make_repo(tmp_path)
|
||||
wp = repo / "workplans" / "STATE-WP-0001-demo.md"
|
||||
patches = []
|
||||
|
|
@ -1015,6 +1015,29 @@ class TestLifecycleRenormalization:
|
|||
|
||||
report = fix_repo("http://unused", "state-hub")
|
||||
|
||||
assert ("/workplans/ws-1", {"status": "active"}) not in patches
|
||||
assert "status: proposed" in wp.read_text(encoding="utf-8")
|
||||
assert not any("C-23 fixed" in fix for fix in report.fixes_applied)
|
||||
|
||||
def test_fix_repo_repairs_planning_workplan_with_active_task(self, tmp_path, monkeypatch):
|
||||
repo = self._make_repo(tmp_path, status="ready")
|
||||
wp = repo / "workplans" / "STATE-WP-0001-demo.md"
|
||||
patches = []
|
||||
|
||||
def fake_patch(_api_base, path, body):
|
||||
patches.append((path, body))
|
||||
return {"ok": True}
|
||||
|
||||
monkeypatch.setattr("consistency_check._api_get", self._api_get_for_repo(repo))
|
||||
monkeypatch.setattr("consistency_check._api_patch", fake_patch)
|
||||
monkeypatch.setattr("consistency_check._detect_behind_remote", lambda _repo_path: False)
|
||||
monkeypatch.setattr("consistency_check._detect_ahead_of_remote", lambda _repo_path: 0)
|
||||
monkeypatch.setattr("consistency_check._git_commit_writeback", lambda *args, **kwargs: True)
|
||||
monkeypatch.setattr("consistency_check._write_custodian_brief", lambda *args, **kwargs: False)
|
||||
monkeypatch.setattr("consistency_check._git_push", lambda _repo_path: (True, "pushed"))
|
||||
|
||||
report = fix_repo("http://unused", "state-hub")
|
||||
|
||||
assert ("/workplans/ws-1", {"status": "active"}) in patches
|
||||
assert "status: active" in wp.read_text(encoding="utf-8")
|
||||
assert any("C-23 fixed" in fix for fix in report.fixes_applied)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue