Fix atomic latest version update to prevent missing isLatest flags (#530)

## Summary
Fixes a race condition where a server could have no version marked as
`isLatest` if the database operations failed independently.

## Changes
- **CreateServer signature**: Now accepts `oldLatestVersionID` parameter
to atomically unmark previous latest version
- **PostgreSQL implementation**: Uses a transaction to ensure UPDATE
(unmark old latest) and INSERT (create new version) happen atomically
- **Service layer**: Maintains `WithPublishLock` for concurrent
operation serialization
- **Two-level protection**: Advisory lock prevents races, transaction
ensures atomicity

## Test plan
- [x] Existing race condition tests pass
- [x] Integration tests pass
- [x] Unit tests pass

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
adam jones 2025-09-24 18:27:36 +01:00 • committed by GitHub
commit 747ad310bb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 243 additions and 233 deletions

View file

@ -37,7 +37,7 @@ func (s *Service) ImportFromPath(ctx context.Context, path string) error {
// Import each server using CreateServer
for _, server := range servers {
_, err := s.db.CreateServer(ctx, server)
_, err := s.db.CreateServer(ctx, nil, server)
if err != nil {
return fmt.Errorf("failed to import server %s: %w", server.Name, err)
}
@ -181,4 +181,4 @@ func convertServerResponseToRecord(response apiv0.ServerJSON) *apiv0.ServerJSON
// The response is already in the correct flattened format
// Just return a pointer to it
return &response
}
}

View file

@ -57,7 +57,7 @@ func TestImportService_LocalFile(t *testing.T) {
require.NoError(t, err)
// Verify the server was imported
servers, _, err := testDB.List(context.Background(), nil, "", 10)
servers, _, err := testDB.List(context.Background(), nil, nil, "", 10)
require.NoError(t, err)
assert.Len(t, servers, 1)
assert.Equal(t, "io.github.test/test-server-1", servers[0].Name)
@ -101,7 +101,7 @@ func TestImportService_HTTPFile(t *testing.T) {
require.NoError(t, err)
// Verify the server was imported
servers, _, err := testDB.List(context.Background(), nil, "", 10)
servers, _, err := testDB.List(context.Background(), nil, nil, "", 10)
require.NoError(t, err)
assert.Len(t, servers, 1)
assert.Equal(t, "io.github.test/http-test-server", servers[0].Name)
@ -187,7 +187,7 @@ func TestImportService_RegistryAPI(t *testing.T) {
require.NoError(t, err)
// Verify both servers were imported
servers, _, err := testDB.List(context.Background(), nil, "", 10)
servers, _, err := testDB.List(context.Background(), nil, nil, "", 10)
require.NoError(t, err)
assert.Len(t, servers, 2)