DataTalksClub / DataTalksClub/website

Preserve cohort learner preferences, progress, dashboard, and calendar behavior

Open
#244 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

accessibility courses data-migration enhancement frontend integration P0 security testing
Dominant language
Python
Stars
0
Forks
0
PR merge metrics
No merged PRs in 30d

Description

Parent epic: #54.

Outcome

Prove that the accepted Course → Cohort and account-owned registration/enrollment changes preserve every adopted learner preference, cohort record, historical destination, progress/result projection, calendar identity, and historical Wrapped reference without cross-cohort leakage. One learner may hold independent enrollments in multiple Cohorts of one Course; a new registration, profile edit, preference edit, score operation, identity alias, or campaign repoint never selects, resets, copies, or rewrites another Cohort's state.

This is a preservation/compatibility slice, not a redesign. Implement only the query/service seams, relationship corrections, compatibility adapters, migration fixtures, and render changes required to keep the characterized behavior valid on the final accepted foundations.

Normative authority

Dependency, characterization, and final-integration order

#244 has exactly three direct source prerequisites. Characterization engineering starts only from one fresh base containing accepted and integrated:

  1. #230 for the adopted peer-review-link behavior preserved here;
  2. #242 for the additive immutable Cohort registration identity and bounded registration migration/quarantine interface; and
  3. #248 for incomplete-account behavior, profile/account-settings routes, and accounts-owned compatibility reads.

These inputs may be delivered independently, but all three must be present by exact merge SHA before a #244 characterization candidate starts. #224/#51 and #231/#234/#247 are transitive foundations already consumed through #242/#248. Their accepted identities, migration leaves, and adopted-source pin remain provenance in the handoff; they are not duplicate direct #244 blockers and #244 does not rerun their acceptance.

#243 owns the explicit registration → Enrollment conversion primitive. It may progress in parallel under its own exact prerequisites (#242, #248, and the ordinary #49 delivery interface), and it does not depend on #230 or on an accepted #244. A #244 engineer may characterize existing enrollments after the three direct source prerequisites above if that work does not change or simulate a #243-owned interface. That characterization is not a final frozen lifecycle handoff.

After #243 is accepted and integrated, the #244 engineer must rebuild/replay the bounded preservation candidate on that current base, record the exact #243 merge SHA, exercise the combined later-Cohort registration/conversion scenarios, regenerate the selective-CI graph/plan and evidence envelopes, and freeze the final uncommitted candidate. Only that post-#243 candidate may enter independent tester and PM acceptance. Evidence may be reused only where the exact validated envelope proves #243 cannot affect it; an older base, plan, or digest is not final evidence.

#49/#50/#227 own email intent, transport, and preference-purpose cutover and are not direct #244 dependencies because #244 sends nothing. #286 and #287 are separate and do not block this preservation slice because it implements neither CourseInterest nor legacy contraction. #60 is downstream: it consumes the accepted #244 fixtures/evidence for authorized production-like rehearsal and redirect compatibility, while #244 performs neither #60 nor production cutover.

Record the direct and inherited identities above in the engineer handoff. Any later prerequisite change touching the characterized fields, routes, mappings, migrations, or interfaces invalidates the affected frozen plan/evidence and returns the candidate to engineering.

Exact preserved data contract

Enrollment identity and preferences

For each legacy/target Enrollment preserve its stable legacy primary key or one retained one-to-one mapping, learner identity resolved through the accepted durable-account/survivor alias, immutable Cohort relation, and original enrollment_date. The database identity is one Enrollment per learner plus Cohort; two Cohorts of the same Course are independent.

Preserve these Cohort-specific values exactly, including explicit false/blank/null distinctions:

  • display_name (generated leaderboard alias), display_on_leaderboard, display_public_profile, and position_on_leaderboard;
  • historical certificate_name, certificate_url, and the accepted explicit Certificate relation/state supplied by the final model;
  • disable_learning_in_public and the already-recorded effect on component and total scores;
  • total_score and every stored/recomputed Cohort rollup; and
  • all created/submitted/scored/reviewed/issued timestamps linked to that Enrollment.

A later Cohort Enrollment starts with the accepted new-enrollment defaults. It does not copy the earlier display alias, visibility, certificate URL/state, learning-in-public choice, score, position, submissions, or results. Updating one Enrollment changes only that Enrollment and invalidates/recomputes only its Cohort projection. Profile/account certificate-name edits affect the accepted future prefill/default path only; they never rewrite a historical Enrollment or issued certificate.

The adopted account/contact communication preference keys email_submission_confirmations and email_deadline_reminders remain attached to the durable account/contact preference boundary, not cloned into an Enrollment or silently reset on later registration. Cohort recipient selection must still use the intended Cohort/Enrollment and then honor the current preference projection. This issue adds no category, consent inference, send, callback, template, Relay/Datamailer write, or transport behavior.

Learner work and progress

Mechanically retain the original Cohort and Enrollment ownership, primary key/mapping, values, and timestamps for:

  • Homework, Question, Submission, Answer, optional-field values, answer-check state, FAQ and learning-in-public contributions, component scores, total score, submission/update time, reveal/statistics state;
  • Project, ProjectSubmission, GitHub/commit reference, optional-field values, peer assignments/reviews, criteria responses, votes, volunteer-review state, pass/fail, component/total scores, result/statistics state, and timestamps;
  • leaderboard rows/breakdown/complaints, graduate/certificate state, course-page progress badges and submission counts, and Cohort dashboard aggregates; and
  • WrappedStatistics, UserWrappedStatistics, stored historical JSON, rank/display name, Course/Cohort/enrollment references, totals, and historical links.

Where a legacy child stores both student and enrollment, preflight must prove that child.student == enrollment.student and that assignment/content belongs to enrollment.cohort. A mismatch, missing relation, duplicate logical answer/submission/review/vote, alias collision, or cross-Cohort reference is a fail-closed quarantine/reconciliation case. It is never reassigned by matching email, current campaign, family, slug/year inference, newest Cohort, or arbitrary primary key.

Derived progress/scores/statistics are recalculated from the same cohort-scoped inputs and compared with stored values. An unexplained difference blocks validation; migration never silently replaces the historical value merely to make a check pass. Historical Wrapped JSON keeps its existing interpretation even while new Wrapped generation remains disabled.

Read and history behavior
  • An incomplete MemberProfile never blocks login, recovery, settings, account preferences, an existing Cohort page, owned enrollment/history, owned homework/project work, score/certificate access, or historical Wrapped access. It blocks only each new course registration and first Slack access under #108/#243.
  • Reads never create an Enrollment, reset defaults, update profile/account projections, or mutate progress. Enrollment creation/conversion belongs only to #243's explicit service.
  • Absorbed legacy account/session/token ownership resolves through the accepted durable alias to the survivor. The survivor sees the same historical records once; the source and survivor do not receive duplicate rows or two independent authorities. Quarantined, broken-survivor, inactive, or colliding identity fails closed without exposing record existence.
  • A learner viewing Cohort B cannot read or mutate Cohort A state by changing an Enrollment, submission, review, result, certificate, or alias identifier. Public aggregate/configured-public views remain public only to the exact characterized degree and never gain private learner values.
  • Empty Cohorts and learners with no work render established empty/progress states rather than errors, invented zero certificates, or state borrowed from another Cohort.

Routes, aliases, and calendar contract

Open decision 5 overrides the older /cohorts/ wording in specification 04. The canonical Cohort base is:

/courses/<course-slug>/<cohort-identifier>

Preserve canonical destinations under that base for the accepted course page, dashboard, calendar.ics, enrollment, homework/<homework-slug> and its characterized stats/submissions paths, project/<project-slug> and its characterized list/eval/results/stats/submissions paths, projects, leaderboard/breakdown/report, and any accepted certificate destination. There is currently no requirement in #244 to invent a new local certificate route: retained external/legacy certificate URLs and the explicit Certificate model/route accepted in #51 remain authoritative.

Preserve the adopted historical Wrapped routes /courses/wrapped/<year>/ and /courses/wrapped/<year>/<legacy-student-id>/ through retained user/enrollment mappings. Generated links point to the exact mapped Course/Cohort; they never guess a family from a year or silently point an old record to the latest Cohort.

All reviewed legacy edition-slug HTML/calendar/API aliases from #224/#51 and the #60 compatibility manifest resolve directly to the same historical Cohort and suffix. Unknown, ambiguous, colliding, or unreviewed aliases are real 404/safe failures. #244 validates adapters; it does not activate the legacy-host redirect or change #60 infrastructure.

ICS behavior is exact:

  • homework event UID stays homework-<retained-legacy-homework-id>@courses.datatalks.club;
  • project UIDs stay project-<retained-legacy-project-id>-submission@courses.datatalks.club and project-<retained-legacy-project-id>-peer-review@courses.datatalks.club;
  • the suffix never changes with host, canonical URL, Course/Cohort rename, learner, deadline, or migration;
  • a deadline/title/description/canonical-link edit updates that VEVENT under the same UID; an event ID is never recycled for another assignment/Cohort;
  • preserve the adopted cancellation/removal behavior: the current feed has no SEQUENCE or STATUS:CANCELLED contract, so #244 does not invent one. Archived/cancelled Cohort history and its feed remain readable during the compatibility/rollback window; a removed event follows the characterized omission behavior while its UID is never reassigned; and
  • canonical and reviewed legacy calendar URLs return equivalent event sets/UIDs. The calendar is Cohort-scoped, contains no learner identity, and never combines sibling Cohorts.

Visibility and security disposition

No owner decision is required to groom this slice because it authorizes no intentional visibility change. Preserve the adopted public/private visibility of submissions, results, leaderboards, dashboards, certificates, and Wrapped exactly. Do not make a currently public result private, make a private learner value public, or broaden a public listing as a side effect of structural work.

Mandatory tightening that does not change product visibility is in scope: authoritative Cohort/Enrollment object scoping, generic 404/denial for mismatched objects, CSRF for mutations, owner/capability checks where already required, private, no-store for personalized/credentialed/error responses, zero shared-cache TTL for dashboard/calendar/enrollment/homework/submission/review/leaderboard-preference/certificate learner state, noindex where required, and bounded/redacted logs. The anonymous aggregate dashboard/calendar or configured-public results may retain characterized anonymous access but remain zero-TTL/no-store under specification 04.

If characterization discovers that preserving current behavior would disclose protected learner data contrary to specification 07, or that a route's current visibility is ambiguous, engineering stops that route and files a separate decision issue with the exact before/after evidence. It must not hide a privacy change inside #244.

No raw email, name, certificate value/URL, submission text/link, review content, preference payload, account alias/UID, token, cookie, IP, or production learner data may appear in logs, metrics, audit text, issue reports, committed fixtures, screenshots, or reversible checksums. Synthetic .invalid identities and safe synthetic URLs only.

Migration, reconciliation, and rollback fixtures

#244 owns deterministic synthetic characterization plus only the bounded learner-relationship, query/service, compatibility-adapter, or data migration needed by its final preservation implementation. It consumes and must not redefine, replay as its own, or broaden #51's Course/Cohort mapping, #230's peer-review fix, #242's registration schema/backfill/quarantine contract, #247/#248's account/profile migrations and write authority, or #243's registration/conversion service. If no #244-owned schema/data change is necessary, migration apply/reverse is not_applicable with graph evidence rather than invented work.

All #244 migration and rollback evidence uses isolated synthetic .invalid data. It may prove its own expand-state compatibility, deterministic preflight, apply/replay/reverse/forward behavior, and compatible application reads; it never accesses protected data, executes a production migration, activates a writer/constraint/redirect/sender, removes a legacy field/mapping, or claims a clean observation window. #60 alone later repeats the accepted combined evidence on an explicitly authorized anonymized production-like source; #287 separately owns any future destructive-contract decision and observation gate.

Build one deterministic synthetic compatibility fixture containing at least:

  1. one reusable Course with two Cohorts plus one unrelated Course/Cohort;
  2. one durable learner enrolled in both sibling Cohorts with deliberately different display/visibility/learning-in-public/certificate/score/timestamp state and work in both;
  3. a second learner used to prove public/another-user boundaries;
  4. homework submissions/answers and project submissions/reviews/criteria/votes/results in each sibling Cohort, including optional/blank/null values and recomputable scores;
  5. an incomplete-profile learner with existing enrollment/history;
  6. a reviewed absorbed-account alias with historical rows plus quarantined/broken/colliding negative cases;
  7. stable legacy Cohort/homework/project/enrollment/student aliases, both calendar URLs, retained certificate URL/state, and historical Wrapped JSON pointing to a specific Cohort/enrollment; and
  8. explicit corruption fixtures for student/enrollment mismatch, cross-Cohort child relation, duplicate logical row, missing mapping, unknown alias, and score/checksum mismatch.

The preflight is read-only, deterministic, and reports bounded reason counts, relation counts, and safe non-PII checksums. Apply is transactional, idempotent, resumable, and backend-portable. Run dry-run, apply twice, supported reverse, and forward again. Preserve row/key/mapping cardinality, null/blank distinctions, timestamps, relationship checksums, per-Cohort work/score/certificate/preference matrices, calendar UID sets, route results, and Wrapped interpretation. Unknown or corrupted input blocks activation without partial reassignment.

Rollback proves that both pre-cutover and compatibility-window Enrollments remain readable by the previous compatible release without resuming anonymous registration, creating a second Enrollment, changing calendar UIDs, losing post-cutover work, or enabling a sender. Successful additive data migration is not destructively reversed. #60 later repeats these checks on an authorized anonymized production-like source; #244 uses synthetic data only and performs no production import.

Acceptance criteria

  • The characterization handoff records exact accepted #230/#242/#248 merge SHAs plus inherited foundation/source/migration provenance; the final handoff additionally records accepted #243 and a refreshed base/head/graph/plan. Its frozen inventory maps every field, relationship, route, alias, visibility state, calendar UID, fixture, and adopted characterization test above to preserved, intentionally changed by named prerequisite, or not applicable; there is no unexplained item or stale pre-#243 final evidence.
  • Enrollment identity is unique by learner+Cohort; all listed preference/display/certificate/score/timestamp values remain Cohort-specific, and a later sibling Cohort Enrollment receives only accepted defaults without copying or rewriting prior state.
  • Account/contact communication preferences retain their account-owned meaning and are honored by cohort recipient selection without a new category, reset, consent inference, transport write, or send.
  • Homework, answers, submissions, projects, reviews, criteria responses, votes, results, progress, score rollups, complaints, certificates, dashboard aggregates, and historical Wrapped records remain attached to the exact original Cohort/Enrollment with stable mappings and timestamps.
  • Multi-Cohort and unrelated-Course tests prove no read, mutation, ID/alias change, recomputation, cache entry, or generated link can select sibling/foreign learner state.
  • Incomplete-profile and reviewed survivor-alias learners retain one-copy access to all existing history; unavailable/colliding identities fail safely and no read creates or resets an Enrollment.
  • Canonical and reviewed legacy routes resolve the same exact historical Cohort/resources; generated URLs use /courses/<course>/<cohort>/...; unknown/ambiguous aliases fail; Wrapped links and certificate destinations do not drift to the latest Cohort.
  • Calendar feeds preserve exact event sets and permanent UID formulas across canonical/legacy URL, migration, deadline/link edit, reverse/forward, and rollback; sibling Cohorts never share an event ID or event set and no new cancellation protocol is invented.
  • The adopted visibility matrix is unchanged, while object scoping, CSRF, generic denial/404, cache/no-store/noindex, redaction, synthetic-fixture, and no-cross-cohort security canaries pass.
  • Deterministic preflight/quarantine and apply-twice/reverse/forward tests preserve safe counts/checksums, IDs/mappings, timestamps, null/blank distinctions, scores, certificates, routes, UIDs, and Wrapped interpretation with no unexplained difference or outbound side effect.
  • Focused Django/adoption/security/migration/backend-portability tests, migration drift, graph-selected Playwright, full versioned verification, and container checks pass with exact base/head/graph/plan/artifact digests and dispositions.
  • The independent tester captures/inspects the required responsive evidence below; PM accepts copy, navigation, empty/history states, isolation, safe denials, and compatibility behavior.

Required Django/integration scenarios

  1. Same learner in two Cohorts of one Course and one unrelated Cohort: distinct Enrollment IDs/defaults/preferences/scores/certificates/work; edit/recompute one; assert exact other-state equality and cohort-scoped cache invalidation.
  2. Existing complete and incomplete profiles, profile/certificate-name edit, new registration/conversion through #243, campaign repoint, later Cohort enrollment, reviewed source→survivor alias, inactive/quarantined/broken/colliding identity, and duplicate replay.
  3. Each preference boolean true/false, blank/generated display name, public-profile/leaderboard combinations, learning-in-public disabling and already-zero/nonzero component scores, certificate null/blank/URL/issued/revoked/version states, and timestamp preservation.
  4. Homework create/update/answer/reveal/scoring/statistics and project submit/review/vote/pass/fail/results/statistics in both Cohorts; malformed student/enrollment/content ownership; duplicate/missing relations; assert no cross-Cohort write/read and no silent score replacement.
  5. Course page progress/certificate, enrollment preferences, dashboard empty/populated aggregates, leaderboard visibility/breakdown/complaint, historical work/result/certificate, Wrapped visible/invisible/no-activity and stored legacy JSON links.
  6. Canonical plus every reviewed legacy suffix; numeric/slug/alias collisions; missing/unknown mapping; GET/HEAD behavior; query compatibility owned by #60; old account/student/enrollment IDs resolved through retained mappings.
  7. Calendar canonical/legacy equivalence; exact UID formulas; sibling assignments with similar slugs; deadline/title/URL edit under same UID; archived/cancelled history; omission without UID reuse; UTC/naive-aware dates, escaping, ordering, CRLF, content type, and filename.
  8. Anonymous, owner, another learner, incomplete profile, reviewed alias survivor, and unavailable identity across public/private routes; direct foreign Enrollment/submission/review/result IDs; CSRF, no-store/noindex/cache canaries, enumeration-safe errors, and artifact/log redaction.
  9. Synthetic expand/backfill/validate/apply twice/reverse/forward/rollback with outbound Datamailer/Relay/SES/jobs/workflows patched to fail the test if called; compare complete per-Cohort matrices and safe checksums.

Focused coverage must include the existing enrollment/profile, course/Cohort split/navigation, course page/progress/certificate, dashboard, calendar, homework/submission/scoring, project/review/results/scoring, leaderboard, Wrapped, single-identity, migration-history, and adoption synchronization suites. Use uv/Make and let the versioned verification graph select the final exact broader command set.

Browser and evidence acceptance

At approximately 1440×900 and 390×844, using only synthetic .invalid identities, the independent tester captures and reads:

  1. one Course family/Cohort page showing sibling Cohort navigation, the current learner's distinct progress/certificate state, and the calendar link;
  2. the same learner's enrollment/preferences page for Cohort A and Cohort B with deliberately different display/visibility values, plus saved-state refresh/back behavior;
  3. empty and populated dashboard states for the correct Cohort, with no sibling-Cohort counts/results;
  4. representative owned homework submitted/scored history and project submitted/reviewed/result states, then a direct foreign-Cohort safe denial;
  5. leaderboard hidden/visible current-learner states and score breakdown/public-profile boundary;
  6. historical certificate destination and historical Wrapped page/link resolving the intended Cohort; and
  7. an incomplete-profile account retaining existing history plus a new-registration gate, without exposing profile fields.

The calendar feed is a non-HTML download: screenshot the course-page calendar link, then store and inspect the synthetic ICS response text under .tmp/ and report UID/event-set/canonical-link equality rather than treating raw ICS as a page screenshot. Capture both normal and JavaScript-disabled paths where the rendered workflow has scripts. Verify expected pages rather than debug/errors, logical headings/landmarks, keyboard access, visible focus, 44px pointer targets where applicable, readable dates/timezones, 200% zoom/text spacing, reduced motion, 320 CSS px reflow/no horizontal overflow, and no real or unnecessary synthetic learner data in image, URL, title, history, console, or artifacts.

Explicit non-goals

  • No new preference category/default, public profile/member directory, cross-Cohort aggregate, score/curriculum/rubric/peer-assignment algorithm, Wrapped generation, certificate issuing policy, calendar cancellation protocol, or learner UX redesign.
  • No anonymous registration, registration form/state machine, CourseInterest, immutable registration schema, registration→Enrollment conversion implementation, profile schema/write, account reconciliation algorithm, campaign behavior, or aggregate-count import; those belong to #242/#243/#108/#100/#133.
  • No intentional privacy/visibility change to adopted submissions/results/dashboard/leaderboard/certificate/Wrapped behavior. A discovered conflict requires a separate owner decision.
  • No Studio/admin API management reads, export, correction, bulk operation, scoring repair, certificate mutation, view-as, or capability implementation; those belong to #245/#246/#59.
  • No direct Datamailer/SES/Relay call, preference webhook mutation, template/sender/callback/job work, communication cutover, resend, or production recipient.
  • No production/protected data, real email/profile/submission/certificate value, legacy-host redirect activation, Terraform/provider change, deployment, commit, merge, push, or #60 production-like rehearsal.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with _docs/PROCESS.md and Specifications 01, 04, 07, 09, and 10, then verify the accepted SHAs for #230, #242, and #248 before inspecting the existing characterization fixtures and compatibility seams. Done means the bounded candidate preserves cohort-scoped learner state, routes, aliases, calendar identity, and historical Wrapped access without cross-cohort leakage, with migration and verification evidence recorded.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
api, backend, database, documentation, testing
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
18/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.