mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
fix(coordination): stop phantom re-delegation from text-vs-id AC-ref mismatch (#259)
A parent's acceptance-criteria coverage is matched by stable criterion id, but a PM may declare covers_parent_criteria on a child by EITHER the criterion's id OR its full text (both happen in practice). _parent_ac_ref_sets unioned the raw refs and matched by id only, so a COMPLETED child that declared coverage by text was invisible to the matcher: the criterion read "uncovered", the roll-up gate refused, and the PM re-delegated the already-finished work as a brand-new empty subtask (0 commits, no PR) that can never close — looping for hours and burning tokens (observed live: a parent's xenon work completed + merged via one child, then re-delegated 2h later as an empty phantom). Normalize every child ref to the criterion id (text -> id via the parent's own criteria) in a small _normalize_ac_refs helper, so coverage counts regardless of how it was declared. Fixes existing mismatched data and future declarations; all three consumers (uncovered/unclaimed/parent_ac_coverage) share the builder. An unknown ref (neither id nor a current criterion text) passes through and matches nothing, exactly as before. Adds two regression tests. Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
@@ -672,6 +672,46 @@ async def test_uncovered_parent_acs_empty_when_all_covered() -> None:
|
||||
assert await svc.uncovered_parent_acceptance_criteria(parent.id) == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_uncovered_parent_acs_recognizes_text_declared_coverage() -> None:
|
||||
# Regression (phantom re-delegation): a PM may declare covers_parent_criteria
|
||||
# by the criterion's full TEXT instead of its id. Matching is by id, so a
|
||||
# COMPLETED child that declared coverage by text used to read "uncovered" —
|
||||
# and the PM re-delegated the already-finished work as an empty phantom
|
||||
# subtask (0 commits, no PR) that could never close. _parent_ac_ref_sets now
|
||||
# normalizes text -> id so coverage counts regardless of how it was declared.
|
||||
parent = _build_task(
|
||||
acceptance_criteria=["crit a", "crit b"],
|
||||
acceptance_criteria_ids=["id-a", "id-b"],
|
||||
)
|
||||
svc = _svc_with_children(
|
||||
parent,
|
||||
[
|
||||
(TaskStatus.COMPLETED, ["crit a"]), # declared by TEXT, not "id-a"
|
||||
(TaskStatus.COMPLETED, ["id-b"]), # declared by id
|
||||
],
|
||||
)
|
||||
assert await svc.uncovered_parent_acceptance_criteria(parent.id) == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_parent_ac_coverage_normalizes_text_refs() -> None:
|
||||
# A text-declared coverage ref from a COMPLETED child surfaces as
|
||||
# claimed+verified, same as an id-declared one.
|
||||
parent = _build_task(
|
||||
acceptance_criteria=["crit a", "crit b"],
|
||||
acceptance_criteria_ids=["id-a", "id-b"],
|
||||
)
|
||||
svc = _svc_with_children(parent, [(TaskStatus.COMPLETED, ["crit a"])])
|
||||
cov = await svc.parent_ac_coverage(parent.id)
|
||||
assert cov[0] == {
|
||||
"id": "id-a",
|
||||
"text": "crit a",
|
||||
"claimed": True,
|
||||
"verified": True,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_parent_ac_coverage_maps_claimed_and_verified() -> None:
|
||||
# Per-criterion visibility: a COMPLETED child both claims and verifies its
|
||||
|
||||
Reference in New Issue
Block a user