Skip to content

Package: living-course

Generated from api/packages/living-course

Source: api/packages/living-course. The sections after the README are extracted from the code on every docs build.

Watches the sources of a course built with the Course Builder (an upload, a Git repository, web pages), detects what changed at fragment level without a model, finds the lessons and quiz questions that cite the changed passages, and proposes cited updates the author reviews as one diff. Learner progress is preserved by explicit rules and every decision lands in a tamper-evident audit trail. Records: ADR 0030 (revisions and proposals), 0031 (change detection), 0032 (connectors), 0033 (progress rules), 0034 (audit trail); plan docs/plans/phase-3.md.

  • Author guide: front/docs-site/src/content/docs/creators/course-builder.mdx (sections on sync and updates)
  • Settings, env variables, commands: front/docs-site/src/content/docs/admin/living-course.mdx
  • Internals, endpoints, events: front/docs-site/src/content/docs/developers/living-course.mdx
  • Writing a connector plugin: docs/living-course/connector-plugins.md
Connector (upload | git | url | plugin) ─▶ Revision (numbered per source, fragments with file_path)
─▶ FragmentDiff (deterministic: unchanged | changed | moved | added | removed; no model)
─▶ ImpactAnalyzer (citations ─▶ elements) ─▶ StalenessService (element_status)
─▶ AnalysisService (one grounded call per group, run kind `sync`, cost caps) ─▶ UpdateProposal + items
─▶ DecisionService (accept / reject / regenerate) ─▶ ApplyService (blueprint version kind `update`)
─▶ ProgressRules + LearnerNotice, AuditLog (hash chain, append-only trigger), Notifier
Terminal window
vendor/bin/phpunit --testsuite living-course # fake driver, recorded live answers replayed
php artisan living-course:eval --fixtures=all --author=1 # free, fake driver
php artisan living-course:poll # due sources (scheduled every 15 minutes)
php artisan living-course:backfill # first revision for existing sessions

Source text is untrusted: it only reaches the model inside a delimited block of the user turn, and the model’s output is validated against a schema and must cite fragments of the new revision. Outbound requests use Ulams\Core\Http\SafeHttp. LMS entities change only through the Course Builder applier and the domain services; tests/Unit/GuardsTest.php enforces this.

Method Path Auth Action
GET /api/admin/living-course/sessions/{session}/sources yes SourcesController@index
GET /api/admin/living-course/sources/{source}/revisions yes SourcesController@revisions
POST /api/admin/living-course/sources/{source}/revisions yes SourcesController@upload
GET /api/admin/living-course/revisions/{revision} yes SourcesController@revision
GET /api/admin/living-course/sessions/{session}/staleness yes StalenessController@show
GET /api/admin/living-course/sessions/{session}/audit yes AuditController@index
GET /api/admin/living-course/sessions/{session}/audit/export yes AuditController@export
GET /api/admin/living-course/sessions/{session}/audit/verify yes AuditController@verify
GET /api/admin/living-course/audit/export yes AuditController@exportAll
GET /api/admin/living-course/audit/verify yes AuditController@verifyAll
GET /api/admin/living-course/sessions/{session}/proposals yes ProposalsController@index
GET /api/admin/living-course/proposals/{proposal} yes ProposalsController@show
POST /api/admin/living-course/proposals/{proposal}/analyse yes ProposalsController@analyse
POST /api/admin/living-course/proposals/{proposal}/items/{item}/accept yes ProposalsController@accept
POST /api/admin/living-course/proposals/{proposal}/items/{item}/reject yes ProposalsController@reject
POST /api/admin/living-course/proposals/{proposal}/items/{item}/reset yes ProposalsController@reset
POST /api/admin/living-course/proposals/{proposal}/items/{item}/regenerate yes ProposalsController@regenerate
POST /api/admin/living-course/proposals/{proposal}/reanalyse yes ProposalsController@reanalyse
PUT /api/admin/living-course/proposals/{proposal}/learner-note yes ProposalsController@learnerNote
GET /api/admin/living-course/connectors yes ConnectController@connectors
POST /api/admin/living-course/sessions/{session}/sources/connect yes ConnectController@connect
POST /api/admin/living-course/connections/{connection}/check yes ConnectController@check
POST /api/admin/living-course/connections/{connection}/webhook-secret yes ConnectController@rotateSecret
PUT /api/admin/living-course/connections/{connection} yes ConnectionsController@update
DELETE /api/admin/living-course/connections/{connection} yes ConnectionsController@destroy
POST /api/admin/living-course/proposals/{proposal}/apply yes ProposalsController@apply
POST /api/admin/living-course/proposals/{proposal}/accept-all yes ProposalsController@acceptAll
POST /api/admin/living-course/proposals/{proposal}/reject yes ProposalsController@rejectAll
GET /api/admin/living-course/revisions/{revision}/changes yes SourcesController@changes
GET /api/living-course/courses/{course}/notices yes NoticesController@index
POST /api/living-course/notices/{notice}/dismiss yes NoticesController@dismiss
GET /api/living-course/courses/{course}/freshness yes NoticesController@freshness
POST /api/living-course/webhooks/{webhookId} WebhookController@receive
Permission Seeded for roles Constant
living_course_review LivingCoursePermissionsEnum::LIVING_COURSE_REVIEW

Registered with AdministrableConfig::registerConfig (editable in the admin panel under Configuration → Settings).

None.

Event Description Notification templates
CourseContentUpdated A learner has new notices after an update. In-app and e-mail, at most one e-mail per course per 7 days. Recipient: the learner. Email
ProposalApplied An update proposal was applied to the course (progress rules and notifications follow).
SourceCheckFailing A connection failed its check three times in a row. In-app and e-mail. Recipient: the session author. Email
SourceRevisionDetected A source revision with impact on the course was detected (in-app only). Recipient: the session author.
UpdateProposalApplied An update proposal was applied (in-app only). Recipient: the session author.
UpdateProposalReady An update proposal is ready for review or waits for the author to start the analysis. In-app and e-mail. Recipients: the session author and the course authors who may review. Email

None.

Command Description Signature
living-course:backfill Record revision 1 for sources built before Living Course existed (idempotent) living-course:backfill {--session= : Only this builder session id}
living-course:eval Evaluate Living Course update proposals on golden v1/v2 fixtures (fake driver by default, –live for the real model) living-course:eval {--fixtures=all : all, or a comma list of coffee, git, injection} {--live : call the real model for the update analysis} {--record : write cassettes (tests/cassettes) from this run} {--author= : user id that owns the eval sessions (default: the first admin)} {--max-usd=1 : total spend cap of a live run}
living-course:poll Check the connected sources that are due and prune old webhook deliveries living-course:poll
Kind Target Frequency
command 'living-course:poll' everyFifteenMinutes()->withoutOverlapping())

LIVING_COURSE_ALLOWED_HOSTS, LIVING_COURSE_AUTO_ANALYSE_USD, LIVING_COURSE_CONNECTORS, LIVING_COURSE_INSECURE_HOSTS, LIVING_COURSE_LEARNER_NOTICE_AFTER_DAYS, LIVING_COURSE_MAX_FRAGMENTS, LIVING_COURSE_MAX_GROUPS, LIVING_COURSE_MIN_POLL_MINUTES, LIVING_COURSE_PROPOSAL_COST_USD, LIVING_COURSE_QUEUE, LIVING_COURSE_QUEUE_CONNECTION, LIVING_COURSE_SOURCE_MONTHLY_USD, LIVING_COURSE_WEBHOOK_DEBOUNCE_SECONDS, QUEUE_CONNECTION