fix(projection): retirement must release the identifier it holds
`slug` is unique across the whole table, so retiring a row by timestamp alone leaves its identifier locked to a record nothing derives any more — and the repository that legitimately owns it can never claim it. core-hub's inherited REPO-WP-0001/0002 rows retired cleanly and repo-seed stayed refused with "slug already belongs to another repository", pointing at two retired rows. Retirement now stamps the slug `<slug>@retired-YYYYMMDD`. The row, its UUID and its progress events are untouched, so history stays attached to the record it happened under; only the human-facing identifier is freed. Re-retiring an already-stamped row replaces the mark rather than stacking it, or the 100-char column overflows after a few passes. The UUIDs never collided here: core-hub's rows predate derived identity and carry v4 UUIDs, while repo-seed derives v5 ones. The identifier was the only thing in the way. 733 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:
parent
4b121f3796
commit
70c585bb39
3 changed files with 60 additions and 11 deletions
|
|
@ -752,3 +752,29 @@ class TestRekeyIsNotRename:
|
|||
def test_title_slugs_keep_path_matching(self, slug):
|
||||
"""Hub-first rows never claimed an identifier; the path is their only link."""
|
||||
assert not fp._slug_is_identifier(slug)
|
||||
|
||||
|
||||
class TestRetirementReleasesTheIdentifier:
|
||||
"""`slug` is unique table-wide, so retirement must free it.
|
||||
|
||||
Retirement that only sets a timestamp leaves the identifier locked to a
|
||||
record nothing derives, and the repository that owns it can never claim it.
|
||||
"""
|
||||
|
||||
def test_the_identifier_is_released(self):
|
||||
from datetime import datetime, timezone
|
||||
when = datetime(2026, 8, 28, tzinfo=timezone.utc)
|
||||
assert fp._tombstone_slug("repo-wp-0001", when) == "repo-wp-0001@retired-20260828"
|
||||
|
||||
def test_re_retiring_does_not_stack_marks(self):
|
||||
"""Otherwise the 100-char column overflows after a few passes."""
|
||||
from datetime import datetime, timezone
|
||||
a = fp._tombstone_slug("repo-wp-0001", datetime(2026, 8, 28, tzinfo=timezone.utc))
|
||||
b = fp._tombstone_slug(a, datetime(2026, 9, 1, tzinfo=timezone.utc))
|
||||
assert b == "repo-wp-0001@retired-20260901"
|
||||
assert b.count(fp.RETIRED_SLUG_MARK) == 1
|
||||
|
||||
def test_a_long_slug_stays_within_the_column(self):
|
||||
from datetime import datetime, timezone
|
||||
out = fp._tombstone_slug("x" * 140, datetime(2026, 8, 28, tzinfo=timezone.utc))
|
||||
assert len(out) <= 100
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue