# Agile Viewer RBAC — Page-by-Page Audit

**Date:** 2026-06-04  
**Role under test:** `ProjectMemberRole::VIEWER` (`permissions.role === 'viewer'`)  
**Mechanism:** API policies (`ProjectVisibilityService`) + `ProjectResource.permissions` + UI gating via `useProjectPermissions` / `ProjectPageShell` banner.

## Legend

| UI | Viewer experience |
|----|-------------------|
| Hidden | Control not rendered |
| Disabled | Visible but non-interactive |
| Read-only | View + filters; no mutations |
| API 403 | Direct mutation API returns forbidden |

---

## Projects list (`/projects`)

| Control | Viewer | Evidence |
|---------|--------|----------|
| Create project | Hidden (no global create for viewer) | Company/dept create rules |
| Archive on card | Hidden | `onArchive` only if `can_manage` |
| Active / Archived / All filters | Allowed | Read-only list |
| Open project | Allowed | `view` policy |

---

## Project overview (`/projects/:id`)

| Control | Viewer | Evidence |
|---------|--------|----------|
| Read-only banner | Shown | `ViewerReadOnlyBanner` in shell |
| Complete / Archive | Hidden | `ProjectLifecycleActions` requires `can_manage` |
| Metrics / links | Read-only | No mutation controls |

---

## Backlog (`/projects/:id/backlog`)

| Control | Viewer | Evidence |
|---------|--------|----------|
| Create item / epic / sprint | Hidden | `!readOnly` on actions |
| Reorder | Disabled | `SortableBacklogList` disabled |
| Move to sprint | Hidden | `showMoveToSprint` false |
| API create | API 403 | `AgileStabilizationTest::test_viewer_cannot_create_backlog_item` |

---

## Sprint planning (`/projects/:id/planning`)

| Control | Viewer | Evidence |
|---------|--------|----------|
| Drag backlog ↔ sprint | Disabled | `dragEnabled = !readOnly` |
| Counters / capacity view | Read-only | No save |

---

## Board (`/projects/:id/board`)

| Control | Viewer | Evidence |
|---------|--------|----------|
| Drag columns | Disabled | Static columns without `DndContext` |
| Dependency override modal | N/A | No drag |
| API board move | API 403 | Ticket/board policies |

---

## Sprints (`/projects/:id/sprints`)

| Control | Viewer | Evidence |
|---------|--------|----------|
| Create sprint | Hidden | `canManage` |
| Start / Close sprint | Hidden | `canManage` |

---

## Capacity (`/projects/:id/capacity`)

| Control | Viewer | Evidence |
|---------|--------|----------|
| Save allocations | Hidden | `canPlanCapacity && !readOnly` |
| Capacity inputs | Disabled | `disabled={!capacityEditable}` |

---

## Milestones (`/projects/:id/milestones`)

| Control | Viewer | Evidence |
|---------|--------|----------|
| Add milestone | Hidden | `!readOnly` |
| Mark complete | Hidden | `!readOnly` |

---

## Risks (`/projects/:id/risks`)

| Control | Viewer | Evidence |
|---------|--------|----------|
| Add risk | Hidden | `!readOnly` |

---

## Reports / Burndown / Velocity / Workload

| Control | Viewer | Evidence |
|---------|--------|----------|
| Charts & filters | Read-only | No mutation affordances on pages |

---

## Gantt / Dependencies

| Control | Viewer | Evidence |
|---------|--------|----------|
| Timeline / graph | Read-only | Portfolio/project view via `view` |
| Edit dependencies | API 403 | Manage endpoints require `update` |

---

## Work item detail (`/projects/:id/work-items/:ticketId`)

| Control | Viewer | Evidence |
|---------|--------|----------|
| Tabs (AC, deps, subtasks, comments) | Read-only | `readOnly` on `WorkItemDetailTabs` |
| API mutations | API 403 | Work item update policies |

---

## Settings (`/projects/:id/settings`)

| Control | Viewer | Evidence |
|---------|--------|----------|
| Complete / Archive | Hidden | `can_manage` |
| Members list | Read-only | No invite/remove UI for viewer |

---

## Direct URL bypass

| Route | Expected |
|-------|----------|
| Mutation APIs | HTTP 403 |
| Read routes | HTTP 200 with read-only UI |

---

## Residual gaps

- Member vs viewer on **risks/milestones** API: UI hidden; confirm dedicated API tests for viewer on those endpoints if required for compliance.
- **Gantt drag** (if enabled elsewhere): not present on project Gantt page in current UI.
