- Shipped
- August 7, 2026 at 1:35 AM UTC
- Author
- Kamo
- Commit
- c491a1d
The member surface asked for MAX_SIZE unconditionally and sliced the result in the browser, so a grid that showed a page had already downloaded a whole employment history to build it. Both member readers now take page/size and the listing pages in the database. Spec.status (one enum) becomes Spec.statuses (a Set) and the wire parameter stays `status`, now accepting a CSV. That is the point: the member grid asks for a BUCKET — the three OPEN_STATES that still owe work, or the four terminal ones — and a single-valued filter cannot express either without three round trips per page. One token parses to a one-element set, so the HR grid's existing status=EXECUTED is byte-identical on the wire and needed no client change. An unknown token in a list is a 400 naming the token, never a silently dropped filter: a compliance grid that claims to be filtered while showing everything is the worst available outcome. clampPage/clampSize are exposed so the member readers clamp through the SAME rule as the HR grid instead of passing MAX_SIZE literally, which is how a "paged" endpoint quietly becomes an unbounded one. listMine forwards its paging without re-deriving it, so the clamp exists once. Authorization is untouched and the tests pin it: member endpoints stay on memberGuarded() + LegalAssignmentOwnership, HR endpoints stay on guarded() + LegalPackageAccess, and the three new @RequestParams are asserted by name to be exactly status/page/size — the whole reason this surface is safe is that nothing on it names a person. No schema change. No new entity. mvn -o test: 391 tests, 0 failures, 0 errors (was 379).