Skip to content

fix(themes): save-as-own provenance guard becomes an atomic conditional upsert (gh-#394) - #496

Merged
genwave-radio merged 2 commits into
mainfrom
fix/gh-394-save-as-own-toctou
Aug 13, 2026
Merged

fix(themes): save-as-own provenance guard becomes an atomic conditional upsert (gh-#394)#496
genwave-radio merged 2 commits into
mainfrom
fix/gh-394-save-as-own-toctou

Conversation

@genwave-radio

Copy link
Copy Markdown
Collaborator

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:

  • New 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 existing SlugHoldsAnImportedTheme 409). Separate method rather than a branch in UpsertAsync (which ThemesImportController still uses unconditionally), mirroring ShowRepository.ImportAsync's documented gh-save-as-own provenance guard is read-then-write (TOCTOU) — make it a conditional upsert #394 conditional-write form.
  • Pre-check GetBySlugAsync read 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.

…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.
@genwave-radio
genwave-radio merged commit 37ed0b4 into main Aug 13, 2026
11 checks passed
@genwave-radio
genwave-radio deleted the fix/gh-394-save-as-own-toctou branch August 13, 2026 19:23
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 13, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

save-as-own provenance guard is read-then-write (TOCTOU) — make it a conditional upsert

1 participant