From 5686b79d8c85708af88bad43d196a579a3eb72a5 Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 28 Aug 2026 11:48:51 +0200 Subject: [PATCH] fix(projection): a resurrected row must enter `matched` to be seen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous commit made a stamped slug count as retired, and skipped such rows before the heuristics. That is right for a genuinely retired row and wrong for a resurrected one: `stale` is computed from `matched`, so a row that never enters `matched` is invisible — not retired, so not stale; path-matching its own file, so never reported. All six kept returning `noop`. A row whose slug carries the mark while its flag is clear now lands in `matched` under its own key, which is what makes it stale and therefore retirable. A row that is actually retired is still skipped outright: nothing to do, nothing to re-decide. This is the same mistake as the original path fallback, in the opposite direction: there a row was matched when it should not have been, here it was not matched when it had to be. 748 pass. Co-Authored-By: Claude Opus 5 Assistant: claude-code Assistant-Model: opus Assistant-Process: 2583210@bnt-lap001 Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006 --- api/services/forge_projection.py | 16 ++++++++++------ tests/test_forge_projection.py | 19 ++++++++++++++++--- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/api/services/forge_projection.py b/api/services/forge_projection.py index 28f1f35..758cc0a 100644 --- a/api/services/forge_projection.py +++ b/api/services/forge_projection.py @@ -732,12 +732,16 @@ async def reset_repository_projection( # which is then deliberately un-retired below. matched[key] = row continue - if row.projection_retired_at is not None or RETIRED_SLUG_MARK in (row.slug or ""): - # A stamped slug is itself evidence of a retirement, so a row - # carrying the mark with a cleared flag is a contradiction to - # repair rather than a live record — six rows reached that state - # before the resurrection was fixed, and nothing else can see them: - # they path-match their own file and report `noop` forever. + if row.projection_retired_at is None and RETIRED_SLUG_MARK in (row.slug or ""): + # A stamped slug with a cleared flag is a resurrected row: the mark + # is evidence a retirement happened and the flag says it did not. + # It must land in `matched` under its own key so it shows up as + # stale — skipping it outright, as a genuinely retired row is + # skipped, is what left these reporting `noop` forever. + matched[key] = row + continue + if row.projection_retired_at is not None: + # Already retired: nothing to do, and nothing to re-decide. # # Past this point only the heuristics remain, and a retired row must # not be matched by those. Retirement is a diff --git a/tests/test_forge_projection.py b/tests/test_forge_projection.py index a1ff7ac..f0cb801 100644 --- a/tests/test_forge_projection.py +++ b/tests/test_forge_projection.py @@ -907,7 +907,7 @@ class TestRetirementIsNotUndoneByPath: def test_retired_rows_are_skipped_before_path_matching(self): import inspect src = inspect.getsource(fp.reset_repository_projection) - skip_at = src.index("if row.projection_retired_at is not None or RETIRED_SLUG_MARK") + skip_at = src.index("if row.projection_retired_at is not None:") path_at = src.index("if bp and _path_key(bp) in want_paths:") assert skip_at < path_at, "retired rows must be skipped before path matching" @@ -916,7 +916,7 @@ class TestRetirementIsNotUndoneByPath: import inspect src = inspect.getsource(fp.reset_repository_projection) assert src.index("want_by_uuid.get(str(row.id))") < src.index( - "if row.projection_retired_at is not None or RETIRED_SLUG_MARK" + "if row.projection_retired_at is not None:" ) def test_a_row_that_derives_again_is_not_tombstoned(self): @@ -935,4 +935,17 @@ class TestRetirementIsNotUndoneByPath: """Otherwise a resurrected row path-matches its file and reports noop forever.""" import inspect src = inspect.getsource(fp.reset_repository_projection) - assert 'projection_retired_at is not None or RETIRED_SLUG_MARK in' in src + assert "projection_retired_at is None and RETIRED_SLUG_MARK in" in src + + def test_a_resurrected_row_becomes_stale_rather_than_being_skipped(self): + """Skipping it outright is what left six rows reporting noop forever. + + `stale` is computed from `matched`, so a row that never enters `matched` + is invisible: not retired, so not stale; path-matching its own file, so + never reported. + """ + import inspect + src = inspect.getsource(fp.reset_repository_projection) + zombie = src.index("projection_retired_at is None and RETIRED_SLUG_MARK in") + tail = src[zombie:zombie + 700] + assert "matched[key] = row" in tail, "a resurrected row must enter `matched`"