feat: redefine viewer as true read-only, promote collaborator to participation tier (#246 follow-on)
Background
Full RBAC audit (#246 (closed)) revealed that collaborator and viewer are treated identically in every permission check. The code pattern throughout boards/views.py is:
if role in (BoardMembership.Role.VIEWER, BoardMembership.Role.COLLABORATOR):
raise PermissionDenied(...)
This means a collaborator cannot do anything a viewer cannot. The role is a trap — anyone assigned collaborator expecting elevated access over viewer gets none.
Additionally, viewer currently allows posting comments, uploading attachments, deleting attachments, and managing checklist items. These are participatory write actions that a read-only viewer should not have.
Note: docs/features/rbac/roles.md and docs/api/cards.md already describe the intended (tighter) behavior — collaborator can comment, viewer cannot. This is a fix to close the gap between docs and code, not a new design decision. Frame the changelog entry as fix:, not feat:.
Current state (before)
| Action | viewer | collaborator | member | admin |
|---|---|---|---|---|
| View board, cards, columns, swimlanes | ||||
| View movements & activities | ||||
| View archived cards | ||||
| Post comments | ||||
| Delete own comments | ||||
| Upload attachments | ||||
| Delete attachments | ||||
| Add / edit / delete checklist items | ||||
| Create / edit / move / delete cards | ||||
| Archive / unarchive cards | ||||
| Columns / swimlanes / labels | ||||
| Member management |
collaborator and viewer are identical in every check.
Proposed change (after)
| Action | viewer | collaborator | member | admin |
|---|---|---|---|---|
| View board, cards, columns, swimlanes | ||||
| View movements & activities | ||||
| View archived cards | ||||
| Post comments | ||||
| Delete own comments | ||||
| Upload attachments | ||||
| Delete attachments | ||||
| Add / edit / delete checklist items | ||||
| Create / edit / move / delete cards | ||||
| Archive / unarchive cards | ||||
| Columns / swimlanes / labels | ||||
| Member management |
viewer = read-only. collaborator = participate without managing cards.
Implementation note
comments, attachments, delete_attachment, and checklist/checklist_item actions currently call self._board() directly — they do not resolve the role. Each write path must be changed to call self._board_and_role() before the role check can be added. Do not introduce a new helper; reuse the existing pattern.
Tasks
Backend — permission checks
-
Change
self._board()→self._board_and_role()incomments,attachments,delete_attachment,checklist,checklist_itemactions -
POST comments/— blockviewer -
DELETE comments/{id}/— blockviewer; collaborator may only delete own comments (checkcomment.author == request.user) -
POST attachments/— blockviewer -
DELETE attachments/{id}/— blockviewer -
POST checklist/— blockviewer -
PATCH checklist/{item}/— blockviewer -
DELETE checklist/{item}/— blockviewer
Backend — tests (test_rbac.py)
- viewer gets 403 on POST comments
- viewer gets 403 on DELETE comments/{id}/
- viewer gets 403 on POST attachments
- viewer gets 403 on DELETE attachments/{id}/
- viewer gets 403 on POST checklist
- viewer gets 403 on PATCH checklist/{item}/
- viewer gets 403 on DELETE checklist/{item}/
- collaborator gets 201/200 on all seven endpoints above
- collaborator cannot delete another user's comment (403)
Frontend
-
Update role picker UI (board member invite modal, group member management) to show all four roles with descriptions:
- Admin — full board control
- Member — can create and edit cards
- Collaborator — can comment and upload files
- Viewer — read-only
Docs
-
docs/features/rbac/roles.md— verify permission table matches the new matrix; update attachment/checklist/comment-delete rows -
docs/api/cards.md— add minimum role requirement to: POST attachments, DELETE attachments, POST checklist, PATCH checklist/{item}/, DELETE checklist/{item}/ (POST comments already documented correctly) -
CHANGELOG.md— add entry under### Fixed: "enforce viewer read-only boundary on comments, attachments, and checklist endpoints; collaborator role now has distinct participatory write access"
Deferred (follow-up issues)
- comment-delete-rbac — whether collaborator can delete any comment vs own-only is in scope above; full audit of edit-comment if that endpoint exists is deferred
-
export-import-rbac —
POST /boards/import/has no documented minimum role; should be member+ → separate issue
Migration
No data migration required. Existing viewer memberships lose comment/attachment/checklist write access. This is a tightening that closes a gap between the documented and actual behavior. Document in changelog with an upgrade note.
Acceptance criteria
-
viewerreceives 403 on all write endpoints listed above -
collaboratorreceives 201/200 on all the same endpoints -
collaboratorcannot delete another user's comment - All nine new test cases pass in
test_rbac.py -
docs/features/rbac/roles.mdpermission table is accurate -
docs/api/cards.mdlists minimum role on all affected endpoints - Changelog entry present under
### Fixed