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-2350Done

`sprint:manage` — the sprint lifecycle, the backlog rank and a work item's sprint, with the sprint READS left at `project:browse`

Today a project viewer can start a sprint, complete it, drag the backlog into a new order and move somebody else's work item into the active sprint. backlogService and sprintsService contain no access assertion of any kind on origin/main — the routes reach a gate only through whatever else they happen to call. This card gives the sprint domain its key.

The split this card makes, which the inventory does not

The inventory maps all thirteen sprint rows to sprint:manage. Three of them are READS and must not need it — a viewer who can see the board can see the backlog and the sprint's issue list, exactly as Jira's Manage Sprints governs creating and starting a sprint while Browse Projects governs seeing one. Three more are ANALYTICS and belong to the reports card rather than here, so no two cards flip the same key.

OperationService methodKey
POST /api/sprints, PATCH/DELETE /api/sprints/[id], /start, /completesprintsService.createSprint / updateSprint / deleteSprint / startSprint / completeSprintsprint:manage
/api/backlog/bulk-move, /api/sprints/[id]/issues/bulk, /api/work-items/[id]/rank, /api/work-items/[id]/sprintbacklogService.bulkMoveToBacklog / bulkAssignToSprint / rankIssue / assignToSprint / moveToBacklogsprint:manage
POST /api/backlog (create an issue into the backlog)backlogService.createBacklogIssuework_item:edit — it creates a work item; ranking it is the sprint act, authoring it is not
GET /api/backlog, GET /api/sprints, GET /api/sprints/[id]/issuesbacklogService.getBacklog / getSprintIssues, sprintsService.listByProjectproject:browse
/api/sprints/[id]/burndown, /report, /pointsreportsService.getSprintCycleGraph, sprintsService.getSprintReport, estimationService.rollupForSprintreport:view — NOT this card; owned by the reports card so one key has one owner

Gates go in the SERVICE, not the route (the 4-layer contract), and every one is projectAccessService.assertPermission(projectId, ctx, key) — the shipped seam, no new assert method.

Acceptance criteria

  • Every write listed above asserts sprint:manage before it reads or writes a row, and the assertion is inside the service method, not the route handler.
  • Each of the three reads asserts project:browse, and createBacklogIssue asserts work_item:edit.
  • sprint:manage flips to enforcement: 'enforced' in lib/permissions/catalog.ts, and tests/permissions/catalog.test.ts's enforced-key list and length pin are extended by exactly this key.
  • The inventory rows for the ten operations this card owns record the key each one landed on, and the three analytics rows are left for the reports card with a note naming it.
  • A unit test per service drives a viewer into the typed refusal on a write and through on a read, using the real resolution rather than a mocked hasPermission.
  • An integration test proves a workspace member with NO project membership cannot start a sprint on an open project, since that actor resolves through IMPLICIT_WORKSPACE_MEMBER_PERMISSIONS rather than a role.
  • The guard's pinned PENDING count is re-derived by running tests/permissions/noUngovernedOperation.test.ts on this branch, and the drop equals the ungated rows this card gated.
  • pnpm test tests/permissions/ tests/services/ is green.

Context refs

  • lib/services/backlogService.ts, lib/services/sprintsService.ts — the two services with no assertion on origin/main.
  • lib/services/projectAccessService.tsassertPermission, and the ProjectNotFoundError-before-PermissionDeniedError ordering every gate inherits.
  • docs/decisions/permission-inventory.md — the thirteen R14 rows.
  • docs/decisions/member-facing-permissions.md — which roles hold sprint:manage (written by the decision).
  • The role-set seam — the card that makes this key resolve to anybody.
  • board:configure — the sibling one story over that did this shape for the board, including what it deliberately did NOT take (the card drag stayed work_item:edit).