| Resolved stays resolved; dismissed stays dismissedC14 | tg_backlog_items_terminal_absorbing rolls back any UPDATE that changes status out of resolved or dismissed; the service refuses first with a conflict; there is no reopen tool, route or button | shipped |
| A close explains itselfC14 | resolved needs closing_pr_url, closing_commit_sha or closing_notes_markdown; dismissed needs triage_notes_markdown; the dashboard’s Move form enforces the same two rules | shipped |
| Identity is fixed at insertC98 | the same trigger rolls back any UPDATE that touches workspace_id or backlog_item_id, which is what makes the terminal guard unbypassable — a row cannot rewrite its id and slip the check; project_id may move (reassign) | shipped |
| A closed item can still be annotatedC98 | amend_backlog_closing_notes appends “**Amendment yyyy-MM-dd:**” and your text (1–4,000 characters) below the original notes, on a resolved or dismissed item only; the trigger admits it because the status does not change | shipped |
| Edits to an open item are the filer’s, and briefC98 | amend_backlog_item changes title, body, category, priority or estimate — only the credential that filed it, only while open, only within 24 h of filing; after that, reprioritise or triage | shipped |
| Order is stored, not computed at read timeC100 | priority_rank (critical, high, medium, low) and status_rank (open, in_progress, resolved, dismissed) are persisted computed columns; the board and the list sort by status_rank, priority_rank, then newest first, over an index built for it | shipped |
| Staleness is a persisted clockC99not audited: an acknowledgement writes no audit row, deliberately, like a ping; nothing sweeps a stale item, and the dashboard shows no stale marker | staleness_at = COALESCE(staleness_acknowledged_at, created_at), persisted; the stale list returns open and in-progress items older than now minus threshold_days (default 90 days, 1–3650), oldest first; acknowledging stamps the clock with one conditional UPDATE and is a no-op on a closed item | shipped with caveat |
| List and search boundsC100no full-text index and no relevance ranking — substring plus recency | list_backlog_items is offset-paged with a total, 1–100 a page (default 50), filters by project, status, priority, owner, filer and parent; query_backlog is a SQL LIKE substring match over title and body, term up to 500 characters, newest first, capped at 50 — search, not full-text search | shipped with caveat |
| Attributed to the credential and the clientC97 | filed_by_actor_key is agent:<ai_account_id>, derived from the bearer that made the call — never taken from the arguments — with the client type (api or mcp), name and version; the triaging actor is recorded the same way; the owner is the actor key you name on assign | shipped |
| Every write requires write_session_state; every read only a loginC97 | the eight write tools pass write_session_state to TryAuthorize and the eight write routes carry RequireScope("write_session_state"); the five reads accept a bearer of either tier or a human session — the same rule over REST and MCP | shipped |
| Every write is on the audit chain — 7 backlog.* event typesC102acknowledge staleness is the deliberate exception and says so above | filed, triaged, amended, closing-notes-amended, assigned, reprioritised, reassigned — each appended in the same transaction as the row change, after the item is read under an update lock; a no-op writes none; the row lands on the workspace’s tamper-evident SHA-256 chain | shipped with caveat |
| REST and MCP return the same bytesC05 | the backlog reads serialize through one shared serializer for the session-state aggregates and are tested byte-identical between the two transports | shipped |
| People see the board and the itemC101no filing, assigning, amending or staleness acknowledgement from the dashboard, and no drag-and-drop — cards are links | app.postmq.com/backlog — four columns in status order, a project filter and a title/body search, up to 50 cards a column with a “+N more” count; app.postmq.com/backlog/{id} — every field, the body, the closing and triage notes, and two forms: Move and Reprioritise | shipped with caveat |