- Shipped
- August 12, 2026 at 6:22 AM UTC
- Author
- Kamo
- Commit
- 917dd34
A team member whose employment is not ACTIVE was refused at the door with "Your account exists but is not active at this time." That was the wrong shape. A terminated employee has a statutory right to their own wage records — California Labor Code 226(c) gives 21 days and a per-violation penalty after that — and an ex-employee who cannot sign in has no way to exercise it. Their compliance acknowledgements are the same story. They now sign in and land in MemberCapabilities.restricted: a strictly tighter tier than unlicensed and orthogonal to it. Unlicensed means the org has not paid for you; restricted means it no longer employs you. What survives is what they have a right to reach after separation — own timecards, own compliance record, own profile, own billing, support. Chat is the one floor capability this tier takes away, because a former employee holding a live line into the company's internal conversation is a different problem from one who cannot send an email. canViewOwnTimecards and canViewOwnLegalCompliance are never withheld in any tier: the people most likely to need their pay records are the ones who just left. The record-active check still REFUSES. A soft-deleted member is not a former employee, it is a member who no longer exists. Three gates opened, one deliberately left closed: - SecurityController login now refuses only on record-active - SessionRefreshController no longer kills an off-boarded session on the next poll, which would have made the access last about a minute - DeviceAuthController matches, so a terminated employee can reach their records from the phone they already had signed in - EnterAs and Impersonation are unchanged: those are admin paths with their own semantics and widening them is not what this is about restrictedIfNotEmployed fails OPEN, and it is the only thing on this path that does. Every other eligibility check fails closed because admitting an unknown member is the dangerous direction. Here it inverts: this runs on every entitlement read for every member, and a repository blip would strip chat from an entire org of perfectly employed people. The login gate upstream already decided whether the session exists. Also adds PayrollRelayController — the authorization boundary for pay periods, payroll export, break compliance, leave and job costing. FINALIZE_PAY_PERIODS gates anything touching the payroll file (committing one pays a company), MANAGE_TIMECARDS gates the HR surfaces, and the /me/** endpoints take no member id at all so they are structurally incapable of acting on someone else — which matters most for break attestation, an employee's own statement about their own day, and for a restricted member who holds no rights whatsoever.