Tutor — Project Bible
The single source of truth for what Tutor is, what it is not, and the rules that keep it coherent. The
README.mdtells you how to build and run; this tells you how to think about the system and why it's shaped the way it is.
1. The one sentence
Drop a book in. Get a course out. Tutor turns books, papers, and documents into structured, navigable courses — with a concept graph, a learning path, baked quizzes, and RAG‑grounded answers — and lets those courses be packaged, shared, loaded, and unloaded.
2. The product promise
- You bring the source. Tutor builds the course. A PDF, EPUB, DOCX, HTML,
legacy
.doc/.rtf/.odt,.mobi/.azw/.azw3, or a Project Gutenberg ID goes in; a hierarchical course (Lessons → Sections → concepts, with quizzes) comes out. No manual authoring required. - The knowledge graph is the product, not the source bytes. Concepts are extracted per chunk, correlated across resources into one graph, and the course is generated from the graph. The source is retained for grounding, not replayed verbatim.
- Answers are grounded. Every quiz prompt and section fill is retrieved from the actual source via embeddings + RAG. Tutor does not hallucinate content that isn't on the page.
- Courses travel. A finished course is a self‑contained
.tutorbundle — structure, concepts, quizzes, and pre‑computed embeddings — that installs without re‑running the (slow, paid) LLM pipeline. - Same engine, two front doors. Anything the CLI can build, the Blazor app can read, because both register the identical service graph.
3. What Tutor is NOT
- Not a live tutor chatbot bolted onto a PDF. The graph and structure are first‑class artifacts; chat is grounded in them, not a free‑floating assistant.
- Not a multi‑tenant SaaS (yet). It is a single‑host Blazor Server app with SQL‑backed auth. Designs should not assume horizontal scale-out or per‑tenant isolation unless that becomes an explicit goal.
- Not provider‑locked. No code path may hard‑code a single LLM vendor; all
LLM calls route through
LlmServiceRouteroverMindAttic.Legion. - Not a content store that merges courses. Bundles are independent and are never merged into one another.
4. Architecture canon
SOURCE FILE ─► PARSE ─► CHUNK ─► EMBED ─► EXTRACT CONCEPTS ─► CONCEPT MAP
│
KNOWLEDGE GRAPH ◄─ CORRELATE ◄──────┘
│
COURSE STRUCTURE ◄──────┘
│
.tutor BUNDLE ◄─────────┘ (share / load / unload)
4.1 Projects (the only legitimate homes for code)
| Project | Owns |
|---|---|
| Tutor.Core | Parsers, the full pipeline, domain models, storage services, and course packaging (Tutor.Core/Services/Packaging). Everything substantive lives here. |
| Tutor.Shared | Razor components shared by the host (and any sibling shell), including the /library page. |
| Tutor.Blazor | Blazor Server host. Composition root (Program.cs), DI wiring, middleware, the bundle re‑share endpoint. |
| Tutor.Cli | tutor headless binary. Mirrors Blazor's DI graph; the terminal front door for build, export, install and delete. |
| Tutor.Tests | NUnit unit/component suite. |
| Tutor.Cypress | E2E browser tests (Node sibling, not in Tutor.slnx). |
4.2 Domain model (the nouns)
- CourseResource — one ingested source (book/paper/doc). Has original + AI‑formatted content. Produces exactly one ConceptMap (1:1).
- ConceptMap — concepts + relationships + complexity ordering for a single resource.
- ConceptMapCollection (
collection_{courseId}) — the aggregated graph across all of a course's resources. - Course — lightweight metadata + references only (resource IDs, the collection ID, the structure ID). Carries no content directly.
- CourseStructure — the learning path: ordered Lessons → hierarchical
Sections → ConceptIds, plus baked
PreGeneratedQuestions. - ContentChunk — a text snippet + its 1536‑dim embedding + LSH/SimHash signatures. The RAG substrate.
- UserProgress — per‑user, per‑course mastery state driving lesson gating.
- BundleManifest — the
manifest.jsonof a.tutorbundle:FormatVersion, stableCourseKey+ whole‑numberCourseVersion, payloadSha256, counts,IncludesEmbeddings, provenance (Author,License,Description,SourceAttribution) and anExtradictionary that round‑trips unknown fields. - InstalledCourse — one registry row per installed bundle (
CourseId,CourseKey,CourseVersion,Name,Sha256,BlobPath,Enabled,InstalledUtc), kept in{AppData}\InstalledCourses\installed-courses.json.
Invariant: a
Coursenever embeds content. Content lives in resources, structure, concept maps, and chunks; the course just points at them. Keep it that way — it's what makes bundles composable and storage deduplicated.
4.3 Key services (the verbs)
LlmServiceRouter (provider selection) · ConceptMapService (extract) ·
KnowledgeGraphService (correlate via LSH+SimHash) · OrphanConceptLinkerService
(reattach strays) · CourseStructureService (graph → learning path) ·
SectionContentService (fill sections) · EmbeddingService + VectorStoreService
(RAG) · QuizService (LLM‑backed quizzing) · LearningPathService (mastery
gating) · FinalExamService + CertificateService (completion).
4.4 Course packaging lifecycle
A .tutor file is a plain zip:
manifest.json BundleManifest (identity, integrity, provenance, counts)
course.json Course metadata (references only)
courseStructure.json Lessons → Sections → ConceptIds + baked quizzes (optional)
resources/{id}.json CourseResource metadata
resources/{id}.original.txt
resources/{id}.formatted.md
conceptMaps/{id}.json One ConceptMap per resource
chunks.json RAG chunks WITH pre-computed embeddings
Both front doors go through one code path, CourseInstallService:
- Export —
CourseExporterwrites the bundle, derivesCourseKeyfrom the course name when none is given, and stores the SHA‑256 of the payload (everything but the manifest, independent of entry order). - Inspect / validate —
CourseManifestValidatoris pure and IO‑free. Hard errors block (no manifest, format newer thanHostMaxFormatVersion, bad key, non‑positive version, no name, missingcourse.json, SHA mismatch, unsafe entry path); a missing key or SHA is a legacy warning, so older bundles still install. - Plan —
CourseInstallResolverdecides a clean install, a no‑op (same key and version already installed), an upgrade (higher version), a refused downgrade, or a side‑by‑side duplicate (only with an explicit allow‑duplicate), comparing against the highest installed version of the same key. - Install —
BundleImporterremaps every GUID and persists through the same storage services the live app uses;InstalledCourseRegistryrecords the row andCourseBlobStorekeeps the verbatim bundle at{AppData}\courses\{key}\{version}.tutorfor re‑share. - Load / unload — flips
InstalledCourse.Enabled; the learning view filters out disabled courses, and no data or progress is touched. - Remove —
CourseDeleteServicehard‑cascades resources, structure, concept maps and embeddings, then drops the registry row and blob (tutor delete, or Remove on/library).
The Blazor /library page ([Authorize]) lists installed courses with a
re‑share download (/api/library/{courseId}/bundle); install, load/unload and
remove are admin‑only. The page only hides those controls from non‑admins; the
handlers themselves run through LibraryAdminActions (Tutor.Shared/Services),
which authorizes the circuit's user against MaPolicies.Admin on the server and
refuses anyone else before anything changes. Install previews the manifest,
provenance, validation messages and the plan before asking to confirm.
5. The Laws (non‑negotiable rules)
Tutor inherits the org‑wide laws from MindAttic.HouseRules.md by reference — they are not restated here. The project‑specific laws below extend them.
Inherited from House Rules (authoritative text lives there):
- Whole‑number versioning — see HOUSE-LAW-1
- Soft‑disable, never hard‑delete — see HOUSE-LAW-2
(Tutor:
IUserAdminServiceis pinned by a contract test exposing no hard delete; course unload hides a course without erasing it, and only an explicit Remove cascades.) - Credentials resolve through MindAttic.Vault — see HOUSE-LAW-3
(Tutor: LLM keys via
%APPDATA%\MindAttic\LLM\providers.jsonorIConfiguration; auth secrets via the VaultSecuritybucket.) - Provider‑agnostic LLMs via MindAttic.Legion — see HOUSE-LAW-4
(Tutor: route through
LlmServiceRouter; never reference a vendor SDK directly from pipeline code.) - Packaging is a guarded zip with a lifecycle — see HOUSE-LAW-5
(Tutor: the
.tutorlifecycle in §4.4. Courses are pure data, so nothing in a bundle is ever loaded as code.) - One engine, many front doors — see HOUSE-LAW-6 (Tutor: a service registered for Blazor must be registered identically for the CLI; a course built by one is readable by the other with zero translation.)
- Authentication via MindAttic.Authentication — see HOUSE-LAW-7
- Definition of done is verified, not asserted — see HOUSE-LAW-8
Project‑specific laws:
- Grounding over generation. Content shown to a learner is retrieved from source via RAG, not invented. Quizzes and section fills cite the page. This is Tutor's defining constraint: the source is retained for grounding, never replayed verbatim, and never substituted by free‑floating generation.
- The graph is the product, not the source bytes. Courses are
generated from the correlated knowledge graph, and a
Coursenever embeds content — it references resources, structure, concept maps, and chunks. This is what keeps bundles composable and storage deduplicated (see the §4.2 invariant). - Bundles are independent; never merge courses. A
.tutorbundle installs as its own course; courses are never merged into one another. Re‑installing the same key and version is a no‑op, a lower version is refused, and only an explicit allow‑duplicate installs a second, independent copy (all GUIDs remapped). - Code style. (from
CLAUDE.md): private fields arecamelCasewithout underscore prefix; constructors usethis.x = x.
6. Verified state
What's proven working, with the build/test evidence. Status legend: ✅ verified · 🟡 partial · ⬜ planned.
Build/test evidence (2026-10-03): dotnet build Tutor.slnx → 0 errors (Debug and
Release); dotnet test Tutor.Tests → Passed: 450, Failed: 0, Skipped: 0, including the
packaging tests in Tutor.Tests/Packaging.
6.1 Authentication canon ✅
Tutor authenticates through MindAttic.Authentication (HOUSE-LAW-7), package 6.0.0 (Debug builds also reference the sibling source project). The shape:
- Storage: SQL Server (
TutorAuthDbContext).AuthUsers(Argon2id + pepper) andAuthSessions(idle 30 min / absolute 8 h, IP+UA bound, revocable). Connection stringTutorAuth, LocalDB fallback in dev. SQL holds auth only; courses and progress stay file‑based. - Wiring:
AddMindAtticAuthentication<TutorAuthDbContext>+UseMindAtticAuthentication()+MapMindAtticAuthEndpoints()(/_ma-auth/*). - Bootstrap: dev migrate →
AuthUserImportServiceimports legacyUsers.jsonaccounts (SHA‑256 carried forward, upgraded transparently on first login, weak dev seeds flaggedMustChangePassword) →AuthBootstrapper.SeedAdminAsync(operator‑provided VaultSecurity:bootstrap-token, fail‑closed). - Auth email: the library delivers reset links and security alerts (password changed or
reset, two-step verification on, recovery code used, repeated failed sign-ins, new-device
sign-in, email changed, account deactivated) over SMTP when the Vault
Notificationsbucket (MindAttic:Vault:Notifications:email, loaded byTutor.Blazor/Program.cs) is complete; otherwise it logs a startup warning and sends nothing. - Self-service reset:
/loginlinks to/forgot-password(MaForgotPassword); the emailed link opens/account/reset(MaResetPassword, the library'sResetPath). Both pages are anonymous static SSR inTutor.Shared. Links useMindAttic:Auth:Reset:PublicBaseUrl=https://localhost:7200(Tutor.Blazor/appsettings.json). Admins can still reset on/users. - UI: shared
UserCircle(avatar/role menu + logout),UserTimeout(idle‑logout modal),UserLogin(stylesMaLogin). Admin Users page at/usersbehind[Authorize(Policy = MaPolicies.Admin)]— create / edit role / reset password / soft‑disable (no hard delete). - Verified by:
AuthUserImportTests,UsersAdminContractTests,PasswordResetFlowTests,auth.cy.ts. See stories C1–C7.
6.2 Course lifecycle ✅
Build → lock → unlock by mastery → final exam → certificate → unload, pinned by
FullCourseLifecycleTests (Student_WorksThroughCourse_FromLockedLessonsToCertificate,
FailingFinalExam_DoesNotCompleteOrCertify). See
stories B1–B4.
6.3 Course packaging 🟡
The lifecycle in §4.4 is built and covered by BundleRoundTripTests,
CourseManifestValidatorTests, CourseInstallResolverTests,
CourseLifecycleRegistryTests and BundleArchiveSafetyTests, all green in the
2026-10-03 run. The /library actions' server-side admin check is covered by
LibraryAdminActionsTests; the page itself has no automated UI test.
See Epic D and Epic E.
6.4 Known limitation 🟡
QuizService reads the user id from IHttpContextAccessor, which is null after
the initial Blazor Server render, so mid‑circuit quiz starts attribute to
"anonymous". Fix is to thread the id from AuthenticationState. Tracked as a
TODO in Tutor.Core/Services/Quiz/QuizService.cs and as
story B5.
7. Active frontier
There are no open RFCs. The open work, in order (see the priority backlog):
- Get the build green again (§6) and record a full
Tutor.Testsrun, which promotes the packaging stories (Epics D and E). - Per‑user quiz attribution (story B5).
- In‑app course building and monitoring (story A6).
- Automated UI coverage for the idle‑timeout modal (C4) and the
/librarypage (E1).
8. Quality bar
- NUnit (
Tutor.Tests) covers parsers, services, the concept‑map JSON shape, the CLI↔Blazor route, auth import/admin contracts, and the full course lifecycle (lock → unlock → final exam → certificate → unload). - Cypress (
Tutor.Cypress) drives the live UI: auth redirect guards and the deterministic course‑flow wiring. The LLM‑driven learning loop is intentionally not automated in‑browser (non‑deterministic, paid); its logic is pinned byFullCourseLifecycleTestsinstead. - Definition of done for a feature: clean
dotnet build Tutor.slnx, greenTutor.Tests, and — for anything user‑facing — a Cypress guard or a lifecycle assertion. No vendor lock‑in introduced. House versioning respected. (See HOUSE-LAW-8.)
9. Glossary
| Term | Meaning |
|---|---|
| Resource | One ingested source document. |
| Concept map | Per‑resource graph of concepts + relationships. |
| Knowledge base / collection | Aggregated concept maps for a whole course. |
| Course structure | Ordered lessons/sections referencing concepts. |
| Chunk | Embedded text snippet for RAG. |
Bundle / .tutor |
A shareable zipped course (with embeddings). |
| Course key / version | A bundle's stable slug identity and whole‑number version; what install plans compare. |
| Install | Validate, plan and import a bundle, recording an InstalledCourse row and keeping the verbatim blob. |
| Load / Unload | Enable / disable an installed course in the learning view without touching its data. |
| Remove | Hard cascade delete of a course, its registry row and its retained blob. |
| Legion | MindAttic.Legion — LLM transport library. |
| Vault | MindAttic.Vault — credential resolution library. |