ADR-006 — Issue Queue State Machine + Delegation
ADR-006: Issue Queue State Machine + Delegation
Status: Accepted | Date: 2026-04-22 | Supersedes: v2 spec 10-admin-system.md §6 (Category E — Deferred banner) | Source: STAKEHOLDER-ANSWERS-2026-04-22.md §B; transcript QF-Backend-Review-Apr-22.md L65–89
Context
The Issue Queue was introduced in v2 (borrowed from Candidate B) as a student-to-admin communication channel for app-reported issues (bugs, content, account, other). The v2 spec as of Apr 15 defined columns and basic actions but left the state machine explicitly deferred — whether Resolved is terminal, whether backward transitions are allowed, and what "Delete" actually means were all flagged as TBD pending a working session.
Two complicating realities surfaced in the Apr 22 session:
- Mixed-content reports: students use the student-side "Report Issue" dialog for both genuine tech issues and non-technical concerns ("my mother is sick and my dog just died"). The queue must be able to distinguish actionable bugs from conversational / pastoral notes that belong with CS or teaching staff.
- Fresh desk overlap: in production, the same message stream will land in both the admin dashboard issue queue AND the Fresh desk inbox used by the CS team. The state machine must not assume the admin is the sole responder, but must also not block on Fresh desk integration (which is deferred).
Decision
1. State set — Open / In Progress / Resolved / Rejected; Archive is a view
| State | Meaning |
|---|---|
| Open | New, unread, unassigned. Default state when an issue lands. |
| In Progress | Admin has begun handling. May carry a delegated_to sub-state (see §2). |
| Resolved | Handled. Kept in history. Terminal unless reopened. |
| Rejected | Not a real issue (spam, misfire, duplicate). Kept in history. Terminal unless reopened. |
"Closed" (present in the Apr 15 draft) is dropped — it had no distinct semantic from Resolved. Archive is a filter/view over Resolved + Rejected, not a fifth state.
2. Delegation — In Progress sub-state with fixed target list
On transition to In Progress, admin may set delegated_to ∈ {CS, IT, Teaching Staff}. Delegation:
- Is informational — the admin retains ownership and time-to-resolution tracking (per Kamran, L76: "Delegated kind of just means that you're informing them. But at the end of the day, whoever is managing the issue queue is still managing it").
- Does not route the issue to another system or user. In v2 mockup, setting
delegated_tois a tag + optional email notification stub. - Is visualized as a badge modifier: "In Progress (Delegated to IT)".
- The target list is fixed at three values. Adding more is a schema change, not a freeform input.
3. Rename Delete → Reject
Per L77–79. "Delete" action becomes "Reject." No issue is hard-deleted in v2. Rejected issues remain queryable in the Archive view.
4. Legal transitions
┌─────────┐
┌────►│ Open │◄────────────────┐
│ └────┬────┘ │
│ │ Take / Delegate │ Reopen
│ ▼ │
│ ┌─────────────┐ │
│ │ In Progress │──────────┐ │
│ │ (+ dlg?) │ │ │
│ └─────┬───────┘ │ │
│ │ Resolve │ Reject
│ ▼ ▼
│ ┌──────────┐ ┌──────────┐
└─────┤ Resolved │ │ Rejected │
Reopen └──────────┘ └──────────┘
│ Reopen │ Reopen
└────────────────────┘
(→ Open)
Forward: Open → In Progress → Resolved | Rejected. Reject may also apply directly from Open (admin triages as non-issue on arrival). Backward / Reopen: Resolved → Open, Rejected → Open. Admin then re-triages. No direct Resolved ↔ Rejected swap — reopen through Open. In Progress → Open: allowed (undo taking).
5. Archive is a view, not a state
- A toolbar filter on the Issue Queue list: "Active" (default) / "Archived" / "All".
- "Archived" shows Resolved + Rejected.
- Supports the stakeholder's primary analytic use case (L89): run reports over historical issues to understand how students use the report-dialog and refine its UX copy.
6. Fresh desk integration is deferred
No v2 spec or mockup work depends on Fresh desk. A future ADR (or amendment here) can specify routing if/when that integration is planned. Delegation to CS in v2 is tag-only.
Rationale
- Reopen through Open (not back to In Progress): matches Lejla's L83 — "go back to open and then adjust accordingly." Forces an explicit re-triage rather than silently re-animating a previously handled item.
- Delegation as sub-state, not parallel state: preserves the admin-owns-tracking framing. Separate parallel state would imply routing + ownership transfer, which the transcript explicitly rules out.
- Archive as view: Lejla specifically framed her interest as "run a report… see how students are using it" (L89). That's a reporting operation on historical data, not a separate lifecycle state. View-only semantics avoids introducing a 5th state that serves the same function as a filter.
- "Closed" dropped: the term was in the Apr 15 draft but never had distinct semantics from Resolved. Lejla's taxonomy (L79) is "Resolve and reject. Yeah" — explicitly two terminals.
Consequences
First-order (10-admin-system.md §6)
- Remove the "Category E — Deferred" banner.
- Update Status column: values become Open / In Progress / Resolved / Rejected. "Closed" removed.
- Add
delegated_tofield: optional, enum {CS, IT, Teaching Staff}, shown as badge modifier on In Progress rows. - Rename "Delete" action → "Reject."
- Add "Reopen" action (visible on Resolved + Rejected rows, resets to Open).
- Add "Delegate" action (visible on Open + In Progress rows; submenu of three targets; can be cleared).
- Add Active / Archived / All view toggle at top of list.
- Add §6.x State Transitions subsection documenting the diagram above.
Second-order
src/data/types.ts:44—type IssueStatuscurrently"Open" | "In Progress" | "Resolved" | "Closed". Must update in mockup pass: drop Closed, add Rejected, add optionaldelegatedTofield on the Issue record. Not in this spec pass.src/pages/v2/admin-system/issue-queue.tsx— filter UI carries "Closed" per discovery sweep. Update in mockup pass.src/data/issues.ts— mock data may include Closed entries. Migrate to Resolved in mockup pass.02-dashboard.mdApp Issues tile — count semantics are Open only (new, untriaged). In Progress issues are excluded from the tile because they are already being handled by an admin — the tile is a triage signal, not a workload meter. Delegated sub-state does not affect the tile count either (delegated issues are by definition In Progress). This matches the existing tile definition and does not require a count-logic change.01-global-patterns.md §4.3status colors — add Rejected (red, same as existing failure/error palette). Resolved stays green. Delegated sub-badge neutral.09-reporting.md— no hard dependency, but an "Issue Queue Report" may be added in a future reporting pass (matches Lejla L89 intent). Flagged as nice-to-have, not built now.
Third-order
- RBAC: CS / IT / Teaching Staff are NOT roles in
01-global-patterns.mdRBAC matrix. Deferred — delegation is tag-only. Do NOT add these as login roles in v2. - Fresh desk: explicitly deferred. Any future integration would add an
external_ticket_idfield + webhook — out of scope here.
What we explicitly preserve
- The
10-admin-system.md §6borrowed-from-Candidate-B provenance note. - The boundary note that Issue Queue is ticketed support, not conversational messaging (Private Messages is the channel for conversation).
- The v2 implementation note that the student-side "Report Issue" feature in the iOS app does not exist yet, so the queue ships empty in mockup.
Implementation notes
Mockup scope:
- Replace "Closed" with "Rejected" in status filter and per-row actions.
- Add Delegate submenu (3 fixed targets) + Reopen action.
- Add Active / Archived / All toggle.
- No routing, no email send — all actions toast + static update.
Production scope (not in this ADR):
- Webhook from student-side Report Issue feature.
- Fresh desk integration strategy (future ADR).
- Delegation routing if CS/IT/Teaching Staff become real logins.
Open questions
- Delegate target cleanup: when transitioning from In Progress → Resolved, is
delegated_tocleared? Assume yes (terminal state, no ongoing delegation). Confirm in mockup pass. - Bulk reject: supported or per-row only? Default to per-row; add bulk in a future ADR if volume demands it.
- Issue Queue report: which slices? (Type × status × time-to-resolve?) Tie to the full reporting working session still owed.