DataTalksClub / DataTalksClub/website
Add Course/Cohort lifecycle, public routes, and complete cohort duplication
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 0
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Description
Parent epic: #5. Shared consumers: #55, #56, #57, #58, #59, and #60.
PM disposition
GROOMED / P0 / DEPENDENCY-BLOCKED. Do not dispatch engineering.
This issue delivers the shared Course/Cohort lifecycle, effective-public-metadata, canonical-route, public teaching-team, archive/delete-guard, and definition-only duplication services consumed by the later course workflow issues. It accepts no current source merely because a partial Course/Cohort model, route, or Django-admin duplication action exists.
Repository delivery is synthetic and side-effect-free. This issue grants no production/protected-data access, migration execution, cross-host redirect activation, sender/provider action, deployment, commit, merge, or push authority.
Normative authority and precedence
_docs/PROCESS.md04 — Course-platform adoption and Course → Cohort, especially Target model, curriculum ownership, expand/contract, Studio coverage, APIs, and high-risk migration checks02 — URL/link/SEO compatibility06 — Studio/admin API, including service, capability, revision, idempotency, operation, audit, and parity rules07 — Security/privacy/operations09 — Migration/rollout10 — Verification- resolved owner decisions #14 and #16
Resolved #16 and open-decisions.md decision 5 override the stale /cohorts/ examples still present in specs 02/04: the accepted canonical Cohort base is /courses/<course-slug>/<cohort-identifier> with no cohorts/ segment. This issue applies that already accepted decision; it does not activate the later legacy-host Lambda.
Accepted baseline inputs — not #53 evidence
- #14: Course owns reusable family identity/default public metadata; each Cohort owns dated settings, curriculum definitions, learner/history state, and definition-only duplication isolation.
- #16: canonical no-
cohorts/route decision, retained compatibility behavior, and permanent@courses.datatalks.clubcalendar UID namespace. - #30: copied CMP characterization at source pin
98a235283904b4ef9ad29e196298540756cf1bcc. - #35: compatibility/parity-gate foundation.
These closed inputs do not prove #53 implementation, migration, tester acceptance, or release.
Exact Course and Cohort contract consumed from #51
#53 does not recreate structural migration. It consumes accepted #51 identities and mechanically retargeted relations:
- Course: immutable UUID, globally unique stable family slug, family/public defaults only, and a revision suitable for guarded mutation;
- Cohort: retained legacy integer primary key and globally unique legacy slug, immutable UUID, required Course UUID, stable nonblank identifier unique within Course, year as display/schedule metadata only, and a revision suitable for guarded mutation;
- no runtime year stripping, fuzzy matching, newest-row inference, family guessing, or renumbering;
- all curriculum, registration, Enrollment, submission, review, score, statistic, complaint, certificate, Wrapped, and historical relations remain scoped to the exact Cohort; and
- retained numeric/slug/calendar identities and the compatible expand/rollback seam remain stable.
An unknown, duplicate, stale, foreign, cross-Course, or cross-Cohort identity fails before mutation. #53 never repairs or moves downstream learner/history rows.
Exact lifecycle and public-state contract
Cohort has one explicit, revisioned lifecycle state:
draft -> registration_open -> active -> grading -> completed -> archived
\ \ \ \
+-------------+-------------+-----------+-> cancelled
cancelled is the terminal pre-completion alternative. No reverse, skip, reopen, unarchive, completed-to-cancelled, clock-triggered, date-inferred, or adapter-local transition exists unless a later separately accepted issue adds one. Archive grouping may display a cancelled Cohort without rewriting it to archived.
- Every transition is an explicit shared service command with current revision/
If-Match,Idempotency-Key, allowed-from state, actor/capability, bounded reason where required, transition timestamp, and redacted audit event. Exact replay returns the original result; changed replay conflicts. Concurrent or stale commands make zero writes. - Dates, IANA timezone, registration window, and assignment states are validated inputs, not hidden lifecycle engines.
start <= end; a registration window is either absent or internally ordered. DST/leap-day display follows the accepted timezone rules. Homework/Project states remain owned by #55/#56 and never derive or mutate Cohort lifecycle implicitly. - Public family/catalog/detail projection is deterministic: Course publication/visibility and Cohort lifecycle are both required.
registration_openappears in open registration,activeandgradingin active delivery,completedandarchivedin the archive, whiledraftandcancelledare absent from ordinary public discovery. Direct unpublished access fails safely under the registered public/private cache policy; no adapter bypasses the service projection. completedpreserves the legacy finished learner-visible behavior;archivedadds operational retirement without deleting history. A later archive command must not rewrite scores, certificates, registrations, Enrollment, or curriculum.- Existing
visible,finished, assignment, registration-window, and date combinations are migrated only through a versioned reviewed row/state disposition. There is no truth-table guess from year/date/slug. The expand state keeps compatibility projections readable until #60 rehearses them; unknown or contradictory legacy state is quarantined rather than silently coerced.
Course has no learner lifecycle. Its explicit public visibility controls the family landing/catalog only and never rewrites Cohort lifecycle or history.
Effective public metadata and public Person boundary
Course owns reusable defaults for title, description/outcome, repository/docs/FAQ links, social hashtag, and ordered public teaching relationships. Cohort owns dated schedule/timezone/registration metadata and may hold explicit allowlisted overrides for the reusable public fields. Override presence is explicit: blank and absent are never conflated through truthiness. Effective values are computed by one pure query/service and serialized identically by public views, Studio, and admin API.
Public teaching relationships resolve only through accepted #40 exact canonical Person identity and the permission-neutral Person-link interface from #52. They preserve explicit role and order. Names, email, account/profile values, employment, course activity, case folding, or fuzzy matching never creates a Person link. Public Person presence grants no staff authority; staff authority remains exclusively #52's assignment/capability policy. Unknown required Person keys fail the candidate, while an explicitly optional unresolved key produces only the accepted bounded diagnostic.
Canonical routes and compatibility boundary
- Course family canonical:
/courses/<course-slug>. - Cohort canonical base:
/courses/<course-slug>/<cohort-identifier>; no/cohorts/segment and no year inference. - Dashboard, calendar, homework, project/review, leaderboard, and certificate paths remain cohort-relative under that exact base as their owning issues deliver them.
- Generated links, canonical metadata, breadcrumbs, sitemap eligibility, cache classification, and Studio preview use the same route resolver. Unknown family/identifier pairs, swapped identifiers, legacy slug in the canonical slot, case/Unicode variants, ambiguous aliases, and unrecorded paths return the accepted safe result; they are never guessed.
- Retained legacy Cohort slug and numeric identities use one explicit compatibility lookup. Legacy same-application compatibility views may continue direct behavior during migration; #53 does not blanket-redirect or activate the old host.
- #60 later proves all 115 accepted legacy HTML/API/calendar/certificate paths and generates the final path map. Cross-host Lambda/DNS activation, authenticated method/body/auth preservation, unknown-path telemetry, and redirect rollback are outside #53.
- Existing calendar UID inputs and the literal
@courses.datatalks.clubnamespace remain byte-stable.
The final #53 route/alias artifact records every family slug, Cohort UUID/legacy PK/legacy slug/identifier, canonical base, reviewed compatibility identity, disposition, and collision status. Duplicate canonical/legacy claims, alias chains/loops, or drift fail closed.
Definition-only Cohort duplication service
One transactional duplicate_cohort command receives the exact source Cohort UUID/revision plus explicit target family, target identifier, retained-compatible target slug, title/year/schedule inputs, and idempotency key. It does not derive a target from the wall clock or mutate the source.
The target starts as a new draft Cohort with new Cohort/curriculum identities and its own revision/history. The service copies the complete accepted definition/settings graph while preserving order and approved within-Cohort sharing:
- allowlisted Cohort settings and explicit metadata overrides;
- selected curriculum format;
- Module, Unit, and curriculum-flow structure/content;
- Homework and ordered Questions/options/settings;
- Projects, ReviewCriteria, and ordered ProjectCriteriaAssignment relations; and
- other definition-only configuration explicitly present in the frozen #30/#51 inventory.
It copies no registration campaign/registration, Enrollment/student, Submission/Answer, ProjectSubmission/review/vote/evaluation/criterion response, score/statistic/result, complaint, certificate, Wrapped, delivery/outbox/job, audit/lifecycle history, read/progress state, source-Cohort link, or production/source provenance that would claim the duplicate is the same source object.
The service freezes a source definition/settings fingerprint before work. A changed source revision, target collision, invalid graph, unsupported row, stale capability, transaction failure, or changed replay creates no partial target. Exact replay returns the same target/result. The result includes safe per-model copied/excluded counts and graph fingerprints, never learner or protected values. Editing the copy changes neither source definitions nor any source/history fingerprint.
Archive and delete protection
- Ordinary retirement uses the lifecycle service; it never cascades learner/history deletion.
- A Cohort may be physically deleted only when it is an unpublished
draft, has never left draft, and the complete protected-relation guard proves zero curriculum, registration, learner, submission/review, score/statistic, complaint, certificate/Wrapped, delivery/job, audit/history, alias, or other dependent rows. Otherwise return a stable conflict reason and direct the operator to archive/cancel. - A Course may be physically deleted only when it has no Cohort, public relationship, staff assignment, alias, or dependent record. Hiding/retiring a Course does not rewrite its Cohorts.
- Delete/retire/duplicate previews show exact scope and safe counts. High-risk confirmation/reauthentication, revision, idempotency, audit, transactionality, and generic scope-first denial come from #28/#52. No signal, view, task, compatibility adapter, Django admin action, or API performs a direct cascade.
Shared services and management parity delivered here
Public views, Studio, /api/v1/admin/, jobs, compatibility adapters, and any retained Django-admin presentation call the same courses queries/commands. At minimum #53 registers these capability/action families through #52's accepted registry (final spelling is frozen before dispatch and becomes part of the fingerprint):
- Course view/manage/publication;
- Cohort view/manage/lifecycle/archive/delete;
- Cohort duplication preview/initiate/status/result; and
- public teaching-relationship view/manage/order.
New management resources use accepted UUIDs, allowlisted schemas, revision/If-Match, Idempotency-Key, scope-first generic errors, bounded reason codes, audit, and asynchronous operation resources only where the frozen graph exceeds the accepted synchronous bound. Studio and admin API have identical service result/error semantics; presentation differences do not change authorization or mutation. No adapter writes models directly.
#59 later proves the complete copied cadmin/Django-admin/command matrix and retires remaining adapters. #53 supplies its bounded lifecycle/duplication rows; it does not claim full course-management parity.
Exact downstream handoff and evidence identity
The accepted #53 handoff publishes one versioned, PII-free CourseCohortLifecycleContract fingerprint containing:
- exact #40, #51, and #52 prerequisite merge SHAs and their Person, identity/mapping/migration-leaf, capability/policy/registry/OpenAPI fingerprints;
- source pin
98a235283904b4ef9ad29e196298540756cf1bcc, current copied-file/integration-patch ledger digests, and pre-change #30 characterization result; - lifecycle schema and transition-policy version, public visibility/archive projection, effective-metadata schema, and schedule/timezone/window validation policy;
- Course/Cohort identity plus revision schema, route/alias manifest digest, canonical/cache/sitemap policy, and permanent calendar-UID policy;
- ordered public-Person relationship schema/fingerprint with no account or permission data;
- duplication input/result schema, complete copy/exclude inventory, source/target graph fingerprint algorithm, and safe copied/excluded count schema;
- archive/delete guard reason-code schema and protected-relation inventory;
- service/capability/action/audit/OpenAPI/operation registry fingerprints; and
- synthetic lifecycle-migration preflight/apply/replay/reverse/forward report digests and the versioned verification graph/plan/report digests.
Consumers use it as follows:
- #55 consumes lifecycle/route/archive/duplication exclusions for Homework/Answer/Submission/statistics.
- #56 consumes the same for Project/criteria/submission/review/vote/result graphs.
- #57 consumes lifecycle and canonical/legacy route identity for leaderboard/completion/complaint visibility.
- #58 consumes lifecycle, route/alias, archive protection, and certificate exclusion/history rules.
- #59 consumes the registered lifecycle/duplication management rows and service/result schemas in its no-gap parity matrix.
- #60 freezes this exact fingerprint for protected production-like rehearsal; it does not reinterpret lifecycle or duplication.
Any prerequisite, source pin, migration leaf, model/relation inventory, route/alias, lifecycle, Person, capability, duplication graph, or downstream consumer-schema drift returns #53 to PM before engineering or invalidates an existing handoff.
Dependencies and readiness
Accepted baseline inputs are #14, #16, #30, and #35.
Engineering hard-depends on accepted and integrated:
- #40 — final reproducible canonical Person adapter/resolver, manifest, aliases, relation census, and transition diagnostics;
- #51 — final Course/Cohort UUID/legacy identity, required parent, identifier, mechanically retargeted relation, mapping, migration-leaf, and compatible rollback contract; and
- #52 — Course/Cohort staff assignments, permission-neutral Person linking, capability/object/field policy, principal/audit/revision/idempotency/operation registry, and admin-API mechanisms.
#253 is transitive through #40. #224 is transitive through #51. #32/#33 and their identity/security prerequisites are transitive through #52. An open dependency blocks engineering even when partial source or a local candidate exists.
#218 is later curriculum-extension work and must preserve this accepted seam; it does not block the basic lifecycle/duplication contract. #54 is a coordination epic, not a dependency. #55–#60 are downstream consumers and never prerequisites.
Current dispatch status remains blocked. #40 is currently returned to grooming behind #253; #51 remains behind #224; #52 remains behind #40/#51 and its accepted security foundations. After all three direct inputs are integrated and green on one current-main base, PM must bind their exact fingerprints and the then-current #55–#60 consumer schemas before selecting one fresh isolated engineer lane.
Acceptance criteria
- Course/Cohort identities, revisions, family defaults, explicit Cohort overrides, schedule/timezone/window rules, and exact relationship scoping consume #51 without inference or cross-Cohort leakage.
- The exact lifecycle graph, explicit transition/replay/concurrency/audit rules, public open/active/archive projection, terminal cancellation, and reviewed legacy-state migration pass without date/slug/assignment inference or silent behavior change.
- Canonical family and no-
cohorts/Cohort routes, generated links/metadata/cache/sitemap behavior, explicit legacy lookup, unknown/collision denials, and permanent calendar UID namespace match #16 and the frozen route artifact. - Public teaching relationships use only accepted #40 exact Person identity and remain permission-neutral under #52; order/role/optional diagnostics pass with no account inference.
-
duplicate_cohortatomically copies the complete definition/settings graph and approved internal sharing, starts a new independent draft, excludes every learner/history/registration/delivery/source-link row, is revision/idempotency/concurrency safe, and leaves no partial target. - Course/Cohort archive/delete guards protect every named dependent relation and history row; only the exact empty never-published cases may be physically deleted through the shared high-risk service.
- Public, Studio, admin API, job, compatibility, and retained-admin adapters use the same query/command services with capability, object/field, revision, idempotency, operation, audit, result/error, and OpenAPI parity; no direct adapter mutation exists.
- Synthetic preflight/migration/apply/exact replay/reverse/forward evidence reports safe state/identity/relation/count/checksum dispositions, rejects all named drift/collision/corruption cases, preserves compatibility, and performs zero outbound work.
- The complete
CourseCohortLifecycleContractfingerprint and exact #55–#60 handoffs above are frozen and contract-tested. - Focused model/service/migration/route/API/security/concurrency/adoption tests and graph-selected full verification pass on one frozen candidate; a separate tester captures and inspects every required desktop/mobile screenshot before PM acceptance.
Required verification scenarios
- One family with sibling Cohorts across every lifecycle state; valid forward transitions, each invalid/skip/reverse/terminal transition, exact and changed replay, stale revision, concurrent transition, timezone/DST/leap-day/window boundaries, and transaction failure.
- Every legacy
visible/finished/date/window/assignment combination in the reviewed migration artifact; unknown/contradictory/unmapped rows quarantine; apply twice/reverse/forward preserves IDs, aliases, projections, history, and safe checksums. - Exact family/Cohort canonicals and every generated relative route with queries/fragments/trailing-slash policy, legacy slug/numeric compatibility, case/Unicode/collision/unknown inputs, cache/noindex/sitemap boundaries, and byte-stable calendar UIDs.
- Course defaults versus explicit Cohort overrides including blank/absent distinctions; ordered Person roles, optional unresolved and required unknown keys, alias/removal transitions, duplicate roles, and proof that account/staff/profile data cannot resolve or authorize.
- Duplicate a complex legacy-format and modules-format Cohort with Homework/Questions, Projects/criteria assignments, Module/Unit/flow ordering and shared-within-Cohort definitions. Compare full graphs/counts, prove every excluded table is empty, edit the copy, and prove source plus historical fingerprints remain unchanged.
- Duplicate collision, stale source revision, changed idempotency replay, unauthorized/cross-Course target, malformed graph, unsupported relation, bounded-operation crash/resume/cancel, and injected transaction failure all produce the exact safe result with no partial target.
- Archive/cancel/delete Course and Cohort with zero/one/many protected relations, aliases, lifecycle/audit rows, downstream learner/history rows, and concurrent creation; prove no cascade and stable reason/audit behavior.
- Studio/admin API/public/compatibility parity for allowed and denied actor, object/field scope, stale
If-Match, exact/changed replay, high-risk confirmation, operation progress/result/failure, audit/log redaction, CSRF/Bearer separation, OpenAPI/registry completeness, and no direct model writes. - Consumer contract tests prove #55–#60 accept only the exact current handoff fingerprint and reject missing/stale/schema-drifted route, lifecycle, archive, duplicate-exclusion, or capability inputs.
Independent browser evidence
Using synthetic .invalid fixtures at approximately 1440×900 and 390×844, the separate tester captures and reads:
- public family landing plus registration-open, active/grading, completed/archived, draft/cancelled denial, empty family, and sibling-Cohort navigation;
- exact canonical versus legacy compatibility navigation, unknown/collision safe state, metadata/breadcrumb/canonical behavior, and JavaScript-disabled navigation;
- authorized and denied Studio Course/Cohort create/edit/publication/lifecycle/archive/delete and public teaching-team order states, including validation, stale/conflict, confirmation, success, and error; and
- duplication preview/progress/result/failure plus the independently editable target.
Include keyboard-only operation, visible focus and announced status/error, 44 CSS px targets, 200% zoom and 320 CSS px reflow, reduced motion, and supported JavaScript-disabled confirmation/error paths. Screenshots contain no real identity, learner/history value, credential, provider, secret, or production data.
Explicit non-goals
No #224/#51 structural reimplementation; reusable/versioned cross-Cohort curriculum; cross-Cohort criterion reuse; Homework/Project/scoring/leaderboard/certificate algorithm; registration/profile/Enrollment conversion; email intent/template/preference/sender/provider behavior; full #59 management matrix; protected #60 rehearsal; cross-host redirect/DNS/Lambda activation; production/protected-data access; destructive contraction; arbitrary lifecycle inference; direct adapter mutation; unrelated redesign; commit, merge, push, workflow dispatch, or deployment during PM/engineering/tester gates.
Lifecycle
After every direct prerequisite is accepted/integrated and PM binds the exact current fingerprints, one engineer leaves an uncommitted frozen candidate with a complete versioned verification report. A separate tester recomputes the plan, verifies every criterion and screenshot, then PM performs product acceptance. Only after both gates pass may the engineer create a focused commit with Closes #53; the orchestrator locally merges with --no-ff, pushes main, and on-call alone observes terminal CI/deployment. No pull request is created.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Begin with _docs/PROCESS.md and specs 04, 02, 06, 07, 09, and 10, then review the accepted inputs from #14, #16, #30, #35, #40, #51, and #52. This issue is dependency-blocked and provides no concrete source file or test entry point. Done requires the complete lifecycle, projection, route, duplication, and deletion contracts, with verification and migration coverage as specified.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- django, python
- Domain
- api, backend, database
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 20/100