## What
Invitation lookups did not filter on `expiresAt`, so expired invitations
were still treated as active. This aligns them with the sibling
`findInvitationsByEmail`, which already applied that filter.
- `WorkspaceInvitationService.getOneWorkspaceInvitation` - added
`deletedAt IS NULL` and `expiresAt > now` (also converted to a typed
`findOne` so the column references are checked).
- `AuthService.findInvitationForSignInUp` - added `expiresAt > now` (it
already filtered `deletedAt`).
- `throwIfOnboardingInvitationLimitReached` - expired tokens no longer
count toward the onboarding invitation limit.
- `createWorkspaceInvitation` - deletes the expired token for that email
before issuing a replacement, so re-invites don't accumulate stale rows.
## Why
Without the filter, an expired pending invitation behaved as if it were
still active:
- On sign-up with a personal invite token, an expired invitation still
granted access to the workspace.
- Re-inviting an email whose invitation had lapsed reported
`INVITATION_ALREADY_EXIST` instead of sending a fresh invite.
- Expired onboarding invitations still consumed quota, so the limit
could be hit by invitations nobody could use.
Once expired tokens are ignored on read, a re-invite would leave the old
row behind, so `createWorkspaceInvitation` now removes it. The delete is
scoped to the same workspace, invitation token types, that exact email,
and `expiresAt <= now`, so it can only remove tokens that are already
unusable.
Closestwentyhq/private-issues#503
## Tests
Integration suites added, run against a real database:
- `auth/sign-up/failing-sign-up-with-expired-invitation` - expired
personal invitation is rejected (snapshot asserts the specific
`FORBIDDEN` error).
- `auth/sign-up/successful-sign-up-with-valid-invitation` - positive
control: a valid invitation still grants access, so the rejection above
cannot pass for an unrelated reason.
- `expired-workspace-invitation` - re-invite over an expired invitation
succeeds and leaves exactly one (fresh) token; a valid invitation is
still reported as already existing.
Each assertion was verified to fail when its corresponding filter is
removed. Unit tests (`workspace-invitation.service.spec.ts`,
`auth.service.spec.ts`), typecheck, and lint all pass.
Not included: invitations that expire and are never re-invited still
linger, since no cron reaps invitation tokens today.