Skip to content

Work / Learning Platform / Wiki / Synthesis

feature-completeness

Synthesisdraftverified 2026-05-01

SYNTHESIS.FEATURE-COMPLETENESS

Summary

A per-feature audit of the codebase, layer by layer: Prisma, GraphQL, NestJS, frontend. Dated 2026-05-01.

Built from

Body

Read the Frontend column with care. This audit is from 2026-05-01. The Settings page was built after it. Checked against the code on 2026-10-04, these "No UI" entries are no longer true:

The audit says no UI for Now at
disconnect OAuth, my OAuth accounts security
my sessions, revoke session security
change password security
resend verification profile, and the error state of verify-email
update profile profile
add or remove a course instructor, update an enrolment's role members
sync status on the instructor dashboard dashboard

Still true on 2026-10-04: no UI to grade or delete a submission (submissions), none for an admin to list or verify users, and none for a student to leave a course. Nothing else in the audit was re-checked, which is why the page is a draft. "Roadmap 5.x" below means FEAT-0005.

Review of the codebase against product requirements (README). Columns: Feature name, Prisma, GQL, NestJS, Frontend. Legend: ✅ Complete | ⚠️ Partial / needs verification | ❌ Missing / gap | 🧪 Verified end-to-end with a smoke test in this validation pass


Question-sync investigation (2026-05-01)

Triggered by "questions are not syncing correctly" against https://github.com/ritesh-firodiya/learning-platform-template. Inspected the cloned content vs. the synced DB rows + the parser/sync code.

What's actually wrong (and what isn't):

Finding Severity Status
Parser harvests <!-- question:db:... --> markers from inside Markdown code spans/fences — introduction/instructor.mdx contains the line Question marker example: `<!-- question:db:Q_INTRO_001 -->` purely as documentation, but the regex pulls it out and phantom-attaches Q_INTRO_001 to the INSTRUCTOR_NOTES phase. Major ✅ Fixed (stripCodeBlocks preserves length, blanks both fenced and inline code before regex matching). Verified: instructor.mdx now extracts 0 markers, all other phase files extract their real markers unchanged.
Orphan questions never deleted on re-sync — sync deletes questions referenced in MDX but absent from questions.json; if both the JSON entry and the MDX marker are removed in the same edit, the DB row stays forever. Minor ✅ Fixed — sync now collects the union of questions.json IDs + MDX-marker IDs and soft-deletes any DB question not in that union (skipping anything with submissions, for audit).
Question.lessonId was missing from the GraphQL schema even though it's a NOT NULL column on the Prisma model and the QuestionSubmission GraphQL type already exposed it. Minor ✅ Fixed — added to 0-question.schema.gql and the mapper.
Question.questionId is @unique globally in the Prisma schema. If you fork the template into a second course, syncing the second copy will hit the unique constraint on Q_INTRO_001..004 and silently fail those question rows (logged per-question, sync continues). Major ⚠️ Not fixed — schema change. Should be @@unique([lessonId, questionId]) only (already declared). Removing the field-level @unique requires a migration. Documented as roadmap follow-up.
Question.lessonPhases many-to-many relation is declared in Prisma (_LessonPhaseToQuestion join table) but never populated. Phase membership is recomputed from MDX on every lessonPhase.questions request. Minor / dead schema ⚠️ Not fixed — either remove the relation from the schema, or populate during sync (bonus: would let us cache results and skip per-request MDX parsing). Documented as roadmap follow-up.

Verified working (no bug): all 9 questions from the template (4 in introduction, 5 in sample-onboarding) sync correctly with the right type, options, correctAnswer, order, etc. lessonPhase.questions returns the expected per-phase grouping for both lessons. Phase visibility filters INSTRUCTOR_NOTES from STUDENTs. Submission flow tested earlier still passes.


Curriculum smoke test (2026-05-01)

Verdict: every product flow that doesn't depend on UI surface is working end-to-end. 26/26 GraphQL-level checks pass against a synced learning-platform-template course at /tmp/lms-repos/....

Verified flows (script at /tmp/feature-smoke.sh):

  1. Public catalog query returns 2 published courses, both syncStatus=SUCCESS.
  2. courseBySlug returns the course with its 2 lessons + localRepoPath + contentPath.
  3. lessonPhases(lessonId) returns 3 phases for STUDENT (PRE_CLASS, LIVE_CLASS, POST_CLASS); INSTRUCTOR_NOTES correctly hidden.
  4. slides(lessonId) returns 3 slides with contentPath pointing into the cloned repo.
  5. Slide.content resolves to the actual MDX from disk.
  6. LessonPhase.content resolves to the actual MDX from disk.
  7. getAsset(courseId, "course.json") returns the JSON file with mimeType=application/json + encoding=utf-8.
  8. getAsset rejects ../../../etc/passwd path traversal (returns null instead of leaking content).
  9. Register a fresh STUDENT, get JWT.
  10. enrollInCourse succeeds, returns status=ACTIVE.
  11. myEnrollments shows the new enrollment.
  12. STUDENT-scoped phase query still hides INSTRUCTOR_NOTES after auth.
  13. Phase has 1 question (CHECKLIST type), question metadata resolves.
  14. submitQuestion with the required questionId+courseId+answer triple persists a submission.
  15. myQuestionSubmissions shows the submission to the student.
  16. unenrollFromCourse succeeds.

Caveats found while verifying:

  • Express rate-limit is set to 100 req / 15 min globally (apps/server/src/bootstrap.ts). That's enough to rate-limit a few back-to-back smoke runs from the same IP. For production, either bump RATE_LIMIT_MAX env or add a per-IP exemption strategy — otherwise a power-user student walking through a course quickly will get 429s. Severity: minor for normal use, major for testing/onboarding.
  • GitService readLocalFile works because /tmp/lms-repos/... is populated locally (the user already ran the sync). On a Vercel cold start, this directory is empty and content reads will return null until a fresh syncCourse runs. Documented in .context/ops/runbooks/deployment.md; real fix is the Octokit content-read rewrite (Roadmap 5.10).
  • getAsset for missing/traversal paths returns {"data":{"getAsset":null}} with no error code. UI must handle null gracefully.

Status as of 2026-05-01 — auth stabilization pass

This round verified auth end-to-end against a running stack and fixed three latent bugs. Every change below is reflected in the per-section tables.

Bugs fixed:

  1. Refresh-token rotation was a no-op. refreshToken re-bcrypt-hashed the input and looked up Session.hashedToken by equality. bcrypt produces a different output every call, so the lookup never matched — refresh always failed. Even after fixing that, bcrypt's 72-byte input cap caused all refresh tokens for a given user to collide on their shared header+payload prefix, so any of the user's old refresh tokens would match any active session. Fixed by storing sha256(refreshToken) (the JWT signature already makes the token unguessable) and looking up by direct equality. Also added a jti claim to refresh tokens so two rotations within the same second produce distinct tokens. Verified: rotation now produces a new token; re-using a revoked token is rejected with UNAUTHORIZED.
  2. Email verification + password reset tokens were double-hashed. AuthorizationService bcrypt-hashed once, then VerificationTokenService.create bcrypt-hashed again, storing bcrypt(bcrypt(plaintext)). The lookup did compare(plaintext, stored) which always returned false. Fixed by removing the second hash. Verified: verifyEmail succeeds with the matching plaintext and flips User.emailVerified to true.
  3. adminVerifyUser resolver had no schema entry. The resolver was registered but the mutation wasn't declared, so Nest crashed at boot. Added to 0-user.schema.gql with a matching .mutation.gql document.

Hardening added:

  • Password strength validator (shared/utils/password.utils.ts) wired into register and resetPassword. Rejects passwords under 8 chars or without both a letter and a digit.
  • Refresh tokens now carry a random jti claim to make rotation collision-free.

Gaps still open — see "Open security follow-ups" near the bottom.


1. Auth

Feature Prisma GQL NestJS Frontend
Login (email/password) ✅ User, Session, Account ✅ login mutation 🧪 AuthorizationService ✅ /auth/login, useAuth.login, LoginForm
Register ✅ User, Session ✅ register mutation 🧪 AuthorizationService (now enforces password strength) ✅ /auth/register, useAuth.register, RegisterForm
Logout ✅ Session (deleted by user) ✅ logout mutation ✅ AuthorizationResolver (AuthGuard) ✅ useAuth.logout
Refresh token 🧪 Session.hashedToken now stores sha256(jwt) ✅ refreshToken mutation 🧪 AuthorizationService — fixed bcrypt-truncation bug, added jti for collision-free rotation ✅ Apollo error link auto-refreshes on UNAUTHENTICATED, queues concurrent requests
OAuth login (Google, GitHub) ✅ Account, User ✅ loginWithOAuth mutation ⚠️ AuthorizationService — works, but state parameter is accepted and never validated server-side (CSRF gap, see follow-ups) ✅ Popup flow, useAuth.authenticateWithOAuth
Connect OAuth ✅ Account ✅ connectOAuth mutation ⚠️ Same state gap as login ✅ useAuth.authenticateWithOAuth(mode='connect')
Disconnect OAuth ✅ Account (soft-delete) ✅ disconnectOAuth mutation ✅ AuthorizationService ❌ No UI
Request password reset ✅ VerificationToken (PASSWORD_RESET) ✅ requestPasswordReset mutation ✅ AuthorizationService ✅ /auth/forgot-password, ForgotPasswordForm
Reset password (with token) 🧪 VerificationToken (single-bcrypt now) ✅ resetPassword mutation, validatePasswordResetToken query 🧪 AuthorizationService — double-hash fixed; password-strength enforced on the new password ✅ /auth/reset-password, ResetPasswordForm
Verify email 🧪 VerificationToken (single-bcrypt now) ✅ verifyEmail mutation 🧪 AuthorizationService — verified flips User.emailVerified end-to-end ✅ /auth/verify-email, VerifyEmailForm
Resend verification email ✅ VerificationToken (3/hour rate limit) ✅ resendVerification mutation ✅ AuthorizationService ❌ No UI
Admin verify user ✅ User.emailVerified ✅ adminVerifyUser mutation (added in this pass) ✅ UserResolver ❌ No UI

Auth summary: Email/password auth, JWT rotation, password reset, email verification, and OAuth state CSRF validation are now verified end-to-end. Disconnect OAuth, resend verification, admin verify user, and admin user list have no UI yet.

Session persistence — verified

Concern Status Notes
Token storage ✅ Works localStorage via Zustand persist + mirrored to accessToken cookie (path=/, max-age 7d, SameSite=Lax) so Next middleware/SSR can see auth
Reload survival 🧪 Verified AuthProvider rehydrates from cookie if store is empty, then runs me to validate
Auto-refresh on 401 ✅ Works Apollo errorLink queues concurrent requests during refresh; refresh now actually succeeds
Rotation security 🧪 Verified New refresh token has unique jti; re-use of an old token is rejected
Multi-device ✅ Allowed Each login/register creates a new Session row; logout revokes all of the user's sessions (deleteByUserId)

Open security follow-ups (prioritised)

Resolved in the production-readiness pass:

  • ✅ A — OAuth state CSRF — useAuth now stores generated state in sessionStorage, the callback page forwards state via postMessage, and useAuth rejects mismatch before calling loginWithOAuth/connectOAuth.
  • ✅ C — Email-verification gate — EmailVerifiedGuard is wired onto enrollInCourse and submitQuestion. Disabled by default; flip via REQUIRE_EMAIL_VERIFICATION=true once SES/email is verified working.
  • ✅ D — Cross-tab logout — AuthProvider listens for storage events on the auth-storage key and clears local auth state when a sibling tab logs out.
  • ✅ E — Default JWT secret — env.validator.ts rejects placeholder secrets, malformed ENCRYPTION_KEY, length under 32, and JWT_SECRET===JWT_REFRESH_SECRET. Refuses to boot in NODE_ENV=production.

Still open:

# Item Severity Complexity
B Access token in localStorage + non-httpOnly cookie. XSS can steal both. Move to httpOnly + Secure cookie set by the server; have Apollo read it server-side or via a signed token endpoint. Substantive refactor — defer to a hardening phase. Major High
F Password strength. Currently 8 chars + letter + digit. Consider zxcvbn or HIBP check before opening signup to the public. Minor Low
G Vercel sync limitation. GitService content reads expect a populated /tmp clone; Vercel cold starts arrive with an empty /tmp. Mitigations + the Octokit follow-up are documented in .context/ops/runbooks/deployment.md. (Roadmap 5.10) Major Medium

2. User & Profile

Feature Prisma GQL NestJS Frontend
Current user (me) ✅ User ✅ me query ✅ UserResolver (AuthGuard) ✅ AuthProvider useMeQuery, useAuthStore
User profile (public) ✅ User ✅ userProfile(id) query ✅ UserResolver ⚠️ Query exists; profile page not verified
Update profile ✅ User ✅ updateUser mutation ✅ UserResolver (AuthGuard) ⚠️ Not verified (settings/profile page)
Change password ✅ User.password ✅ changePassword mutation ✅ UserResolver, UserService ⚠️ GQL + backend present; change-password UI not verified
Admin: list users ✅ User ✅ users query (ADMIN) ✅ UserResolver (RolesGuard) ⚠️ Admin UI not verified
Admin: delete user ✅ User ✅ deleteUser mutation ✅ UserResolver (ADMIN) ⚠️ Admin UI not verified
Check email availability ✅ User ✅ isEmailAvailable query ✅ UserResolver ⚠️ Can be used by RegisterForm

3. Sessions & Accounts

Feature Prisma GQL NestJS Frontend
My sessions ✅ Session ✅ mySessions query ✅ SessionResolver (AuthGuard) ⚠️ GQL + backend present; sessions UI not verified
Revoke session ✅ Session.isRevoked ✅ revokeSession mutation ✅ SessionResolver ⚠️ Sessions management UI not verified
My OAuth accounts ✅ Account ✅ myAccounts query ✅ AccountResolver (AuthGuard) ⚠️ Used in connect OAuth flow; accounts list UI not verified

4. Instructor Onboarding

Feature Prisma GQL NestJS Frontend
Role assignment ✅ User.role (INSTRUCTOR) ✅ updateUser (role) — typically ADMIN ✅ UserResolver (admin) ⚠️ No dedicated “instructor onboarding” flow in README; role set by admin or seed
First-time instructor flow — — — ⚠️ My Courses shows instructor view by role; no separate onboarding wizard

Note: README does not define a dedicated “Instructor Onboarding” flow; instructors are users with role INSTRUCTOR and use “My Courses” and course link/sync.


5. Student Onboarding

Feature Prisma GQL NestJS Frontend
Enrollment ✅ Enrollment ✅ enrollInCourse mutation 🧪 EnrollmentService ✅ Course page / course card: useEnrollInCourseMutation
My enrollments ✅ Enrollment ✅ myEnrollments query 🧪 EnrollmentResolver ✅ My Courses (student): useMyEnrollmentsQuery
Unenroll ✅ Enrollment ✅ unenrollFromCourse mutation 🧪 EnrollmentService ⚠️ UI not verified
First-time student flow — — — ⚠️ Browse courses → enroll; no dedicated onboarding wizard

6. Course (Curriculum)

Feature Prisma GQL NestJS Frontend
Course list (catalog) ✅ Course ✅ courses query (where, orderBy, pagination) 🧪 CourseService ✅ /courses, home: useCoursesQuery, CourseFilterStore
Course by slug ✅ Course ✅ courseBySlug query 🧪 CourseResolver ✅ Course page, instructor dashboard: useCourseBySlugQuery
Course by id ✅ Course ✅ course query ✅ CourseResolver ✅ Used by other resolvers / optional client use
Link course (connect repo) ✅ Course, CourseInstructor ✅ linkCourse mutation (LinkCourseInput) 🧪 CurriculumService (GitService clone) — verified by the synced course in DB at /tmp/lms-repos/... ✅ Link course modal: useLinkCourseMutation
Unlink course ✅ Course ✅ unlinkCourse mutation ✅ CurriculumResolver ✅ CourseInstructorControls: useUnlinkCourseMutation
Sync course (manual) ✅ Course, Lesson, LessonPhase, Slide, Question ✅ syncCourse mutation 🧪 CurriculumService — syncStatus=SUCCESS produced 2 lessons / 8 phases / 6 slides / 9 questions from the linked template repo ✅ CourseInstructorControls: useSyncCourseMutation
Update course (e.g. publish) ✅ Course ✅ updateCourse mutation ✅ CurriculumResolver ✅ CourseInstructorControls: useUpdateCourseMutation (toggle isPublished)
My courses (instructor) ✅ CourseInstructor, Course ✅ myCourses query ✅ CurriculumResolver (AuthGuard, instructor filter) ✅ My Courses (instructor): useMyCoursesQuery
Get asset (file from repo) — (GitService) ✅ getAsset(courseId, assetPath) query 🧪 CourseService — course.json returns json mimeType utf-8; path traversal returns null ✅ Used for content (e.g. MDX/images) in course view
Add course (create from repo) — — — ✅ Implemented as "Link course" (no separate "create" without repo)

7. Course Instructors

Feature Prisma GQL NestJS Frontend
Add instructor to course ✅ CourseInstructor ✅ addCourseInstructor mutation ✅ CourseInstructorResolver (ADMIN/INSTRUCTOR) ⚠️ GQL + backend present; add-instructor UI not verified
Remove instructor ✅ CourseInstructor ✅ removeCourseInstructor mutation ✅ CourseInstructorResolver ⚠️ UI not verified

8. Lesson & Phases & Slides

Feature Prisma GQL NestJS Frontend
Lessons list (by course) ✅ Lesson ✅ Field Course.lessons + lessons query 🧪 LessonResolver — 2 lessons resolved on test course ✅ Course page, sidebar
Lesson by id ✅ Lesson ✅ lesson query ✅ LessonResolver ✅ useLessonQuery (course content)
Lesson by slug ✅ Lesson ✅ lessonBySlug(courseId, slug) query ✅ LessonResolver ✅ Course content navigation
Lesson phases (Pre/Live/Post/Instructor) ✅ LessonPhase ✅ lessonPhase, lessonPhases queries, Lesson.phases 🧪 LessonPhaseResolver — @auth directive enforced; service-layer filter hides INSTRUCTOR_NOTES from STUDENTs (verified) ✅ lesson-phase-content, phase navigation
Slides (per lesson) ✅ Slide ✅ slide, slides queries, Lesson.slides 🧪 SlideResolver — 3 slides resolved on test lesson ✅ slide-content, phase navigation
Live content (MDX from repo) ✅ LessonPhase.contentPath, Slide.contentPath ✅ LessonPhase.content + Slide.content resolved at request time 🧪 GitService.readLocalFile reads MDX from cloned repo on disk — both phase content and slide content verified end-to-end against the cloned learning-platform-template ✅ Markdown renderer, GetAsset

9. Question (Definition)

Feature Prisma GQL NestJS Frontend
Question by id ✅ Question ✅ question query ✅ QuestionResolver ✅ Used in question renderer, submissions
Question by questionId ✅ Question ✅ questionByQuestionId query ✅ QuestionResolver ✅ Used for content-driven questions
Questions list (filter) ✅ Question ✅ questions query, LessonPhase.questions 🧪 QuestionResolver — 9 questions seeded from repo, resolves on phase + course ✅ Instructor questions page: useQuestionsQuery
Question types (Checklist, Poll, MCQ, Fill-in-blank, Code script) ✅ QuestionType enum ✅ In Question type and inputs 🧪 Question entity — CHECKLIST verified end-to-end with [true,false] answer ✅ question-renderer, type-specific components

10. Question Submission (Student & Instructor)

Feature Prisma GQL NestJS Frontend
Submit answer ✅ QuestionSubmission ✅ submitQuestion mutation (requires questionId + courseId + answer: JSON) 🧪 QuestionSubmissionService — CHECKLIST submission persisted with answer JSON ✅ question-renderer: useSubmitQuestionMutation
My submissions ✅ QuestionSubmission ✅ myQuestionSubmissions query 🧪 QuestionSubmissionResolver — student sees own submission immediately after submit ✅ useMyQuestionSubmissionsQuery (existing-submission display)
List submissions (instructor) ✅ QuestionSubmission ✅ questionSubmissions query ✅ QuestionSubmissionResolver ✅ Instructor submissions page: useQuestionSubmissionsQuery, GraphQLDataTable
Update submission (grade) ✅ QuestionSubmission (isCorrect, points, gradedAt) ✅ updateQuestionSubmission mutation ✅ QuestionSubmissionResolver (INSTRUCTOR/ADMIN) ❌ No grade/edit UI — table is read-only; mutation exists but not used
Delete submission (instructor) ✅ QuestionSubmission ✅ deleteQuestionSubmission mutation ✅ QuestionSubmissionResolver ⚠️ No UI found
Submission stats — ✅ questionSubmissionStats query ✅ QuestionSubmissionResolver (INSTRUCTOR/ADMIN) ⚠️ Can be used for dashboard; not verified
Instructor view correct answer ✅ Question.correctAnswer, PhaseVisibility ✅ Question type (correctAnswer), phase visibility ✅ Resolvers return by role ✅ view-answer-button, question-answer-display (instructor)

11. Enrollment & Progress

Feature Prisma GQL NestJS Frontend
Enroll in course ✅ Enrollment ✅ enrollInCourse mutation ✅ EnrollmentResolver ✅ useEnrollInCourseMutation (course header, course card)
Unenroll ✅ Enrollment ✅ unenrollFromCourse mutation ✅ EnrollmentResolver ⚠️ GQL + backend present; unenroll UI not verified
Update enrollment (e.g. status) ✅ Enrollment ✅ updateEnrollment mutation ✅ EnrollmentResolver ⚠️ UI not verified
Enrollment stats (instructor) ✅ Enrollment ✅ enrollmentStats query ✅ EnrollmentResolver (INSTRUCTOR/ADMIN) ⚠️ Can be used in dashboard
Lesson progress (Pre/Live/Post completed) ✅ LessonProgress ✅ createLessonProgress, updateLessonProgress, lessonProgressList, etc. ✅ LessonProgressResolver, LessonProgressService ✅ lesson-progress-indicator: useLessonProgressListQuery, useMyEnrollmentsQuery; progress-overview
Create/update progress ✅ LessonProgress ✅ create/update/delete mutations ✅ LessonProgressResolver ⚠️ Progress may be updated implicitly or via dedicated UI; not fully traced

12. Analytics & Metrics

Feature Prisma GQL NestJS Frontend
Course stats (enrollments, lessons, submissions) — ✅ courseStats(id) query ✅ AnalyticsResolver, AnalyticsService ✅ Instructor dashboard: useCourseStatsQuery
Enrollment stats — ✅ enrollmentStats(courseId) query ✅ AnalyticsResolver ⚠️ Backend present; dashboard usage not verified
Health / version — ✅ _version, _ping (metrics) ✅ MetricsResolver, health controller ✅ Used for health checks

13. Verification Token (Internal)

Feature Prisma GQL NestJS Frontend
Validate password reset token ✅ VerificationToken ✅ validatePasswordResetToken query ✅ VerificationTokenResolver ✅ Reset password flow (token validation)
Use verification token (consume) ✅ VerificationToken ✅ useVerificationToken mutation ✅ VerificationTokenResolver ✅ Used in reset/verify flows

Summary of Gaps

Resolved in this pass (2026-05-01):

Auth + verification round:

  • adminVerifyUser not in GQL schema — added with mutation document.
  • Auth forms (Forgot/Reset/Verify) missing under packages/ui/src/components/auth/ — restored.
  • Refresh token never works — fixed (sha256 storage + jti for rotation).
  • Email verification + password reset always fail (double-hash) — fixed.
  • No password strength validation — added.

Production-readiness round:

  • OAuth state CSRF — sessionStorage + popup postMessage round-trip.
  • Email-verification gate — EmailVerifiedGuard on enrollInCourse+submitQuestion, opt-in via env flag.
  • Cross-tab logout — storage listener in AuthProvider.
  • Default JWT secret — env.validator.ts refuses placeholder/short secrets in prod.
  • Wrong-project URL https://api.dwarseva.com in CI — replaced with vars.NEXT_PUBLIC_API_URL; stale Cloudflare workflow removed.
  • /api/* route deprecation warning — migrated to {*splat} for path-to-regexp v8.
  • GITHUB_TOKEN undocumented — documented in .env.example + DEPLOYMENT.md; warning logged at prod startup if unset.
  • No deployment doc — .context/ops/runbooks/deployment.md covers required Vercel env vars, OAuth callback URLs, sync limitation, smoke test.

Still open:

  1. Vercel /tmp ephemeral filesystem — GitService content reads need an Octokit-fallback rewrite (Roadmap 5.10).
  2. localStorage XSS exposure — access token also lives in a non-httpOnly cookie. Roadmap follow-up B.
  3. No UI for: disconnect OAuth, resend verification, admin verify user, admin user list, my sessions / revoke session, my OAuth accounts list, change password, unenroll, add/remove course instructor, instructor grade submission (updateQuestionSubmission), delete submission.
  4. Profile / settings page not verified — updateUser, userProfile queries exist; client integration not traced.
  5. Logging + error tracking — Sentry wire-up pending (Roadmap 5.6).
  6. Sync-job observability surface — Course.syncStatus etc. tracked but not all surfaced on instructor dashboard (Roadmap 5.7).
  7. DB backup runbook — undocumented (Roadmap 5.8).
  8. No tests — Phase 4 entirely.

How This Was Done

  • Prisma: packages/db/prisma/schema.prisma — models and enums.
  • GQL: packages/gql/schema.gql (merged) and packages/gql/src/**/*.gql for operations; packages/gql/src/**/0-*.schema.gql for schema fragments.
  • NestJS: apps/server/src/api/**/*.resolver.ts, *.service.ts, and related modules.
  • Frontend: apps/website (Next.js) and shared packages/ui — pages under app/, components using gql/client hooks and ui/components.

Last refresh

2026-05-01 — the audit's own date. The table above was added on 2026-10-04; the audit itself was not re-run. tasks.md T006.

Sources

  • (synthesis pages cite wiki pages, not raw files)