Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Work items

MOTIR-2052Done

11.2.10 `POST /api/v1/work-items/{key}/archive` and `/restore` — the reversible soft-remove, gated on `work_items:archive`

The only removal operation /api/v1 exposes, and the reason it can be exposed at all: archiving is a reversible soft-remove that does not cascade (workItemsService.archiveWorkItem sets archivedAt on ONE row and records a revision; children are untouched). The irreversible subtree delete stays unexposed — ADR §3 rejects it for the first cut, work_items:delete is off by default in DEFAULT_TOKEN_SCOPES, and exposing it later is additive under §8 while withdrawing it could not be.

Two routes: app/api/v1/work-items/[key]/archive/route.ts and …/restore/route.ts, both POST, both scope: 'work_items:archive' — its own scope, distinct from work_items:write, exactly as the ADR §3 map says. A token that may edit an item may not therefore remove it.

Scopes NARROW, never widen. The services gate on projectAccessService.assertCanEdit; the scope gate is an ADDITIONAL condition, so a work_items:archive token held by someone without project edit rights is still refused. Assert both directions — the scope without the role, and the role without the scope.

What to build

  • POST …/archivearchiveWorkItem(id, ctx), returning the updated resource with its archivedAt set.
  • POST …/restoreunarchiveWorkItem(id, ctx), returning it with archivedAt null.
  • Neither service raises an already-archived / already-restored error — re-archiving simply re-stamps — so both endpoints are idempotent and say so in their schema description rather than inventing a conflict status the services do not produce.
  • The only domain code either raises is WORK_ITEM_NOT_FOUND → 404, already mapped by 11.2.2; add no duplicate row.

Scope BOUNDARY

Ends at archive and restore of ONE item. It exposes no delete — and the story's test gate asserts that no /api/v1 route reaches deleteWorkItem at all, so the omission cannot be undone by accident. It does not archive a subtree (the service does not cascade, and a client wanting that walks the children itself), does not expose the archived-items LIST (listArchivedWorkItems serves the archive-management surface; a v1 archive collection is not in this story's endpoint set), and changes no service or repository.

Acceptance criteria

  • POST …/archive archives the item and returns it with a non-null archivedAt; POST …/restore clears it — both asserted by reading the row back.
  • Archiving does NOT touch children: an item with children is archived and every child's archivedAt stays null.
  • An archived item disappears from GET /api/v1/projects/{projectKey}/work-items and reappears after restore — the behaviour a client actually observes, asserted through the endpoints rather than the repository.
  • Both endpoints are idempotent: a second archive (or restore) succeeds with the same result.
  • A token with work_items:write but NOT work_items:archive is 403 on both; a token with work_items:archive whose owner lacks project edit rights is also refused — the narrowing rule proven in both directions.
  • A cross-tenant or unknown key is 404 on both.
  • Both routes compose withV1Route, declare scope: 'work_items:archive', and touch no Prisma and no transaction.
  • The per-file coverage floor (≥90%) holds on every new file.

Context refs

  • lib/services/workItemsService.tsarchiveWorkItem / unarchiveWorkItem (single-row, revision-recording, non-cascading) and deleteWorkItem, the one this story does not expose.
  • lib/mcp/scopes.tswork_items:archive vs work_items:delete, and DEFAULT_TOKEN_SCOPES.
  • lib/mcp/tools/archiveWorkItem.ts — the shipped tool proving both service paths and the single-item scope.
  • docs/decisions/public-api-conventions.md — §3's scope map and its rejected "expose delete in the first cut".
  • Producer: 11.2.2. Parent story: 11.2.