fix: bound task identity ambiguity
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Multi-Context Image / build-and-push (push) Successful in 26s

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a053ff-1d6f-7fe2-ac1c-a6eb40a42a0c
This commit is contained in:
tegwick 2026-08-31 01:57:46 +02:00
parent 14e865ab30
commit ddce470944
4 changed files with 394 additions and 8 deletions

View file

@ -218,12 +218,21 @@ class _Row:
class _FakeSession:
"""Stands in for AsyncSession: enough to prove intent without a database."""
def __init__(self, repo, rows, foreign=None, slug_clash=None, task_rows=None):
def __init__(
self,
repo,
rows,
foreign=None,
slug_clash=None,
task_rows=None,
task_collision_rows=None,
):
self._repo = repo
self.rows = list(rows)
self._foreign = list(foreign or [])
self._slug_clash = list(slug_clash or [])
self._task_rows = task_rows
self._task_collision_rows = list(task_collision_rows or [])
self.added = []
self.deleted = []
self.committed = False
@ -244,12 +253,14 @@ class _FakeSession:
payload = rows
elif update_only and self._calls == 3:
payload = self._task_rows
elif update_only and self._calls == 4:
payload = self._task_collision_rows
elif self._calls == 3:
payload = self._foreign
elif self._calls == 4:
payload = self._slug_clash
elif self._calls == 5:
payload = self._task_rows or []
payload = self._task_collision_rows
else:
payload = []
@ -1129,3 +1140,124 @@ class TestExistingWorkplanTasks:
derived=self._derived(("DEMO-WP-0001-T01", "Do it", "todo")),
)
assert orphan.status == "todo"
@pytest.mark.asyncio
async def test_duplicate_identity_inside_current_workplan_is_refused(self):
row = _Row(slug="demo-wp-0001", status="active", path="workplans/a.md")
first = _TaskRow("DEMO-WP-0001-T01", workplan_id=row.id)
second = _TaskRow("demo-wp-0001-t01", workplan_id=row.id)
session = _FakeSession(repo=_Repo(), rows=[row], task_rows=[first, second])
out = await fp.reset_repository_projection(
session,
"demo",
derived=self._derived(("DEMO-WP-0001-T01", "Do it", "todo")),
)
assert out.status == "refused"
assert out.refused[0]["reason"].startswith("current workplan contains duplicate")
assert session.added == []
@pytest.mark.asyncio
async def test_derived_task_uuid_held_elsewhere_is_refused_before_insert(self):
row = _Row(slug="demo-wp-0001", status="active", path="workplans/a.md")
derived = self._derived(("DEMO-WP-0001-T01", "Do it", "todo"))
holder = _TaskRow("DEMO-WP-0001-T01", workplan_id=__import__("uuid").uuid4())
holder.id = __import__("uuid").UUID(derived.workplans[0].tasks[0].uuid)
session = _FakeSession(
repo=_Repo(),
rows=[row],
task_rows=[],
task_collision_rows=[holder],
)
out = await fp.reset_repository_projection(session, "demo", derived=derived)
assert out.status == "refused"
assert out.refused[0]["reason"].startswith("task identity already belongs")
assert session.added == []
@pytest.mark.asyncio
async def test_tasks_on_retired_copy_do_not_block_current_projection(self):
from datetime import datetime, timezone
current = _Row(slug="demo-wp-0001", status="active", path="workplans/a.md")
current.id = __import__("uuid").UUID(fp.derived_record_uuid("DEMO-WP-0001"))
retired = _Row(
slug="demo-wp-0001@retired-20260831",
status="finished",
path="workplans/a.md",
)
retired.projection_retired_at = datetime.now(tz=timezone.utc)
task = _TaskRow("DEMO-WP-0001-T01", status="todo", workplan_id=current.id)
session = _FakeSession(repo=_Repo(), rows=[current, retired], task_rows=[task])
out = await fp.reset_repository_projection(
session,
"demo",
derived=self._derived(("DEMO-WP-0001-T01", "Do it", "todo")),
)
assert out.status in {"applied", "noop"}
assert out.refused == []
def test_diff_reports_duplicate_current_task_identity_as_ambiguous():
derived = TestExistingWorkplanTasks()._derived(
("DEMO-WP-0001-T01", "Do it", "todo")
)
workplan_id = derived.workplans[0].uuid
hub_workplans = [
{
"id": workplan_id,
"slug": "demo-wp-0001",
"status": "active",
"backing_relative_path": "workplans/a.md",
}
]
hub_tasks = {
workplan_id: [
{
"id": "11111111-1111-4111-8111-111111111111",
"record_id": "DEMO-WP-0001-T01",
"title": "a",
"status": "todo",
},
{
"id": "22222222-2222-4222-8222-222222222222",
"record_id": "demo-wp-0001-t01",
"title": "b",
"status": "todo",
},
]
}
diff = fp.diff_against_hub(derived, hub_workplans, hub_tasks)
assert diff.clean is False
assert diff.to_dict()["counts"]["ambiguous"] == 1
assert diff.ambiguous[0]["source"] == "hub"
@pytest.mark.asyncio
async def test_reset_refuses_duplicate_task_identity_in_forge_projection():
first = TestExistingWorkplanTasks()._derived(
("SHARED-WP-0001-T01", "First", "todo")
).workplans[0]
second = fp.DerivedWorkplan(
record_id="DEMO-WP-0002",
uuid=fp.derived_record_uuid("DEMO-WP-0002"),
title="Second",
status="active",
relative_path="workplans/b.md",
archived=False,
tasks=[
fp.DerivedTask(
record_id="SHARED-WP-0001-T01",
uuid=fp.derived_record_uuid("SHARED-WP-0001-T01"),
title="Second claim",
status="todo",
priority="medium",
)
],
)
derived = fp.DerivedProjection(
repo_slug="demo", commit="c0ffee", workplans=[first, second]
)
session = _FakeSession(repo=_Repo(), rows=[])
out = await fp.reset_repository_projection(session, "demo", derived=derived)
assert out.status == "refused"
assert any(item.get("source") == "forge" for item in out.refused)
assert session.added == []