feat(marketplace): versioning foundation + copy-on-publish (Phase A / Slices 1–2)#8514
Draft
LWS49 wants to merge 5 commits into
Draft
Conversation
…tions Add the additive versioning foundation: the listing_versions table, nullable version/provenance/freshness columns on listings and adoptions, the ListingVersion model + factory, and the Listing versioning + provenance associations. belongs_to :listing (inverse_of: :versions) and Listing has_many :versions are a mutually-dependent pair, so Slice 1 Tasks 1 and 2 land together in one commit (neither is green in isolation).
On first publish, PublishService (re)activates the listing, captures denormalized provenance (source course + name + fallback maintainer; code/period reserved-nil — Coursemology has no such fields), snapshots the source assessment into the hidden container course via ObjectDuplicationService, and cuts ListingVersion v1 with current_version pointing at the snapshot. The controller create action delegates to it. Also exposes idempotent ensure_first_version! and a global backfill_all! (scoped to never-versioned listings via where.missing(:versions) so reruns are safe). No rename, no read repoint this slice — listing.assessment still resolves to the origin.
…ity assertion Code-review follow-up on Slice 1: - Convert the .container before-hook to an around-hook that destroys the container course each example created and restores the settings pointer, so the permanently-committing test DB no longer accumulates orphan Version Store courses across runs. - Strengthen the 'no other user sees it' example: enroll other_user in a real course and assert containing_user surfaces THAT course but never the container, so the negative assertion is no longer vacuous.
LWS49
force-pushed
the
lws49/feat-marketplace-pr6d-audit
branch
from
July 24, 2026 09:29
74232c9 to
ec36419
Compare
LWS49
force-pushed
the
lws49/feat-marketplace-pr8a-ver-foundation
branch
from
July 24, 2026 09:29
d8b1f5a to
79ce311
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
Phase A of Assessment Marketplace versioning — the additive data spine + the copy-on-publish snapshot engine. Reverses the MVP live-reference model without touching any read path yet: publishing now cuts an immutable version 1 snapshot into a hidden container course and records provenance, while browse / preview / duplicate still read the origin. Purely additive, so it stays merge-safe with the PreviewAttempt line (7c).
Built per
docs/superpowers/plans/versioning-execution/(gameplan + Slice 1 & 2 plans) on top of the versioning design spec (decisions V1–V12).What's in it (5 commits, 2 slices)
Slice 1 — foundation
listing_versionstable + nullablecurrent_version_id/ provenance (source_course,source_course_name,fallback_maintainer, …) columns on listings +adopted_version/dismissed_version/reminders_mutedon adoptions.Course::Assessment::Marketplace::ListingVersionmodel +Listingversioning/provenance associations.Course::Assessment::Marketplace.container— a single hidden system containerCourselocated viaInstance.default.settings(:marketplace).container_course_id(find-or-heal; nocoursescolumn).Slice 2 — copy-on-publish + backfill
PublishService: on first publish, (re)activate the listing, capture provenance, snapshot the assessment into the container viaObjectDuplicationService, cutListingVersionv1, pointcurrent_versionat it. TheMarketplaceListingsController#createseam delegates to it.ensure_first_version!+ globalbackfill_all!, and a data-only migration that versions every existing published listing (v1) and stampsadopted_version = 1.Deliberately NOT in this PR (Phase B, needs 7c present)
assessment_id → authoring_assessment_idrename + atomic read-path repointing (browse/preview/PreviewAttemptsController/duplicate →current_version.assessment) — Slice 3.There is no user-visible change in this PR: no new UI, and the only action that behaves differently under the hood is the existing admin-only Publish to Marketplace.
Deviations from the plans (each verified)
inverse_of:ListingVersion belongs_to :listing, inverse_of: :versionscan't validate withoutListing has_many :versions, so the plan's Task 1 & Task 2 aren't green in isolation → folded into one commit.backfill_all!scope changed fromwhere(current_version_id: nil)towhere.missing(:versions)— keys idempotency on version-existence (equivalent in production, robust to partial state).source_course_code/source_academic_periodare reserved-nil— Coursemology models neither a course code nor an academic period.Testing
d8b1f5af7.docs/superpowers/testing/marketplace/versioning-phase-a-manual-testing.md(console-verification based, since Phase A has no new UI).Stacking
lws49/feat-marketplace-pr6d-audit. Sibling of the PreviewAttempt line (PR7a — feat(assessment): Attempt base-record split (Phase 1, additive) #8510→PR7b — feat(marketplace): preview attempt (Phase 2) #8511→PR 7c - feat(marketplace): preview attempt frontend #8513) — both branch frompr6d-auditand converge before Slice 3.