Skip to content

course-service Agent Tasks

Agent workflow: follow README.md for Audit -> Investigate -> Code -> Test -> Fix; keep this pack's writable scope and verification commands authoritative.

Dispatch type: per-service

Dispatch ID: SVC-007

Owns: services/course-service.

Must read:

  • docs/agents/handoff-phase-8-course-foundation.md
  • legacy course, enrollment, progress, and public catalog modules: /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/courses/courses.service.ts, /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/courses/courses.controller.ts, /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/courses/courses.spec.ts.

Legacy source evidence:

  • /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/courses/**
  • course/progress Prisma models in /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/prisma/schema.prisma.

Writable files:

  • services/course-service/**
  • contracts/openapi/services/course-service.yaml
  • docs/api/course-service.md

Orchestrator-owned files:

  • billing/wallet paid-access decisions, file/question/exam shared contracts, gateway route cutovers, and shared deploy manifests unless delegated.

Delegated gateway verification scope:

  • Agents may maintain only non-default student course rehearsals in deploy/gateway/routes.student-courses-read-native-example.json, deploy/gateway/routes.student-courses-read-native-localhost-example.json, deploy/gateway/routes.student-course-progress-native-example.json, and deploy/gateway/routes.student-course-progress-native-localhost-example.json; default public course routes remain orchestrator-owned.
  • Route proof must use scripts/test/student-course-route-coverage.sh and scripts/test/student-course-progress-route-coverage.sh, with QA evidence under docs/qa/student-course-*.md.
  • Makefile targets: test-student-course-routes, test-student-course-progress-routes, and test-student-course-browser.

Initial tasks:

  • [x] Add workflow tests for course authoring -> sections -> lessons -> enrollment -> progress.
  • [x] Audit material references so file bytes remain in file/document-service.
  • Done: define and persist management-only quiz/question references. GET /v1/courses/integration-contracts/question-exam names question-bank-service and exam-service owner APIs; native /v1 routes keep only ids, placement metadata, and optional copied titles in course-service.
  • [x] Add usecase-level tests for enrollment, archive, public catalog, and progress behavior; HTTP-only coverage is too thin for public promotion.
  • The cross-service mastery/recommendation/report task is analytics-course-learning-projection.md. It requires Course catalog and progress snapshots plus a trusted course-attempt context; Course must not derive mastery or map an attempt by examId alone. File-streaming, owner API validation, and public/Gateway question/quiz delivery remain separate from the local reference slice.

Current hardening:

  • services/course-service/internal/usecase/course_test.go covers manager guards, section unlinking, lesson-owned material/progress cleanup, enrollment expiry, video progress auto-completion, material view counting, and public preview filtering.
  • Student/public material projections now respect the visibility of the attached lesson; standalone course materials continue to use their own published/preview flags.
  • Done: add SVC-007-student-course-route-proof HTTP coverage for enrolled student list/detail/progress routes, published lesson/material projection, cross-organization denial, and non-student denial. Static route guards and the browser self-test remain non-default evidence; live/browser promotion proof is still required.
  • Student-web's opt-in native course detail now applies a successful lesson progress write locally instead of serially reloading the same detail. The owner-service refresh remains the next route load; unit and browser-intercept regressions require no additional detail read after the write. This is a frontend latency improvement only and does not change the default route.
  • Course search projection now emits copied COURSE documents at GET /v1/courses/search-projections for search-service rebuilds. Keep search indexing as copied data owned by search-service, never a direct cross-service DB read.
  • 000003_course_search_projection_outbox.sql now adds a Course-owned durable projection revision/outbox and opt-in JetStream publisher. Parent course writes, count-affecting lesson/material/enrollment/progress mutations, and an organization move commit the copied event with the owner transaction. Archive is an UPSERT with ARCHIVED because the owner rebuild route includes archived courses; only a physical course delete emits DELETE. Raw lesson/material content, video/view changes, student ids, file URLs, and storage keys are not copied. The publisher is disabled unless COURSE_SEARCH_PROJECTION_EVENT_TRANSPORT=nats (or an equivalent supported NATS selector) is configured with a Postgres store.
  • 000004_course_question_exam_references.sql adds local lesson-question and quiz reference rows with no foreign keys to owner-service databases. Duplicate (lesson,question) and (course,exam) saves update local placement metadata. Lesson delete cascades question refs and unbinds quiz lesson placement; section delete unbinds quiz section placement. Course read/search projection count fields are updated, but external ids and copied titles are not emitted.
  • 000005_course_learning_catalog_events.sql adds an independent transactional Course catalog event feed for Analytics. It must never reuse the Search outbox/NATS subject. GET /v1/courses/learning-catalog-events is an ADMIN-only direct owner API with organization/cursor scoping and no Gateway route. Each payload has a stable sourceEventId, monotonic catalog revision, UPSERT or DELETE intent, only published topic-bound lesson placements and their lesson-bound quiz references. It excludes raw lesson content, questions/answers, media URLs, profile fields, and ambiguous topicless or course-level quiz placements.
  • 000006_course_learning_catalog_outbox.sql adds a separate Course-owned delivery outbox for the immutable 000005 snapshots. When explicitly enabled with COURSE_LEARNING_ANALYTICS_DELIVERY_ENABLED=true and an Analytics URL, its worker lease-claims, retries, and dead-letters delivery to the normal Analytics event-ingestion contract. It is disabled by default, never blocks the Course mutation that wrote the source event, never lets Analytics query Course storage, and does not materialize mastery rows or add a Gateway route.
  • 000007_course_learning_progress_outbox.sql makes organization-scoped course.lesson_progress.saved.v1 delivery durable too. Progress writes never call Analytics on the learner request path: the Course transaction records a bounded, replayable payload, while the same opt-in worker lease-claims, retries, and dead-letters the later delivery. It must remain separate from the catalog snapshot feed and must not add a default Gateway route.
  • COURSE_LEARNING_ANALYTICS_RUNTIME_CONFIRM=local-postgres make test-course-learning-analytics-runtime is the owner-to-owner runtime proof: fresh Course and Analytics databases plus their native binaries create a published topic-bound lesson, wait for catalog delivery, record a student's completion, and read the resulting mastery through Analytics HTTP. It proves the opt-in local transport/materialization slice only; it does not promote a Gateway route, reuse a configured database, or establish deployed evidence.
  • Course now resolves a private quiz-attempt context at POST /v1/internal/courses/{courseId}/quiz-links/{referenceId}/attempt-context for the authenticated Gateway adapter only. It validates student active enrollment, published Course and lesson, nonblank lesson topic, local quiz reference, and matching Exam id without querying Exam or Attempt databases. The response is bounded ids plus Course validation metadata; a browser may identify the selected placement but cannot assert this context. The adapter binds it to Attempt's v2 producer contract; it is not a public/default route.
  • Course enrollment grants now emit optional best-effort analytics-service producer events when ANALYTICS_SERVICE_URL or ANALYTICS_SERVICE_BASE_URL is configured. The event is course.enrollment.granted.v1 with sourceService=course-service, sourceEventId=course_enrollment:{enrollmentId}:granted, and a bounded enrollment payload. Analytics outages must not fail enrollment writes.
  • Course video progress and material views now also emit optional best-effort analytics-service producer events. The events are course.video_progress.saved.v1 and course.material_view.saved.v1 with timestamped source ids for mutable rows. Payloads include progress counters and display metadata only; no lesson content, media URLs, storage keys, file URLs, or profile PII. Remaining analytics work: durable rebuild/backfill policy and broader mastery projections.

Acceptance:

  • Course owns course/progress state only.
  • No wallet purchase or question source-of-truth logic is added here.
  • Public course projections are covered by route/gateway tests before cutover.
  • Purchase and paid access stay legacy or future billing/wallet integrations; no local wallet logic is added to course-service.
  • Analytics producer events are replay-safe source metadata only; course-service must not read analytics-service databases or own analytics projections.
  • Search projection outbox payloads remain redacted copied-index data; do not add raw course lesson/material content, student identifiers, file URLs, or storage keys to the event.

Verification:

  • GOTOOLCHAIN=go1.25.11 go test ./services/course-service/...
  • GOTOOLCHAIN=go1.25.11 go test ./services/course-service/internal/usecase -run TestQuestionExamIntegrationContractPreservesOwnerBoundaries -count=1 -v
  • GOTOOLCHAIN=go1.25.11 go test ./services/course-service/internal/http -run TestQuestionExamIntegrationContractRoute -count=1 -v
  • GOTOOLCHAIN=go1.25.11 go test ./services/course-service/internal/usecase -run TestCourseSearchProjectionListsRebuildDocuments -count=1 -v
  • GOTOOLCHAIN=go1.25.11 go test ./services/course-service/internal/http -run TestCourseSearchProjectionBackfillRoute -count=1 -v
  • COURSE_SEARCH_PROJECTION_OUTBOX_POSTGRES_CONFIRM=disposable-postgres make test-course-search-projection-outbox-postgres
  • COURSE_SEARCH_PROJECTION_OUTBOX_POSTGRES_CONFIRM=disposable-postgres make test-course-learning-catalog-events-postgres
  • COURSE_SEARCH_PROJECTION_OUTBOX_POSTGRES_CONFIRM=disposable-postgres make test-course-learning-catalog-delivery-postgres
  • COURSE_SEARCH_PROJECTION_OUTBOX_POSTGRES_CONFIRM=disposable-postgres make test-course-learning-progress-delivery-postgres
  • GOTOOLCHAIN=go1.25.11 go test ./services/course-service/internal/usecase -run 'TestCourseReferences|TestCourseReferenceValidation' -count=1 -v
  • GOTOOLCHAIN=go1.25.11 go test ./services/course-service/internal/http -run TestCourseReferenceRoutes -count=1 -v
  • COURSE_SEARCH_PROJECTION_BROKER_SMOKE_CONFIRM=disposable-runtime make test-course-search-projection-broker-live
  • make test-student-course-routes test-student-course-progress-routes
  • STUDENT_COURSE_BROWSER_SELF_TEST=1 make test-student-course-browser; live mode requires a student token and must observe frontend /student/courses traffic through the gateway before public route promotion.
  • docs/qa/student-course-route-rehearsals.md records the current proof gate: static route-table coverage is not public promotion evidence until a later live/browser smoke captures real student frontend traffic through the gateway.

Go-platform documentation is generated from repository Markdown.