fix(projection): refuse slug collisions, and record the first fleet 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 26s

The identifier refusal checked id only. slug carries its own unique constraint
across the whole table, so two repositories can derive different identifiers
whose slugs still collide — which left disaster-control raising IntegrityError.

First fleet-wide pass over 121 repositories: 91 applied (737 updated, 8
created), 16 refused covering 64 records, 12 errored. 745 workplans now carry
the commit they derived from, satisfying ADR-012 decision 2 for the first time.

64 is the measured size of the stale-row problem CUST-WP-0068-T09 has waited on.

Eleven of the twelve errors are private repositories the pod cannot clone
anonymously — a real limit on "the forge is the projection source", since their
absence currently looks like an error rather than a policy.

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-26 16:39:26 +02:00
parent 5d09e8f5b0
commit 6c1262ef6e
3 changed files with 119 additions and 3 deletions

View file

@ -212,10 +212,11 @@ class _Row:
class _FakeSession:
"""Stands in for AsyncSession: enough to prove intent without a database."""
def __init__(self, repo, rows, foreign=None):
def __init__(self, repo, rows, foreign=None, slug_clash=None):
self._repo = repo
self.rows = list(rows)
self._foreign = list(foreign or [])
self._slug_clash = list(slug_clash or [])
self.added = []
self.deleted = []
self.committed = False
@ -226,7 +227,16 @@ class _FakeSession:
repo, rows = self._repo, self.rows
# 1st call resolves the repo, 2nd loads its workplans, 3rd is the
# foreign-identifier lookup.
payload = rows if self._calls == 2 else self._foreign
# 1 resolves the repo, 2 loads its workplans, 3 is the identifier
# lookup, 4 the slug lookup.
if self._calls == 2:
payload = rows
elif self._calls == 3:
payload = self._foreign
elif self._calls == 4:
payload = self._slug_clash
else:
payload = []
class R:
def scalar_one_or_none(self_inner):
@ -384,3 +394,26 @@ class TestFleetReset:
await fp.reset_fleet_projection(self._Factory(s), ["applier", "refuser"])
assert s[0].committed is True
assert s[1].committed is False
class TestSlugCollisionRefusal:
"""slug carries its own unique constraint (STATE-WP-0083-T04).
Checking the identifier alone left disaster-control raising IntegrityError:
two repositories can derive different identifiers whose slugs still collide.
"""
@pytest.mark.asyncio
async def test_refuses_when_the_slug_belongs_elsewhere(self):
derived = fp.DerivedProjection(
repo_slug="demo", commit="c0ffee",
workplans=[fp.DerivedWorkplan(
record_id="REPO-WP-0001", uuid=fp.derived_record_uuid("REPO-WP-0001"),
title="x", status="active", relative_path="workplans/a.md",
archived=False, tasks=[])])
clash = _Row(slug="repo-wp-0001", status="finished", path="workplans/other.md")
session = _FakeSession(repo=_Repo(), rows=[], foreign=[], slug_clash=[clash])
out = await fp.reset_repository_projection(session, "demo", derived=derived)
assert out.status == "refused"
assert out.refused[0]["reason"].startswith("slug already belongs")
assert session.added == []