Files
multica/server/pkg/db/queries/invitation.sql
T
da7131ab59 MUL-6625: enforce prepaid member seat capacity (#7491)
* MUL-6625: enforce prepaid member seat capacity

Co-authored-by: multica-agent <[email protected]>

* fix(seat-capacity): address review findings

Co-authored-by: multica-agent <[email protected]>

* fix(seat-capacity): close retry and worker races

Co-authored-by: multica-agent <[email protected]>

* fix(seat-capacity): bound lock waits during rollback

Co-authored-by: multica-agent <[email protected]>

* fix(seat-capacity): pin the cross-repo confirm settlement budget

Multica Cloud must wait longer than this worker's whole retry budget before it
treats a member row as one its ledger never recorded; adopting a member whose
confirm is still retrying counts one person as two seats, and an over-estimate
is what asks a customer to buy seats they do not owe.

That budget was only implicit here — 10 attempts of exponential backoff plus a
tick and a lock wait each — and Cloud's horizon had been set from a single
retry. Extract the backoff into retryBackoff, expose SettlementBudget, and
assert the worst case (29m15s) stays inside Cloud's 45m horizon, so raising
maxAttempts or the backoff cap fails review here instead of widening the window
in which Cloud cannot quantify a ledger.

Co-authored-by: multica-agent <[email protected]>

* Revert "fix(seat-capacity): pin the cross-repo confirm settlement budget"

This reverts commit d0ec36f647.

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-08-25 12:51:44 +08:00

64 lines
2.0 KiB
SQL

-- name: CreateInvitation :one
INSERT INTO workspace_invitation (id, workspace_id, inviter_id, invitee_email, invitee_user_id, role, expires_at)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING *;
-- name: GetInvitation :one
SELECT * FROM workspace_invitation
WHERE id = $1;
-- name: ListPendingInvitationsByWorkspace :many
SELECT wi.*,
u.name AS inviter_name,
u.email AS inviter_email
FROM workspace_invitation wi
JOIN "user" u ON u.id = wi.inviter_id
WHERE wi.workspace_id = $1 AND wi.status = 'pending' AND wi.expires_at > now()
ORDER BY wi.created_at DESC;
-- name: ListPendingInvitationsForUser :many
SELECT wi.*,
w.name AS workspace_name,
u.name AS inviter_name,
u.email AS inviter_email
FROM workspace_invitation wi
JOIN workspace w ON w.id = wi.workspace_id
JOIN "user" u ON u.id = wi.inviter_id
WHERE wi.status = 'pending'
AND (wi.invitee_user_id = $1 OR wi.invitee_email = $2)
AND wi.expires_at > now()
ORDER BY wi.created_at DESC;
-- name: AcceptInvitation :one
UPDATE workspace_invitation
SET status = 'accepted', updated_at = now()
WHERE id = $1 AND status = 'pending'
RETURNING *;
-- name: DeclineInvitation :one
UPDATE workspace_invitation
SET status = 'declined', updated_at = now()
WHERE id = $1 AND status = 'pending'
RETURNING *;
-- name: RevokeInvitation :execrows
DELETE FROM workspace_invitation
WHERE id = $1 AND status = 'pending';
-- name: GetPendingInvitationByEmail :one
SELECT * FROM workspace_invitation
WHERE workspace_id = $1 AND invitee_email = $2 AND status = 'pending' AND expires_at > now();
-- name: ExpireStalePendingInvitations :many
-- Mark any past-due pending invitations for (workspace_id, invitee_email) as expired,
-- so the next CreateInvitation does not collide with the partial unique index
-- idx_invitation_unique_pending (which is WHERE status = 'pending' and cannot
-- itself reference now() in its predicate).
UPDATE workspace_invitation
SET status = 'expired', updated_at = now()
WHERE workspace_id = $1
AND invitee_email = $2
AND status = 'pending'
AND expires_at <= now()
RETURNING *;