Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .importlinter
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,21 @@ layers=
# various types of content.
openedx_content.applets.publishing

# This is "applet" layering, within our Learning djangoapp.
# Every new applet should be added to this list when it is created.
[importlinter:contract:learning_applet_layering]
name = "openedx_learning's internal applets are layered correctly"
type = layers
layers =
# The public API is at the top. None of the internal applets should call to it.
openedx_learning.api

# The "pathways" applet holds the versioned definition of what a learner must complete
# to earn a larger achievement. The "cbe" applet models learner mastery of competencies.
# These are independent of each other today; if a future fulfillment type lets a
# competency attainment fulfill a Pathway Item, pathways would move above cbe.
openedx_learning.applets.pathways | openedx_learning.applets.cbe

# Mirrors openedx-platform's isolated-apps contract (see test_utils/isolated_apps_contract.py):
# code outside an isolated app may only import its public modules (api, models_api, data,
# tests). src_layering above only controls which top-level packages may depend on which.
Expand Down
20 changes: 17 additions & 3 deletions docs/openedx_learning/decisions/0006-pathway-item-fulfillment.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,17 @@ Decisions

- Each Item holds an **author-defined list of course runs** that fulfill it. Passing *any one* of them fulfills
the Item. The runs may belong to different catalog courses.
- Items are never shared: each belongs to exactly one Pathway. Course runs are what Pathways share - the same run
may appear in the lists of Items in several Pathways.
- The list is explicit rather than "any run of this catalog course", because we don't necessarily want every
possible older version of a course to count.
- "Passing" is determined by each course's own grading policy. The grade and passed/failed state are read directly
from the course; Pathways define no grading of their own.
- Each Item designates a **default course run** - the one a learner is enrolled in when they begin the Item - and,
if that course uses multiple enrollment tracks, the track to enroll them in. The default can change over time.
- The list is **ordered by priority**. Priority decides which run a learner is enrolled in when they begin the Item
and, for a learner already enrolled in several of the runs, which one to show them. It plays no part in
fulfillment, which always considers every run in the list. Putting a newer run ahead of an older one is how an
author gives a learner who failed the older run another attempt. Each run also carries the enrollment track to
enroll learners in, if that course uses several. The order can change over time.

2. Fulfillment types attach at this layer only. Potential future types - section completion, competency attainment,
admin override - plug in as alternative ways to fulfill an Item, without touching Item identity, Pathway structure,
Expand Down Expand Up @@ -89,8 +94,17 @@ Consequences
- Because passing state comes directly from course grading, there is no Pathway-side duplication of grades to keep in
sync.
- Only the third trigger needs an async task: the first two act on a single learner, while publishing an Item change
fans out across everyone enrolled in the Pathways that contain it.
fans out across everyone enrolled in the Pathway that contains it.
- Publishing an Item change on a large Pathway is therefore not instantaneous. Retroactive credentials appear once the
task completes.
- The fulfillment mapping is the natural extension point for everything post-MVP, and the part of the model we
expect to iterate on in code.

Changelog
---------

2026-09-23:

* Replaced the single default course run with a priority order over the whole list of runs, so that a learner enrolled
in several of an Item's runs is shown a well-defined one.
* Stated that Items belong to exactly one Pathway, while course runs may be shared between Pathways.
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ Decisions
``openedx_catalog`` app.

- **Pathway content** - the definition of the Pathway: its Items and its completion criteria. The content is
**versioned**, so that we can always tell what the definition was at any given moment. A version of the Pathway
content *implements* a Catalog Pathway.
**versioned**, so that we can always tell what the definition was at any given moment. The Pathway content
*implements* exactly one Catalog Pathway, and a Catalog Pathway is implemented by at most one Pathway. That link
is fixed for the Pathway's whole life, so it is not versioned: every version implements the same Catalog Pathway.

2. The **Category** is a student-facing label for the kind of Pathway (e.g. "Master's Degree", "Annual Training").
Learners see the Category rather than the word "Pathway". It is always required: rather than falling back to
Expand All @@ -63,15 +64,15 @@ Decisions

Example content of each model:

============================ ===================================
============================ =====================================
Catalog Pathway Pathway content
============================ ===================================
============================ =====================================
Display name Pathway Items
Category Completion criteria
Description References to CourseRuns
SEO metadata Link to the related Catalog Pathway
Enrollment
============================ ===================================
Enrollment (one-to-one, not versioned)
============================ =====================================

.. Run `dot -Tsvg images/pathway-catalog-content.dot > images/pathway-catalog-content.svg` to regenerate the diagram
after making changes to `images/pathway-catalog-content.dot`.
Expand All @@ -87,5 +88,19 @@ Consequences
- Because evaluation follows the published version rather than the enrollment-time version, edits to a Pathway apply
to learners who are already enrolled, which is what we want, but it means edits need care and re-evaluation.
- The unversioned Catalog Pathway can be long-lived even if its content definition is changed significantly over time.
- Because the link is fixed and one-to-one, a Catalog Pathway can't be switched over to a different Pathway; changing
what it requires means a new version of the Pathway that already implements it. In exchange, anything that records a
content version, such as a credential, can always tell which Catalog Pathway that version implemented.
- The dependency direction means a Catalog Pathway cannot, on its own, tell which content implements it. Queries in
that direction start from the content side.

Changelog
---------

2026-09-23:

* The link to the Catalog Pathway is held by the Pathway content as a whole rather than by each of its versions, and is
one-to-one: a Pathway implements exactly one Catalog Pathway for its whole life, and a Catalog Pathway is implemented
by at most one Pathway.
* The diagram now shows the Pathway content in ``openedx_learning``, where it lives, built on the versioning that
``openedx_content`` provides.
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@ digraph pathway_catalog_content {
}

subgraph cluster_content {
label="openedx_content (versioned)";
label="openedx_learning (versioned)";
labelloc="b";
fontsize=10;
fontcolor="#4d4d4d";
style=dashed;
color="#b3b3b3";
v1 [label="content v1\n\nItems, criteria"];
v2 [label="content v2\n\nItems, criteria\n(currently published)"];
pathway [label="Pathway"];
v1 [label="version 1\n\nItems, criteria"];
v2 [label="version 2\n\nItems, criteria\n(currently published)"];
pathway -> v1 [style=dotted, color="#808080", arrowsize=0.7];
pathway -> v2 [style=dotted, color="#808080", arrowsize=0.7];
v1 -> v2 [style=dotted, color="#808080", arrowsize=0.7, label="revision"];
}

Expand All @@ -33,8 +37,8 @@ digraph pathway_catalog_content {
enrollment -> catalog [label="enrolled in"];
enrollment -> v2 [label="progress evaluated\nagainst the currently\npublished version", style=dashed, color="#808080", fontcolor="#4d4d4d"];

v2 -> catalog [label="implements"];
pathway -> catalog [label="implements\n(one-to-one, fixed)"];
v2 -> courserun [label="Items reference"];

direction [label="dependency direction:\nopenedx_content knows about\nopenedx_catalog, never the reverse", shape=plaintext, fontsize=10, fontcolor="#4d4d4d"];
direction [label="dependency direction:\nthe content side knows about\nopenedx_catalog, never the reverse", shape=plaintext, fontsize=10, fontcolor="#4d4d4d"];
}
Loading
Loading