feat(marketplace): serve versioned snapshots instead of live content - #8524
Open
LWS49 wants to merge 6 commits into
Open
feat(marketplace): serve versioned snapshots instead of live content#8524LWS49 wants to merge 6 commits into
LWS49 wants to merge 6 commits into
Conversation
LWS49
force-pushed
the
lws49/feat-marketplace-pr7a-versioning-foundation
branch
from
July 29, 2026 13:32
b54c334 to
b1449fb
Compare
Collaborator
Author
|
Note on the |
A version IS its publication datetime, and the snapshot it serves is duplicated into a `preview` container course so a published version can never change under the courses that adopted it. Non-admins cannot edit container content.
Deleting the source assessment now orphans the listing instead of destroying it: the marketplace keeps serving the snapshot, and the authoring copy is rebuilt in the container automatically so a new version can still be published.
LWS49
force-pushed
the
lws49/feat-marketplace-pr7a-versioning-foundation
branch
from
July 30, 2026 02:02
b1449fb to
0bbcfea
Compare
Nothing rolls back in this suite, so two unscoped `delete_all`s leaked into every later spec file and failed them under some seeds: `User::Email.delete_all` stripped the address off every earlier file's users (surfacing as `SMTP To address may not be blank` in the mailer specs and a nil `user.email` in the external-assessment import specs), and allow-list rows left behind granted `:access_marketplace` to users the ability spec asserts cannot have it. Scope the email cleanup to the domains that file hardcodes, and clear the allow-list tables in the ability spec the way access_list_query_spec.rb already does.
LWS49
force-pushed
the
lws49/feat-marketplace-pr7a-versioning-foundation
branch
from
July 30, 2026 02:58
99f8c3c to
213421c
Compare
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Gives the Assessment Marketplace a versioned content model. Until now a listing served whatever its source assessment currently contained, so an adopter's copy and the marketplace's own preview both drifted the moment the publisher edited anything, and deleting the source course destroyed the listing along with every adopter's adoption record. This PR introduces
Course::Assessment::Marketplace::ListingVersion: publishing snapshots the assessment into a single content-frozen container course, and the listing points at that snapshot rather than at the live source.It also makes losing a source survivable.
authoring_assessment_idbecomes nullable with a nullifying foreign key, so deleting the origin orphans a listing instead of cascading through its version chain, and a background job rebuilds the authoring copy inside the container so the listing can be published from again.Nothing here is user-visible. The surfaces that read this model land in the two PRs stacked on top.
The first three commits are pre-existing bug fixes that the versioning work happened to surface, kept separate so they stay individually cherry-pickable: a nil-course guard in the Cikgo destroy push, a toast that could not render a React node, and admin nav losing its selected tab on nested routes.
Design decisions
published_atas the listing's first-publication date is what removes the read-time special case for v1 entirely.Course#previewflag, and those same rows are what marketplace previews run against - a snapshot is the preview copy, so a preview can never show content that differs from what a duplicate would produce. One flag serves both the previewer sandbox and the snapshots' immutability guarantee.Course::Assessment#updated_atdoes not move when a question, option, test case or rubric is edited, and walking the object graph misses edits below any fixed depth and misses deletions entirely, so the publisher decides when to cut a version.Course#set_defaultsfabricatesend_atfromstart_atso an untouched default is indistinguishable from a real range, and a value frozen at first publish would contradict the live course linked beside it. Version datetime pluslast_published_atare the real staleness signals and both already exist.db:migratefails onPG::UndefinedColumnwhiledb:schema:loadmasks it.Regression prevention
Covers: version creation and the datetime uniqueness constraint per listing; first-publish versus deliberate re-cut, and that re-listing an already-versioned listing does not mint a version; provenance capture surviving origin-course deletion; the orphaned, unlisted, purgeable, marketplace-hosted and source-deleted predicates including the case where a rebuilt listing is simultaneously visible and origin-deleted; cross-tenant container resolution, which is the regression the tenant-free lookups exist for; the restore-authoring job and the purge service, including that snapshots are destroyed after their version rows so the foreign key holds; ability rules for the frozen container; and both backfills being idempotent.
Manually verified: publishing cuts a version and points the listing at the container snapshot; deleting the origin course orphans the listing and rebuilds its authoring copy in the container.
Also adds a regression test for the Cikgo destroy push, which previously raised when an assessment was destroyed as part of its whole course.
Backward compatible. The migration backfills every existing published listing to a v1 snapshot and stamps its adoptions, so no listing is left version-less; existing adopted copies are untouched.