* feat(white-label): WL-14 cockpit landing for BankID and OAuth/magic-link logins
Byra staff logging in via BankID or the Google/magic-link callback on
their brand domain landed on /select-company resp. / instead of the
cockpit, because those two paths bypassed the WL-14 landing rule.
- Extract the rule into resolveLandingDestination
(lib/company/landing-server.ts) so server code can call it without an
HTTP round-trip; /api/clients/landing becomes a thin wrapper.
- Auth callback: with no explicit destination, AAL1 sessions resolve the
landing from the request host, degrading to / on any failure
(MFA-enrolled users already get the rule via /mfa/verify).
- BankID login: byra staff on their brand host get /clients; everyone
else keeps the deliberate /select-company picker byte-identically.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(white-label): address PR 1972 review findings
- /api/clients/landing: requireAuth() directly instead of
withRouteContext, which 4xxed byra staff without a company of their
own (COMPANY_CONTEXT_MISSING) and silently sent the cockpit's primary
persona to /select-company. MFA enforcement unchanged.
- landing-server: log the byra membership query error before degrading
to '/' so a persistent failure is distinguishable from no membership.
- Deduplicate the clientWithTeamMembership test mock to file scope.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(white-label): paginate the byra membership query
fetchAllRows per repo convention: PostgREST silently caps unpaginated
selects at 1000 rows, which could hide a qualifying owner/admin
membership. Errors still degrade to '/' with a log.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(byra): gate the automatic cockpit landing to owner/admin
Plain byra members now land like regular users; owner/admin keep the
cockpit landing at both decision sites (post-login /api/clients/landing
and the '/' bounce). The middleware zero-company steer stays ungated:
a member with zero companies has nowhere else to land. Cockpit access
itself is unchanged (nav + /clients remain membership-based).
Supersedes the 2026-08-05 all-members widening (DECISIONS.md).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(byra): keep byra members out of the first-run wizard on auto-landing
Skeptic finding: a member whose auto-resolved active company is
onboarding-incomplete (e.g. mid migration-reset, which repoints
active_company_id itself) fell through the new role gate into
/onboarding, a dead end for role member (WL-15 refuses client
creation). Byra members without a picked-company cookie now go to
/byra at the onboarding check, restoring the pre-gate shield.
Also pins the role column into the landing route's select assertion
so dropping it can't pass the mocked tests silently.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>