Contracts and core:
- Feature gains auth and stop; FeatureFactory; core composeServer builds the
context, runs the factories, then the job queue from the union of their jobs
(R8/R14). startServer uses it; main.ts composes learner, import, studio, AI.
- AppConfig/loadConfig own the layout and settings: courseDirs include
<dataDir>/courses and <dataDir>/worktrees, studioReposDir, cacheDir,
importRoots, uploadMaxMb, aiAllowedBaseUrls, transcribeUrl, python.
resolveConfig derives the same for tests; cachePaths shares the importer's
cache layout.
- canView: the global author role views every course (R14); the learner,
studio and AI packages all call it.
- migrate(db, packages): core first, then packages in name order.
- Media: nosniff on every response, a sandbox CSP on svg/html/js; a Range on
an empty file is 416.
- courseUrl/lessonUrl and the lesson route patterns live in @stc/contracts.
Packages:
- import, studio and AI read AppConfig instead of process.env; the importer
refuses ids used by studio/repos.
- learner migrations run through core migrate; library covers fall back to
the importer's cached candidate behind a canView-checked route.
- studio preview renders through the learner LessonView (anchors honoured,
draft URLs); AI drafts go to the studio draft branch for studio courses (as
the requesting user) and to <dataDir>/drafts otherwise, and the generator
page says which.
Web: tsconfig paths for @stc/<feature>/web browser entries; features.ts
imports them by package name; nav.roles hides links by role.
Tests: compose, config, canView, media headers, migrations order, covers,
preview, nav, SPA bundle without server code, and an end-to-end flow on
SQLite and Postgres.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Learner links go through lessonUrl/courseUrl (src/web/urls.ts).
- Generation, the studio course list and file listing require an authoring
right and core canView, so a global author needs a grant for a restricted
course. Contract requests 8 (global author in canView) and 9
(STC_AI_ALLOWED_BASE_URLS in AppConfig).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
read_file and read_lesson URL-decode, posix-normalise, reject .. and absolute
paths, resolve through CourseRepository.resolvePath (symlinks followed), then
guard the real course-relative path and compare real paths with every
manifest browser.solution. Hidden files are refused too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
STC_SECRET, database URLs and API keys are no longer inherited by MCP child
processes. Test: a fake server reports its environment.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- A user's own key goes only to https://api.anthropic.com, https://api.openai.com/v1
or an origin listed in STC_AI_ALLOWED_BASE_URLS / the server config's
allowedBaseUrls; anything else is a 400, and a stored key whose origin was
later removed stops being used.
- User-scope provider calls never follow redirects, and their errors reach the
user only as the HTTP status plus a fixed message; the full text is logged.
- The admin's server-scope provider may still use any base URL.
- Admin UI: allowed origins field; MCP stdio env editable, HTTP headers kept.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Spec 9.2 has the assistant retrieve from the learner's notes and progress;
progress was missing. Reads progressRepo(db).get for the signed-in user.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Retrieval now reads <dataDir>/cache/decks/<courseId>/<deckId>/text.json and
<dataDir>/cache/transcripts/<courseId>/<lessonId>.json (skipped when the lesson
has a transcript file). DraftSink returns { location?, branch?, commit? }, so
the studio's createDraftWriter(ctx) needs no adapter; generateDraft computes
paths itself and reports the draft branch.
Co-Authored-By: Claude Opus 5.5 <[email protected]>