Publishing and access
Publishing puts a version of content into circulation for learners; access decides who gets in and under what conditions. These decisions live separately from the editorial body, and every content uses the same publication path.
How publishing works
Every piece of content is a single row in the database, edited in place. It holds two documents:
| Document | Role |
|---|---|
draftDocument | The editable draft — what the team sees in the editor. |
publishedDocument | The document the learner reads — a copy of draftDocument, written by Publish. |
While you edit, the learner sees nothing new. When you Publish, Cursare copies the draft into publishedDocument, stamps publishedAt, and derives a versioned publishedManifest with the complete ordered navigation graph. Before the first publish, the content exists but cannot be read or used by a published parent.
What Publish does, step by step
- Verifies that you have edit rights in the content domain.
- Validates every reachable reference and derives the learner manifest from the exact draft.
- Under a per-organization advisory lock (held until commit), writes the published document, date, and manifest atomically.
The per-organization lock serializes simultaneous publishes. Without it, two contents could each pass a cycle check against the other's previous state and form a cycle together. The second publish proceeds only after the first commits its manifest.
Rules applied to references on publish
The reference graph is computed from the document being published. Publication is refused when any target is invalid:
| Blocking reference | Reason |
|---|---|
| Target in another organization | Prevents bypassing the paywall by reading another organization's paid/unlisted content for free. |
| Target with no published version | Avoids an inert card that would make the course permanently uncompletable. |
| Deleted target | The target no longer exists. |
| Reference that would create a cycle | Keeps the graph a DAG within the organization. |
| Self-reference (to itself) | A content cannot grant access to itself. |
When validation fails, the response identifies the invalid targets and the previous published document and graph remain unchanged.
Visibility: listed vs. unlisted
Publishing creates the readable snapshot. A public entry route also requires an eligible active offer; catalog visibility belongs to that offer.
| State | In the catalog | By direct link | Typical use |
|---|---|---|---|
| Published with an active public listed offer | Yes — appears in the organization's storefront catalog | Yes | Offered content the school wants visitors to discover. |
| Published with an active unlisted or restricted offer | Does not appear as a card | Yes, subject to the offer credential | Content distributed by direct or private link. |
| Published with no active offer | No | No public entry page | Reusable content reached only through an enrolled root's graph. |
An organization's public catalog lists only published content backed by an active, public, listed offer. Every content has its own stable route segment, but a public entry route resolves only while an eligible offer exists. References navigate through the enrolled root's URL and access context. Reusing content never creates a second enrollment.
Toggling visibility sits on the boundary between catalog and finance: both the content role and the finance role can turn it on or off.
Private offers
Privacy is configured per offer, so the same content can have one public door and separate corporate ones. An offer may accept anyone, only users with a verified email in an allowed domain, only visitors carrying a private link, or either of those two credentials.
Restricted offers are always excluded from catalogs. Private links use a revocable secret, can expire after 7, 30, or 90 days (or have no automatic expiry), and are shown only when generated. Opening the link exchanges the secret for a protected cookie and removes it from the URL; the cookie lasts at most 24 hours and remains subject to link expiry, rotation, or revocation. The policy is checked before enrollment and before checkout starts; after entry, only an active enrollment grants reading access, including on module URLs.
The access rule
Access is a property of the root enrollment, not of a purchase row and not of the references between contents. The offered root is what gets enrolled; monetization is not a concern of each edge.
The core rule is short: only an active learner reads the learning body; eligible visitors see the safe public preview. Free changes how the learner becomes active, not the access predicate.
| Access scenario | What the learner sees |
|---|---|
| Free offer, not enrolled | The safe preview and one-click enrollment action. |
| Free offer, enrolled | The full body, with progress, release rules, and root navigation. |
| Priced offer, no active learner | The safe preview and checkout action. A purchase awaiting fulfillment or approval does not reveal learning sections. |
| Priced offer, active learner | The full body under the learner's progress and release rules. |
| Referenced content with its own offer | Its direct page uses that offer. Inside another enrolled root it opens contextually, without a second enrollment. |
| Referenced content without an offer | It has no direct public entry page and is reached only through an enrolled root. |
| Refunded / delinquent enrollment | No access — the row is kept for history and reactivation, but grants no reading. |
The sales preview
Without an active learner, Cursare renders the header plus the consecutive presentation-safe preamble. Projection stops before the first reference or learning-only block, so assessments, private resources, completion state, and referenced bodies never reach unauthenticated output. If there is no safe preamble, the header description is the complete public introduction.
Referenced content with an independent offer
Because access follows the active root enrollment, referenced content with its own free offer can show its safe preview when visited directly. Within another root it still follows that root's enrollment. Referenced content without an active offer has no independent public entry route.
Composition = bundling
References assemble a learning experience from existing contents. Enrollment happens once, at the offered root: there is no separate sign-up per descendant, at any nesting depth. The learner navigates continuously through the tree under that single enrollment. Access to every reachable descendant follows from the root enrollment — never from buying each node in isolation.
Drip release: drip and prerequisites
Within a root experience, each step has a release rule. The rule lives on the reference owned by the parent document, so the same target content is paced independently in every context. Two mechanics combine:
- Drip (time delay) — releases the step after an interval. The delay is measured in hours (
afterHours) and counts from an anchor. - Prerequisites — explicit dependencies: the step only opens when the required steps are completed.
Drip anchors
| Anchor | Counts from |
|---|---|
enroll (default) | The learner's enrollment date. |
prev | The completion of the immediately preceding step in the path ("after the last section"). |
A step anchored on prev whose predecessor is not yet completed has no anchor — it stays locked, and the timer only starts when the predecessor is completed.
For an offer with deliveryMode: "cohort", drip anchors on the learner's cohort start, not the enrollment date — the whole cohort is released together. self_paced offers use enrollment as the anchor.
How sections and modules differ
The availability evaluator applies per-step-type semantics:
| Step type | Release rule |
|---|---|
| Section (title/heading) | Prerequisites. requires: null defaults to the immediately preceding step (sequential); [] = always open; [ids] = all listed ones completed. |
| Module (reference) | The edge's rule: drip (afterHours, anchored on enroll or prev) plus explicit prerequisites. A module is never locked by mere order in the document. |
A prerequisite id that no longer exists in the content is treated as satisfied (fail-open), so a removed or stale target never locks the learner out forever. Prerequisites are kept acyclic: the "Unlocks after" selector in the editor refuses a choice that would create a cycle.
What the learner perceives
Each step of the path carries a state: available (the rule was satisfied), locked, the lock reason (drip or prerequisite) and, for drip, the unlockAt instant when it opens. The reader delivers only the released steps — the content of locked steps is never sent to the client, so there is no "reading ahead." Steps locked by drip show a live countdown until unlock.
Offers decide what is sellable
Creating content does not create an offer. Add one only when the content should accept free enrollment, approval requests, or payment. The first active offer becomes the default; another can be selected later. Archiving the final active offer stops new sales and enrollments without changing the content, its references, published manifest, or historical records.
Enrollment, approval, and seats
A published content becomes enrollable only after it has an active offer — the way in depends on that offer's configuration:
- Free, open entry — the learner enrolls directly (answering the intake form, if there is one).
- Free, with approval — self-signup is closed: people request to join and the team approves. Each request carries the intake answers and becomes an enrollment once approved.
- Priced — the learner pays first and lands in the queue (when there is approval); a rejection refunds automatically. Buyers skip the intake by definition — the payment is the form.
- Seat limit — when set, the page shows the remaining seats Cal-style ("3 seats left") before the click, and new enrollments are serialized so they never oversell the limit.
Enrollment is idempotent within the same offer: enrolling again returns the existing row. Each learner has one enrollment per root content and it must store the chosen offer; while active or past due, another offer, request, or checkout for the same root is refused. Referenced contents share that root access and keep independent progress keys. A refunded or delinquent enrollment keeps the row for history, but grants no access while it is not active.
Sharing and embedding
Once published, a content's canonical link ({organization}.cursare.com/{content}) is what you share and, when an offer exists, doubles as its sales page. It generates rich link cards (title, description, and cover) when pasted into WhatsApp, LinkedIn, or X. Unlisted content stays link-only and gets noindex.
Referenced links stay within the root content's URL space ({organization}.cursare.com/{content}/{segment}), preserving the continuity of enrollment and navigation at any depth. The Embed setting offers a way to present the content in another context; see the settings page for the embedding details.