Skip to content

feat(marketplace): versioning foundation + copy-on-publish (Phase A / Slices 1–2)#8514

Draft
LWS49 wants to merge 5 commits into
lws49/feat-marketplace-pr6d-auditfrom
lws49/feat-marketplace-pr8a-ver-foundation
Draft

feat(marketplace): versioning foundation + copy-on-publish (Phase A / Slices 1–2)#8514
LWS49 wants to merge 5 commits into
lws49/feat-marketplace-pr6d-auditfrom
lws49/feat-marketplace-pr8a-ver-foundation

Conversation

@LWS49

@LWS49 LWS49 commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

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_versions table + nullable current_version_id / provenance (source_course, source_course_name, fallback_maintainer, …) columns on listings + adopted_version / dismissed_version / reminders_muted on adoptions.
  • Course::Assessment::Marketplace::ListingVersion model + Listing versioning/provenance associations.
  • Course::Assessment::Marketplace.container — a single hidden system container Course located via Instance.default.settings(:marketplace).container_course_id (find-or-heal; no courses column).

Slice 2 — copy-on-publish + backfill

  • PublishService: on first publish, (re)activate the listing, capture provenance, snapshot the assessment into the container via ObjectDuplicationService, cut ListingVersion v1, point current_version at it. The MarketplaceListingsController#create seam delegates to it.
  • Idempotent ensure_first_version! + global backfill_all!, and a data-only migration that versions every existing published listing (v1) and stamps adopted_version = 1.

Deliberately NOT in this PR (Phase B, needs 7c present)

  • The assessment_id → authoring_assessment_id rename + atomic read-path repointing (browse/preview/PreviewAttemptsController/duplicate → current_version.assessment) — Slice 3.
  • Publish-new-version, fork/direct-edit, admin listings view — Slice 4.
  • Freshness banner / dismiss / mute / "Import latest version" — Slice 5.

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)

  • Cyclic inverse_of: ListingVersion belongs_to :listing, inverse_of: :versions can't validate without Listing has_many :versions, so the plan's Task 1 & Task 2 aren't green in isolation → folded into one commit.
  • backfill_all! scope changed from where(current_version_id: nil) to where.missing(:versions) — keys idempotency on version-existence (equivalent in production, robust to partial state).
  • Provenance: source_course_code / source_academic_period are reserved-nil — Coursemology models neither a course code nor an academic period.

Testing

  • Automated: full marketplace regression 140/140 green across multiple seeds (model + service + controller). TDD throughout; both slices' idempotency/provenance guards verified by mutation.
  • Code review: dispatched reviewer — verdict "with fixes," no Critical_; the blocking test-hygiene finding (orphan container-course accumulation in the non-transactional test DB) is fixed in d8b1f5af7.
  • Manual: not yet performed — a step-by-step manual test guide is at docs/superpowers/testing/marketplace/versioning-phase-a-manual-testing.md (console-verification based, since Phase A has no new UI).

Stacking

LWS49 added 5 commits July 24, 2026 17:26
…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
LWS49 force-pushed the lws49/feat-marketplace-pr6d-audit branch from 74232c9 to ec36419 Compare July 24, 2026 09:29
@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr8a-ver-foundation branch from d8b1f5a to 79ce311 Compare July 24, 2026 09:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant