fix(projection): a retirement must survive the next pass
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 24s

Five rows came back tombstoned but not retired — a zombie beside the correct
record derived from the same file. Two of my own changes combined to produce it.

Releasing the identifier destroys the evidence that made the row a re-key: the
tombstoned slug is no longer a work-record identifier, and a legacy row is not
UUID-derived, so both guards fall through to path matching — which matches the
very file the row was retired for and resurrects it.

And the release loop ran before matching, so it also stamped rows that were
about to derive again, leaving them unable to match their own file and never
un-retired.

Two changes. A retired row is now matched by UUID or by its own identifier, and
never by the path heuristics; a record that genuinely returns still un-retires,
because its identifier is unchanged. And the release exempts any row the forge
still derives.

746 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:16:12 +02:00
parent 240627937d
commit 3c884dd3f9
2 changed files with 80 additions and 11 deletions

View file

@ -887,3 +887,46 @@ status: todo
def test_uuid_follows_the_qualified_id(self):
t = fp._parse_tasks(self.BODY, "LLM-WP-0001")[0]
assert t.uuid == fp.derived_record_uuid("LLM-WP-0001-T01")
class TestRetirementIsNotUndoneByPath:
"""A retired row is matched by UUID or not at all.
Releasing the identifier removes the evidence that made the row a re-key:
the tombstoned slug is no longer an identifier, and a legacy row is not
UUID-derived, so both guards fall through to path matching which matches
the very file the row was retired for and resurrects it beside the correct
record created from that same file.
"""
def test_a_tombstoned_slug_is_not_an_identifier(self):
"""This is the signal the tombstone destroys."""
assert fp._slug_is_identifier("adhoc-2026-06-01")
assert not fp._slug_is_identifier("adhoc-2026-06-01@retired-20260827")
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:")
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"
def test_uuid_match_still_precedes_the_skip(self):
"""A record that genuinely returns must still be un-retired."""
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:"
)
def test_a_row_that_derives_again_is_not_tombstoned(self):
"""Releasing it would change the slug out from under the matching.
The row would then fail to match its own file and never be un-retired
which is how five rows ended up tombstoned but not retired.
"""
import inspect
src = inspect.getsource(fp.reset_repository_projection)
rel = src.index("outcome.released.append(before)")
guard = src.index('in want or str(r.id) in want_by_uuid')
assert guard < rel, "the release must exempt records the forge still derives"