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

`board:configure` — the boards, their columns, swimlanes and WIP limits (and NOT the card drag, which stays `work_item:edit`)

Board CONFIGURATION — creating a board, adding or deleting a column, swimlanes, WIP limits — is administrative and is board:configure.

⚠️ Re-scoped 2026-08-06 against the branch — this card's original premise was BACKWARDS

The card was written from the guard's output and claimed "four of its operations reach boardsService with no project gate at all today… any signed-in workspace member can add or delete a column on any board they can reach. Closing that is most of this card's value." That is false, and the opposite is true. Every board-shape write in lib/services/boardsService.tscreateBoard, renameBoard, setDefaultBoard, deleteBoard, addColumn, renameColumn, reorderColumn, deleteColumn, setColumnWipLimit, setSwimlaneGroupBy — already calls the module-private assertBoardConfigAdmin (boardsService.ts:1138), which resolves isOwnerRole(workspaceMembership.role) and throws NotBoardAdminError ("You must be a workspace owner to change board configuration.").

The guard could not see it because its GATE pattern is a whitelist of NAMES and assertBoardConfigAdmin is not one of them — MOTIR-2304 repairs the instrument and this card is blocked_by it, so the enumeration below is run against a walk that can see the gate.

So this card LOOSENS, it does not close a hole. Board configuration today is workspace-OWNER-only — tighter than project:administer. After the split it is board:configure, which a workspace admin and a project admin also hold. That is a real grant to real actors and it is named here rather than smuggled in behind a refactor.

It is also required, not optional. MOTIR-2293 already put board:configure into BUILTIN_ROLE_PERMISSIONS.admin. Leaving the gate at workspace-owner-only would have the catalog advertise a permission a project admin holds while the code refuses them — the exact lie lib/permissions/catalog.ts's opening rule exists to prevent. Both mirrors agree: Jira team-managed and Plane both put board configuration under project administration, not under the org owner.

⚠️ The inventory's board:configure mapping is too wide, and following it literally would break the product

Under reason R9 it also assigns the key to:

  • /api/board GET — the board PROJECTION read (assertCanBrowse today). Stays project:browse.
  • /api/board PATCH — resolves to setSwimlaneGroupBy / renameBoard, which ARE board configuration. It IS board:configure; the row must be split by verb because the two halves land on different keys.
  • /api/board/movedragging a card between columns. assertCanEdit today, and the single most-used write in the product. An administrative key here would make the board read-only for every member and viewer in every project. It stays work_item:edit.

Correct those rows as part of this card. A mapping that would move an operation into the wrong domain is a defect in the map, not an instruction.

Acceptance criteria

  • Enumerate first, against the REPAIRED guard (MOTIR-2304 is blocked_by): list its board:configure operations, then open each row in the inventory's board section. The PR body carries the resulting table — operation, gate today (the real one, naming assertBoardConfigAdmin where it applies), gate after — with a one-line reason for each row that does NOT move to board:configure.
  • Every board/column/swimlane/WIP write in lib/services/boardsService.ts asserts board:configure through projectAccessService.assertPermission, threading tx where the method already runs inside one. assertBoardConfigAdmin is DELETED — it is the third private re-implementation of the access policy this story has found, and leaving it would keep a second answer alive next to the first.
  • NotBoardAdminError and its NOT_BOARD_ADMIN wire code: git grep -n "NOT_BOARD_ADMIN\|NotBoardAdminError" -- app lib components tests e2e FIRST and state in the PR what each consumer receives after. Its message ("You must be a workspace owner…") is false once a project admin may configure a board, so either the error is retired in favour of PermissionDeniedError or its copy is corrected — say which, and keep every consumer compiling.
  • The WIDENING is asserted, not just performed. A test proves the new actor set explicitly: a project admin can now add a column (they could not before this card); a workspace admin can too; a project member and viewer are refused 403; a non-browser gets 404. The project-admin case is the one that changes, so it is the one that must be named.
  • /api/board GET stays on the browse gate. /api/board/move is untouched — a diff that changes its gate fails this card.
  • A member can still MOVE a card and still READ the board, asserted explicitly — the regression this card is most likely to cause.
  • boardsService.mapStatusToColumn is NOT touched here — the column→status mapping is workflow:manage and belongs to the workflow card, which edits the same file. Keep this PR to the board-shape methods so the two do not collide beyond a trivial rebase.
  • board:configure flips to enforcement: 'enforced' in lib/permissions/catalog.ts, and is added to the wired list tests/permissions/catalog.test.ts pins.
  • The inventory's board rows carry the corrected gate-today and gate-after, with newexisting on the ones this card wires, and the /api/board row split by verb.
  • The guard's pending pin is re-derived by running the suite on the branch.
  • pnpm test green; pnpm lint and the prettier check pass repo-wide.

Context refs

  • lib/services/boardsService.tsassertBoardConfigAdmin (line ~1138), addColumn, the board CRUD, swimlane and WIP methods, and mapStatusToColumn (the workflow card's, not this one's).
  • lib/boards/errors.tsNotBoardAdminError and its copy.
  • app/api/boards/route.ts · app/api/boards/[id]/route.ts · app/api/board/columns/route.ts · app/api/board/columns/[columnId]/route.ts — the four the card called ungated; they are gated, through the service.
  • app/api/board/route.ts · app/api/board/move/route.ts — the two the inventory over-claims; read them, do not re-point them blindly.
  • docs/decisions/permission-inventory.md — the board section and reason R9.
  • The instrument repair this waits on · the seam this calls · the workflow card that shares boardsService.