- Shipped
- August 12, 2026 at 5:52 PM UTC
- Author
- Kamo
- Commit
- 8eb9f78
Pointing the composer at close-readiness widened privilege. That relay's own mapping guards with FINALIZE_PAY_PERIODS, and resolveRole does not admit MANAGE_TIMECARDS to approval authority at all — not as PAYROLL, not as MANAGER. So a MANAGE_TIMECARDS-only holder, refused both the approve action and the direct read, would have learned the org-wide awaiting-approval count and every member's timecardState through a composite. Raising the whole endpoint to require both rights would be the wrong fix: it would break the deliberate parity between this gate and the /hr/attendance tab gate in hrTabs.ts and 403 a member the UI offered the tab to. Instead the approval half carries a second, narrower gate, in the same shape DocsService uses for a block its caller cannot reach — null, never 403. The controller derives the right from the session and passes a plain boolean; the composer still never sees the request. Without it the relay is NOT ISSUED — computing it and discarding the result would still have read data this caller has no right to. awaitingApproval comes back null rather than 0, no member carries a timecardState, and attention[] holds no APPROVAL rows. null is "not available to you"; 0 is "nothing is waiting", and showing the first as the second would tell an HR admin a queue they cannot see is clear. Everything MANAGE_TIMECARDS is entitled to — hours, on-the-clock, exceptions, tracked headcount, current period — is untouched, and asserted so. Tests assert the NON-INVOCATION of the relay, not merely the output shape.