fix(projection): a resurrected row must enter matched to be seen
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 21s

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 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
This commit is contained in:
tegwick 2026-08-28 11:48:51 +02:00
parent 95672e0c47
commit 5686b79d8c
2 changed files with 26 additions and 9 deletions

View file

@ -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

View file

@ -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`"