Aug 17 - Update customer inspector can update issue status

This commit is contained in:
2026-08-17 16:46:26 -04:00
parent 436ef5dafb
commit 607da7ae47
4 changed files with 72 additions and 7 deletions
+19
View File
@@ -295,6 +295,25 @@ let x = all.filter { ... }
---
### Role gates — `Constants.Roles` (Aug 2026)
**Never write `role == "inspector"` in a view.** `external_inspector` ("Customer Inspector" — an inspector employed by the customer) has the same powers as our own `inspector` and the API scopes it identically, so a literal equality check locks that account out of actions the server would happily accept. It fails **silently**: no error, no 403 to debug — the control simply is not drawn.
That is exactly what happened to Update Status, Handled By and Start Follow-up, which were three separate hand-written lists in two files:
| Site | Was | Now |
|---|---|---|
| `IssuesView.canUpdateStatus` | `admin \| director \| inspector` | `Constants.Roles.issueActors` |
| `IssuesView.canEditHandler` | `admin \| director \| inspector \| project_manager` | same |
| `InspectionHistoryView.canStartFollowUp` | `admin \| director \| inspector \| project_manager` | same |
`Constants.Roles` in `Utils/Constants.swift` is the single definition, mirroring `User.INSPECTOR_ROLES` / `User.is_inspector` on the server (server rule 87):
- `inspectorRoles` = `{inspector, external_inspector}` — test membership, never `==`.
- `issueActors` = `{admin, director, project_manager} inspectorRoles` — a **subset** of the API's `_ALLOWED_ROLES` for these endpoints, so every role it admits is one the server accepts. `auditor` is deliberately excluded (read-only in the app).
The server stays the authority and additionally enforces facility scope; these gates only decide whether to draw the control.
## 9. API Client (APIClient)
`actor APIClient` — singleton via `APIClient.shared`. All methods are `async throws`.