Phase 17 — Bulk finding actions¶
Last updated: 2026-05-08
A partner reviewing 50–200 findings shouldn't have to click each one. Phase 17 adds checkbox selection in the finding list and a single endpoint that applies the same status (accepted / rejected / refined) to many findings at once. Per-id outcomes are returned so a partial failure doesn't poison the batch.
Endpoint¶
POST /auditforge/findings/bulk-action?engagement_id=<id>
Content-Type: application/json
{
"finding_ids": ["f-abc", "f-def", "f-ghi"],
"action": "accepted", // accepted | rejected | refined
"auditor_notes": "approved as part of remediation backlog (partner)"
}
Auth: same as the singular endpoints — admin token or session token belonging to a user with admin or partner role within the engagement's firm. Associates get 403 (Phase 15 read-only enforcement).
finding_ids must be non-empty and ≤200 IDs per call.
Response¶
{
"ok": true,
"engagement_id": "eng-abc123",
"action": "accepted",
"total": 3,
"succeeded": 2,
"failed": 1,
"results": [
{ "id": "f-abc", "ok": true, "status": "accepted" },
{ "id": "f-def", "ok": true, "status": "accepted" },
{ "id": "f-ghi", "ok": false, "error": "finding_not_found" }
]
}
The endpoint returns 200 even with partial failure — succeeded / failed counts and per-id results let the UI show "47/50 accepted, 3 not found" without aborting the whole call.
UX¶
In the engagement detail view, every finding row now has a checkbox at its left edge. Selecting one or more findings reveals the bulk action bar above the list:
- Accept all — flips selection to
accepted - Reject all — flips selection to
rejected - Mark refined — flips selection to
refined - Clear selection — empties the selection set
A confirmation dialog appears before the call fires (Accept 47 findings?). Following the action, a banner reports the outcome (accept → 47/50 · 3 failed).
The Select all visible button in the filter bar selects every finding that passes the current filters — useful for "accept every low-severity finding flagged in the last run" without scrolling.
Why per-id results instead of fail-fast¶
A 200-id bulk could fail on the 50th id (e.g., a stale UI showing a deleted finding). Aborting at id 50 would leave 49 already-flipped findings in the new state and 150 untouched, with no clean way for the partner to recover or retry. Per-id outcomes mean the operation is commutative: the partner can re-select the failures and try again, or just dismiss the banner.
The store-level update_status is itself idempotent — applying accepted to an already-accepted finding is a no-op. So a retry of the whole batch (failures + successes) is also safe.
Files¶
app/auditforge_endpoints.py—POST /findings/bulk-actionhandler +BulkFindingActionRequestfrontend/src/api/auditforge.ts—bulkFindingActiontyped clientfrontend/src/components/FindingList.tsx— checkbox column + select-all buttonfrontend/src/components/EngagementDetail.tsx— selection state + bulk action bartests/test_auditforge_endpoints.py— 8 endpoint tests covering auth, role gating, partial failure, validation