fix(themes): save-as-own provenance guard becomes an atomic conditional upsert (gh-#394) - #496
Merged
Merged
Conversation
…t (gh-#394) Replaces ThemesSaveAsOwnController's read-then-write provenance guard (GetBySlugAsync check, then a plain UpsertAsync) with IThemeStore.SaveAsOwnAsync — a single INSERT ... ON CONFLICT (slug) DO UPDATE ... WHERE imported_from IS NULL statement. Zero rows affected (an import committed to the slug between the former read and write) now maps to the same SlugHoldsAnImportedTheme 409, but the repository itself is the one refusing — there is no gap left for a concurrent import to land in.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #394.
The T207 fail-closed ruling (an authored save must never erase an imported theme's provenance) was enforced by a read-then-write pair — a save-as-own racing an import to the same slug could still clobber the imported row and NULL its provenance. Per the reviewer-prescribed fix:
IThemeStore.SaveAsOwnAsync:INSERT … ON CONFLICT (slug) DO UPDATE … WHERE station.theme.imported_from IS NULL— the WHERE gates the UPDATE arm only, so a fresh slug inserts plainly, an authored re-save updates, and a conflict with an imported row does neither (zero rows →false→ the existingSlugHoldsAnImportedTheme409). Separate method rather than a branch inUpsertAsync(whichThemesImportControllerstill uses unconditionally), mirroringShowRepository.ImportAsync's documented gh-save-as-own provenance guard is read-then-write (TOCTOU) — make it a conditional upsert #394 conditional-write form.GetBySlugAsyncread removed — the write IS the check now; response contract byte-identical.New Postgres-backed specs (dedicated disposable container, not the shared dev db) prove the repository itself refuses with the row byte-for-byte untouched, plus fresh-insert and authored-re-save success. Build zero warnings; MediaLibrary theme specs 10/10, Host theme specs 19/19, Architecture 104/104. 🤖 built by a bash-day builder agent, reviewed by ATLAS.