{"openapi":"3.1.0","info":{"title":"Atlas","description":"Backend for Atlas, an adaptive learning game.","version":"0.1.0"},"paths":{"/api/health":{"get":{"tags":["health"],"summary":"Health","description":"Report process liveness, whether the SQLite file is readable, and which content is loaded.","operationId":"health_api_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}}}}},"/api/learner":{"post":{"tags":["learner"],"summary":"Create Learner","description":"Create the learner from onboarding's answers and open their first session.\n\n409 when a learner already exists: the slice holds one, and silently\nreturning the existing one would hide a client bug.","operationId":"create_learner_api_learner_post","parameters":[{"name":"X-Atlas-Now","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled.","title":"X-Atlas-Now"},"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LearnerCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LearnerView"}}}},"400":{"description":"Malformed request: an unusable `X-Atlas-Now`, an event type a client may not emit, or seeds the server did not issue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"409":{"description":"A learner already exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["learner"],"summary":"Get Learner","description":"Return the learner, their preferences and the computed game state.\n\n404 before onboarding has run — the shell reads that as \"send them to\n/onboarding\", not as an error.","operationId":"get_learner_api_learner_get","parameters":[{"name":"X-Atlas-Now","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled.","title":"X-Atlas-Now"},"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LearnerView"}}}},"400":{"description":"Malformed request: an unusable `X-Atlas-Now`, an event type a client may not emit, or seeds the server did not issue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"404":{"description":"No such id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/learner/prefs":{"patch":{"tags":["learner"],"summary":"Update Prefs","description":"Change some preferences and/or the rest days; returns the whole learner view.\n\nA partial body: fields left out keep their current value. Rest days are a\nscheduling preference the learner owns; the support level is not, and is\ndeliberately absent here (§2).","operationId":"update_prefs_api_learner_prefs_patch","parameters":[{"name":"X-Atlas-Now","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled.","title":"X-Atlas-Now"},"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrefsUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LearnerView"}}}},"400":{"description":"Malformed request: an unusable `X-Atlas-Now`, an event type a client may not emit, or seeds the server did not issue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"404":{"description":"No such id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/sessions/start":{"post":{"tags":["sessions"],"summary":"Start Session","description":"Open or resume today's session and return its composed plan.\n\nResumes the live session if one is still warm; rotates it if it has been\nidle 4 h or more. `budget_min` overrides the learner's usual budget for\nthis session only. The plan comes from `engines.plan.compose` — the one\nplanner (§5.6).","operationId":"start_session_api_sessions_start_post","parameters":[{"name":"X-Atlas-Now","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled.","title":"X-Atlas-Now"},"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionStart"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Plan"}}}},"400":{"description":"Malformed request: an unusable `X-Atlas-Now`, an event type a client may not emit, or seeds the server did not issue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"404":{"description":"No such id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/plan":{"get":{"tags":["plan"],"summary":"Get Plan","description":"Return the current session's plan, with the reason attached to each activity.\n\n404 when no session is open. Every activity carries `why` in plain English,\nbecause \"always know where you are\" is answered structurally, not by a tip\n(ARCHITECTURE §9).","operationId":"get_plan_api_plan_get","parameters":[{"name":"X-Atlas-Now","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled.","title":"X-Atlas-Now"},"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Plan"}}}},"400":{"description":"Malformed request: an unusable `X-Atlas-Now`, an event type a client may not emit, or seeds the server did not issue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"404":{"description":"No such id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/events":{"post":{"tags":["events"],"summary":"Post Events","description":"Append a batch of client events and return anything they moved.\n\nIdempotent on each event's `client_id`, so a retried batch is accepted and\nappended once. 400 when a type is outside the client-emittable set or a\npayload does not match its type. The reply carries the current\n`session_id` because this request may itself have rotated the session.","operationId":"post_events_api_events_post","parameters":[{"name":"X-Atlas-Now","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled.","title":"X-Atlas-Now"},"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventBatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventBatchResult"}}}},"400":{"description":"Malformed request: an unusable `X-Atlas-Now`, an event type a client may not emit, or seeds the server did not issue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"404":{"description":"No such id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/items/present":{"post":{"tags":["items"],"summary":"Present Item","description":"Render an item family at the learner's current support level and issue its seeds.\n\n`context` says what the item is for, which sets the weight the resulting\nattempt can carry. On a multiple-choice item `covert_first` is true: the\nlearner types a production before the options appear, because a click\nwithout one certifies nothing (§2).","operationId":"present_item_api_items_present_post","parameters":[{"name":"X-Atlas-Now","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled.","title":"X-Atlas-Now"},"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ItemPresent"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ItemInstance"}}}},"400":{"description":"Malformed request: an unusable `X-Atlas-Now`, an event type a client may not emit, or seeds the server did not issue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"404":{"description":"No such id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/attempts":{"post":{"tags":["attempts"],"summary":"Post Attempt","description":"Grade one attempt, record the evidence, and return the feedback it earned.\n\nIdempotent on `client_id`: a retried submission returns the first result\nrather than double-counting evidence. Feedback is always task-level and\nnames the probable cause; a failed attempt never gets a bare \"wrong\", and\nnever gets trait language (§5, BUILDER_RULES §5). `explain_act`, when\npresent, must be answered before a format-shift credit finalises (§5.4).","operationId":"post_attempt_api_attempts_post","parameters":[{"name":"X-Atlas-Now","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled.","title":"X-Atlas-Now"},"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttemptSubmission"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttemptResult"}}}},"400":{"description":"Malformed request: an unusable `X-Atlas-Now`, an event type a client may not emit, or seeds the server did not issue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"404":{"description":"No such id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/map":{"get":{"tags":["map"],"summary":"Get Map","description":"Return the drawable map for one subject, already filtered by visibility.\n\n`nodes` holds only full and silhouette nodes; fogged regions carry a\n`hidden_count` and no node rows. `current` is where the learner is now and\n`fringe` is what is reachable next, so the client never computes\navailability itself.","operationId":"get_map_api_map_get","parameters":[{"name":"subject","in":"query","required":false,"schema":{"type":"string","default":"cs","title":"Subject"}},{"name":"X-Atlas-Now","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled.","title":"X-Atlas-Now"},"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MapView"}}}},"400":{"description":"Malformed request: an unusable `X-Atlas-Now`, an event type a client may not emit, or seeds the server did not issue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"404":{"description":"No such id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/dashboard":{"get":{"tags":["dashboard"],"summary":"Get Dashboard","description":"Return the Basecamp view: mission, checks due, rhythm, projects and evidence.\n\n`mission` is the head of the current plan — the same activity `/api/plan`\nwould hand back — so \"Continue\" and the plan can never disagree.","operationId":"get_dashboard_api_dashboard_get","parameters":[{"name":"X-Atlas-Now","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled.","title":"X-Atlas-Now"},"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DashboardView"}}}},"400":{"description":"Malformed request: an unusable `X-Atlas-Now`, an event type a client may not emit, or seeds the server did not issue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"404":{"description":"No such id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/placement/start":{"post":{"tags":["placement"],"summary":"Start Placement","description":"Begin placement in a subject and return the first item plus a progress line.\n\n`progress.about_left` is honest but approximate: the adaptive test stops\nwhen it knows enough, so an exact count would be a promise it cannot keep.","operationId":"start_placement_api_placement_start_post","parameters":[{"name":"X-Atlas-Now","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled.","title":"X-Atlas-Now"},"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlacementStart"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlacementStartResult"}}}},"400":{"description":"Malformed request: an unusable `X-Atlas-Now`, an event type a client may not emit, or seeds the server did not issue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"404":{"description":"No such id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/placement/answer":{"post":{"tags":["placement"],"summary":"Answer Placement","description":"Grade one placement answer and return the next item, or the finished map.\n\nWhen placement ends, `item` is absent and `knowledge_map` carries the band\nper topic, the recommended starting point, and every correction — the\nreview screen the learner sees before starting.","operationId":"answer_placement_api_placement_answer_post","parameters":[{"name":"X-Atlas-Now","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled.","title":"X-Atlas-Now"},"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlacementAnswer"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlacementAnswerResult"}}}},"400":{"description":"Malformed request: an unusable `X-Atlas-Now`, an event type a client may not emit, or seeds the server did not issue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"404":{"description":"No such id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/content/lesson/{lesson_id}":{"get":{"tags":["content"],"summary":"Get Lesson","description":"Return one authored lesson: its steps, their goals, and the acts on each.\n\nSteps carry `item_family_ids` rather than rendered items — the player asks\n`/api/items/present` for those, so the support level is decided per\nlearner at the moment of asking, not baked into the lesson.","operationId":"get_lesson_api_content_lesson__lesson_id__get","parameters":[{"name":"lesson_id","in":"path","required":true,"schema":{"type":"string","title":"Lesson Id"}},{"name":"X-Atlas-Now","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled.","title":"X-Atlas-Now"},"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Lesson"}}}},"400":{"description":"Malformed request: an unusable `X-Atlas-Now`, an event type a client may not emit, or seeds the server did not issue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"404":{"description":"No such id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/content/sim/{sim_id}":{"get":{"tags":["content"],"summary":"Get Sim","description":"Return the simulation module source, its callable API, and its level templates.\n\n`api` is the bare-call names the shim installs (`move`, `turn_left`, …), so\nthe editor can offer exactly those and the linter can reject the rest.","operationId":"get_sim_api_content_sim__sim_id__get","parameters":[{"name":"sim_id","in":"path","required":true,"schema":{"type":"string","title":"Sim Id"}},{"name":"X-Atlas-Now","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled.","title":"X-Atlas-Now"},"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimBundle"}}}},"400":{"description":"Malformed request: an unusable `X-Atlas-Now`, an event type a client may not emit, or seeds the server did not issue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"404":{"description":"No such id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/content/project/{project_id}":{"get":{"tags":["content"],"summary":"Get Project","description":"Return a project brief with the learner's state, issued seeds and sequence number.\n\nThe seeds are issued here for the same reason items issue them: the\nmilestone's tests run in the worker, and the server accepts only the seeds\nit handed out.","operationId":"get_project_api_content_project__project_id__get","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","title":"Project Id"}},{"name":"X-Atlas-Now","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled.","title":"X-Atlas-Now"},"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectBundle"}}}},"400":{"description":"Malformed request: an unusable `X-Atlas-Now`, an event type a client may not emit, or seeds the server did not issue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"404":{"description":"No such id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/glossary":{"get":{"tags":["glossary"],"summary":"Get Glossary","description":"Return the learner's glossary: each term, its state, and any check it would cost.\n\n`state` runs unseen → introduced → interacted → verified, so the list\ndoubles as a record of which words the learner has actually used, not just\nread.","operationId":"get_glossary_api_glossary_get","parameters":[{"name":"X-Atlas-Now","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled.","title":"X-Atlas-Now"},"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GlossaryEntry"},"title":"Response Get Glossary Api Glossary Get"}}}},"400":{"description":"Malformed request: an unusable `X-Atlas-Now`, an event type a client may not emit, or seeds the server did not issue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"404":{"description":"No such id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/tutor":{"post":{"tags":["tutor"],"summary":"Ask Tutor","description":"Return the next tutor turn for what the learner is stuck on.\n\n`source` says whether the text was authored or generated: the LLM is asked\nonly when the authored ladder is exhausted or the learner wrote something\nof their own, and every generated turn passes the guards before it is sent\n(§6). `remaining_llm_turns` is the per-item, per-session budget left.","operationId":"ask_tutor_api_tutor_post","parameters":[{"name":"X-Atlas-Now","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled.","title":"X-Atlas-Now"},"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TutorAsk"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TutorTurn"}}}},"400":{"description":"Malformed request: an unusable `X-Atlas-Now`, an event type a client may not emit, or seeds the server did not issue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"404":{"description":"No such id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/portfolio":{"get":{"tags":["portfolio"],"summary":"Get Portfolio","description":"Return every project record: what was built, what broke, and what was learned.\n\n`cold_check` carries the project's later unwarmed check — a finished\nproject is not a proven one until that passes on a different day (§5.7).","operationId":"get_portfolio_api_portfolio_get","parameters":[{"name":"X-Atlas-Now","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled.","title":"X-Atlas-Now"},"description":"Pretend the request happened at this ISO-8601 instant (an offset is required). Honoured only when ATLAS_DEV_CLOCK=1; otherwise the request is refused with 400, so a deployed Atlas cannot be time-travelled."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PortfolioEntry"},"title":"Response Get Portfolio Api Portfolio Get"}}}},"400":{"description":"Malformed request: an unusable `X-Atlas-Now`, an event type a client may not emit, or seeds the server did not issue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"404":{"description":"No such id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"Achievement":{"properties":{"id":{"type":"string","title":"Id"},"sentence":{"type":"string","title":"Sentence"},"earned_seq":{"type":"integer","title":"Earned Seq"}},"additionalProperties":false,"type":"object","required":["id","sentence","earned_seq"],"title":"Achievement","description":"An earned achievement: a sentence about what the learner can now do."},"Act":{"properties":{"kind":{"type":"string","enum":["predict","locate","why_alt_fails","trace"],"title":"Kind"},"prompt":{"type":"string","title":"Prompt"},"answer_spec":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"string"}],"title":"Answer Spec"}},"additionalProperties":false,"type":"object","required":["kind","prompt","answer_spec"],"title":"Act"},"Activity":{"properties":{"kind":{"type":"string","enum":["lesson","item","review","cold_check","mixed_set","project","stop"],"title":"Kind"},"ref":{"oneOf":[{"$ref":"#/components/schemas/LessonRef"},{"$ref":"#/components/schemas/ItemRef"},{"$ref":"#/components/schemas/ProjectRef"},{"$ref":"#/components/schemas/MixedSetRef"},{"$ref":"#/components/schemas/StopRef"}],"title":"Ref","discriminator":{"propertyName":"kind","mapping":{"item":"#/components/schemas/ItemRef","lesson":"#/components/schemas/LessonRef","mixed_set":"#/components/schemas/MixedSetRef","project":"#/components/schemas/ProjectRef","stop":"#/components/schemas/StopRef"}}},"node_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Node Id"},"label":{"$ref":"#/components/schemas/Label"},"support_level":{"type":"integer","maximum":4.0,"minimum":0.0,"title":"Support Level"},"why":{"type":"string","title":"Why"},"eta_min":{"type":"integer","minimum":0.0,"title":"Eta Min"},"is_cold_check":{"type":"boolean","title":"Is Cold Check"},"next_check_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Next Check At"}},"additionalProperties":false,"type":"object","required":["kind","ref","label","support_level","why","eta_min","is_cold_check"],"title":"Activity","description":"One thing to do next, with the plain-English reason it was chosen.\n\n`support_level` runs 0 (worked example) to 4 (unaided) — the planner's\ndecision, never the learner's (§5.5)."},"AttemptResult":{"properties":{"evidence_seq":{"type":"integer","title":"Evidence Seq"},"session_id":{"type":"string","title":"Session Id"},"correct":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Correct"},"partial":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Partial"},"feedback":{"$ref":"#/components/schemas/Feedback"},"skill_update":{"$ref":"#/components/schemas/SkillUpdate"},"xp":{"items":{"$ref":"#/components/schemas/XpGrant"},"type":"array","title":"Xp"},"achievements":{"items":{"$ref":"#/components/schemas/Achievement"},"type":"array","title":"Achievements"},"allowed_rung":{"type":"integer","maximum":7.0,"minimum":0.0,"title":"Allowed Rung"},"explain_act":{"anyOf":[{"$ref":"#/components/schemas/ExplainAct"},{"type":"null"}]},"plan_changed":{"type":"boolean","title":"Plan Changed"}},"additionalProperties":false,"type":"object","required":["evidence_seq","session_id","correct","partial","feedback","skill_update","allowed_rung","plan_changed"],"title":"AttemptResult","description":"`POST /api/attempts` reply: the verdict, the feedback and everything it moved.\n\n`correct` is `null` when nothing could be judged (a skip, or an ungraded\nfree-text act).  `explain_act`, when present, must be answered before the\nformat-shift credit finalises (§5.4)."},"AttemptSubmission":{"properties":{"client_id":{"type":"string","format":"uuid","title":"Client Id"},"session_id":{"type":"string","title":"Session Id"},"target":{"oneOf":[{"$ref":"#/components/schemas/ItemTarget"},{"$ref":"#/components/schemas/MilestoneTarget"},{"$ref":"#/components/schemas/ExplainActTarget"}],"title":"Target","discriminator":{"propertyName":"kind","mapping":{"explain_act":"#/components/schemas/ExplainActTarget","item":"#/components/schemas/ItemTarget","milestone":"#/components/schemas/MilestoneTarget"}}},"response":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"number"},{"items":{"type":"string"},"type":"array"},{"items":{"$ref":"#/components/schemas/ParsonsOrderEntry"},"type":"array"},{"additionalProperties":{"additionalProperties":{"type":"string"},"type":"object"},"type":"object"},{"type":"null"}],"title":"Response"},"response_type":{"type":"string","enum":["answer","idk","skip"],"title":"Response Type"},"confidence_pre":{"anyOf":[{"type":"integer","maximum":5.0,"minimum":1.0},{"type":"null"}],"title":"Confidence Pre"},"covert_answer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Covert Answer"},"latency_ms":{"type":"integer","minimum":0.0,"title":"Latency Ms"},"runs_before_pass":{"type":"integer","minimum":0.0,"title":"Runs Before Pass"},"pasted":{"type":"boolean","title":"Pasted"},"program_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Program Source"},"client_grade":{"anyOf":[{"$ref":"#/components/schemas/ClientGrade"},{"type":"null"}]}},"additionalProperties":false,"type":"object","required":["client_id","session_id","target","response_type","latency_ms","runs_before_pass","pasted"],"title":"AttemptSubmission","description":"`POST /api/attempts` body: one graded attempt, idempotent on `client_id`.\n\n`is_cold`, `format_shift` and the evidence weight are **never** sent from\nhere — the server derives them (§4.5).  `covert_answer` is the production\nthe learner typed before the options appeared; without it a multiple-choice\nhit cannot certify anything."},"Calibration":{"properties":{"bins":{"items":{"$ref":"#/components/schemas/CalibrationBin"},"type":"array","title":"Bins"},"sentence":{"type":"string","title":"Sentence"}},"additionalProperties":false,"type":"object","required":["bins","sentence"],"title":"Calibration","description":"Confidence against reality, plus the one sentence that says what it means."},"CalibrationBin":{"properties":{"lower":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Lower"},"upper":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Upper"},"n":{"type":"integer","minimum":0.0,"title":"N"},"mean_confidence":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Mean Confidence"},"accuracy":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Accuracy"}},"additionalProperties":false,"type":"object","required":["lower","upper","n","mean_confidence","accuracy"],"title":"CalibrationBin","description":"One confidence bucket: how often the learner was right when they felt this sure."},"ChecksDue":{"properties":{"count":{"type":"integer","minimum":0.0,"title":"Count"},"eta_min":{"type":"integer","minimum":0.0,"title":"Eta Min"},"next_check_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Next Check At"}},"additionalProperties":false,"type":"object","required":["count","eta_min"],"title":"ChecksDue","description":"Today's due checks: how many, how long, and when the next one opens."},"ClientEvent":{"properties":{"client_id":{"type":"string","format":"uuid","title":"Client Id"},"type":{"type":"string","enum":["lesson_step_viewed","sim_action","term_seen","project_artifact_saved","prefs_changed","session_ended"],"title":"Type"},"node_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Node Id"},"payload":{"anyOf":[{"$ref":"#/components/schemas/LessonStepViewed"},{"$ref":"#/components/schemas/SimAction"},{"$ref":"#/components/schemas/TermSeen"},{"$ref":"#/components/schemas/ProjectArtifactSaved"},{"$ref":"#/components/schemas/PrefsChanged"},{"$ref":"#/components/schemas/EmptyPayload"}],"title":"Payload"}},"additionalProperties":false,"type":"object","required":["client_id","type"],"title":"ClientEvent","description":"One thing the browser observed. Only these six types may come from a client.\n\nEverything that decides mastery, XP or scheduling is emitted by the server\n(§4.4); a client event that claims a server-only type is a 400.  The\npayload must match its `type` — `sim_action` with a `TermSeen` body is\nrefused here rather than misfiling evidence downstream."},"ClientGrade":{"properties":{"passed":{"type":"integer","minimum":0.0,"title":"Passed"},"total":{"type":"integer","minimum":0.0,"title":"Total"},"seeds_run":{"items":{"type":"integer"},"type":"array","title":"Seeds Run"},"seeds_passed":{"items":{"type":"integer"},"type":"array","title":"Seeds Passed"},"tests":{"anyOf":[{"items":{"$ref":"#/components/schemas/TestOutcome"},"type":"array"},{"type":"null"}],"title":"Tests"},"trace_summary":{"anyOf":[{"$ref":"#/components/schemas/TraceSummary"},{"type":"null"}]},"error_class":{"type":"string","enum":["none","fluency","runtime","logic","world"],"title":"Error Class"},"checks_failed":{"items":{"type":"string"},"type":"array","title":"Checks Failed"},"stdout":{"anyOf":[{"type":"string","maxLength":2048},{"type":"null"}],"title":"Stdout"}},"additionalProperties":false,"type":"object","required":["passed","total","seeds_run","seeds_passed","error_class"],"title":"ClientGrade","description":"What the Pyodide worker says happened when it ran the learner's code (ADR-001).\n\nThe server re-checks `ast_lint` and the trace invariants itself and rejects\na `seeds_run` list it did not issue, so this is evidence, not a verdict."},"ColdCheck":{"properties":{"delay_days":{"type":"integer","title":"Delay Days","default":2},"scenario":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"string"}],"title":"Scenario"},"twist":{"type":"string","title":"Twist"}},"additionalProperties":false,"type":"object","required":["scenario","twist"],"title":"ColdCheck"},"Correction":{"properties":{"family_id":{"type":"string","title":"Family Id"},"seed":{"type":"integer","title":"Seed"},"prompt":{"type":"string","title":"Prompt"},"your_answer":{"type":"string","title":"Your Answer"},"correct_answer":{"type":"string","title":"Correct Answer"},"what_happens":{"type":"string","title":"What Happens"}},"additionalProperties":false,"type":"object","required":["family_id","seed","prompt","your_answer","correct_answer","what_happens"],"title":"Correction","description":"One placement miss, shown back with the right answer and what it does.\n\nCorrected retrieval is the point of placement, so nothing is graded and\ndropped (§2.3)."},"DashboardView":{"properties":{"mission":{"anyOf":[{"$ref":"#/components/schemas/Activity"},{"type":"null"}]},"checks_due":{"$ref":"#/components/schemas/ChecksDue"},"rhythm":{"$ref":"#/components/schemas/Rhythm"},"projects":{"items":{"$ref":"#/components/schemas/ProjectSummary"},"type":"array","title":"Projects"},"solid":{"items":{"$ref":"#/components/schemas/MapNode"},"type":"array","title":"Solid"},"shaky":{"items":{"$ref":"#/components/schemas/MapNode"},"type":"array","title":"Shaky"},"discoveries":{"items":{"$ref":"#/components/schemas/Discovery"},"type":"array","title":"Discoveries"},"achievements":{"items":{"$ref":"#/components/schemas/Achievement"},"type":"array","title":"Achievements"},"calibration":{"anyOf":[{"$ref":"#/components/schemas/Calibration"},{"type":"null"}]}},"additionalProperties":false,"type":"object","required":["checks_due","rhythm"],"title":"DashboardView","description":"`GET /api/dashboard` reply: Basecamp in one payload (§9 \"Dashboard\").\n\nNo percentages, no time-on-task, no retention curves — `shaky` is worded\n\"due for a check\", not \"weak\"."},"Diagnosis":{"properties":{"match":{"type":"string","title":"Match"},"misconception_id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]*$","title":"Misconception Id"},"text":{"type":"string","title":"Text"}},"additionalProperties":false,"type":"object","required":["match","misconception_id","text"],"title":"Diagnosis"},"Discovery":{"properties":{"id":{"type":"string","title":"Id"},"title":{"type":"string","title":"Title"},"seq":{"type":"integer","title":"Seq"}},"additionalProperties":false,"type":"object","required":["id","title","seq"],"title":"Discovery","description":"Something the learner uncovered, in the order it was found."},"Editor":{"properties":{"starter":{"type":"string","title":"Starter"},"readonly_ranges":{"items":{"prefixItems":[{"type":"integer"},{"type":"integer"}],"type":"array","maxItems":2,"minItems":2},"type":"array","title":"Readonly Ranges","default":[]}},"additionalProperties":false,"type":"object","required":["starter"],"title":"Editor"},"EmptyPayload":{"properties":{},"additionalProperties":false,"type":"object","title":"EmptyPayload","description":"No payload — `session_ended` carries nothing but its own timestamp."},"ErrorDetail":{"properties":{"detail":{"type":"string","title":"Detail"}},"additionalProperties":false,"type":"object","required":["detail"],"title":"ErrorDetail","description":"FastAPI's error body — the only error shape Atlas returns."},"EventBatch":{"properties":{"session_id":{"type":"string","title":"Session Id"},"events":{"items":{"$ref":"#/components/schemas/ClientEvent"},"type":"array","title":"Events"}},"additionalProperties":false,"type":"object","required":["session_id","events"],"title":"EventBatch","description":"`POST /api/events` body: a batch of client observations for one session."},"EventBatchResult":{"properties":{"accepted":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Accepted"},"session_id":{"type":"string","title":"Session Id"},"skill_updates":{"items":{"$ref":"#/components/schemas/SkillUpdate"},"type":"array","title":"Skill Updates"},"achievements":{"items":{"$ref":"#/components/schemas/Achievement"},"type":"array","title":"Achievements"}},"additionalProperties":false,"type":"object","required":["accepted","session_id"],"title":"EventBatchResult","description":"`POST /api/events` reply: which events landed, plus anything they moved."},"Evidence":{"properties":{"node_id":{"type":"string","pattern":"^[a-z]{2,5}(\\.[a-z0-9_]+){3}$","title":"Node Id"},"dimension":{"type":"string","title":"Dimension"}},"additionalProperties":false,"type":"object","required":["node_id","dimension"],"title":"Evidence"},"ExplainAct":{"properties":{"act_id":{"type":"string","title":"Act Id"},"kind":{"type":"string","enum":["predict","trace","locate","why_alt_fails"],"title":"Kind"},"prompt":{"type":"string","title":"Prompt"},"widget":{"$ref":"#/components/schemas/Widget"}},"additionalProperties":false,"type":"object","required":["act_id","kind","prompt","widget"],"title":"ExplainAct","description":"A short question about the learner's *own* program — what turns \"it ran\" into \"I know why\".\n\nGenerated from the learner's trace; the answer key (`expected`) stays on\nthe server (§5.4)."},"ExplainActTarget":{"properties":{"kind":{"type":"string","const":"explain_act","title":"Kind","default":"explain_act"},"family_id":{"type":"string","title":"Family Id"},"seed":{"type":"integer","title":"Seed"},"presented_seq":{"type":"integer","title":"Presented Seq"},"act_id":{"type":"string","title":"Act Id"}},"additionalProperties":false,"type":"object","required":["family_id","seed","presented_seq","act_id"],"title":"ExplainActTarget","description":"The attempt answers an explain act attached to an item the learner already ran."},"ExplanationRecord":{"properties":{"milestone_id":{"type":"string","title":"Milestone Id"},"act":{"type":"string","title":"Act"},"response":{"type":"string","title":"Response"},"correct":{"type":"boolean","title":"Correct"}},"additionalProperties":false,"type":"object","required":["milestone_id","act","response","correct"],"title":"ExplanationRecord","description":"An explain act answered during a project, kept with its verdict."},"Feedback":{"properties":{"probable":{"type":"string","title":"Probable"},"locus":{"$ref":"#/components/schemas/FeedbackLocus"},"next":{"type":"string","title":"Next"},"misconception_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Misconception Id"},"correct_answer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Correct Answer"},"forfeited_cold":{"type":"boolean","title":"Forfeited Cold","default":false}},"additionalProperties":false,"type":"object","required":["probable","next"],"title":"Feedback","description":"Task-level corrective feedback: what probably went wrong, and one thing to do.\n\nNever trait language, never a bare \"wrong\" — the probable cause is named\nand exactly one next action is offered (§2, §9)."},"FeedbackLocus":{"properties":{"line":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Line"},"step":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Step"},"check_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Check Id"}},"additionalProperties":false,"type":"object","title":"FeedbackLocus","description":"Where the failure is: a source line, a trace step, or a named check."},"GameState":{"properties":{"xp":{"type":"integer","minimum":0.0,"title":"Xp"},"level":{"type":"integer","minimum":0.0,"title":"Level"},"rhythm":{"$ref":"#/components/schemas/Rhythm"}},"additionalProperties":false,"type":"object","required":["xp","level","rhythm"],"title":"GameState","description":"The computed game layer: XP, level and weekly rhythm (never stored, §4.4)."},"GlossaryEntry":{"properties":{"id":{"type":"string","title":"Id"},"term":{"type":"string","title":"Term"},"plain":{"type":"string","title":"Plain"},"example":{"type":"string","title":"Example"},"state":{"type":"string","enum":["unseen","introduced","interacted","verified"],"title":"State"},"introduced_by":{"type":"string","title":"Introduced By"},"forfeits_check_on":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Forfeits Check On"}},"additionalProperties":false,"type":"object","required":["id","term","plain","example","state","introduced_by"],"title":"GlossaryEntry","description":"One term in plain language, with where it was introduced and what it may cost.\n\n`forfeits_check_on` names the node whose pending cold check reading this\nentry would turn into practice — said before the click, never after (§6)."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"Health":{"properties":{"status":{"type":"string","title":"Status"},"version":{"type":"string","title":"Version"},"db":{"type":"string","title":"Db"},"content_hash":{"type":"string","title":"Content Hash"}},"type":"object","required":["status","version","db","content_hash"],"title":"Health"},"ItemInstance":{"properties":{"family_id":{"type":"string","title":"Family Id"},"seed":{"type":"integer","title":"Seed"},"presented_seq":{"type":"integer","title":"Presented Seq"},"node_id":{"type":"string","title":"Node Id"},"format":{"type":"string","title":"Format"},"format_class":{"type":"string","enum":["predict","produce","debug","select","explain"],"title":"Format Class"},"surface":{"type":"string","title":"Surface"},"prompt":{"type":"string","title":"Prompt"},"widgets":{"items":{"$ref":"#/components/schemas/Widget"},"type":"array","title":"Widgets"},"checks_public":{"items":{"$ref":"#/components/schemas/PublicCheck"},"type":"array","title":"Checks Public"},"solution":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Solution"},"completion_blanks":{"anyOf":[{"items":{"prefixItems":[{"type":"integer"},{"type":"integer"},{"type":"integer"}],"type":"array","maxItems":3,"minItems":3},"type":"array"},{"type":"null"}],"title":"Completion Blanks"},"confidence_asked":{"type":"boolean","title":"Confidence Asked"},"covert_first":{"type":"boolean","title":"Covert First"},"hint_count":{"type":"integer","maximum":7.0,"minimum":0.0,"title":"Hint Count"},"seeds":{"items":{"type":"integer"},"type":"array","title":"Seeds"},"legend":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Legend"},"label":{"$ref":"#/components/schemas/Label"},"is_cold_check":{"type":"boolean","title":"Is Cold Check"}},"additionalProperties":false,"type":"object","required":["family_id","seed","presented_seq","node_id","format","format_class","surface","prompt","widgets","confidence_asked","covert_first","hint_count","seeds","label","is_cold_check"],"title":"ItemInstance","description":"One item, rendered for one learner at one moment — what the player screen draws.\n\nThe server issues `seeds` here and refuses any attempt whose `seeds_run`\ndiffer (ADR-001).  `solution` appears only at support ≤ 1 and\n`completion_blanks` only at support == 1, so a faded item cannot leak the\nanswer it is testing."},"ItemPresent":{"properties":{"family_id":{"type":"string","title":"Family Id"},"seed":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Seed"},"context":{"type":"string","enum":["lesson","review","cold_check","scouting","project"],"title":"Context"},"lesson_step_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lesson Step Id"},"project_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Id"}},"additionalProperties":false,"type":"object","required":["family_id","context"],"title":"ItemPresent","description":"`POST /api/items/present` body: which family to render, and what for."},"ItemRef":{"properties":{"kind":{"type":"string","const":"item","title":"Kind","default":"item"},"family_id":{"type":"string","title":"Family Id"},"seed":{"type":"integer","title":"Seed"},"presented_seq":{"type":"integer","title":"Presented Seq"}},"additionalProperties":false,"type":"object","required":["family_id","seed","presented_seq"],"title":"ItemRef","description":"Points at one issued rendering of an item family."},"ItemTarget":{"properties":{"kind":{"type":"string","const":"item","title":"Kind","default":"item"},"family_id":{"type":"string","title":"Family Id"},"seed":{"type":"integer","title":"Seed"},"presented_seq":{"type":"integer","title":"Presented Seq"}},"additionalProperties":false,"type":"object","required":["family_id","seed","presented_seq"],"title":"ItemTarget","description":"The attempt answers one issued item rendering."},"KnowledgeMap":{"properties":{"topics":{"items":{"$ref":"#/components/schemas/KnowledgeMapTopic"},"type":"array","title":"Topics"},"recommended_start":{"$ref":"#/components/schemas/RecommendedStart"},"corrections":{"items":{"$ref":"#/components/schemas/Correction"},"type":"array","title":"Corrections"}},"additionalProperties":false,"type":"object","required":["topics","recommended_start"],"title":"KnowledgeMap","description":"The end of placement: bands per topic, where to start, and every correction."},"KnowledgeMapTopic":{"properties":{"id":{"type":"string","title":"Id"},"title":{"type":"string","title":"Title"},"band":{"type":"string","enum":["PLACED_OUT","STRONG","WEAK","NOVICE"],"title":"Band"},"label":{"type":"string","title":"Label"}},"additionalProperties":false,"type":"object","required":["id","title","band","label"],"title":"KnowledgeMapTopic","description":"One topic's placement verdict, with the learner-facing wording for its band."},"Label":{"properties":{"kind":{"type":"string","enum":["node","group"],"title":"Kind"},"text":{"type":"string","title":"Text"}},"additionalProperties":false,"type":"object","required":["kind","text"],"title":"Label","description":"What to call an activity: the node's name, or its interference group.\n\n`kind == \"group\"` while a grouped item is ungraded, so naming the node\ncannot give the answer away (BUILD_PLAN; ARCHITECTURE §9)."},"Learner":{"properties":{"id":{"type":"string","title":"Id"},"display_name":{"type":"string","title":"Display Name"},"timezone":{"type":"string","title":"Timezone"},"prior_exp":{"type":"string","enum":["never","little","yes"],"title":"Prior Exp"},"mode":{"type":"string","const":"explorer","title":"Mode"},"disclosure_level":{"type":"integer","enum":[0,1,2],"title":"Disclosure Level"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"additionalProperties":false,"type":"object","required":["id","display_name","timezone","prior_exp","mode","disclosure_level","created_at"],"title":"Learner","description":"Who is learning: identity, timezone and how much of the game is revealed."},"LearnerCreate":{"properties":{"display_name":{"type":"string","maxLength":80,"minLength":1,"title":"Display Name"},"prior_exp":{"type":"string","enum":["never","little","yes"],"title":"Prior Exp"},"budget_min":{"type":"integer","maximum":240.0,"minimum":1.0,"title":"Budget Min"},"timezone":{"type":"string","minLength":1,"title":"Timezone"},"prefs":{"anyOf":[{"$ref":"#/components/schemas/Prefs"},{"type":"null"}]}},"additionalProperties":false,"type":"object","required":["display_name","prior_exp","budget_min","timezone"],"title":"LearnerCreate","description":"`POST /api/learner` body: onboarding's answers. 409 if a learner exists."},"LearnerView":{"properties":{"learner":{"$ref":"#/components/schemas/Learner"},"game":{"$ref":"#/components/schemas/GameState"},"prefs":{"$ref":"#/components/schemas/Prefs"},"rest_days":{"items":{"type":"integer","maximum":6.0,"minimum":0.0},"type":"array","title":"Rest Days"}},"additionalProperties":false,"type":"object","required":["learner","game","prefs","rest_days"],"title":"LearnerView","description":"Everything the shell needs about the learner — the reply to every /api/learner call."},"Lesson":{"properties":{"id":{"type":"string","pattern":"^[a-z][a-z0-9_]*$","title":"Id"},"topic_id":{"type":"string","pattern":"^[a-z]{2,5}(\\.[a-z0-9_]+){2}$","title":"Topic Id"},"node_ids":{"items":{"type":"string","pattern":"^[a-z]{2,5}(\\.[a-z0-9_]+){3}$"},"type":"array","title":"Node Ids"},"steps":{"items":{"$ref":"#/components/schemas/Step"},"type":"array","title":"Steps"}},"additionalProperties":false,"type":"object","required":["id","topic_id","node_ids","steps"],"title":"Lesson"},"LessonRef":{"properties":{"kind":{"type":"string","const":"lesson","title":"Kind","default":"lesson"},"lesson_id":{"type":"string","title":"Lesson Id"}},"additionalProperties":false,"type":"object","required":["lesson_id"],"title":"LessonRef","description":"Points at a lesson to read and work through."},"LessonStepViewed":{"properties":{"lesson_id":{"type":"string","title":"Lesson Id"},"step_id":{"type":"string","title":"Step Id"}},"additionalProperties":false,"type":"object","required":["lesson_id","step_id"],"title":"LessonStepViewed","description":"The learner reached a lesson step — which warms the node for the day."},"LevelInstance":{"properties":{"level_id":{"type":"string","title":"Level Id"},"template":{"type":"string","title":"Template"},"params":{"additionalProperties":true,"type":"object","title":"Params"},"seed":{"type":"integer","title":"Seed"},"grid":{"items":{"type":"string"},"type":"array","title":"Grid"},"robot":{"$ref":"#/components/schemas/RobotPose"},"goals":{"items":{"type":"string"},"type":"array","title":"Goals"},"max_program_lines":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Program Lines"}},"additionalProperties":false,"type":"object","required":["level_id","template","seed","grid","robot","goals"],"title":"LevelInstance","description":"A robot level rendered for one seed: the grid, the start pose and the goals."},"LevelTemplate":{"properties":{"id":{"type":"string","pattern":"^[a-z][a-z0-9_-]*$","title":"Id"},"teaches":{"items":{"type":"string","pattern":"^[a-z]{2,5}(\\.[a-z0-9_]+){3}$"},"type":"array","title":"Teaches"},"template":{"type":"string","title":"Template"},"params":{"additionalProperties":true,"type":"object","title":"Params","default":{}},"fixed_params":{"items":{"type":"string"},"type":"array","title":"Fixed Params","default":[]},"allowed_calls":{"items":{"type":"string"},"type":"array","title":"Allowed Calls","default":[]},"max_program_lines":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Program Lines"},"hidden_seeds":{"type":"integer","minimum":1.0,"title":"Hidden Seeds","default":3},"goals":{"items":{"type":"string","enum":["all_gems","reach","no_crash"]},"type":"array","title":"Goals"},"worked_example":{"$ref":"#/components/schemas/WorkedExample"}},"additionalProperties":false,"type":"object","required":["id","teaches","template","goals","worked_example"],"title":"LevelTemplate"},"MapNode":{"properties":{"id":{"type":"string","title":"Id"},"topic_id":{"type":"string","title":"Topic Id"},"title":{"type":"string","title":"Title"},"type":{"type":"string","enum":["declarative","procedural","discriminative","fluency"],"title":"Type"},"authored":{"type":"boolean","title":"Authored"},"mastery_view":{"type":"string","enum":["not_started","practicing","check_due","solid","mastered"],"title":"Mastery View"},"review_due":{"type":"boolean","title":"Review Due"},"visibility":{"type":"string","enum":["full","silhouette"],"title":"Visibility"},"prereqs":{"items":{"type":"string"},"type":"array","title":"Prereqs"},"missing_evidence":{"items":{"type":"string"},"type":"array","title":"Missing Evidence"},"next_check_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Next Check At"}},"additionalProperties":false,"type":"object","required":["id","topic_id","title","type","authored","mastery_view","review_due","visibility"],"title":"MapNode","description":"A node on the map with its standing — no percentage, ever.\n\n`missing_evidence` is the honest answer to \"why is this not solid yet\":\nthe chips name what has not happened (\"a cold check on a later day\")."},"MapRegion":{"properties":{"id":{"type":"string","title":"Id"},"title":{"type":"string","title":"Title"},"tier":{"type":"integer","title":"Tier"},"x":{"type":"number","title":"X"},"y":{"type":"number","title":"Y"},"w":{"type":"number","title":"W"},"h":{"type":"number","title":"H"},"visibility":{"type":"string","enum":["full","silhouette","fog"],"title":"Visibility"},"hidden_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Hidden Count"}},"additionalProperties":false,"type":"object","required":["id","title","tier","x","y","w","h","visibility"],"title":"MapRegion","description":"A region box on the world map, at generated coordinates.\n\n`visibility` \"fog\" means the learner sees that something is there and how\nmuch (`hidden_count`), never what (§8)."},"MapTopic":{"properties":{"id":{"type":"string","title":"Id"},"region_id":{"type":"string","title":"Region Id"},"title":{"type":"string","title":"Title"},"x":{"type":"number","title":"X"},"y":{"type":"number","title":"Y"},"authored":{"type":"boolean","title":"Authored"},"band":{"anyOf":[{"type":"string","enum":["PLACED_OUT","STRONG","WEAK","NOVICE"]},{"type":"null"}],"title":"Band"}},"additionalProperties":false,"type":"object","required":["id","region_id","title","x","y","authored"],"title":"MapTopic","description":"A topic dot inside a region; `band` is set once placement has an opinion."},"MapView":{"properties":{"regions":{"items":{"$ref":"#/components/schemas/MapRegion"},"type":"array","title":"Regions"},"topics":{"items":{"$ref":"#/components/schemas/MapTopic"},"type":"array","title":"Topics"},"nodes":{"items":{"$ref":"#/components/schemas/MapNode"},"type":"array","title":"Nodes"},"current":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Current"},"fringe":{"items":{"type":"string"},"type":"array","title":"Fringe"}},"additionalProperties":false,"type":"object","required":["regions","topics","nodes"],"title":"MapView","description":"`GET /api/map` reply: the drawable world, already filtered by visibility."},"MasteryTransition":{"properties":{"from":{"type":"string","enum":["UNSEEN","LEARNING","ESTABLISHED","MASTERED"],"title":"From"},"to":{"type":"string","enum":["UNSEEN","LEARNING","ESTABLISHED","MASTERED"],"title":"To"}},"additionalProperties":false,"type":"object","required":["from","to"],"title":"MasteryTransition","description":"A mastery move, from one state to another."},"Milestone":{"properties":{"id":{"type":"string","pattern":"^[a-z][a-z0-9_]*$","title":"Id"},"goal_plain":{"type":"string","title":"Goal Plain"},"node_ids":{"items":{"type":"string","pattern":"^[a-z]{2,5}(\\.[a-z0-9_]+){3}$"},"type":"array","title":"Node Ids"},"tests":{"items":{"$ref":"#/components/schemas/MilestoneTest"},"type":"array","title":"Tests"},"explain":{"$ref":"#/components/schemas/MilestoneExplain"},"hint_ladder":{"items":{"type":"string"},"type":"array","title":"Hint Ladder","default":[]}},"additionalProperties":false,"type":"object","required":["id","goal_plain","node_ids","tests","explain"],"title":"Milestone"},"MilestoneExplain":{"properties":{"kind":{"type":"string","enum":["predict","trace","locate","why_alt_fails"],"title":"Kind"},"prompt":{"type":"string","title":"Prompt"},"answer_spec":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"string"}],"title":"Answer Spec"},"broken_variant":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Broken Variant"}},"additionalProperties":false,"type":"object","required":["kind","prompt","answer_spec"],"title":"MilestoneExplain"},"MilestoneTarget":{"properties":{"kind":{"type":"string","const":"milestone","title":"Kind","default":"milestone"},"project_id":{"type":"string","title":"Project Id"},"milestone_id":{"type":"string","title":"Milestone Id"},"step":{"type":"string","enum":["tests","explain"],"title":"Step"},"presented_seq":{"type":"integer","title":"Presented Seq"}},"additionalProperties":false,"type":"object","required":["project_id","milestone_id","step","presented_seq"],"title":"MilestoneTarget","description":"The attempt is a project milestone: either its tests or its explain step."},"MilestoneTest":{"properties":{"id":{"type":"string","pattern":"^[a-z][a-z0-9_]*$","title":"Id"},"kind":{"type":"string","enum":["sim_state","io","ast_lint","trace_invariant"],"title":"Kind"},"spec":{"additionalProperties":true,"type":"object","title":"Spec"},"diagnoses":{"items":{"$ref":"#/components/schemas/Diagnosis"},"type":"array","title":"Diagnoses","default":[]}},"additionalProperties":false,"type":"object","required":["id","kind","spec"],"title":"MilestoneTest"},"MistakeRecord":{"properties":{"ts":{"type":"string","format":"date-time","title":"Ts"},"diagnosis":{"type":"string","title":"Diagnosis"},"misconception_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Misconception Id"},"diff":{"type":"string","title":"Diff"}},"additionalProperties":false,"type":"object","required":["ts","diagnosis","diff"],"title":"MistakeRecord","description":"A mistake worth keeping: what was diagnosed and what changed afterwards."},"MixedSetRef":{"properties":{"kind":{"type":"string","const":"mixed_set","title":"Kind","default":"mixed_set"},"set_id":{"type":"string","title":"Set Id"},"family_ids":{"items":{"type":"string"},"type":"array","title":"Family Ids"}},"additionalProperties":false,"type":"object","required":["set_id","family_ids"],"title":"MixedSetRef","description":"Points at an interleaved block of families answered as one set."},"ParsonsLine":{"properties":{"id":{"type":"string","title":"Id"},"text":{"type":"string","title":"Text"}},"additionalProperties":false,"type":"object","required":["id","text"],"title":"ParsonsLine","description":"One draggable line of a Parsons problem."},"ParsonsOrderEntry":{"properties":{"id":{"type":"string","title":"Id"}},"additionalProperties":false,"type":"object","required":["id"],"title":"ParsonsOrderEntry","description":"One line id in the order the learner arranged it."},"PlacementAnswer":{"properties":{"subject":{"type":"string","title":"Subject"},"family_id":{"type":"string","title":"Family Id"},"seed":{"type":"integer","title":"Seed"},"response":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"number"},{"items":{"type":"string"},"type":"array"},{"items":{"$ref":"#/components/schemas/ParsonsOrderEntry"},"type":"array"},{"additionalProperties":{"additionalProperties":{"type":"string"},"type":"object"},"type":"object"},{"type":"null"}],"title":"Response"},"dont_know":{"type":"boolean","title":"Dont Know","default":false},"confidence_pre":{"anyOf":[{"type":"integer","maximum":5.0,"minimum":1.0},{"type":"null"}],"title":"Confidence Pre"}},"additionalProperties":false,"type":"object","required":["subject","family_id","seed"],"title":"PlacementAnswer","description":"`POST /api/placement/answer` body: server-graded, so no `client_grade`.\n\n\"I don't know\" is a first-class answer (`dont_know`), not a wrong one."},"PlacementAnswerResult":{"properties":{"item":{"anyOf":[{"$ref":"#/components/schemas/ItemInstance"},{"type":"null"}]},"progress":{"$ref":"#/components/schemas/PlacementProgress"},"knowledge_map":{"anyOf":[{"$ref":"#/components/schemas/KnowledgeMap"},{"type":"null"}]}},"additionalProperties":false,"type":"object","required":["progress"],"title":"PlacementAnswerResult","description":"`POST /api/placement/answer` reply: the next item, or the finished knowledge map."},"PlacementProgress":{"properties":{"answered":{"type":"integer","minimum":0.0,"title":"Answered"},"about_left":{"type":"integer","minimum":0.0,"title":"About Left"}},"additionalProperties":false,"type":"object","required":["answered","about_left"],"title":"PlacementProgress","description":"How far placement has got, in items — deliberately vague about the end."},"PlacementStart":{"properties":{"subject":{"type":"string","title":"Subject"}},"additionalProperties":false,"type":"object","required":["subject"],"title":"PlacementStart","description":"`POST /api/placement/start` body: which subject to place in."},"PlacementStartResult":{"properties":{"item":{"$ref":"#/components/schemas/ItemInstance"},"progress":{"$ref":"#/components/schemas/PlacementProgress"}},"additionalProperties":false,"type":"object","required":["item","progress"],"title":"PlacementStartResult","description":"`POST /api/placement/start` reply: the first item and the progress line."},"Plan":{"properties":{"session_id":{"type":"string","title":"Session Id"},"budget_min":{"type":"integer","minimum":0.0,"title":"Budget Min"},"activities":{"items":{"$ref":"#/components/schemas/Activity"},"type":"array","title":"Activities"},"reviews_capped":{"type":"boolean","title":"Reviews Capped"},"next_check_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Next Check At"}},"additionalProperties":false,"type":"object","required":["session_id","budget_min","activities","reviews_capped"],"title":"Plan","description":"The session's ordered activities — recomposed after every graded attempt."},"PortfolioEntry":{"properties":{"project_id":{"type":"string","title":"Project Id"},"title":{"type":"string","title":"Title"},"status":{"type":"string","enum":["locked","available","active","complete","abandoned"],"title":"Status"},"objectives":{"items":{"type":"string"},"type":"array","title":"Objectives"},"skills":{"items":{"type":"string"},"type":"array","title":"Skills"},"snapshots":{"items":{"$ref":"#/components/schemas/Snapshot"},"type":"array","title":"Snapshots"},"test_runs":{"items":{"$ref":"#/components/schemas/TestRun"},"type":"array","title":"Test Runs"},"mistakes":{"items":{"$ref":"#/components/schemas/MistakeRecord"},"type":"array","title":"Mistakes"},"explanations":{"items":{"$ref":"#/components/schemas/ExplanationRecord"},"type":"array","title":"Explanations"},"free_zone":{"anyOf":[{"$ref":"#/components/schemas/atlas__api__models__FreeZone"},{"type":"null"}]},"reflection":{"items":{"type":"string"},"type":"array","title":"Reflection"},"cold_check":{"anyOf":[{"$ref":"#/components/schemas/ProjectColdCheck"},{"type":"null"}]}},"additionalProperties":false,"type":"object","required":["project_id","title","status"],"title":"PortfolioEntry","description":"One project's whole record: what was built, what broke, and what was learned.\n\nThis is the artefact a learner can show someone — mistakes included, because\nthe diagnosis and the fix are the evidence (§5.7)."},"Prefs":{"properties":{"reduced_motion":{"type":"boolean","title":"Reduced Motion"},"theme":{"type":"string","enum":["light","dark","system"],"title":"Theme"},"text_scale":{"type":"number","maximum":1.6,"minimum":1.0,"title":"Text Scale"},"letter_spacing":{"type":"number","maximum":0.12,"minimum":0.0,"title":"Letter Spacing"},"time_factor":{"type":"number","maximum":3.0,"minimum":1.0,"title":"Time Factor"},"sim_step_mode":{"type":"boolean","title":"Sim Step Mode"},"show_timers":{"type":"boolean","title":"Show Timers"}},"additionalProperties":false,"type":"object","required":["reduced_motion","theme","text_scale","letter_spacing","time_factor","sim_step_mode","show_timers"],"title":"Prefs","description":"Accessibility and pacing preferences — the complete set, all required.\n\n`time_factor` stretches every timeout the learner can feel (idle nudge,\nreveal dwell); it never gates anything (WCAG 2.2.1)."},"PrefsChanged":{"properties":{"prefs":{"$ref":"#/components/schemas/Prefs"}},"additionalProperties":false,"type":"object","required":["prefs"],"title":"PrefsChanged","description":"The learner changed preferences; the full new `Prefs` travels with it."},"PrefsPatch":{"properties":{"reduced_motion":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Reduced Motion"},"theme":{"anyOf":[{"type":"string","enum":["light","dark","system"]},{"type":"null"}],"title":"Theme"},"text_scale":{"anyOf":[{"type":"number","maximum":1.6,"minimum":1.0},{"type":"null"}],"title":"Text Scale"},"letter_spacing":{"anyOf":[{"type":"number","maximum":0.12,"minimum":0.0},{"type":"null"}],"title":"Letter Spacing"},"time_factor":{"anyOf":[{"type":"number","maximum":3.0,"minimum":1.0},{"type":"null"}],"title":"Time Factor"},"sim_step_mode":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Sim Step Mode"},"show_timers":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Show Timers"}},"additionalProperties":false,"type":"object","title":"PrefsPatch","description":"A partial `Prefs` — every field optional, for `PATCH /api/learner/prefs`."},"PrefsUpdate":{"properties":{"prefs":{"anyOf":[{"$ref":"#/components/schemas/PrefsPatch"},{"type":"null"}]},"rest_days":{"anyOf":[{"items":{"type":"integer","maximum":6.0,"minimum":0.0},"type":"array"},{"type":"null"}],"title":"Rest Days"}},"additionalProperties":false,"type":"object","title":"PrefsUpdate","description":"`PATCH /api/learner/prefs` body: change some preferences, or the rest days."},"Project":{"properties":{"id":{"type":"string","pattern":"^[a-z][a-z0-9_]*$","title":"Id"},"region_id":{"type":"string","pattern":"^[a-z]{2,5}\\.[a-z0-9_]+$","title":"Region Id"},"tier":{"type":"string","enum":["tiny","playful","useful","open"],"title":"Tier"},"brief":{"$ref":"#/components/schemas/ProjectBrief"},"requires":{"items":{"type":"string","pattern":"^[a-z]{2,5}(\\.[a-z0-9_]+){3}$"},"type":"array","title":"Requires"},"stretch":{"anyOf":[{"type":"string","pattern":"^[a-z]{2,5}(\\.[a-z0-9_]+){3}$"},{"type":"null"}],"title":"Stretch"},"evidences":{"items":{"$ref":"#/components/schemas/Evidence"},"type":"array","title":"Evidences","default":[]},"world":{"$ref":"#/components/schemas/ProjectWorld"},"starter":{"$ref":"#/components/schemas/Starter"},"milestones":{"items":{"$ref":"#/components/schemas/Milestone"},"type":"array","title":"Milestones"},"free_zone":{"$ref":"#/components/schemas/atlas__content__schemas__FreeZone"},"cold_check":{"$ref":"#/components/schemas/ColdCheck"},"reveal":{"$ref":"#/components/schemas/Reveal","default":{"mode":"replay_all_scenarios"}},"workspace":{"type":"string","enum":["pyodide","container"],"title":"Workspace","default":"pyodide"},"est_minutes":{"type":"integer","minimum":0.0,"title":"Est Minutes"}},"additionalProperties":false,"type":"object","required":["id","region_id","tier","brief","requires","world","starter","milestones","free_zone","cold_check","est_minutes"],"title":"Project"},"ProjectArtifactSaved":{"properties":{"project_id":{"type":"string","title":"Project Id"},"kind":{"type":"string","enum":["free_zone","reflection","snapshot"],"title":"Kind"},"data":{"additionalProperties":true,"type":"object","title":"Data"}},"additionalProperties":false,"type":"object","required":["project_id","kind"],"title":"ProjectArtifactSaved","description":"The learner saved project work: a free-zone world, a reflection or a snapshot."},"ProjectBrief":{"properties":{"plain":{"type":"string","title":"Plain"},"why_it_matters":{"type":"string","title":"Why It Matters"},"deliverable":{"type":"string","title":"Deliverable"}},"additionalProperties":false,"type":"object","required":["plain","why_it_matters","deliverable"],"title":"ProjectBrief"},"ProjectBundle":{"properties":{"project":{"$ref":"#/components/schemas/Project"},"state":{"$ref":"#/components/schemas/ProjectState"},"seeds":{"items":{"type":"integer"},"type":"array","title":"Seeds"},"presented_seq":{"type":"integer","title":"Presented Seq"}},"additionalProperties":false,"type":"object","required":["project","state","seeds","presented_seq"],"title":"ProjectBundle","description":"`GET /api/content/project/{id}` reply: the brief, the state and the issued seeds."},"ProjectColdCheck":{"properties":{"due":{"type":"string","format":"date-time","title":"Due"},"result":{"anyOf":[{"type":"string","enum":["pass","fail","practice"]},{"type":"null"}],"title":"Result"}},"additionalProperties":false,"type":"object","required":["due"],"title":"ProjectColdCheck","description":"The project's later, unwarmed check: when it is due and how it went."},"ProjectRef":{"properties":{"kind":{"type":"string","const":"project","title":"Kind","default":"project"},"project_id":{"type":"string","title":"Project Id"},"milestone_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Milestone Id"}},"additionalProperties":false,"type":"object","required":["project_id"],"title":"ProjectRef","description":"Points at a project, optionally at the milestone to work on next."},"ProjectState":{"properties":{"project_id":{"type":"string","title":"Project Id"},"title":{"type":"string","title":"Title"},"status":{"type":"string","enum":["locked","available","active","complete","abandoned"],"title":"Status"},"milestone_idx":{"type":"integer","minimum":0.0,"title":"Milestone Idx"},"needs":{"items":{"type":"string"},"type":"array","title":"Needs"},"cold_check_due":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Cold Check Due"},"support_level":{"type":"integer","maximum":4.0,"minimum":0.0,"title":"Support Level"}},"additionalProperties":false,"type":"object","required":["project_id","title","status","milestone_idx","support_level"],"title":"ProjectState","description":"A project's standing plus the scaffolding level it is currently served at."},"ProjectSummary":{"properties":{"project_id":{"type":"string","title":"Project Id"},"title":{"type":"string","title":"Title"},"status":{"type":"string","enum":["locked","available","active","complete","abandoned"],"title":"Status"},"milestone_idx":{"type":"integer","minimum":0.0,"title":"Milestone Idx"},"needs":{"items":{"type":"string"},"type":"array","title":"Needs"},"cold_check_due":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Cold Check Due"}},"additionalProperties":false,"type":"object","required":["project_id","title","status","milestone_idx"],"title":"ProjectSummary","description":"A project's standing: where it is and what it still needs."},"ProjectWorld":{"properties":{"sim":{"type":"string","pattern":"^[a-z][a-z0-9_]*$","title":"Sim"},"scenarios":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Scenarios"}},"additionalProperties":false,"type":"object","required":["sim","scenarios"],"title":"ProjectWorld"},"PublicCheck":{"properties":{"id":{"type":"string","title":"Id"},"text":{"type":"string","title":"Text"}},"additionalProperties":false,"type":"object","required":["id","text"],"title":"PublicCheck","description":"A check the learner can read before answering (\"the robot ends on the gem\")."},"RecommendedStart":{"properties":{"kind":{"type":"string","enum":["lesson","project"],"title":"Kind"},"id":{"type":"string","title":"Id"},"title":{"type":"string","title":"Title"},"copy":{"type":"string","title":"Copy"}},"additionalProperties":false,"type":"object","required":["kind","id","title","copy"],"title":"RecommendedStart","description":"Where placement says to begin, and the sentence that explains why."},"Reveal":{"properties":{"mode":{"type":"string","const":"replay_all_scenarios","title":"Mode","default":"replay_all_scenarios"}},"additionalProperties":false,"type":"object","title":"Reveal"},"Rhythm":{"properties":{"days_this_week":{"type":"integer","maximum":7.0,"minimum":0.0,"title":"Days This Week"},"rest_days":{"items":{"type":"integer","maximum":6.0,"minimum":0.0},"type":"array","title":"Rest Days"},"week_start":{"type":"string","format":"date","title":"Week Start"}},"additionalProperties":false,"type":"object","required":["days_this_week","rest_days","week_start"],"title":"Rhythm","description":"This week's shape: days practised and the days the learner chose to rest.\n\nRest days are honoured, never punished — there is no streak to break (§2)."},"RobotFinal":{"properties":{"x":{"type":"integer","title":"X"},"y":{"type":"integer","title":"Y"},"heading":{"type":"string","enum":["N","E","S","W"],"title":"Heading"},"bag":{"type":"integer","minimum":0.0,"title":"Bag"},"gems_left":{"type":"integer","minimum":0.0,"title":"Gems Left"},"halted":{"type":"boolean","title":"Halted"},"halt_reason":{"anyOf":[{"type":"string","enum":["WallHit","NothingToPick","StepLimit","Exception"]},{"type":"null"}],"title":"Halt Reason"}},"additionalProperties":false,"type":"object","required":["x","y","heading","bag","gems_left","halted"],"title":"RobotFinal","description":"The world after the last command: position, bag, gems left, and how it ended."},"RobotPose":{"properties":{"x":{"type":"integer","title":"X"},"y":{"type":"integer","title":"Y"},"heading":{"type":"string","enum":["N","E","S","W"],"title":"Heading"}},"additionalProperties":false,"type":"object","required":["x","y","heading"],"title":"RobotPose","description":"Where the robot stands and which way it faces."},"SessionStart":{"properties":{"budget_min":{"anyOf":[{"type":"integer","maximum":240.0,"minimum":1.0},{"type":"null"}],"title":"Budget Min"}},"additionalProperties":false,"type":"object","title":"SessionStart","description":"`POST /api/sessions/start` body: an optional override of today's budget."},"SimAction":{"properties":{"level_id":{"type":"string","title":"Level Id"},"seed":{"type":"integer","title":"Seed"},"run_idx":{"type":"integer","minimum":0.0,"title":"Run Idx"},"outcome":{"type":"string","title":"Outcome"}},"additionalProperties":false,"type":"object","required":["level_id","seed","run_idx","outcome"],"title":"SimAction","description":"One Run of the robot: which level, which seed, which attempt, how it ended."},"SimBundle":{"properties":{"module_source":{"type":"string","title":"Module Source"},"api":{"items":{"type":"string"},"type":"array","title":"Api"},"levels":{"items":{"$ref":"#/components/schemas/LevelTemplate"},"type":"array","title":"Levels"}},"additionalProperties":false,"type":"object","required":["module_source","api","levels"],"title":"SimBundle","description":"`GET /api/content/sim/{sim_id}` reply: the simulation source the worker executes.\n\n`module_source` is `robot_world.py` verbatim — the same file CPython runs,\nso a trace produced in the browser and one produced in a test are the same\nartifact (ARCHITECTURE §3)."},"SkillUpdate":{"properties":{"node_id":{"type":"string","title":"Node Id"},"mastery":{"type":"string","enum":["UNSEEN","LEARNING","ESTABLISHED","MASTERED"],"title":"Mastery"},"mastery_view":{"type":"string","enum":["not_started","practicing","check_due","solid","mastered"],"title":"Mastery View"},"support_level":{"type":"integer","maximum":4.0,"minimum":0.0,"title":"Support Level"},"stage":{"type":"string","enum":["acquisition","compiling","autonomous"],"title":"Stage"},"review_due":{"type":"boolean","title":"Review Due"},"next_check_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Next Check At"},"upgrade":{"anyOf":[{"$ref":"#/components/schemas/MasteryTransition"},{"type":"null"}]},"downgrade":{"anyOf":[{"$ref":"#/components/schemas/MasteryTransition"},{"type":"null"}]}},"additionalProperties":false,"type":"object","required":["node_id","mastery","mastery_view","support_level","stage","review_due"],"title":"SkillUpdate","description":"What this attempt did to one node's standing, in both machine and learner words.\n\nUpgrades render immediately; downgrades are held to the end of the session\nso a bad minute never derails a good one (§9)."},"Snapshot":{"properties":{"milestone_id":{"type":"string","title":"Milestone Id"},"ts":{"type":"string","format":"date-time","title":"Ts"},"source":{"type":"string","title":"Source"}},"additionalProperties":false,"type":"object","required":["milestone_id","ts","source"],"title":"Snapshot","description":"The learner's program at a milestone, kept verbatim."},"Stage":{"properties":{"sim_level_id":{"anyOf":[{"type":"string","pattern":"^[a-z][a-z0-9_-]*$"},{"type":"null"}],"title":"Sim Level Id"},"editor":{"anyOf":[{"$ref":"#/components/schemas/Editor"},{"type":"null"}]},"notes_pane":{"type":"boolean","title":"Notes Pane","default":false}},"additionalProperties":false,"type":"object","title":"Stage"},"Starter":{"properties":{"by_support_level":{"additionalProperties":{"type":"string"},"type":"object","title":"By Support Level"}},"additionalProperties":false,"type":"object","required":["by_support_level"],"title":"Starter"},"Step":{"properties":{"id":{"type":"string","pattern":"^[a-z][a-z0-9_]*$","title":"Id"},"kind":{"type":"string","enum":["hook","question","discovery","explanation","interaction","attempt","feedback","variation","retrieval","transfer","application"],"title":"Kind"},"phase":{"type":"string","enum":["explore","learn","do","prove"],"title":"Phase"},"body":{"type":"string","title":"Body"},"terms_introduced":{"items":{"type":"string","pattern":"^[a-z][a-z0-9_]*$"},"type":"array","title":"Terms Introduced","default":[]},"stage":{"$ref":"#/components/schemas/Stage","default":{"notes_pane":false}},"goal":{"type":"string","pattern":"^(viewed|sim_goal|item_correct|any_attempt|lines_at_least:[1-9][0-9]*)$","title":"Goal","default":"viewed"},"item_family_ids":{"items":{"type":"string","pattern":"^[a-z]{2,5}(\\.[a-z0-9_]+){4}$"},"type":"array","title":"Item Family Ids","default":[]},"acts":{"items":{"$ref":"#/components/schemas/Act"},"type":"array","title":"Acts","default":[]},"deferred":{"type":"boolean","title":"Deferred","default":false}},"additionalProperties":false,"type":"object","required":["id","kind","phase","body"],"title":"Step"},"StopRef":{"properties":{"kind":{"type":"string","const":"stop","title":"Kind","default":"stop"}},"additionalProperties":false,"type":"object","title":"StopRef","description":"The end of the plan: the stop cue, which is an activity like any other."},"TermSeen":{"properties":{"term_id":{"type":"string","title":"Term Id"}},"additionalProperties":false,"type":"object","required":["term_id"],"title":"TermSeen","description":"A glossary term was shown; the term's state and the cold predicate both move."},"TestOutcome":{"properties":{"id":{"type":"string","title":"Id"},"ok":{"type":"boolean","title":"Ok"}},"additionalProperties":false,"type":"object","required":["id","ok"],"title":"TestOutcome","description":"One named check and whether it passed."},"TestRun":{"properties":{"milestone_id":{"type":"string","title":"Milestone Id"},"ts":{"type":"string","format":"date-time","title":"Ts"},"passed":{"type":"integer","minimum":0.0,"title":"Passed"},"total":{"type":"integer","minimum":0.0,"title":"Total"}},"additionalProperties":false,"type":"object","required":["milestone_id","ts","passed","total"],"title":"TestRun","description":"One run of a milestone's tests, and how many passed."},"TraceConstructs":{"properties":{"for":{"type":"integer","minimum":0.0,"title":"For"},"while":{"type":"integer","minimum":0.0,"title":"While"},"if":{"type":"integer","minimum":0.0,"title":"If"},"assign":{"type":"integer","minimum":0.0,"title":"Assign"},"def":{"type":"integer","minimum":0.0,"title":"Def"},"call":{"type":"integer","minimum":0.0,"title":"Call"}},"additionalProperties":false,"type":"object","required":["for","while","if","assign","def","call"],"title":"TraceConstructs","description":"How many of each construct the program actually used, counted from its AST."},"TraceSummary":{"properties":{"ticks":{"type":"integer","minimum":0.0,"title":"Ticks"},"lines":{"type":"integer","minimum":0.0,"title":"Lines"},"constructs":{"$ref":"#/components/schemas/TraceConstructs"},"body_executions":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Body Executions"},"sensors_used":{"items":{"type":"string"},"type":"array","title":"Sensors Used"},"lines_per_step":{"items":{"type":"integer"},"type":"array","maxItems":500,"title":"Lines Per Step"},"loop_var_values":{"items":{"type":"integer"},"type":"array","maxItems":100,"title":"Loop Var Values"},"final":{"anyOf":[{"$ref":"#/components/schemas/RobotFinal"},{"type":"null"}]}},"additionalProperties":false,"type":"object","required":["ticks","lines","constructs"],"title":"TraceSummary","description":"The gradeable digest of a run — small enough to store on every attempt.\n\nCapped on purpose: 500 step lines and 100 loop values are enough to place a\nfailure and to grade an explain act, and bound the event payload (§4.3)."},"TutorAsk":{"properties":{"session_id":{"type":"string","title":"Session Id"},"target":{"oneOf":[{"$ref":"#/components/schemas/ItemTarget"},{"$ref":"#/components/schemas/MilestoneTarget"},{"$ref":"#/components/schemas/ExplainActTarget"}],"title":"Target","discriminator":{"propertyName":"kind","mapping":{"explain_act":"#/components/schemas/ExplainActTarget","item":"#/components/schemas/ItemTarget","milestone":"#/components/schemas/MilestoneTarget"}}},"kind":{"type":"string","enum":["hint","reveal","free_form"],"title":"Kind"},"learner_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Learner Text"},"code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"},"error_class":{"anyOf":[{"type":"string","enum":["none","fluency","runtime","logic","world"]},{"type":"null"}],"title":"Error Class"}},"additionalProperties":false,"type":"object","required":["session_id","target","kind"],"title":"TutorAsk","description":"`POST /api/tutor` body: what the learner is stuck on and what they asked for."},"TutorTurn":{"properties":{"rung":{"type":"integer","maximum":7.0,"minimum":0.0,"title":"Rung"},"text":{"type":"string","title":"Text"},"source":{"type":"string","enum":["authored","llm"],"title":"Source"},"allowed_rung":{"type":"integer","maximum":7.0,"minimum":0.0,"title":"Allowed Rung"},"remaining_llm_turns":{"type":"integer","minimum":0.0,"title":"Remaining Llm Turns"},"forfeited_cold":{"type":"boolean","title":"Forfeited Cold"},"guard_result":{"type":"string","title":"Guard Result","default":""},"kind":{"type":"string","title":"Kind","default":"hint"}},"additionalProperties":false,"type":"object","required":["rung","text","source","allowed_rung","remaining_llm_turns","forfeited_cold"],"title":"TutorTurn","description":"One tutor reply, with the ladder position it came from and what it cost.\n\n`allowed_rung` is the policy's ceiling right now; `forfeited_cold` is true\nwhen this hint turned a pending cold check into practice (§4.5).\n\n`guard_result` is why the turn came out the way it did, and the drawer\nneeds it to pick a screen: `\"gaming_gate\"` means no rung was served and\nthe learner is asked what they think the question asks;\n`\"ladder_exhausted\"` means the ladder topped out. It was documented as\npart of this contract by WP-G2 and dropped on the way to the wire\n(HANDOFFS 2026-09-06, WP-I item 2), so the drawer could not tell a\ngaming-gate turn from an ordinary hint."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"Widget":{"properties":{"id":{"type":"string","title":"Id"},"type":{"type":"string","enum":["code","number","text","choice","trace_table","robot","parsons","command_list"],"title":"Type"},"options":{"$ref":"#/components/schemas/WidgetOptions"}},"additionalProperties":false,"type":"object","required":["id","type"],"title":"Widget","description":"One input surface inside an item: an editor, a grid, a choice list, a table."},"WidgetOptions":{"properties":{"choices":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Choices"},"lines":{"anyOf":[{"items":{"$ref":"#/components/schemas/ParsonsLine"},"type":"array"},{"type":"null"}],"title":"Lines"},"columns":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Columns"},"rows":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rows"},"starter":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Starter"},"readonly_ranges":{"anyOf":[{"items":{"prefixItems":[{"type":"integer"},{"type":"integer"}],"type":"array","maxItems":2,"minItems":2},"type":"array"},{"type":"null"}],"title":"Readonly Ranges"},"level":{"anyOf":[{"$ref":"#/components/schemas/LevelInstance"},{"type":"null"}]},"allowed_calls":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Allowed Calls"}},"additionalProperties":false,"type":"object","title":"WidgetOptions","description":"Everything a widget might need; each widget type uses a few of these keys."},"WorkedExample":{"properties":{"seed":{"type":"integer","title":"Seed"},"program":{"type":"string","title":"Program"},"subgoals":{"items":{"type":"string"},"type":"array","title":"Subgoals","default":[]}},"additionalProperties":false,"type":"object","required":["seed","program"],"title":"WorkedExample"},"XpGrant":{"properties":{"rule":{"type":"string","enum":["first_cold_success","established","mastered","transfer_success","project_milestone","project_complete","project_cold_check","misconception_resolved","calibration_improved"],"title":"Rule"},"amount":{"type":"integer","minimum":0.0,"title":"Amount"},"sentence":{"type":"string","title":"Sentence"},"evidence_seq":{"type":"integer","title":"Evidence Seq"}},"additionalProperties":false,"type":"object","required":["rule","amount","sentence","evidence_seq"],"title":"XpGrant","description":"One XP award, tied to the event that earned it and phrased as capability."},"atlas__api__models__FreeZone":{"properties":{"grid":{"items":{"type":"string"},"type":"array","title":"Grid"},"source":{"type":"string","title":"Source"}},"additionalProperties":false,"type":"object","required":["grid","source"],"title":"FreeZone","description":"The world the learner built for themselves, and the program that built it."},"atlas__content__schemas__FreeZone":{"properties":{"prompt":{"type":"string","title":"Prompt"},"suggestions":{"items":{"type":"string"},"type":"array","title":"Suggestions","default":[]}},"additionalProperties":false,"type":"object","required":["prompt"],"title":"FreeZone"}}}}