6.11.2 Decision — the triage model: work_item + `triage` state, read-exclusion, promote/decline/merge semantics
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:
- A submission IS a work_item, in a
triagestate (Yue). Not a separate submissions table. A submission is born awork_item(kindbugfor a bug report,taskfor a feature request — the request grammar) with NO parent and atriagemarker. Decide the marker shape: a dedicated boolean/triagedAtcolumn vs. a reservedworkflow_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). - 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
wherefragment 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." - 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+ anexternalSubmitterJSON/embedded fields) and that external submitters get no tenant access. - 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 (abugcan parent to epic/story/task; atasksimilarly) — promotion must respect it. - 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
snoozedUntilstorage + 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.