feat: initial commit — Curio mastery tutor

Full Next.js App Router application with:
- AI-graded lesson checkpoints (BKT/Elo mastery tracking)
- Auth.js v5: credentials, Google, GitHub, generic OIDC
- Anonymous-session-first with migrate-on-signin
- Admin panel: users, blueprints, reports, site settings
- Password reset + email verification (nodemailer/SMTP)
- Site config: require_auth + signups_enabled flags
- server-only guards on all DB/generation/verification modules
- PostgreSQL 16 + pgvector, Redis cache, Drizzle ORM
- 271 unit tests (Vitest), golden-eval harness, Playwright e2e stubs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-21 22:25:43 +02:00
commit 4e38b5a791
194 changed files with 30789 additions and 0 deletions
+381
View File
@@ -0,0 +1,381 @@
# CURIO — PRODUCT & BUILD SPECIFICATION
*An AI mastery tutor. Follow your curiosity.*
---
## 1. OVERVIEW
Curio is a curiosity-led learning platform where a learner states what they want to learn and receives a personalized **Journey** of short, bounded **Lessons**. Lessons are AI-generated, AI-verified, and — crucially — **AI-tutored**: the learner does not passively consume content, they *produce* (predict, explain, solve), and the AI diagnoses what they misunderstand and adapts.
The product is built on one idea: **a generative model can do what a course or a feed cannot — respond to the individual learner.** Curio is organized entirely around that capability.
---
## 2. CORE CONCEPT & DIFFERENTIATOR
Curio is not a course catalog and not a content feed. Learning is **generated, not selected**, and **diagnosed, not just delivered**.
The defensible capability — the moat — is **misconception diagnosis**: inferring *what* a learner misunderstands and *why* from what they actually say, and targeting exactly that. Generating explanations is now commodity; diagnosing a specific learner's wrong mental model and correcting it is not. **The product is built around the misconception model, not the content stream.**
---
## 3. THE LEARNING UNIT: THE LESSON
The atomic unit is a **Lesson**: a self-contained microlearning unit, completable in one sitting (~10 minutes), built from 24 **segments**.
```
Lesson (~10 min, bounded, one sitting)
├─ Segment 1: short readable explanation (~300500 words, reads like an article)
│ └─ Checkpoint: learner produces (predict / explain-back / solve)
│ └─ AI diagnosis + targeted feedback (live)
├─ Segment 2: explanation → Checkpoint → diagnosis
└─ Segment 3: explanation → Checkpoint → diagnosis → Mastery gate
```
Design rules:
- **Reads like an article, not a quiz.** Explanation prose is continuous and calm; checkpoints *punctuate* (24 per Lesson) rather than interrupting every sentence.
- **Bounded.** Clear start and end, finishable in ~10 minutes. The session cap bounds learner attention *and* live token cost.
- **Active within the bound.** A passive and an active 10-minute session cost the same minutes; the active one retains far more. Microlearning brevity is preserved; passivity is removed.
- **Open-ended where possible.** Checkpoints favor free-text production over multiple choice — free text is what makes diagnosis possible.
A **Journey** is an ordered sequence of Lessons for a topic, with adaptive branching driven by the memory engine.
---
## 4. THE TUTORING LOOP
Per checkpoint:
1. **Read** — a short AI-generated explanation segment.
2. **Produce** — the learner predicts, explains in their own words, or solves a small problem.
3. **Diagnose** — the AI evaluates the *actual response* against a pre-verified rubric, reference answer, and per-concept misconception library; it names the specific misunderstanding.
4. **Adapt** — targeted feedback; re-explain differently if it didn't land; escalate difficulty if the learner is solid.
5. **Gate** — advancement is earned by demonstrated production, not by clicks. The outcome feeds the memory engine (§11).
---
## 5. CURIOSITY RAILS
The entry point is open: *"What do you want to learn today?"* Learners may pull tangents mid-Lesson ("wait, why does that happen?"). A tangent spawns a short sub-explanation and then **returns to the checkpoint**, so exploration never decays into aimless dabbling. Curiosity is encouraged; it is always brought back onto the attempt→diagnose loop.
---
## 6. LEARNING MODES
- **Journey mode** — structured progression through a topic's Lessons with adaptive branching and mastery tracking.
- **Daily Review** — surfaces concepts due for reinforcement from the spaced-repetition schedule. This is the substantive retention loop and the intended source of habit, in place of dopamine-driven gamification.
---
## 7. DESIGN LANGUAGE & EXPERIENCE
Curio should feel **modern, human, inspired, and beautiful** — not as decoration, but because the product is a reading-and-thinking environment, and the craft of that environment is part of the teaching. The interface should feel like a brilliant, warm tutor who has set your work down in front of them and is reading it carefully.
### 7.1 Thesis
The experience is **a calm reading surface that quietly comes alive when you respond.** Reading is unhurried and editorial; the moment of diagnosis is where the product breathes. Nothing competes with those two states. This is explicitly *not* a gamified, dashboard-dense, or chatbot-bubble app.
### 7.2 Signature: marginalia
The one memorable, bold element is the **diagnosis-as-marginalia** moment: when a learner submits a checkpoint response, the tutor's feedback arrives threaded against *their own words* — the way a great teacher annotates your answer in the margin, pointing at the exact phrase where the misconception lives (the `evidence_span` from §10.3). The feedback references what the learner actually wrote. Spend the design's boldness here; keep everything around it quiet.
### 7.3 Direction (a starting point, not a mandate)
Build by working the two-pass design process (plan a token system → critique it against this brief for genericness → then build). The following is the intended direction; refine it, don't default to it. Deliberately avoid the current AI-design clichés (cream + high-contrast serif + terracotta; near-black + acid accent; broadsheet hairline columns).
- **Palette — "ink & light."** A soft, slightly cool paper (not cream) as the reading ground; an inky near-black for text; a considered deep indigo as the focus/brand accent (depth, study); a warm ochre reserved *only* for the human tutor moments (marginalia, encouragement). Diagnosis states are calm and legible, never an alarm: mastered = a quiet sage/teal, partial = ochre, misconception = a soft clay/rose that says *let's look again*, never buzzer-red. A genuinely comfortable dark mode (warm charcoal, not pure black).
- **Typography — reading is the product.** A humanist, low-contrast **reading serif** for body (e.g. Newsreader / Source Serif / Literata as candidates), set at a generous measure (~6268ch) and line-height (~1.6); a characterful **display face used with restraint** for the prompt and headings; a humanist **mono** for mastery-map data and labels. Make the type a memorable part of the page, not a neutral delivery vehicle.
- **Motion — meaning, not decoration.** No animation library. Use CSS transitions and the **View Transitions API**. Reserve the one orchestrated motion moment for diagnosis: a brief "considering" beat, then feedback easing into the margin — it should feel like a person responding, not a form validating. Everything else is still. `prefers-reduced-motion` is honored everywhere.
- **Layout — one thing at a time.** Single reading column, generous margins, minimal chrome. The checkpoint is inset in the reading flow; feedback appears as marginalia, not a modal. The mastery map is a separate, richer view — it does not crowd the learn surface.
### 7.4 Voice (copy is design material)
Feedback copy *is* the tutor's character. Warm, specific, plain, and on the learner's side — names the exact thing that tripped them, never condescending, never gamified ("Not quite — the bit that slipped is **", never "❌ Wrong! 10 XP"). Active voice; actions say what they do. Empty and error states give direction in the interface's voice, not apologies.
### 7.5 Quality floor (non-negotiable)
Responsive to mobile; visible keyboard focus; reduced motion respected; WCAG-AA contrast on text and on every diagnosis-state color. Loading is part of the experience — the cold-start outline (§8.3) is a designed state, never a spinner.
### 7.6 Anti-patterns
No XP counters, badges, streak confetti, or leaderboards; no dense dashboard on the learn surface; no generic component-library default look; no chat-bubble UI; no autoplay or attention-grabbing motion.
---
## 8. CONTENT ARCHITECTURE
Curio is a **growing library of validated, generated content that gets personalized** — not a pure live-generation engine. The first learner to request a topic pays the generation cost; everyone after reuses validated content. This makes real-time UX, accuracy, and cost simultaneously feasible.
### 8.1 Three tiers
1. **Blueprint** — the canonical, fully verified Journey for a normalized intent: an ordered list of concepts with Lesson templates. Generated and verified **once**, reused by all learners.
2. **Variants** — pre-generated explanation/checkpoint bodies per concept at 23 difficulty levels, generated and verified at blueprint time.
3. **Live** — for long-tail intents with no blueprint hit: generated fresh through a lighter synchronous gate, then queued for full verification and **promoted into the blueprint cache** so the next learner gets a hit.
Two cost classes follow:
| Cached + verified once, reused by everyone | Live, per learner (pay tokens exactly here) |
|---|---|
| explanation segments | evaluation of the learner's free-text response |
| checkpoints + reference answers + rubrics | misconception diagnosis + targeted feedback |
| worked examples | adaptive re-explanation |
| per-concept misconception libraries | tangent handling |
### 8.2 Intent normalization (drives cache hit rate)
"Learn Python", "teach me python basics", and "i wanna learn python" must collapse to one key:
- **Structured key:** one cheap classification call → `{domain, subtopic, goal_type: skill|exam|concept, level_hint}` → canonical string.
- **Embedding fallback:** embed the raw intent; pgvector nearest-neighbor search against existing blueprint embeddings; reuse if cosine > threshold.
- Miss only when both fail. `goal_type` and `level_hint` are **parameters into** a blueprint, not separate blueprints.
### 8.3 Serve flow (skeleton-first + buffer)
Perceived latency is won by streaming *structure* before *content*: the Journey outline appears in under a second; the learner can't move faster than the first segment arrives.
```
on submit(intent):
key, ctx = normalize(intent) # ~100ms
bp = blueprint_cache.lookup(key, ctx) # exact key, then embedding NN
if bp: # WARM
lesson = hydrate(bp, next_concept, user) # variant SELECTION, ~150ms
buffer.push(session, lesson); stream(lesson)
else: # COLD
bp = gen_blueprint(intent, ctx) # structured call → concept list
stream(bp.outline) # outline visible now (designed state)
lesson = gen_lesson(bp, concept[0], rag) # light sync gate before serve
buffer.push(session, lesson); stream(lesson)
enqueue(promote_to_blueprint, bp) # full verification + cache, async
on advance / buffer_low(session):
enqueue(generate_next, session, next_concept) # background job
```
The serve path reads from a Redis buffer fronting Postgres; a background job refills it. Concurrent generations per session are capped for backpressure. Postgres remains the source of truth — a Redis outage degrades latency, not availability.
---
## 9. AI GENERATION PIPELINE
Generation is **RAG-grounded** to constrain hallucination at the source. Per concept the generator produces, all grounded in retrieved trusted sources and stored with provenance (`source_chunk_ids`):
- explanation segments
- checkpoints (prompts) + **reference answers** + **grading rubrics**
- worked examples
- a **misconception library**: likely wrong mental models for the concept, each with a tag and a diagnostic signature
Rubrics and misconception libraries are first-class generated artifacts — they are what make live grading constrained and verifiable. All prompts live in a versioned registry; generation output is **structured (schema-constrained) JSON** via the model's native structured-output mode.
---
## 10. AI VERIFICATION SYSTEM
Curio verifies with AI on two surfaces. Both are made reliable by the same discipline: **ground the check against real artifacts, decorrelate from the generator, structure the output, and allow abstention.**
### 10.1 Content verification — the cascade
A naive "second LLM checks the card" shares the generator's blind spots and rubber-stamps confident-wrong content. Instead:
- **Grounded, not free-judged.** The verifier checks each atomic claim against the retrieved source chunks ("is this supported by these sources?"), not against its own priors.
- **Independent solving.** The verifier never sees the marked answer; it **solves checkpoints/quizzes blind** (n-sample, majority vote) and compares to the reference answer. Mismatch → flag; sample disagreement → uncertainty.
- **Decorrelation.** A different model/provider and an adversarial prompt, so failure modes don't overlap the generator's.
Run as a **cascade**, so expensive scrutiny is amortized:
| Tier | Runs | Cost | Checks |
|---|---|---|---|
| **T0 Structural** | every item, always | ~free | schema valid; exactly one correct option; reference answer references a real option; refs resolve; non-empty; length bounds |
| **T1 Cheap-model** | every item at generation | low | claim grounding; blind solve; obvious safety. Clears the easy majority, else escalate |
| **T2 Adversarial** | blueprint promotion + escalations + low confidence | medium, **amortized once per blueprint** | full rubric, claim-by-claim grounding, n-sample blind solve, pedagogical soundness, cross-item consistency |
| **T3 Human** | top-traffic templates + persistent uncertainty | high, rare | spot review, golden-set labeling |
Because content is cached at the template level, T2 runs **once per blueprint** and is reused by thousands of learners — near-zero per-serve cost.
### 10.2 Response-grading verification
The AI also grades free-text learner responses. Making this trustworthy:
- **Grade against pre-verified artifacts.** At runtime the grader checks the response against the already-verified rubric + reference answer + misconception library — constrained, not freeform.
- **Misconception matching.** The grader maps the response to a known misconception tag or flags `novel` (→ escalate). Closed-set classification beats open-ended critique.
- **Two-sided error awareness.** A false-pass wastes learning; a **false-fail (telling a correct learner they're wrong) corrodes trust and is worse.** The grader is biased toward generous grading and always reveals its reasoning so the learner can contest.
- **Abstention + fallback.** The grader may return `uncertain` → reveal the model answer for self-assessment rather than confidently mis-grading.
- **Cascade.** A cheap model grades the rubric-matched majority; a strong model handles `novel`/`uncertain` only.
### 10.3 Output contracts
Content verifier:
```json
{
"verdict": "pass | fail | uncertain",
"claims": [{"text": "<atomic claim>", "status": "supported|unsupported|contradicted",
"evidence_chunk_ids": ["..."]}],
"solve_check": {"independent_answers": ["B","B","C"], "majority": "B",
"reference": "B", "agreement": 0.67, "status": "match|mismatch|ambiguous"},
"pedagogy": {"teaches_concept": true, "prereqs_respected": true},
"safety": {"in_scope": true, "mode_compliant": true},
"confidence": 0.0
}
```
Response grader:
```json
{
"verdict": "mastered | partial | misconception | uncertain",
"rubric_hits": ["criterion_id"],
"misconception_tag": "tag | novel | none",
"evidence_span": "<quote from learner response>",
"feedback_directive": "advance | re-explain:<angle> | probe:<question>",
"confidence": 0.0
}
```
Contracts are defined as **Zod schemas** shared between the LLM layer, the API, and the frontend.
### 10.4 Keeping verifiers honest
- **Golden sets** of human-labeled cases for both surfaces; measure precision/recall continuously, tracking **false-fail rate** separately for grading. A model upgrade triggers an offline re-sweep of the cached library.
- **User-signal outer loop (free):** collective checkpoint failure (many learners miss the same item → suspect the reference answer), a report button, and dwell-time anomalies auto-trigger re-verification.
- **Versioning:** content keyed by `(intent_key, content_version, model_version)`; invalidation is lazy; a flagged item regenerates just itself.
---
## 11. MEMORY ENGINE
A **mastery-tracking engine**, not a knowledge graph:
- **Per-concept mastery score** updated from demonstrated production at checkpoints and from misconception tags, via Bayesian Knowledge Tracing or an Elo-style update.
- **Spaced-repetition schedule** per concept (SM-2-like) driving Daily Review.
This drives difficulty scaling, review timing, reinforcement, and the mastery gate. A relational knowledge graph is deferred until there is data to justify it.
---
## 12. PEDAGOGY
The design implements the mechanics behind the **2-sigma** finding (1:1 tutoring + mastery learning outperforms classroom instruction by ~two standard deviations) — exactly what generative AI can deliver and a feed structurally cannot:
- **Active production / retrieval practice** at every checkpoint.
- **Mastery gating** before advancement.
- **Spaced repetition** via Daily Review.
- **Interleaving** of related concepts rather than blocking.
---
## 13. PERSONALIZATION
- **Variant selection** by mastery: pick the pre-generated difficulty variant matching the learner's score. Deterministic, sub-millisecond, zero live LLM cost.
- **Live personalization** is the feedback and re-explanation themselves — which is the core value, not overhead, and is where live tokens are deliberately spent.
---
## 14. SAFETY & QUALITY CONTROL
Three strictness modes — **open**, **controlled** (default), **enterprise-safe** — applied as parameters to generation, content verification, and response grading (including how off-topic or unsafe free-text input is handled). Filtered domains are enforced at intent normalization and at T0/T1.
---
## 15. TELEMETRY & EVALUATION
Because the product *is* diagnosis, instrumentation is a first-class requirement, not an afterthought:
- **Diagnosis telemetry:** every `response → grade` is logged; track false-fail rate, misconception-hit rate, and mastery gain per concept. You cannot improve a diagnosis engine you don't measure.
- **LLM observability:** per-call tracing of latency, tokens, and cost (Langfuse or OpenTelemetry). Without it the false-fail and cost metrics are aspirational.
- **Eval harness in CI:** golden sets for content verification and response grading run on every prompt change. Prompts are code — versioned and tested. The harness doubles as the build's definition of "good enough."
- **Cost/latency guards:** per-session token cap (the Lesson bound enforced in code), a provider circuit breaker, an **edge rate limiter on the cold-start generation endpoint** (it spends money on demand), and graceful degradation to abstain→reveal.
---
## 16. FEATURE SET
**Core (in the product):** intent entry; Journey mode; bounded Lessons with checkpoints; live diagnosis with abstention; mastery + spaced repetition; Daily Review; mastery map (learner-facing view of mastered / shaky / due); source citations (surfacing the provenance already stored).
**Loop reinforcers (near-term):** confidence rating before reveal (catches *confidently wrong*); Socratic follow-up probe on a `partial` verdict.
**Deferred:** learn from your own material (PDF/notes → personalized Journey — strong differentiator, leverages existing RAG infra, but a large surface); voice.
**Explicitly out of scope:** XP / badges / leaderboards, infinite feed, social features, multi-provider fallback orchestration, knowledge graph. Microlearning + Daily Review + mastery map supply motivation intrinsically; bolt-on gamification risks reintroducing passive, low-retention "junk-food" learning.
---
## 17. DATA MODEL
```
source_chunk(id, doc_ref, embedding, text)
blueprint(id, intent_key, embedding, title, model_version, content_version,
status[draft|verifying|published|flagged])
concept(id, blueprint_id, ord, name, retrieval_ctx_ref)
lesson(id, blueprint_id, ord, est_minutes)
segment(id, lesson_id, ord, body_json, source_chunk_ids[], verify_status, content_version)
checkpoint(id, segment_id, prompt, kind[predict|explain|solve],
reference_answer, rubric_json, verify_status)
misconception(id, concept_id, tag, signature, verify_status)
verify_report(id, target_id, target_type, verdict, confidence, payload_json, model_version)
user(id, ...)
journey_instance(id, user_id, blueprint_id, position)
mastery(user_id, concept_id, score, last_seen, next_review)
response(id, user_id, checkpoint_id, text, ts)
grade(id, response_id, verdict, misconception_tag, confidence, payload_json)
job(id, type, idempotency_key, status, payload_json, attempts, created_at) -- durable ledger for paid background work
-- Redis: buffer:{session} -> [hydrated lesson/segment ids]; generation-concurrency locks
```
---
## 18. TECH STACK
**Single language, end to end (TypeScript).** One type system, one toolchain, and request/response/LLM contracts shared across the boundary. Nothing here requires a Python ML runtime — the work is hosted-LLM calls, embeddings, and pgvector queries, all first-class in TypeScript.
- **App framework:** Next.js (App Router, TypeScript) full-stack — React Server Components for the reading surface, Route Handlers for the API, native streaming/SSE for skeleton-first delivery. Split out a thin service (e.g. Hono) only if the API outgrows Next.
- **LLM orchestration:** Vercel AI SDK — provider-agnostic, native streaming and **schema-constrained structured output** (Zod). Generator and grader configured to **different providers/models** (decorrelation). Exploit two cost levers: native structured output for verifier/grader contracts, and **prompt caching** for the large, session-reused grounding context (sources + rubric + misconception library).
- **Contracts/validation:** Zod, shared across API I/O, LLM outputs, and DB-derived types.
- **Database:** PostgreSQL + pgvector (single store, source of truth).
- **ORM & migrations:** Drizzle ORM + drizzle-kit (type-safe, SQL-first, clean pgvector support).
- **Cache / buffer:** Redis — a cache and serve-buffer in *front* of Postgres, never load-bearing for availability.
- **Background work:** for the vertical slice, Route Handlers + the `job` ledger table suffice. When scale work arrives: **BullMQ** (Redis-backed, TS), with **idempotency keys and durable Postgres job state** — these jobs spend money, so a retry must never regenerate-and-rebill.
- **Styling & motion:** Tailwind CSS + CSS custom properties for design tokens; motion via CSS transitions and the **View Transitions API** (no animation library). `prefers-reduced-motion` respected.
- **Observability:** Langfuse (or OpenTelemetry) for LLM call tracing; standard app metrics/logs.
- **Rate limiting:** edge limiter (e.g. Upstash Ratelimit on Redis) on the cold-start generation endpoint.
- **Testing:** Vitest (unit; LLM mocked) + a golden-eval runner (real-model, CI-gated) + Playwright for the core learn-flow e2e.
- **Hosting:** Next on Vercel (or Node host); managed Postgres+pgvector (Neon / Supabase / RDS) and managed Redis (Upstash). Keep the cold-start generation path on a runtime with adequate timeout (Node serverless/long-running, not edge).
---
## 19. COST MODEL
- **Scaffolding** (explanations, checkpoints, rubrics, misconception libraries) is generated and T2-verified **once per blueprint**, amortized to near-zero per serve.
- **Live cost** is grading + feedback turns: per Lesson ≈ 24 checkpoints × (grade + feedback) + occasional re-explanation/tangent. The ~10-minute Lesson bound **directly caps live turns per session** — microlearning bounds cost as well as attention.
- **Levers:** a grading **cascade** (cheap model for the rubric-matched majority, strong model only on `novel`/`uncertain`) and **prompt caching** of the reused grounding context keep per-turn cost low.
- Net: with a high cache-hit rate, live LLM spend concentrates on cold long-tail topics (throttleable) and on the irreplaceable act of responding to the learner. Model this against the chosen provider's pricing at assumed hit rates and checkpoint counts before building.
---
## 20. MVP SCOPE & BUILD ROADMAP
**The MVP answers one question:** *can an AI tutor diagnose what a learner misunderstands and fix it, well enough that they learn and return?*
**MVP loop:** prompt → one Lesson (23 segments) → read + open-text checkpoints → live grading against verified rubric + misconception library → mastery gate.
**Phases:**
- **P0 — Scaffold & infra.** Next.js + TypeScript + Tailwind app; docker-compose (Postgres+pgvector, Redis); Drizzle schema + migrations; Vitest; provider-agnostic LLM layer (Vercel AI SDK) with a mockable client; design tokens stubbed.
- **P1 — Vertical slice (single hardcoded topic).** Content schema + seeded corpus + pgvector retrieval → RAG-grounded Lesson generation (segments, checkpoints, reference answers, rubrics) → serve + render the reading surface and checkpoint → **response grading with abstention****golden eval harness gating false-fail rate in CI.** Build the marginalia feedback moment here — it is the core experience. This milestone validates diagnosis quality.
- **P2 — Diagnosis depth.** Misconception-library quality + the full content-verification cascade.
- **P3 — Adaptivity & scale.** Mastery + spaced repetition; blueprint cache, intent normalization, BullMQ workers with idempotent paid jobs.
- **P4 — Roadmap features**, one at a time: confidence rating, Socratic probe, Daily Review, mastery map, source citations, variant personalization, accounts.
Anonymous-session-first; accounts arrive in P4.
---
## 21. OPEN DECISIONS
1. **Checkpoint count per Lesson** — fixed (e.g., 3) or adaptive to mastery? Sets the cost ceiling and the est-minutes contract.
2. **False-fail tolerance** — how generous is the grader, and when does it abstain vs. reveal? Sets the trust/rigor balance; prototype with real responses. *Settle before writing the grader.*
3. **Cold-start consistency** — may a cold long-tail Lesson be graded against a T1-only rubric (full T2 async), or does first serve block on T2?
4. **Concept granularity** — one concept per Lesson, or per Lesson-cluster?
5. **Source corpus policy** — define allowed, non-infringing sources before generation begins.
---
## 22. NORTH STAR
A learner types *"Teach me astrophysics."* A short, readable Lesson opens — finishable in ten minutes, set in calm, beautiful type. They read a few hundred words, then are asked to predict what happens to a collapsing star. They type a guess. The tutor reads it, pauses for a beat, and writes back in the margin — pointing at the exact phrase where their thinking went wrong, explaining the missing piece a different way, and asking again. They get it. Mastery updates; the next review is scheduled. Every explanation, problem, and rubric was generated and verified once and reused — only the part that read their actual answer ran live, which is the whole point.