Define and enforce project publication semantics for v1 course APIs¶
Problem¶
The LMS currently calls GET /api/v1/projects with status=published and
locale=<locale> when resolving published courses. That route accepts those
query params, but does not enforce them:
statusis ignored by the handler.localeis ignored for filtering semantics.- The
projectstable has no canonical publication-status column, so "published courses only" is not enforceable at the data layer.
This also affects OG/public metadata safety assumptions:
- LMS route behavior and social metadata generation rely on "published" course expectations.
- Without explicit publication semantics, unpublished content can be accidentally exposed through listing and downstream metadata paths.
Why this matters¶
- Security/content governance: Draft or internal projects can leak to learner-facing routes and social crawlers.
- Contract drift: LMS consumer code assumes
status=publishedis authoritative while the CMS producer does not enforce it. - Operational ambiguity: Product and engineering currently lack one source of truth for "project is publishable/public."
Implemented direction¶
- Defined canonical project visibility states as
draft | publishedin CMS validation and authoring UI. - Added explicit publication state to CMS project persistence (
projects.status) with migration and index support. - Updated CMS v1 list/detail APIs to enforce publication filtering (
status) for project discovery routes. - Documented and enforced locale filtering as an exact match against
projects.languagefor cross-tenant list semantics. - Aligned LMS fetchers and adapters to consume real
statusvalues and filter non-published projects at the contract boundary.
Acceptance criteria¶
- A canonical publication field exists for projects and is documented.
-
/api/v1/projectsenforces publication filtering for published-only requests. - Public LMS course discovery paths only return published projects.
- Locale semantics for project listing are documented and enforced.
- Contract tests cover
statusandlocalebehavior end-to-end.
Out of scope¶
- Reworking page-level publication semantics.
- Redesigning non-v1 internal CMS admin endpoints unrelated to public listing.