MotirBuilding in public
MOTIR · moooon
onMotir
You’re viewing a public project. Anyone can view it — no account needed. Sign in to submit, upvote, or comment on requests.View-only — you can’t edit work items
MOTIR-399

6.11.2 Decision — the triage model: work_item + `triage` state, read-exclusion, promote/decline/merge semantics

Done
Description

Estimate: 45m

⚠️ Partially superseded (Yue, 2026-06-14). §3 below — submitter attribution "member OR external (captured name/email)" — is revised: a work item is created only by a signed-in account, so EVERY triage item carries a real submittedByUserId (a workspace member, or a signed-in non-member via 6.12’s canSubmitToTriage); the captured-external externalSubmitter and the unauthenticated portal are dropped. 6.11.10 amends triage-model.md §3 + retires the schema. The rest of this ADR (triage-state, read-exclusion, promote/decline/merge/snooze) stands. Kept as the record of the original decision.

Type: decision (the keystone ADR the schema + service cards build against; no app behavior ships, but the shapes it fixes are load-bearing). Write motir-core/docs/decisions/triage-model.md. It MUST fix:

  1. A submission IS a work_item, in a triage state (Yue). Not a separate submissions table. A submission is born a work_item (kind bug for a bug report, task for a feature request — the request grammar) with NO parent and a triage marker. Decide the marker shape: a dedicated boolean/triagedAt column vs. a reserved workflow_status — choose the one that makes the read-exclusion a cheap, indexable predicate AND survives the item later taking a normal status on promote (a column is the durable choice; justify).
  2. Read-exclusion is total and central. EVERY normal read — the tree, every board, every list, the ready set, and 6.1.1 FilterAST search — excludes triage items; the triage-queue read is the ONLY read that includes only them. Decide WHERE the predicate lives so it can't be forgotten by a future read (a repository-level default scope / a shared where fragment threaded through every list query — NOT N independent filters). This mirrors Linear: "we exclude triage issues from all views since triage is considered to be outside the normal workflow."
  3. Submitter attribution — member OR external. A triage item records its origin: a member (userId) or an external portal submitter (a captured name/email, no account). Decide the storage (nullable submittedByUserId + an externalSubmitter JSON/embedded fields) and that external submitters get no tenant access.
  4. Promote semantics. Promote = clear the triage marker + set parent (backlog = no parent but triage-cleared / sprint / epic / story, per the kind-parent matrix) + set position/backlogRank — ALL through workItemsService (never raw). Accept = promote to the backlog at the default status with an optional comment. The kind-parent matrix still governs (a bug can parent to epic/story/task; a task similarly) — promotion must respect it.
  5. Decline / mark-duplicate / merge / snooze. Decline → a canceled terminal status (+ optional comment). Mark-duplicate/merge → pick a canonical item; the duplicate is canceled and its comments + attachments fold into the canonical item (mirror Linear moving attachments/customer-requests to the canonical issue). Snooze → hidden from the queue until a chosen time OR new activity, whichever first (decide the snoozedUntil storage + the return-on-activity trigger).

Acceptance criteria

  • The ADR fixes all five sections with the chosen column/relation shapes and a one-paragraph justification for the triage-marker column over a reserved status, and for the central exclusion predicate over per-read filters.
  • It enumerates EVERY normal read that must exclude triage items (tree, each board read, each list read, ready set, FilterAST search) as the checklist 6.11.3 + 6.11.8 implement and test.
  • It states that promotion/decline/merge all route through workItemsService (write authority unchanged) and respect 6.4 permissions + the kind-parent matrix.
  • Linear Triage is cited as the verified mirror for the state-outside-the-workflow exclusion and the action taxonomy.

Context refs

  • Linear Triage (https://linear.app/docs/triage) — exclusion + action semantics.
  • Jira Product Discovery / JSM intake (https://www.atlassian.com/software/jira/product-discovery) — the triaged-gate-before-promote pattern.
  • motir-core/lib/services/workItemsService.ts — the write authority promote/decline commit through.
  • prisma/sql/work_item_triggers.sql — the kind-parent matrix promotion must satisfy.
  • 6.1.1 FilterAST search (shipped) — the search read that must also exclude triage items.