---
id: STATE-PFM-SEC-005
title: Invitation
object: DOM-PFM-SEC-003
product_area: platform
module: security
revision: 1
status: draft
design_intent: proposed
implementation_evidence: observed
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
states:
- name: issued
  meaning: The invitation record exists before a usable token digest has been issued.
  provenance: observed
  source: 'services-platform at ad3f6a65: modules/security/db/user-invitations.table.ts; services/persistence/invitations.ts;
    services/invitations/invitation-lifetime.ts, resend-invitation.ts, accept-invitation.ts; services/provisioning/provision-user.ts.'
- name: live
  meaning: A usable token digest has been issued; e-mail delivery can be sent, withheld or failed independently.
  provenance: observed
  source: services-platform:modules/security/services/persistence/invitations.ts:issueInvitationToken
- name: expired
  meaning: The validity deadline passed. Time-based refusal does not remove the outstanding row.
  provenance: observed
  source: services-platform:modules/security/services/invitations/invitation-lifetime.ts
- name: accepted
  meaning: Conditional consumption succeeded and activated the membership.
  provenance: observed
  source: services-platform:modules/security/services/persistence/invitations.ts:acceptInvitation
- name: superseded
  meaning: A replacement invitation invalidated this row’s link.
  provenance: observed
  source: services-platform:modules/security/services/persistence/invitations.ts:supersedeOutstandingInvitations
- name: revoked
  meaning: Withdrawal invalidated the invitation link.
  provenance: observed
  source: services-platform:modules/security/services/persistence/invitations.ts:revokeOutstandingInvitations
transitions:
- from: issued
  to: live
  trigger: The post-commit provisioning step issues a token before attempting delivery.
  guard: The pool account has been prepared; delivery-policy approval is checked after token issuance.
  side_effects:
  - Store the token digest and record the delivery attempt separately
  who: Platform System (inside the invite or resend request, after commit)
  provenance: observed
  source: services-platform:modules/security/services/persistence/invitations.ts:issueInvitationToken
- from: live
  to: accepted
  trigger: The person accepts with password or finishes provider linking.
  guard: The invitation is unexpired, unaccepted, unrevoked and not superseded; the conditional write must
    match.
  side_effects:
  - Activate the membership and pending assignments
  who: The invited person
  provenance: observed
  source: services-platform:modules/security/services/persistence/invitations.ts:acceptInvitation
- from: live
  to: expired
  trigger: The 168-hour validity interval ends.
  guard: Use-time evaluation checks the deadline; no expiry job is required.
  side_effects:
  - Refuse the unusable token and require a replacement invitation
  who: nobody (clock)
  provenance: observed
  source: services-platform:modules/security/services/invitations/invitation-lifetime.ts
- from: live
  to: superseded
  trigger: An administrator resends a usable invitation.
  guard: The membership remains invited.
  side_effects:
  - Replace the invitation in one transaction
  who: Customer Administrator or Rhythm staff
  provenance: observed
  source: services-platform:modules/security/services/persistence/invitations.ts:supersedeOutstandingInvitations
- from: expired
  to: superseded
  trigger: An administrator resends an expired invitation.
  guard: The membership remains invited.
  side_effects:
  - Create a replacement with a fresh seven-day deadline
  who: Customer Administrator or Rhythm staff
  provenance: observed
  source: services-platform:modules/security/services/invitations/resend-invitation.ts
- from: issued
  to: superseded
  trigger: An administrator replaces an invitation that has not received a usable token.
  guard: The membership remains invited.
  side_effects:
  - Create a new invitation and run post-commit preparation
  who: Customer Administrator or Rhythm staff
  provenance: observed
  source: services-platform:modules/security/services/invitations/resend-invitation.ts
- from: live
  to: revoked
  trigger: Membership suspension or revocation withdraws the invitation.
  guard: The membership capability controls withdrawal.
  side_effects: []
  who: Customer Administrator or Rhythm staff
  provenance: observed
  source: services-platform:modules/security/services/persistence/invitations.ts:revokeOutstandingInvitations
invalid:
- from: accepted
  to: live
  why: An accepted link is not reopened. Replay for an already active membership returns the earlier success
    without another activation.
  provenance: observed
  source: 'services-platform at ad3f6a65: modules/security/db/user-invitations.table.ts; services/persistence/invitations.ts;
    services/invitations/invitation-lifetime.ts, resend-invitation.ts, accept-invitation.ts; services/provisioning/provision-user.ts.'
- from: expired
  to: accepted
  why: Expiry is checked when the token is used and produces the neutral unusable-token refusal.
  provenance: observed
  source: 'services-platform at ad3f6a65: modules/security/db/user-invitations.table.ts; services/persistence/invitations.ts;
    services/invitations/invitation-lifetime.ts, resend-invitation.ts, accept-invitation.ts; services/provisioning/provision-user.ts.'
- from: revoked
  to: live
  why: Reinstatement cannot revive a withdrawn invitation; a deliberate replacement is needed.
  provenance: observed
  source: 'services-platform at ad3f6a65: modules/security/db/user-invitations.table.ts; services/persistence/invitations.ts;
    services/invitations/invitation-lifetime.ts, resend-invitation.ts, accept-invitation.ts; services/provisioning/provision-user.ts.'
concurrency: A unique index admits one outstanding invitation, including an expired row until replacement.
  Conditional token consumption allows one database acceptance winner. Resend invalidates earlier invitations
  regardless of which e-mail finishes sending last; validity follows the current invitation and digest, not
  delivery order. Provider work precedes acceptance’s database transaction and is not atomically protected
  by token consumption.
invariants:
- INV-DOM-PFM-SEC-003-03
- INV-DOM-PFM-SEC-003-04
evidence:
- 'services-platform at ad3f6a65: modules/security/db/user-invitations.table.ts; services/persistence/invitations.ts;
  services/invitations/invitation-lifetime.ts, resend-invitation.ts, accept-invitation.ts; services/provisioning/provision-user.ts.'
examined_at: '2026-10-23'
examiner: codex
---

# Invitation

## The Lifecycle

```mermaid
stateDiagram-v2
    state "Issued" as issued
    state "Live" as live
    state "Expired" as expired
    state "Accepted" as accepted
    state "Superseded" as superseded
    state "Revoked" as revoked
    issued --> live
    live --> accepted
    live --> expired
    live --> superseded
    expired --> superseded
    issued --> superseded
    live --> revoked
```

The diagram summarizes the observed transition rows; declared edges without an observed writer remain outside the operational graph.

## Transitions Explained

These are interpreted invitation states, combining token presence, timestamps and clock evaluation. Issuing a digest creates a potentially usable link before e-mail delivery is known.

Delivery outcome is separate from invitation usability. Concurrent resends can deliver obsolete links, and the last delivered message need not carry the valid token. Acceptance’s provider call also lies outside the database consumption transaction.

## What Cannot Happen

An accepted link is not reopened. Replay for an already active membership returns the earlier success without another activation.

Expiry is checked when the token is used and produces the neutral unusable-token refusal.

Reinstatement cannot revive a withdrawn invitation; a deliberate replacement is needed.

## Concurrency and Timing

A unique index admits one outstanding invitation, including an expired row until replacement. Conditional token consumption allows one database acceptance winner. Resend invalidates earlier invitations regardless of which e-mail finishes sending last; validity follows the current invitation and digest, not delivery order. Provider work precedes acceptance’s database transaction and is not atomically protected by token consumption.
