feat/pedagogy-plugin #4
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/pedagogy-plugin"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
createApi(ctx) wraps ctx.rest: idempotency_key / expected_revision are injected into the JSON body (query string on DELETE) because PluginRestOptions has no headers; ApiError decodes the backend envelope from the bridge's '<status>: <body>' rejection. useOperation polls GET /operations/{id} every 1.5 s via the app's React Query client, stops on succeeded|failed, maps kind -> pending label and surfaces Retry after 60 s (POST /operations/{id}/retry). useMutationGuard disables controls while a gesture is in flight and retries with the same key and input.Default export { id: 'hermes-learning', name, register(ctx) } contributing a ROUTES_AREA page at /learning and a SIDEBAR_NAV_AREA row. HealthGate calls GET /health and renders the plugins.enabled + dashboard-restart instructions when backend_registered is false (or the call fails); the placeholder course list warns when no search provider is configured.- diagnostic_question_added payload is now {question, closure}: the closure is persisted with every question because its expansion depends on model output and cannot be rebuilt deterministically across restarts. - New events diagnostic_offer_made{reason, low_confidence, summary, closure} (pending syllabus offer, folded into diagnostic.offer/closure) and diagnostic_offer_declined{} (clears offer, bumps continue_count; REQ-003.10). - DiagnosticState gains optional offer/continue_count (legacy docs validate); _new_diagnostic_state seeds them so the golden snapshot changed deliberately. - diagnostics.step(force_question=) lets a declined offer yield one more probe past sufficiency/cap, deepening the closure when the frontier is empty.GET /courses/{cid}/diagnostic (pure DiagnosticView with placement disclosure), POST .../next (202 diagnostic_question operation, or 200 view while a question is pending), .../answer (records diagnostic_answer, no feedback, sentinel allowed), .../finish (diagnostic_too_short below 3, else early_exit low-confidence offer; diagnostic stays open), .../continue (diagnostic_offer_declined then one forced probe past sufficiency/cap). diagnostic_closed is left to the syllabus publish route (task 5.1).- step(force_question=True) always yields a question: deepen the shallowest unexpanded node; if nothing can be deepened re-probe the node with the least evidence (dont_know < unsatisfied < satisfied); an empty closure gets a foundation-level node about the target (REQ-003.10). - diagnostics.close_events(doc) builds diagnostic_closed{reason, closure} from the pending offer (validation_error when none); the syllabus publish route (5.1) appends it inside its transact — documented in the route module. - POST /diagnostic/next returns 200 with the current view while an offer is pending (no op, no model call); /continue is the only way past an offer. - POST /diagnostic/continue is refused while a diagnostic_question op for the course is in flight so a decline is never lost to an op replay.POST /courses/{cid}/syllabus (operation: generate + validate + close diagnostic + publish in one transact; diagnostic_open 409 without an offer or while a question op is in flight), GET /courses/{cid} CourseView, GET /courses/{cid}/modules/{mid} ModuleView (REQ-004, REQ-006, REQ-005.3).A model-supplied id like 'a/b' or '..' could be published (the syllabus is immutable, REQ-004.9) and then never reach /modules/{mid} or the transcript file, stranding the module. Also assert storage extras reach the envelope.checkpoint_saved{objectives_covered?} folds as a union into modules[mid].objectives_covered (REQ-009.1 unlock rule input); SourceDoc gains optional objective_ids (REQ-018.3). Golden snapshot updated.useOperation now exposes failed/retryable and reports retry_available for a failed-retryable op (the backend accepts POST /operations/{id}/retry for those); retry() resolves whether the backend accepted it. The module page restores the sent text into the composer on failure, shows the envelope message, and its Retry uses the op retry path, falling back to a fresh say with the same text when that path is unavailable.- unlock rule uses objectives_covered ∪ evidenced objectives in both the tutor save and check-answer (design §Homework unlock) - 'unsupported' exempts only the claim named by challenge.claim_id; a challenged turn must carry challenge{claim_id, confidence, rationale} (REQ-018.8/9/10) - expected_revision accepted on enter/say/check-answer (409 revision_conflict) - check-answer transacts evidence first (re-checking 'answered' under the course lock), appends the answer line only afterwards; the line now carries the check's explanation for the UI (REQ-008.2) - routes persist the learner/system line before creating the op, so retry and relaunch never duplicate it; enter gates BEGIN on 'no tutor line yet' - say while a tutor turn is in flight -> 409 operation_in_flight {op_id} - enter/check-answer idempotency replay matches kind, course and subject - research: host-tools ImportError -> retryable generation_failed - GET /transcript?after=<bad> returns the plugin error envelope - tests for all of the above- agents/review.py: local, deterministic mapping of wrong answers (incl. the I-don't-know sentinel) on objectives of already-passed modules to review_suggested events (module_id + objective_id + evidence source check|homework:<aid>|final:<aid>), validated against DegradedKnowledgeReport; pending pairs are not duplicated - attempts.py: one hook in the grade closure, same transact as attempt_graded - instruction.py: wrong check on a passed module -> suggestion (REQ-012.6) - routes/review.py: GET /courses/{cid}/review-suggestions and POST .../{sid}/dismiss (idempotency_key in body, idempotent, 404 suggestion_not_found, course_locked when archived) - projections.pending_suggestions made public; api_errors suggestion_not_found - tests/test_api_review.py covers REQ-012.1/2/4/6/7/8 and REQ-013.1/2/9/10/11- routes/settings.py: GET/PUT /settings (mastery_threshold 0-100, default 75, invalid_threshold); threshold_changed recorded once in settings.json history - routes/courses.py: POST /courses/{cid}/archive|restore (one event via transact, idempotent), DELETE /courses/{cid}?confirm=true&idempotency_key=.. (confirmation_required, operation_in_flight, deletion_incomplete) - storage: CourseStore.check_reference/find_owner shared cross-course guard, ModuleNotFound/AttemptNotFound/InvalidThreshold, set_mastery_threshold, DeletionIncomplete retryable - operations: OperationRunner.inflight_for_course - tests/test_api_settings.py contract tests (REQ-014, REQ-016, REQ-017, NFR-003)- live regions carry progress only; errors are left to the role=alert InlineError so nothing is read twice; spinners beside a LiveRegion use the default label; course list no longer announces a count on every visit - CourseDag pans the keyboard-focused node into view and settles wheel-zoom bursts before announcing the zoom level - tests: ModulePage (transcript, check card, citation chip -> sources panel, composer, objectives) and AttemptPage (stepper, radios, save status, submit confirmation) spot checks; mocked api.post answers {} - walkthrough: exact View results name, ConfirmDialog focus marked verify, updated live-region expectations; a11y.ts comment corrected