Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt

Use this file to discover all available pages before exploring further.

Module: Platform Foundation (PF-02) Audience: Organization administrators and platform admins Last updated: 2026-04-30 This guide describes how users are added, invited, and removed from an organization in Encore Health OS, and which audit fields are recorded for each lifecycle event.

1. The three ways to add a user

Settings → User Management exposes a single Add User menu with three options. Pick the one that matches the situation:
OptionWhen to useResult
Invite by email (recommended)The person does not yet have an Encore account.Sends a branded invitation email with a magic link; row added to pf_user_invitations with status pending.
Add existing Encore userThe person already has an Encore profile (e.g. they belong to a sister organization on the same platform).Sends a magic link and grants access immediately on first sign-in.
Create with temporary password (Legacy)Air-gapped onboarding where email cannot be used.Creates the auth user with a temporary password the admin must convey out-of-band. Discouraged — prefer invite-by-email so the user sets their own credentials.
The role catalog presented in all three flows comes from ASSIGNABLE_TENANT_ROLES in src/shared/lib/valid-roles.ts. Legacy module-specific roles (e.g. hr_admin, fm_technician) are intentionally filtered out of this primary flow; they will be retired in a future release (see Phase C of the user-management modernization plan).

2. Invitation lifecycle

 pending ──► accepted   (user signed in via magic link)

    ├────► expired      (passed expiry date)

    └────► revoked      (admin cancelled before acceptance)
The pf_user_invitations table tracks every transition. Rows are never hard-deleted; this preserves the audit trail.

Audit columns

ColumnSet whenPurpose
statusAlwaysCurrent lifecycle state. CHECK constraint enforces one of pending, accepted, expired, revoked.
expires_atAt creationDefault 7 days; refreshed on resend.
accepted_at, accepted_byAcceptanceRecords who actually signed in.
revoked_at, revoked_byRevocationRecords the admin who clicked Revoke.
revoked_reasonRevocation (optional)Free-text reason captured in the Revoke dialog (≤ 500 chars).
resent_at, resend_countEach resendUsed to enforce rate limits.

3. Revoking a pending invitation

  1. Open Settings → User Management → Invitations tab.
  2. Click Revoke on the row.
  3. Optionally enter a reason (recommended for compliance — examples: “sent to wrong address”, “role change”, “no longer needed”).
  4. Confirm.
The action calls the revoke_invitation(_invitation_id, _reason) RPC. The RPC is a SECURITY DEFINER function and only succeeds when the caller has admin access to the invitation’s organization (enforced by RLS on the underlying tables). Revoked invitations remain visible in admin queries but cannot be accepted — the magic-link flow rejects any token whose row is not in pending state.

4. Resending an invitation

  1. Open Settings → User Management → Invitations tab.
  2. Click Resend on the row.
Behind the scenes the resend-invitation-email Edge Function:
  • Verifies the caller has org admin access.
  • Refreshes expires_at (rolling 7-day window from the resend time).
  • Increments resend_count and stamps resent_at.
  • Re-sends the branded email through the shared invitation-mailer.ts.

Rate limits

LimitDefaultBehaviour when hit
Cooldown between resends per invitation60 secondsToast warning: “Please wait before resending again.”
Maximum resends per invitation per 24 h5Toast warning: “Resend limit reached for this invitation.”
These thresholds are applied inside the Edge Function and do not require any client-side bookkeeping.

5. Permissions reference

ActionRequired role / permission
View user listOrg admin, site admin, or any user with pf.users.view
Invite by emailOrg admin or site admin
Add existing userOrg admin or site admin
Create with temporary passwordOrg admin (and the action is gated to be removed for non-platform admins in Phase B)
Revoke pending invitationOrg admin or platform admin
Resend invitationOrg admin or platform admin
Manage roles for an existing userOrg admin or platform admin
Reset another user’s passwordOrg admin or platform admin
Phase B of the modernization plan introduces dedicated permission keys (pf.users.invite, pf.users.create_direct, pf.users.revoke) so admins can delegate individual capabilities without granting full org admin.
ConcernLocation
Page entry pointsrc/platform/users/UserManagement.tsx
Invite dialogsrc/platform/users/InviteUserDialog.tsx
Add-existing dialogsrc/platform/users/AddExistingUserDialog.tsx
Legacy direct-create dialogsrc/platform/users/CreateUserDirectDialog.tsx
Revoke confirm dialogsrc/platform/users/RevokeInvitationDialog.tsx
Role catalogsrc/shared/lib/valid-roles.ts (ASSIGNABLE_TENANT_ROLES)
Invitation mailer (shared)supabase/functions/_shared/invitation-mailer.ts
Send invitation Edge Functionsupabase/functions/send-invitation-email/index.ts
Resend invitation Edge Functionsupabase/functions/resend-invitation-email/index.ts
Revoke RPCmigration 20260430004002_* (public.revoke_invitation)
Lifecycle columns migrationmigration 20260429194049_*

7. Roadmap

The following improvements are tracked under the user-management modernization plan:
  • Phase B (Medium-Term): unified Add People dialog, server-side pf_assignable_roles table, role-permission preview, hardened route guards on /settings/users.
  • Phase C (Long-Term): Postgres state machine (pf_transition_invitation_status), hourly expire-invitations cron, full pf_audit_logs integration, domain events (user.invited, user.invitation_revoked, user.invitation_accepted, user.created), retirement of legacy module-roles from pf_user_role_assignments.system_role, HMAC-signed invitation tokens.