# AccountIdentity Cutover Runbook

## Scope and current architecture

AccountIdentity is the canonical authentication identity for newly linked
accounts. Client and Provider remain role profiles. The transition is dual-stack:

- Legacy JWTs resolve the profile from `sub + userType` and remain role-pinned.
- Account JWTs resolve AccountIdentity from `sub`, validate its token version,
  read the database `activeMode`, and then load the current role profile.
- `req.user` remains the Client/Provider profile.
- `req.authAccount` is the linked AccountIdentity when available.
- `req.userType` remains `client` or `provider`.
- Pending, rejected, disabled, blocked, deleted, or unlinked provider state never
  grants operational Provider access.

No migration, backfill, or automatic link is part of this cutover.

## Provider approval bridge

Dashboard approval keeps two explicit paths:

- A legacy ProviderMeta without `accountIdentity` continues to require and
  update its existing Provider row with the same ID.
- An AccountIdentity-backed pending ProviderMeta does not require a Provider
  beforehand. Accepting it creates one allowlisted Provider profile, promotes
  its upload folder, links `AccountIdentity.providerProfile`, and accepts the
  request in one MongoDB transaction. Provider-only activated identities move
  from `provider_pending` to `provider`; hybrid identities remain in Client
  mode until an explicit switch.
- Rejecting an AccountIdentity-backed request updates only the request inside a
  transaction. It never creates a Provider, never changes a password, and never
  grants Provider operational access.

The bridge does not auto-link any unlinked legacy record.

## Feature flags

| Flag | Default | OFF | ON |
|---|---:|---|---|
| `AUTH_ACCOUNT_IDENTITY_SIGNUP_ENABLED` | `false` | Existing signup writes remain unchanged. | New signups only dual-write AccountIdentity and their Client or pending ProviderMeta profile. Existing records are not changed. |
| `AUTH_ACCOUNT_IDENTITY_TOKEN_ENABLED` | `false` | Existing profile JWT issuance remains unchanged. | Linked, active accounts receive opaque AccountIdentity-subject JWTs. Legacy JWTs remain resolvable. |

Neither flag may be enabled by a code default. Environment rollout is the only
approved activation mechanism.

## Contract inventory

| Area | Method and path | Contract note |
|---|---|---|
| Auth | `POST /api/signin` | Legacy/account issuance follows the token flag; envelope unchanged. |
| Auth | `PATCH /api/send-code` | Activation or forgot-password OTP. |
| Auth | `PATCH /api/forget-password` | Existing compatibility recovery-code endpoint. |
| Auth | `PATCH /api/activate` | Activates an account or verifies recovery/change-phone OTP. |
| Auth | `PATCH /api/change-password` | Consumes the recovery reset token. |
| Profile security | `POST /api/change-phone/verify-password` | Uses the canonical password for linked accounts. |
| Profile security | `PATCH /api/update-password` | Advances invalidation metadata and revokes linked sessions. |
| Account roles | `POST /api/account/provider-request` | Adds only a pending ProviderMeta request. |
| Account roles | `POST /api/account/client-role` | Adds a Client profile to the same identity. |
| Account roles | `POST /api/account/switch-mode` | Changes only `activeMode`; creates no role. |
| Profile/Home | `GET /api/profile` | Returns the current profile and additive `accountMode` when linked. |
| Profile/Home | `GET /api/client/home` | Guest/client contract; provider mode is not accepted. |
| Profile/Home | `GET /api/provider/home` | Accepted active Provider only. |
| Dashboard | `PUT /dashboard/clients/edit/:id` | Existing admin Client edit/reset behavior. |

There is no dedicated Provider admin password-reset endpoint. The existing
`PUT /dashboard/providers/edit/:id` contract accepts profile fields only.

## QA state matrix

The release gate covers:

1. Client only.
2. Provider pending only.
3. Provider accepted only.
4. Hybrid Client + Provider pending.
5. Hybrid Client + Provider accepted.
6. Provider rejected.
7. Provider disabled.
8. Provider blocked.
9. Account blocked/deleted.
10. Legacy Client without AccountIdentity.
11. Legacy Provider without AccountIdentity.
12. D2 conflicts remain unlinked.

The focused D1-D7 tests cover signup, role-add, accountMode, switching,
password ownership/invalidation, legacy JWT compatibility, account JWT
resolution, profile IDs, and provider operational denial.

## D2.1 audit snapshot

The latest read-only masked dry-run report was generated on
`2026-07-29T13:32:55.434Z`:

- Clients scanned: 1
- Providers scanned: 1
- ProviderMeta records scanned: 1
- Safe automatic candidates: 0
- Manual review: 1
- Invalid identities: 0
- Collision class: `MANUAL_REVIEW_PHONE_EMAIL_CONFLICT` (1)

The report is stored under
`artifacts/account-identity-audit/account-identity-audit-report.json`, is masked,
and the entire `artifacts/` directory is ignored by Git. This snapshot is not an
authorization to migrate or link that record.

## Rollout

1. Deploy with both flags OFF.
2. Verify legacy signup, signin, activation, password reset/change, profile,
   Client Home, and Provider Home.
3. Enable `AUTH_ACCOUNT_IDENTITY_SIGNUP_ENABLED=true` in staging only.
4. Test new Client signup through activation and signin.
5. Test new Provider signup through pending state and confirm Provider Home is
   denied.
6. Test admin approval using the existing approved flow, then role-add and
   switch-mode for eligible profiles.
7. Enable `AUTH_ACCOUNT_IDENTITY_TOKEN_ENABLED=true` in staging only.
8. Run legacy and account JWTs side by side. Confirm `req.user` remains the
   profile and UserToken sessions use the correct owner.
9. Change/reset the shared password and confirm old sessions fail while a new
   signin succeeds.
10. Monitor authentication failures by safe error class only; never log tokens,
    OTPs, passwords, cookies, phone numbers, or emails.
11. Resolve the D2 manual-review collision before any future migration plan.
12. Production flag rollout requires explicit approval after the staging gate.

## Rollback

1. Set `AUTH_ACCOUNT_IDENTITY_TOKEN_ENABLED=false` first to stop issuing new
   account JWTs. Already issued account JWTs remain resolvable during the
   transition, preventing an uncontrolled logout.
2. Set `AUTH_ACCOUNT_IDENTITY_SIGNUP_ENABLED=false` to return new signups to the
   unchanged legacy write path.
3. Do not delete AccountIdentity records or unlink profiles during rollback.
4. Keep the dual-stack resolver deployed so existing legacy and already issued
   account JWTs remain supported.
5. If credential compromise is suspected, use the existing password
   change/reset invalidation path to advance `tokenVersion` and delete linked
   UserToken sessions.
6. Investigate from masked audit/security metadata. Never run a migration,
   backfill, seed, or destructive cleanup as an emergency rollback.

## Release blockers

- Any failing full test, OpenAPI/Postman export, secret scan, syntax check, or
  global `git diff --check`.
- Either feature flag defaulting to true.
- A D2 conflict being linked automatically.
- Account-internal password, OTP/reset hashes, `tokenVersion`, or
  `passwordChangedAt` appearing in a response or generated delivery artifact.
- Pending or non-accepted Provider access to Provider Home.
- Any migration/backfill or production data write.
