# Journey: Themis academic-integrity flag → student appeal → teacher override

This journey is a real browser-to-BFF chain: a Metis assessment submission runs
detection and canonical Themis adjudication, an eligible learner files an
appeal, an operator decides it in `/operator/metis`, the gradebook receives a
terminal correction, and the learner receives an in-app message. The chain is
deep but process-local: verdicts, disputes, appeal ids, and the gradebook
adapter are memory stores with no restart hydration in these modules.

| Stage                 | Source of record in this implementation         |
| --------------------- | ----------------------------------------------- |
| Detection and verdict | `MetisThemisCanonicalBridge`                    |
| Appeal workspace      | In-memory `AcademicIntegrityAppealStore`        |
| Operator decision     | `POST /v1/metis/integrity/appeals/:id/decision` |
| Grade correction      | In-memory `MetisGradebookStore`                 |
| Learner notice        | Customer message-center in-app incident         |
| External delivery     | Not implemented for this decision               |

## Personas

- **Learner** — submits the assessment, reads only their own verdict/audit
  record, and may file an allowed appeal.
- **Metis operator / teacher** — holds an admin Metis scope, reads the full
  appeal queue, selects an outcome, and supplies the resolution note.
- **Themis canonical bridge** — detects/classifies the record, owns the verdict,
  dispute, due-process profile, timeline, and audit model.
- **Assigned adjudicator** — optional bridge participant selected from the
  registered shortlist.
- **Customer message center** — receives the learner-facing decision notice
  after the workspace closes.

## Pre-conditions

- The learner is signed in with Metis scope and opens `/metis/assessment`.
- The submitted answer satisfies the assessment editor's rubric gate.
- The BFF has a default academic-integrity policy binding and the canonical
  bridge is available.
- A non-clear verdict is required for an appeal; clear verdicts carry
  `appealAllowed: false`.
- The operator has the admin scope required by `hasOperatorScope`.
- Browser continuity assumes the same BFF process because the appeal and
  gradebook stores are not durable.

## Steps

### 1. Learner submits and receives a canonical verdict

- [x] `MetisAssessmentEditor` posts assessment id, course id, answer, and
      timestamp to `POST /v1/metis/assessment-submissions`.
- [x] The route derives a submission/verdict id, runs
      `detectAcademicIntegritySignals`, and passes detection signals, excerpts,
      classifier outputs, and model versions into the bridge.
- [x] The bridge returns a verdict, classification, enforcement outcome, and
      audit record with `sourceOfRecord: themis`.
- [x] Appealable non-clear verdicts emit a gradebook entry with
      `integrityVerdict: pending`.
- [x] The assessment editor locks the submitted answer and renders compact
      verdict class, severity, signal count, source, recommended action, and
      appeal link.
- [ ] The compact panel is not the full evidence record; the learner-safe audit
      detail is on the appeal page.

### 2. Learner opens the appeal workspace

- [x] The verdict-provided route `/integrity/:verdictId/appeal` loads
      `GET /v1/metis/integrity/:verdictId/audit`.
- [x] Ownership is fail-closed: a learner may read only a verdict whose learner
      id matches their authenticated user id. A mismatch returns the same 404 as
      an unknown record.
- [x] The page renders rationale, policy binding, signal/evidence counts,
      learner-safe excerpts, and classifier provenance.
- [x] A clear/teacher-cleared verdict renders an `appeal-not-allowed` state
      without a form; a direct POST returns 409.

### 3. Learner files the appeal

- [x] The form collects narrative, requested relief, and an optional preferred
      adjudicator id.
- [x] `POST /v1/metis/integrity/:verdictId/appeal` checks verdict ownership and
      opens the canonical dispute.
- [x] The bridge reuses the verdict's evidence excerpts as dispute evidence,
      shortlists adjudicators, assigns one when available, and returns dispute,
      evidence, timeline, deadlines, events, and due-process summary.
- [x] Filed confirmation renders status plus optional case and adjudicator
      fields.
- [ ] There is no learner evidence-upload field; only the canonical excerpts
      travel into the dispute.

### 4. Operator reviews the live queue

- [x] `/operator/metis` reads `GET /v1/metis/integrity/appeals` with operator
      scope.
- [x] Each row renders dispute parties, evidence count, deadline stages, and
      due-process summary.
- [x] The decision form blocks a blank resolution note.
- [x] Outcome controls expose **Uphold**, **Revise** ( `inconclusive`),
      **Dismiss** (`clear`), and **Escalate** (`severe`).
- [ ] This queue is process-local; an appeal disappears on BFF restart.

### 5. Operator decides and closes the case

- [x] The UI posts `outcome`, `resolutionNote`, and `closeCase` to
      `POST /v1/metis/integrity/appeals/:appealId/decision`.
- [x] `uphold` retains the prior verdict class; the other controls map directly
      to `violation | inconclusive | clear | severe`.
- [x] The route constructs a server-side teacher override, re-adjudicates the
      canonical verdict, then closes the dispute and records the resolution
      timeline note.
- [x] A changed class becomes `overturned`; an unchanged class becomes `upheld`.
- [x] A second decision against an already closed appeal returns 409.
- [ ] The sibling `/resolve` endpoint can close a workspace without selecting a
      new verdict class. The browser decision flow uses `/decision` and should
      not be described as the same transition.

### 6. Gradebook and in-app message reflect the decision

- [x] The decision route emits a terminal gradebook version with trigger
      `integrity_verdict_change` and `cascadeFrom: appeal`.
- [x] The correction preserves the prior version and exposes the corrected
      version through `GET /v1/metis/gradebook/:assessmentId/latest`.
- [x] Severe results receive an evidence-pack reference when one was not already
      present.
- [x] The message center receives a high-priority in-app incident named
      **Academic integrity appeal decided** with the appeal-record deep link.
- [x] Following the link records a clicked receipt without implicitly marking
      the row read; mark-read is separate.
- [ ] No push, email, or institution webhook adapter is invoked.

### 7. Learner and operator inspect the resulting record

- [x] Learner and operator can re-read the canonical audit record; unrelated
      learners receive 404.
- [x] The updated decision rationale includes the teacher-override line.
- [x] The appeal workspace reads closed, and the gradebook correction is
      available to the permitted role.
- [x] The learner sees the message in `/messages` and can return to the appeal
      record.
- [ ] These records are not a durable institutional audit ledger; they live in
      the singleton bridge and memory gradebook store.

## Post-conditions

- The appeal dispute is closed with a resolution note and terminal verdict
  status.
- A terminal gradebook version corrects the pending version when the assessment
  had an appealable verdict.
- The learner has an in-app decision message and explicit delivery/read/click
  receipts.
- Learner ownership checks remain enforced on verdict, audit, and workspace
  reads.
- Restart durability, external notification, and LMS grade export are not
  implied by the completed browser flow.

## Failure modes

- **Unknown or another learner's verdict** — an unknown record and another
  learner's record both return 404.
- **Appeal disabled** — clear verdicts show no form and POST returns
  `academic_integrity_appeal_not_allowed`.
- **No adjudicator registered** — the appeal can remain triaged with no
  assignment.
- **Blank decision note** — client and route validation block it.
- **Parallel operator decisions** — the first close wins at the HTTP guard, but
  the underlying bridge maps have no general optimistic concurrency version.
- **BFF restart** — verdict, appeal, gradebook, and their indexes are lost.
- **Notification duplicate or suppression** — the decision remains closed;
  notification status reports the delivery outcome.
- **External delivery expected** — no push/email path exists for this event.
- **Learner evidence needed** — the current appeal accepts narrative and relief,
  not an uploaded evidence bundle.
- **Bias review needed** — the enum admits `paused_bias_review`, but this route
  does not drive that transition.

## E2E coverage

- [`apps/oshun/web/e2e/metis-assessment-submit.spec.ts`](../../apps/oshun/web/e2e/metis-assessment-submit.spec.ts)
  drives browser submission, clear and suspicious verdicts, read-only
  post-submit state, audit detail, and the appeal link.
- [`apps/oshun/web/e2e/integrity-appeal.spec.ts`](../../apps/oshun/web/e2e/integrity-appeal.spec.ts)
  drives ownership-safe audit reads, form anatomy, filing, disabled appeal,
  direct operator resolve, teacher override/uphold branches, and error cases
  over the real BFF.
- [`apps/oshun/web/e2e/operator-metis-themis-review.spec.ts`](../../apps/oshun/web/e2e/operator-metis-themis-review.spec.ts)
  drives the operator queue and decision controls, terminal gradebook read-back,
  learner message, followed deep link, clicked receipt, and mark-read.

Coverage is **deep for one-process behavior**. It does not prove persistence
across restart, external delivery, learner evidence upload, or bias-pause
routing.

## Per-view files touched by this journey

- [`customer/08-metis/metis-assessment.md`](../customer/08-metis/metis-assessment.md)
  — submit, compact verdict, and appeal link.
- [`operator/operator-metis.md`](../operator/operator-metis.md) — live appeal
  queue and decision form.
- [`customer/02-home-discovery/messages.md`](../customer/02-home-discovery/messages.md)
  — in-app decision notification and receipts.
- [`shell/04-auth-session.md`](../shell/04-auth-session.md) — learner/operator
  scope boundary.
- [`shell/05-notifications.md`](../shell/05-notifications.md) — in-app delivery
  versus absent external channel.

## Cross-references

- [`metis-lesson-completion.md`](./metis-lesson-completion.md) — upstream
  learning path.
- [`tenant-audit-log-investigation.md`](./tenant-audit-log-investigation.md) — a
  different persistent/admin audit surface.
- `apps/oshun/bff/src/routes/metis-integrity.ts` — HTTP ownership, decision,
  notification, and gradebook orchestration.
- `apps/oshun/bff/src/metis/academic-integrity-appeal-store.ts` — process-local
  canonical bridge holder.
- `apps/oshun/bff/src/metis/gradebook-store.ts` — in-memory gradebook emitter
  and correction index.
- `libs/metis/integrations/src/themis-canonical-wiring.ts` — adjudication,
  disputes, due process, and audit model.

## Open questions

- [ ] Which durable store owns verdicts, disputes, audit records, and gradebook
      corrections in production?
- [ ] What concurrency token prevents two teacher decisions from racing before
      the closed-state check?
- [ ] How can a learner attach and safely disclose supporting evidence?
- [ ] Which external channels, if any, deliver academic-integrity decisions?
- [ ] How does a terminal grade correction leave this memory adapter for the
      tenant LMS?
- [ ] What policy and UI drive `paused_bias_review`?
- [ ] Should the generic `/resolve` endpoint remain distinct from the
      verdict-selecting `/decision` path?
