- Shipped
- August 20, 2026 at 11:34 PM UTC
- Author
- Kamo
- Commit
- 18d2387
GET /api/security/leads returned every lead in the organisation. It now takes page/size/sort plus the filters the grid actually uses, and returns a page with a total. Measured on production 2026-08-20: 1,427 leads fetched and audited so that seven could be displayed, 23.3 s a load, of which 23.3 s was the audit loop. That loop is why paging matters more than it looks: recordLeadList writes one phi_access_log row per lead RETURNED, so a 50-row page writes 50 rows. It now hands the batch to recordAll (one transaction, not 1,427), and the writer is wrapped in AsyncPhiAccessLogWriter so list and view records leave the request thread. Fail-closed kinds still write synchronously and can still refuse the read; destroyMethod drains the queue so a rolling restart does not discard the record of reads that already happened. VIEW_UNASSIGNED_LEADS moves from a post-filter on the materialised list into the query, so the pager's total counts only rows the caller may see and no query-string value can widen it. assigned=ME resolves from the session for the same reason. Filters arrive as one bound LeadGridParams rather than twenty-two @RequestParams: in this codebase a signature change is the highest-risk edit there is, because it compiles everywhere except the tests and a service whose tests do not compile never builds an image. LeadGridSpecificationSqlTest compiles the specification against real Hibernate metadata on 6.2.13 — the version this service runs, not the 6.5 the library builds on. It caught the paths; the controller tests mock the service and never touch them. Config: jdbc.batch_size + order_inserts so the batched audit write is one round trip, and default_batch_fetch_size for Lead's EAGER associations. open-in-view is now stated explicitly as true — several comments in this service assert it is off and they were wrong. The leads path no longer depends on it either way; turning it off globally needs an endpoint-by-endpoint audit and is deliberately not done here. Also: System.out.println removed from the request path, including a full header dump that was printing live session tokens to stdout on every lead read. Error paths keep their throwable at ERROR. docs/phi-access-log-growth.md records the retention and index review: all three secondary indexes back a documented compliance query and are kept; the growth problem was the row count, which paging fixes.