Skip to content

Compliance course workspace Implementation Plan

For agentic workers: Use superpowers:executing-plans for native execution, or superpowers:subagent-driven-development if the user selects delegated execution. Steps use checkboxes for tracking. This is a planning deliverable, not authorization to deploy or evidence that implementation has passed tests.

Goal: Deliver the reviewed course-workspace concept with contextual Ask Kwilo, honest next-task guidance, resumable review and a complete explainable course evidence workflow.

Architecture: Keep the existing OBE domain, proposal contracts, deterministic calculators and Compliance routes. Extract only the Ask Kwilo presentation shell from Analytics. Add a read-only course workspace projection, persisted proposal-review progress and a grounded question adapter; extend course configuration for reusable policies and explicit assessment bindings.

Tech Stack: Existing React/TypeScript, Tailwind, @kwilo/ui, TanStack Query, React Hook Form/Zod; FastAPI, async SQLAlchemy, PostgreSQL/Alembic; existing chatbot specialist and server-to-server transport. No new agent framework.

Spec: Compliance course workspace.

Global constraints

  • Preserve the existing Compliance workspace and /compliance/outcomes routes, including old section query parameters and history access.
  • Server data uses TanStack Query; forms use React Hook Form + Zod; copy uses t(); route/API strings stay in their existing constants/services layers.
  • Reuse existing @kwilo/ui primitives. A new design-system component requires its own Storybook story in the same change; the proposed shared assistant shell is app-level initially.
  • Read operations and assistant questions never generate academic drafts, approve records, calculate or issue results as side effects.
  • Do not change formula semantics, permissions or issued evidence to make progress indicators look complete.
  • New schema belongs in the dedicated apps/backend/alembic_obe/ migration chain, using alembic_obe.ini, not the default application chain.
  • Branch from current origin/staging in a clean worktree at execution time. Preserve the current webapp/docs worktrees' unrelated changes. Code PRs target staging; docs PR targets the docs repository's configured base.
  • No app code, database, live records or deployment is changed by this plan. New documentation is in kwiloai-docs only.

Review focus

  1. Course/tenant switch while an answer is in flight: old evidence must never render or apply in the new context (Tasks 3, 5).
  2. Empty or unsuitable syllabus and existing approved evidence: repair the source without inventing suggestions or deleting approved work (Tasks 1, 4, 5).
  3. Two compatible revisions, historical issued evidence and concurrent editing: require an explicit selection or conflict resolution, never silently choose/overwrite (Tasks 2, 4, 6).
  4. A valid preview or saved draft must not become approved/issued through generic continuation, queue acceptance or free text (Tasks 1, 5, 7).
  5. Revoked assignment or disabled institution: all new endpoints enforce source access while permitted history remains readable (Tasks 2, 4, 5, 8).

Scope and delivery order

PR/task Reviewable result Depends on
1 Honest empty-proposal recovery and explicit review actions None
2 Evidence-derived course task status and safe next actions 1
3 Shared responsive Ask Kwilo presentation shell; Analytics preserved None
4 Outcome rows, citations and resumable reviewed-draft state 1, 2
5 Grounded Ask Kwilo questions and integrated course workspace 2, 3, 4; chatbot companion PR
6 Contextual assessment/CO–PO review and compatible marks selection 5
7 Reusable course method, explicit attainment and course-file completion 6
8 Coordinator setup/readiness and contextual-assistance navigation parity 7
9 Role-based browser/workbook acceptance and release verification Each release

First release: Tasks 1–5 and their Task 9 acceptance checks. It delivers the shown Outcomes screen with working Q&A. Existing editors remain reachable for the other tabs. Second release: Tasks 6–7. Third release: Task 8. Tasks 2 and 3 can proceed independently after their contracts are agreed; integration remains ordered.

Baseline inspected: webapp 0193e2a1d55792c9ec365f5334ce25b2655ca8d7, chatbot a97f63d6fd49fa27ae509e12af579d71f458f8d1. Graphify was unavailable, so source inspection supplied the map. Refresh these anchors before implementation and reconcile changes without reverting others' work.

Task 1 — Make suggestion recovery and acceptance honest

Modify: backend src/obe/review/{service,schemas,router}.py, src/obe/agents/acceptance.py, src/obe/runs/stages/attainment.py, src/obe/runs/schemas.py; web src/obe/ReviewQueuePage/QueueItem.tsx, src/obe/OfferingPage/RunPanel.tsx, src/obe/components/{ProposalCard,ProposalReview}.tsx, src/i18n/locales/en/obe.json. Add a pure src/obe/components/proposalState.ts helper. Extend tests/integration/obe/test_review_queue.py, test_course_chain_e2e.py, web ReviewQueuePage/index.test.tsx and components/ProposalReview.test.tsx.

Interface: proposalReviewState(status, rowCount, unresolvedCount) returns waiting | failed | needs_source | reviewable | no_suggestion | closed. ready with zero rows and unresolved reasons is needs_source; zero rows without unresolved reasons is no_suggestion, never reviewable. Do not add a new persisted proposal enum just to change presentation.

  • [ ] Add pure cases below in proposalState.test.ts, then run them red.
    expect(proposalReviewState('ready', 0, 1)).toBe('needs_source')
    expect(proposalReviewState('ready', 0, 0)).toBe('no_suggestion')
    expect(proposalReviewState('ready', 3, 0)).toBe('reviewable')
    
  • [ ] Extend queue/API tests: empty selection is rejected; unresolved source is displayed with a syllabus repair link; saving all valid rows through proposal /accept creates a draft without approval audit events or a run resume. Use existing seed_enabled_unit, headers, synthetic_provider, client and async_session fixtures.
  • [ ] Replace queue Accept all with Review draft and a deep link carrying offering/proposal/source context. Remove confidence-threshold acceptance from the default surface; preserve source rationale and human decisions.
  • [ ] Migrate web consumers to existing draft-only /proposals/{id}/accept. After migration, retire legacy accept-all with HTTP 409 and code explicit_review_required; its error points to the draft review flow. Test the old path cannot silently approve and publish through a stale browser.
  • [ ] Remove automatic snapshot issuance from the attainment run stage in this first safety change. When no applicable issued result exists, return a new awaiting_attainment_review blocker linking to the existing Attainment editor. Its existing preview and explicit issue controls are the interim path. Update run/schema translations and chain tests to perform explicit approval and issuance. An existing issued result is reported separately from unfinished improvement work; Task 7 completes the redesigned configuration/result UI.
  • [ ] Render unresolved explanation before any review controls. Keep generation retry only for retryable failure, and reject/cancel available where supported. Do not auto-regenerate on load.
  • [ ] Run focused tests and commit fix: clarify OBE proposal recovery and review actions.

Task 2 — Derive next tasks from applicable evidence

Create: backend src/obe/workspace/{__init__,schemas,service,router}.py; web src/services/obe/{workspace-schemas,workspace}.ts. Modify: backend src/obe/router.py, web src/services/obe/hooks.ts, OfferingPage/RunPanel.tsx. Tests: new tests/integration/obe/test_workspace.py, extend OfferingPage/RunPanel.test.tsx.

Interface: GET /api/v1/obe/units/{unit}/offerings/{offering}/workspace, served by CourseWorkspaceService.read(resolved, offering_id) -> CourseWorkspaceResponse. It returns the following wire shape; define matching Pydantic and Zod schemas before UI integration.

type TaskId = 'outcomes' | 'assessments' | 'marks' | 'attainment' | 'course_file'
type TaskState = 'not_started' | 'needs_input' | 'in_review' | 'ready' |
  'complete' | 'stale' | 'unavailable'
type WorkspaceAction = {
  id: string; task: TaskId; label_key: string;
  resource_kind: string | null; resource_id: string | null;
  enabled: boolean; reason_key: string | null
}
type CourseWorkspaceResponse = {
  context_hash: string;
  tasks: Array<{ id: TaskId; state: TaskState; reason_key: string | null;
    actions: WorkspaceAction[] }>;
  next_action_id: string | null;
  sources: Array<{ kind: string; id: string; revision: number; hash: string }>;
  conflicts: Array<{ kind: string; candidate_ids: string[] }>
}
- [ ] Pin the initial action registry to review_syllabus, suggest_outcomes, review_outcomes, review_assessments, review_roster, review_marks, configure_policy, calculate_preview, review_result, issue_result, draft_report, review_report, issue_report, review_improvement. Runtime schemas enumerate these IDs; no backend-supplied arbitrary URL or executable command. - [ ] Add a database test with a parked missing_paper run plus an approved blueprint for the same accessible offering. The response must expose the approved assessment and diagnose the applicable next missing input rather than repeat the stored blocker. Assert run, proposal, approval and snapshot row counts are unchanged after two GETs. - [ ] Cover two approved candidates, mismatched syllabus, historical snapshot/new evidence, zero required assessments, manager/faculty grants, revoked access and read-only history. Reuse existing OBE source visibility checks; never bypass them to satisfy a status test. - [ ] Implement bounded queries and compatible selection rules from the spec. Keep projection separate from the mutation-driving runs/stages methods. Name next-action owner through permissions/context, not a hardcoded HOD role. - [ ] Add cache keys containing actor/unit/offering; invalidate workspace after evidence mutations. Retain old run activity as secondary information, not the canonical completion display. - [ ] Verify named actions open exact sections/resources. Preserve existing query values syllabus, papers, marks, attainment, reports; map the new task labels to them. - [ ] Run focused tests and commit feat: derive course tasks from OBE evidence.

Task 3 — Extract the shared Ask Kwilo presentation shell

Create: web src/components/assistant/AskKwiloShell.tsx, .test.tsx, .stories.tsx. Modify: src/analytics/components/AskKwiloPanel.tsx and its tests. Existing useAskThread.ts, AnswerCard.tsx, AskThreadMessages.tsx remain Analytics-owned.

Interface: the shell is presentation only; callers own messages, context, composer and requests.

type AskKwiloShellProps = {
  open: boolean; title: string; scopeLine: string;
  onClose: () => void; onNewConversation: () => void;
  children: React.ReactNode; composer: React.ReactNode;
}
- [ ] Add component tests for accessible name, close/Escape, focus return and retained children while hidden; add an Analytics regression proving its question → preview → add-card flow still uses its existing adapter. - [ ] Extract layout/header only, using @kwilo/ui primitives. Dock when the work container can provide at least 640px content plus panel/gap; use a drawer otherwise. Do not equate the viewport's lg breakpoint with enough actual content width. - [ ] Add stories for closed, empty, pending, answer, error, long context/title and narrow drawer. Keep form fields in caller RHF/Zod composers. - [ ] Verify the Analytics layout at 1200px no longer squeezes its header into a narrow text column; inspect Compliance matrix width with a dummy content slot. - [ ] Run Analytics/component tests and app Storybook build; commit refactor: share Ask Kwilo panel presentation.

Task 4 — Persist outcome review and display source-grounded rows

Create: backend src/obe/agents/{review_progress,review_progress_schemas,review_progress_routes}.py, model in agents/models.py; web src/obe/OfferingPage/outcomes/{OutcomeReviewList,OutcomeReviewRow,SyllabusSourcePanel}.tsx, reviewForm.ts; services review-progress.ts. Add one migration in alembic_obe/versions/, register in src/obe/models.py and router. Modify: OfferingPage/{SyllabusSection,OutcomesSection}.tsx, components/ProposalCard.tsx. Tests: tests/integration/obe/test_review_progress.py, outcomes/OutcomeReviewList.test.tsx.

Interface: GET/PUT /obe/units/{unit}/offerings/{offering}/proposals/{proposal}/review-progress. PUT payload below; response adds revision, updated_at and current proposal input hash. Store one record per unit/proposal/actor, with tenant FK constraints and existing OBE RLS conventions. Cap at 50 rows and bound JSON size.

type ReviewProgressPut = {
  expected_revision: number; expected_input_hash: string;
  rows: Array<{ index: number; decision: 'pending' | 'reviewed' | 'excluded';
    edited_row: unknown | null; exclusion_reason: string | null }>
}
edited_row is the existing discriminated proposal-row schema at the API boundary, not arbitrary JSON. A reviewed decision is tied to the canonical hash of the saved edited row. Editing resets it; regenerated input hashes never inherit prior reviewed marks.

  • [ ] Add integration tests: first save/load; two-tab version conflict; cross-tenant and wrong-proposal IDs; actor isolation; edited invalid citation; stale input hash; terminal proposal; grant revocation. Initial creation expects revision 0; successful save increments atomically; conflict is 409.
  • [ ] Generate additive migration using uv run alembic -c alembic_obe.ini revision -m "add proposal review progress"; implement constraints, indexes, RLS and downgrade. Never repurpose the accepted proposal's review audit payload for in-progress edits.
  • [ ] Implement source-authorized read/save using existing proposal_scope, proposal loading and revalidation. Save a progress snapshot only; no outcome set is created by marking a row reviewed.
  • [ ] Build compact rows and source viewer, using the existing confirmed syllabus and exact citation offsets. Track selected row separately from selected/accepted rows. RHF changes autosave after 600ms with a visible pending/error indicator; serialize requests and preserve unsaved values on 409.
  • [ ] Add UI tests: after reviewing CO1/CO2, reload shows 2 reviewed; editing CO2 makes it pending; stale save retains typed content; unavailable AI leaves manual outcome entry usable; a saved approved set opens as approved with an explicit revise action.
  • [ ] Final Save reviewed draft requires at least one included row and every row reviewed or explicitly excluded. Submit selected/edited rows through existing acceptance validation; then link to the created outcome-set draft. Approval remains a separate current route/action with its exact revision token.
  • [ ] Run progress/outcome/source/concurrency tests; commit feat: add resumable course outcome review.

Task 5 — Add grounded questions and assemble Release A

Create, webapp backend: src/obe/assistant/{__init__,schemas,context,service,router}.py. Create, web: src/obe/assistant/ComplianceAskPanel.tsx, useComplianceThread.ts, actions.ts, src/services/obe/{assistant,assistant-schemas}.ts; OfferingPage/{CourseTaskTabs,NextTaskCard}.tsx. Modify: OBE router/hooks, OfferingPage/index.tsx, OfferingHeader.tsx, translation files. Create, chatbot: src/agent/specialists/obe/explain.py, explain_contracts.py, src/agent/agents/obe_explain_runner.py; register obe-explain in src/agent/agents/registry.py. No edits to existing five capability schemas to pretend they accept arbitrary questions.

Interface: POST /obe/units/{unit}/offerings/{offering}/assistant/ask consumes question (1–2000 chars), task, expected_context_hash, nullable selected_resource_id, and up to six prior session messages. The server rebuilds/authorizes all sources, limits the total pack to the existing OBE context budget, and returns a typed answer.

type AskResult = {
  context_hash: string;
  status: 'answered' | 'missing_evidence' | 'unsupported';
  blocks: Array<
    { kind: 'text'; text: string } |
    { kind: 'citation'; source_id: string; start: number; end: number; quote: string } |
    { kind: 'fact'; ref: string } |
    { kind: 'action'; action_id: string }
  >;
  facts: Array<{ ref: string; label: string; value: string; source_id: string }>
}
- [ ] Add chatbot tests/test_obe_explain.py: supported syllabus answer; insufficient source; invented citation; unknown fact/action; source prompt injection; request to approve; provider timeout. Add matching webapp contract tests and a fixture exchanged between services to prevent schema drift. - [ ] Implement a no-tools specialist using current provider routing and S2S identity. Validate references on both services. Numeric facts come from backend entries; reject model-authored quantitative claims outside exact allowed source quotations/canonical academic labels. Prefer an honest unsupported answer over a guessed institutional policy. - [ ] Webapp tests must prove GET/ask does not call generation, approval, calculation or issuance; input references are in the addressed offering; no learner identity/row-level marks enter an explanation pack by default. Disabled activity cannot call the model; allowed historical evidence remains accessible through history. - [ ] Implement capped, non-streaming Q&A using run_agent, with existing authorization/quota gates and bounded timeout. Return sanitized retryable failures and 409 on stale context. Do not claim answering covers the complete syllabus if only a bounded excerpt was included. - [ ] Implement the session thread keyed by actor/unit/offering. Ignore responses unless request ID and context hash match; abort on scope change. Hiding the panel preserves the thread, logout clears it. A suggested action requires a button click and fresh source validation before calling an existing proposal mutation. - [ ] Compose the wide work area, next-task card, five tabs and side panel. Existing editor URLs remain valid. Wire 'Why this outcome?' to selected-row context, source links to the syllabus viewer and 'Suggest outcomes' to existing proposal create/generate with one idempotency key per intent. Preserve approved outcomes on failure. - [ ] Add frontend integration tests using MSW: query answer with source; switch offering before response; hide/reopen; missing source; existing approved outcomes; save vs approve; drawer keyboard flow. A question saying 'approve all' must render explanation/action guidance, with zero academic mutation requests. - [ ] Deploy compatible chatbot then additive backend before enabling the new web composer in the staging release. Verify availability through the existing service integration, not a client-side model fallback. Commit separate chatbot/webapp PRs linked to this spec.

Release A acceptance: reproduce the concept using synthetic course data, refresh a half-reviewed draft, ask a source-grounded question, save an unapproved outcome set and explicitly approve through an authorized account. Complete manual entry with the AI service unavailable. No claim of an entire simplified attainment workflow yet.

Task 6 — Carry context through mappings and marks

Modify: web OfferingPage/PapersSection.tsx, MarksSection/index.tsx, ProgramPage/{index,ProgramProposal,MappingRevisionEditor,MappingEntryEditor}.tsx. Create: OfferingPage/assessments/AssessmentPicker.tsx, ProgramPage/mapping/MappingMatrix.tsx, associated test/story files. Backend extend src/obe/discovery/{catalog_routes,catalogs,schemas}.py and existing assessment adapters only where picker metadata is missing. Services remain under src/services/obe/.

Interfaces: use existing proposal capability source sets exactly: question mapping consumes approved outcome set plus paper OR blueprint; programme mapping consumes program address plus offering/outcome/framework. Matrix edits consume/produce existing mapping rows (co_id, target_id, strength 0–3, rationale), never an alternate matrix persistence model. - [ ] Add picker tests for same-name assessments in two courses, stale/inaccessible selection, paging and incompatible scoring; assert chosen records call the existing binding path and do not create a duplicate assessment. - [ ] Build a course/period-filtered picker with title/date/type/scoring labels. Review selection rather than silently auto-linking an ambiguous candidate. Keep external upload/manual paper entry. - [ ] Add mapping tests for arrow-key navigation, per-cell rationale, zero/unmapped states, multiple framework editions and sum-of-question allocations. Exercise existing backend paper-intake and proposal-source tests. - [ ] Render the matrix and route Ask actions to the focused row/cell. Keep proposed changes as a diff until saved. Current programme permissions remain in force even when the matrix is opened from a course. - [ ] Add marks-default tests with no roster, one compatible approved roster, two compatible rosters, missing marks, absence, exemption and totals-only batches. Workspace supplies unambiguous candidates; the form always shows what was selected. - [ ] Keep source versions and technical IDs available in an evidence detail disclosure. Remove repeated selectors only after equivalent explicit override/history access exists. - [ ] Run targeted picker/mapping/marks tests and commit feat: carry course context through evidence review.

Task 7 — Reuse course policy and make finalization explicit

Create: backend src/obe/programs/course_policy_templates.py, schemas/router companion modules; src/obe/workspace/{configuration,configuration_models,configuration_schemas}.py. Modify: attainment/{models,schemas,policy}.py, runs/stages/attainment.py, run schemas, model/router registration; web AttainmentSection/{CalculationForm,CalculationResult,PolicyRows}.tsx, ReportsSection/index.tsx, ReportsPage/index.tsx, workspace/assistant actions. Add additive OBE migrations and backend test_course_policy_templates.py, test_workspace_configuration.py, test_explicit_issuance.py.

Interfaces: programme /course-policy-templates create/list/detail/revise/approve routes follow existing programme authorization and optimistic revisions. Offering /workspace/configuration GET/PUT pins an approved course policy and explicit component bindings.

type CourseConfigurationPut = {
  expected_revision: number;
  policy_id: string;
  component_bindings: Array<{ component_id: string; blueprint_id: string }>
}
Templates contain the existing validated course PolicyDefinition, named components, scheme/period applicability and immutable version/hash. Applying a template explicitly creates an offering policy copy with template ID/hash provenance; approving an institution template does not silently approve the copied course configuration. Preserve existing offering policies without forcing conversion. - [ ] Test copied policy definition matches its approved template; draft templates cannot be applied; changed templates do not mutate course copies or snapshots; cross-program access fails; old explicit policies remain usable. - [ ] Create template/configuration tables and migrations. Store bindings against the selected policy revision and approved blueprint identity; reject duplicate/missing/foreign components. If configuration is absent, exact legacy UUID component matches may be offered for explicit confirmation, not silently rewritten. - [ ] Replace _aligned's user-visible component-ID convention with validated bindings. Construct existing CalculationCreate.batches through these bindings; preserve calculator and snapshot hash semantics. - [ ] Replace Task 1's interim link with the redesigned preview/issue workspace. A generic run step still cannot call AttainmentService.issue. Existing issued snapshots remain complete even if CQI is rejected/unavailable; track follow-up separately. - [ ] Extend test_course_chain_e2e.py's explicit draft save → approval → preview → issue flow to cover template bindings. Assert zero snapshot rows before issue and exactly one after repeated idempotent issue requests. - [ ] Add an independently calculated fixture: two equally weighted question opportunities, one assessed learner at 6/10, one at 2/10, one fully absent; threshold 50%, absence excluded means 1 of 2 succeeds, not 1 of 3. Bind explicit institution-test level thresholds and compare numerator, denominator and final level. Extend with optional-question and missing-mark fixtures from existing calculator tests. - [ ] Course file labels drafts/approved/issued accurately, shows course names in selectors, drafts narrative from issued facts and links improvement actions without making completion of later interventions a publication prerequisite. - [ ] Run calculator, run-chain, report, template and frontend tests. Commit separately for template/configuration and explicit issuance if review size requires it; both are dependencies of Release B acceptance.

Task 8 — Programme setup, honest readiness and assistant parity

Modify: web ProgramPage/index.tsx, compliance/pages/ReadinessPage/, obe/OverviewPage/, compliance/nav.ts, OutcomeAssistantsPage/index.tsx, compliance/routes.tsx; backend programs/readiness.py, discovery/catalogs.py, review/{service,schemas}.py. Extend existing programme, readiness aggregate and route tests.

Interface: readiness query/response explicitly identifies reporting period/cohort, participating/required course counts and exclusions. Keep course policy templates separate from programme rollup policies. All setup and readiness links retain unit/program/period/cohort in existing route builders. - [ ] Test seven curriculum courses with only one prepared: readiness must show the agreed included-course denominator and distinguish exclusions/not-participating from missing work. Do not label every course approved because one offering has evidence. - [ ] Reorganize existing editors into setup/evidence/reporting sections, in dependency order. Programme entry shows named programmes and next actions; an empty list has a create/setup action, not an unexplained blank selector. - [ ] Add programme mapping/report drafts to the appropriate review view, or explicitly label any narrower queue scope. Add ordinary saved drafts needing approval with deduplication by target identity; do not imply actor-specific review-progress marks constitute approval. - [ ] Verify a five-row capability parity checklist: course outcomes, question mappings, programme mappings, report narrative, improvement suggestions. Each needs an in-context entry, source selection, review, recovery and historical target link. - [ ] Only after parity, remove Assistants from primary navigation. Keep its legacy route as an orientation/redirect surface, carrying known context and asking for only the missing context. Preserve Readiness and Improvement access. - [ ] Fix known translation keys and number formatting; run navigation key, programme, readiness and old deep-link tests. Commit feat: guide programme setup and contextual assistance.

Task 9 — Verification, rollout and handoff

  • [ ] Use isolated test offerings and synthetic/anonymized evidence. Keep the audited institution's approved records untouched. Reuse the existing course-chain integration harness; mock only model output for deterministic tests, then separately run real staging inference.
  • [ ] For each release run changed-file tests first. From apps/backend: uv run pytest tests/integration/obe tests/unit/obe -q, uv run ruff check src/obe, uv run mypy src/obe; exercise OBE migration upgrade/downgrade on a disposable database and uv run alembic -c alembic_obe.ini check. Expand required CI checks according to app AGENTS.
  • [ ] Web: pnpm --filter @kwilo/web test --run src/obe src/compliance src/analytics/components/AskKwiloPanel.test.tsx src/components/assistant, pnpm --filter @kwilo/web type-check, app build and changed-file lint. Build Storybook for the app-level shell; if the UI package is changed, its dedicated stories/build are required too.
  • [ ] Chatbot: uv run pytest tests/test_obe_explain.py tests/test_obe_agents.py tests/test_obe_contracts.py tests/test_obe_paper_intake.py -q; run the S2S contract case with actual webapp request/response validation.
  • [ ] Browser roles: assigned instructor, unassigned instructor, programme coordinator, manager and permitted history-only user. Verify each permitted action and refused direct API attempt, not merely hidden buttons.
  • [ ] Browser tasks: outcome review/reload, supported/unsupported question, edit-after-review, source revision change, approval conflict, assessment linking, question allocation, marks import/absence, preview/issue, report/revision/history and failed-agent recovery. Record outcomes and screenshots at 1440/1200/768/390 widths with keyboard focus checks.
  • [ ] Pilot Release A with faculty before removing the agent catalogue. Pilot Release B against the institution's approved workbook before declaring calculation agreement; resolve every unexplained difference.
  • [ ] Deploy additive chatbot capability → OBE migrations/backend → web, using existing repo deployment workflows and separate deployment authorization. A failed assistant leaves manual work available. Roll back web first if necessary; retain additive tables and issued records rather than destructively downgrading live evidence.
  • [ ] Before rolling back to a web version that still calls accept-all, retain the server's explicit-review rejection; do not restore hidden approval merely for UI compatibility. Tell users to reload or use the supported manual review path.
  • [ ] Cross-link code/chatbot/docs PRs, update the feature status/changelog and add both docs to MkDocs navigation in the isolated docs PR. Review and merge only these files; the current local mkdocs.yml has unrelated edits.

Completion and remaining product decisions

The planned first release is complete only when outcomes can be reviewed and resumed, grounded questions work, manual fallback works and the other four task tabs preserve existing functionality. Release B adds the simplified complete faculty flow; Release C adds coordinator consolidation. Do not market Release A as the entire redesign.

Formal assigned-approver submission, cross-session conversation history and institution-wide assistant memory are deliberately outside this plan. They require separate workflow/data-retention decisions. Existing permissions are preserved; any institution requiring segregation of reviewer and approver is validated as a separate requirement before rollout there.

Planning artifacts are ready for review; implementation tests have not been run. Recommended execution: native task-by-task work with a fresh review of each PR, because state, approval and evidence-selection changes need small independently checked diffs.