waves 11-13: planning docs for CR-019, CR-020, D18
CR-019 - usage/activity metrics (admin console), wave 11 CR-020 - bulk editing of users, wave 12 D18 - Okta/AD deprovisioning detection and auto-disable, wave 13 Raised by Matt Mabrey 2026-09-17. Decision record and task breakdowns only in this commit - no feature code yet.
This commit is contained in:
193
docs/waves/wave-12.md
Normal file
193
docs/waves/wave-12.md
Normal file
@@ -0,0 +1,193 @@
|
||||
# Wave 12 — Bulk editing of users
|
||||
|
||||
**Items:** `CR-020`
|
||||
**Depends on:** wave 10 merged (it is). Independent of wave 11 (`CR-019`) — no
|
||||
shared files, may build in either order or in parallel.
|
||||
**Decision record:** `docs/waves/decisions-2026-09-17.md`
|
||||
|
||||
Six tasks, in build order.
|
||||
|
||||
---
|
||||
|
||||
### T12.1 — CR-020: bulk endpoints
|
||||
|
||||
- **Items:** `CR-020`
|
||||
- **Depends on:** nothing (first task)
|
||||
- **Blocks:** T12.2, T12.4
|
||||
- **Surface:** `server/`
|
||||
- **Files:** `server/app.py`
|
||||
|
||||
**Do:** Add bulk variants of the four existing single-user actions — role
|
||||
change, active/disabled, project assignment (add/remove + project-role), and
|
||||
delete. Each takes a list of `user_id`s plus the action's parameters and
|
||||
applies `require_see_user`/`require_manage_user`/`grantable_roles` **per row**,
|
||||
exactly as the single-user endpoint does today — a super user's bulk request
|
||||
cannot reach further than their existing single-user requests can. Do not skip
|
||||
the self-action guard: an actor's own account is rejected out of any batch
|
||||
that would disable, demote, or delete it, same as today.
|
||||
|
||||
Each successful row writes its own `AuditLog` entry via `log_event`, same
|
||||
action names the single endpoints already use. A row that fails is reported in
|
||||
the response (user id, reason) and does not stop the rest of the batch from
|
||||
being attempted.
|
||||
|
||||
**Do not:** invent a single opaque "bulk_action" audit entry in place of the
|
||||
per-row entries. Do not build a soft-delete path for bulk delete that doesn't
|
||||
exist for the single case — bulk delete stays a hard delete, matching
|
||||
`delete_user` today.
|
||||
|
||||
**Done when:**
|
||||
|
||||
- [ ] each of the four bulk actions is callable with a list of user ids and a
|
||||
single set of parameters
|
||||
- [ ] a batch containing the actor's own account rejects only that row, not
|
||||
the whole batch — verified for disable, demote, and delete
|
||||
- [ ] a super user's batch that includes a user/project outside what they
|
||||
manage rejects only that row, with a stated reason
|
||||
- [ ] every successful row produces its own `AuditLog` entry, identical in
|
||||
shape to what the single-user endpoint would have written
|
||||
- [ ] a batch with some failing rows still applies to the rows that succeed,
|
||||
and the response lists exactly which rows failed and why
|
||||
|
||||
---
|
||||
|
||||
### T12.2 — CR-020: row selection in the User Directory table
|
||||
|
||||
- **Items:** `CR-020`
|
||||
- **Depends on:** T12.1
|
||||
- **Blocks:** T12.3
|
||||
- **Surface:** `html/`
|
||||
- **Files:** `html/users.js`, `html/users.html`
|
||||
|
||||
**Do:** Add a checkbox per row and a select-all control, respecting whatever
|
||||
filter (`role`, `active`/`disabled`, project) is currently applied — select-all
|
||||
selects the filtered set, not every user in the system regardless of what's
|
||||
shown. A bulk-action toolbar appears once at least one row is checked and
|
||||
disappears at zero.
|
||||
|
||||
**Done when:**
|
||||
|
||||
- [ ] select-all selects exactly the rows currently visible under the active
|
||||
filter, not the full unfiltered table
|
||||
- [ ] changing the filter while rows are selected does something sane and
|
||||
visible (either clears the selection or keeps it explicit which rows are
|
||||
still selected) — pick one and state it, don't leave it undefined
|
||||
- [ ] the toolbar is keyboard-reachable and only present when >=1 row is
|
||||
selected
|
||||
|
||||
---
|
||||
|
||||
### T12.3 — CR-020: bulk-action toolbar
|
||||
|
||||
- **Items:** `CR-020`
|
||||
- **Depends on:** T12.2
|
||||
- **Blocks:** T12.5
|
||||
- **Surface:** `html/`
|
||||
- **Files:** `html/users.js`
|
||||
|
||||
**Do:** Wire the toolbar to T12.1's endpoints for role change, activate/
|
||||
disable, and project assignment (add to / remove from a project + project-
|
||||
role). Confirmation before applying uses the `wp-dialog` kit (`T7.9`) —
|
||||
`wpConfirmDialog`, not a native `confirm()` — naming exactly how many users are
|
||||
affected. On completion, report per-row results if anything failed (T12.1
|
||||
already returns this) rather than a single success/failure toast that hides a
|
||||
partial failure.
|
||||
|
||||
**Delete is built separately, in T12.5** — do not wire delete here.
|
||||
|
||||
**Done when:**
|
||||
|
||||
- [ ] role change, activate/disable, and project assignment each work end to
|
||||
end against a multi-row selection
|
||||
- [ ] the confirmation dialog names the exact affected count before anything is
|
||||
sent
|
||||
- [ ] a batch with a partial failure shows which rows failed, not just an
|
||||
undifferentiated error
|
||||
- [ ] `aria-live` announces the outcome
|
||||
|
||||
---
|
||||
|
||||
### T12.4 — CR-020: CSV upload path
|
||||
|
||||
- **Items:** `CR-020`
|
||||
- **Depends on:** T12.1
|
||||
- **Blocks:** T12.6
|
||||
- **Surface:** `html/` + `server/`
|
||||
- **Files:** `html/users.js`, `server/app.py`
|
||||
|
||||
**Do:** An upload accepting a list of usernames plus the action to apply,
|
||||
following the validate-and-report pattern `CR-005` established: reject and
|
||||
report bad rows (username not found, actor lacks permission over that user)
|
||||
rather than silently dropping them. This is a second entry point onto the same
|
||||
T12.1 endpoints, not a third implementation of the bulk logic.
|
||||
|
||||
**Done when:**
|
||||
|
||||
- [ ] a CSV with a mix of valid and invalid usernames applies to the valid
|
||||
rows and reports the invalid ones by row, with a reason
|
||||
- [ ] the same permission/self-action guards from T12.1 apply here — a CSV
|
||||
cannot reach a user a checkbox-driven batch couldn't
|
||||
- [ ] duplicate usernames in one CSV are handled without double-applying or
|
||||
erroring confusingly
|
||||
|
||||
---
|
||||
|
||||
### T12.5 — CR-020: bulk delete confirmation
|
||||
|
||||
- **Items:** `CR-020`
|
||||
- **Depends on:** T12.3
|
||||
- **Blocks:** T12.6
|
||||
- **Surface:** `html/`
|
||||
- **Files:** `html/users.js`
|
||||
|
||||
**Do:** Wire delete into the toolbar with a heavier confirmation than the other
|
||||
three actions, per the decision record's recommendation: list the affected
|
||||
usernames and require typing a confirmation phrase (e.g. `DELETE`) before the
|
||||
request is sent, regardless of how many rows are selected. This is flagged in
|
||||
the decision record as a recommendation Matt has not explicitly signed off on
|
||||
— if the PR reviewer wants a lighter or heavier mechanism, that's the moment to
|
||||
change it, not a reason to skip building a real confirmation now.
|
||||
|
||||
**Done when:**
|
||||
|
||||
- [ ] the affected usernames are listed in the confirmation dialog before
|
||||
delete is sent
|
||||
- [ ] the request is not sent until the confirmation phrase is typed correctly
|
||||
- [ ] the actor's own account, if somehow selected, is rejected with a clear
|
||||
reason rather than silently included or silently dropped
|
||||
|
||||
---
|
||||
|
||||
### T12.6 — CR-020: verification
|
||||
|
||||
- **Items:** `CR-020`
|
||||
- **Depends on:** T12.4, T12.5
|
||||
- **Blocks:** nothing
|
||||
- **Surface:** `html/` + `server/`
|
||||
- **Files:** as touched above
|
||||
|
||||
**Do:** Full verification per `CLAUDE.md`: run locally, exercise bulk role
|
||||
change, activate/disable, project assignment, CSV upload, and bulk delete at
|
||||
390px and 1440px, before/after screenshots, smoke test, `seed_demo.py`, full
|
||||
suite.
|
||||
|
||||
**Done when:**
|
||||
|
||||
- [ ] all `CR-020` acceptance criteria in `decisions-2026-09-17.md` are met or
|
||||
a failure is stated with a reason
|
||||
- [ ] screenshots committed
|
||||
- [ ] smoke test and `seed_demo.py` both still pass
|
||||
- [ ] full test suite passes
|
||||
|
||||
---
|
||||
|
||||
## Wave 12 exit criteria
|
||||
|
||||
- [ ] all four bulk actions work against both a checkbox selection and a CSV
|
||||
upload
|
||||
- [ ] every existing single-user guardrail (self-action, scope, grantable
|
||||
roles) holds under bulk use
|
||||
- [ ] bulk delete requires typed confirmation and lists affected usernames
|
||||
- [ ] partial failures are always reported, never hidden behind a blanket
|
||||
success
|
||||
- [ ] `CR-020` fully accounted for, no open acceptance criteria
|
||||
Reference in New Issue
Block a user