diff --git a/api/services/forge_projection.py b/api/services/forge_projection.py index f0c0a48..a0732f6 100644 --- a/api/services/forge_projection.py +++ b/api/services/forge_projection.py @@ -966,6 +966,17 @@ async def reset_repository_projection( continue changed = False + # The title is derived like every other field, and not syncing it left + # `cust-wp-0010` reading "Domain and Repository Goals" while its file + # said "Workplan Lifecycle Documentation" — a record correctly + # identified and correctly backed, describing the wrong work. + # + # An empty derived title is not an answer: three activity-core files + # parse to no title at all, and blanking a real one is worse than + # leaving it stale. + if w.title and w.title.strip() and row.title != w.title.strip(): + row.title = w.title.strip() + changed = True if w.status and row.status != w.status: row.status = w.status changed = True diff --git a/tests/test_forge_projection.py b/tests/test_forge_projection.py index 63f7592..fa8dbc9 100644 --- a/tests/test_forge_projection.py +++ b/tests/test_forge_projection.py @@ -198,11 +198,15 @@ class _Repo: class _Row: - def __init__(self, slug, status, path): + def __init__(self, slug, status, path, title=None): import uuid as _u self.id = _u.uuid4() self.slug = slug self.status = status + # The title is a derived field the reset syncs; the fixture carried no + # `title` at all, so it could not have caught the drift that left + # cust-wp-0010 describing another workplan's work. + self.title = title self.backing_relative_path = path self.backing_filename = path.rsplit("/", 1)[-1] self.backing_archived = False @@ -979,3 +983,50 @@ class TestCompetingClaims: assert src.index("UUID_MATCH, SLUG_MATCH, PATH_MATCH, PREFIX_MATCH = 0, 1, 2, 3") > 0 # Strength is ordered strongest-first, so a lower number wins. assert "if strength < held:" in src + + +class TestTitleIsDerivedToo: + """The title is a derived field and must follow the file. + + Not syncing it left `cust-wp-0010` correctly identified and correctly backed + while describing a different workplan's work. + """ + + def test_title_is_synced(self): + import inspect + src = inspect.getsource(fp.reset_repository_projection) + assert "row.title = w.title.strip()" in src + + def test_an_empty_derived_title_does_not_blank_a_real_one(self): + """Three activity-core files parse to no title; blanking is worse than stale.""" + import inspect + src = inspect.getsource(fp.reset_repository_projection) + i = src.index("row.title = w.title.strip()") + guard = src[max(0, i - 200):i] + assert "w.title and w.title.strip()" in guard + + +class TestTitleSyncBehaviour: + """Exercised against the fake session rather than asserted on source.""" + + @pytest.mark.asyncio + async def test_a_drifted_title_is_corrected(self): + row = _Row(slug="demo-wp-0001", status="active", + path="workplans/a.md", title="Some Other Workplan") + session = _FakeSession(repo=_Repo(), rows=[row]) + out = await fp.reset_repository_projection( + session, "demo", + derived=TestReset()._derived(("DEMO-WP-0001", "active", "workplans/a.md")), + ) + assert out.status == "applied" + assert row.title != "Some Other Workplan" + + @pytest.mark.asyncio + async def test_an_empty_derived_title_leaves_the_existing_one(self): + row = _Row(slug="demo-wp-0001", status="active", + path="workplans/a.md", title="Real Title") + d = TestReset()._derived(("DEMO-WP-0001", "active", "workplans/a.md")) + d.workplans[0].title = None + session = _FakeSession(repo=_Repo(), rows=[row]) + await fp.reset_repository_projection(session, "demo", derived=d) + assert row.title == "Real Title"