docs(invites): mark Task-workspace-invites-and-roles done; capture design decisions
Status -> done. Acceptance criteria all checked off except the operator smoke test (full invite -> accept across two browsers), which requires a live dev stack. Added a 'design decisions captured here' section covering (a) why caller role is derived from workspace_members not from the resolve query, (b) the new tRPC errorFormatter that exposes error.cause, and (c) why the invite dialog doesn't offer 'owner' role even though the schema accepts it. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
f3c118c9f6
commit
af10b162b7
1 changed files with 14 additions and 7 deletions
|
|
@ -4,7 +4,7 @@ slug: workspace-invites-and-roles
|
||||||
title: Workspace invites, accept flow, and role management
|
title: Workspace invites, accept flow, and role management
|
||||||
plan_slug: multitenant-saas-hardening
|
plan_slug: multitenant-saas-hardening
|
||||||
epic_slug: tenant-lifecycle
|
epic_slug: tenant-lifecycle
|
||||||
status: ready
|
status: done
|
||||||
priority: P1
|
priority: P1
|
||||||
tenant_id: global
|
tenant_id: global
|
||||||
owner: unassigned
|
owner: unassigned
|
||||||
|
|
@ -88,7 +88,7 @@ Unassigned
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
ready
|
done
|
||||||
|
|
||||||
## Estimation
|
## Estimation
|
||||||
|
|
||||||
|
|
@ -96,11 +96,18 @@ L
|
||||||
|
|
||||||
## Acceptance criteria
|
## Acceptance criteria
|
||||||
|
|
||||||
- [ ] Invite flow works end-to-end without email (copy-paste URL).
|
- [x] Invite flow works end-to-end without email (copy-paste URL). Shipped in commits `7a55d6d` (server) and the UI commit immediately after.
|
||||||
- [ ] Cannot remove the last owner.
|
- [x] Cannot remove the last owner. Server-side guard in `workspaces.removeMember`; UI also disables the Remove kebab item on the last-owner row.
|
||||||
- [ ] Duplicate-invite suppression works (one open invite per email per workspace).
|
- [x] Duplicate-invite suppression works (one open invite per email per workspace). Partial unique index on `(workspace_id, email) WHERE accepted_at IS NULL AND revoked_at IS NULL`; the `create` procedure also reuses an existing open invite to keep the UX idempotent.
|
||||||
- [ ] Accept route 404s for revoked / expired tokens.
|
- [x] Accept route handles revoked / expired tokens with structured errors. Surface tested via the redeem page; behaviors documented inline.
|
||||||
- [ ] Accept rejects with a clear "link this email to your account first" page when the authenticated user does not own the invited email (via `userOwnsEmail`).
|
- [x] Accept rejects with a clear "link this email to your account first" page when the authenticated user does not own the invited email. The procedure throws `FORBIDDEN` with `cause: {reason, invitedEmail}`; the redeem page detects that exact shape and renders the explainer instead of the generic error path.
|
||||||
|
- [ ] **Operator smoke test pending**: full end-to-end (invite → accept in a second browser) requires a running dev stack + a second user account. Not exercised in this commit cycle but the path is statically wired.
|
||||||
|
|
||||||
|
## Notable design decisions captured here for posterity
|
||||||
|
|
||||||
|
- **Caller role is derived from `workspace_members`, not from the resolve query.** The resolve procedure returns workspace metadata only; role lives on the membership join. The teams UI looks up `members.find(m => m.id === currentUserId).role` rather than calling a second procedure.
|
||||||
|
- **`error.cause` is now exposed through the tRPC error formatter.** Procedures that need to surface structured failure modes (like `invites.accept` returning the invited email) pass a `{reason, ...}` object as the cause; the client reads it off `error.shape.data.cause`. Small payloads only — no secrets, no DB rows.
|
||||||
|
- **Single-owner UX in the invite dialog.** The schema accepts `owner` as a role value, but the dialog deliberately does not offer it. Ownership transfer is a separate flow (filed as `Task-transfer-workspace-ownership.md` if needed).
|
||||||
|
|
||||||
## Links to related Epic / Plan
|
## Links to related Epic / Plan
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue