# Phase 19 completion — administration operations

Release **0.19.1**, 2026-09-12. Entry: `/admin`.

The five feature gaps recorded in the 0.19.0 checkpoint are implemented in this release. The admin catalog now contains 23 business workspaces, 11 operational workspaces and Overview. Phase 20 remains deployment and production acceptance; the five features below are no longer deferred to that phase.

## Completed workflows

| Workflow                 | Delivered behavior                                                                                                                                                                                                    |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Administrators and roles | Invite, renew/cancel invitation, independent password/TOTP enrollment, one-time recovery codes, status/role changes, session revocation, custom roles and permission editing with named selectors.                    |
| User restrictions        | Account, earning, withdrawal and referral restrictions; optional expiry; scope-specific unban; session revocation; account ban/unban uses the existing transactional notification outbox.                             |
| Fraud review             | Audited evidence reveal with secret redaction; observing, held, cleared or confirmed decisions; version checks; final resolutions cannot be overwritten.                                                              |
| Wallet adjustments       | Exact minor-unit correction request, durable request key, different reviewer, approval/rejection, immutable double-entry ledger posting and insufficient-funds protection. Held withdrawal accounts cannot be edited. |
| Failed jobs              | Worker writes linked failure records atomically; eligible delivery gets one additional queued attempt; original payload, deduplication key and attempt history are preserved.                                         |

## Access and transaction controls

Every new mutation passes existing administrator authentication, IP policy, CSRF, sensitive rate limiting, exact route permission and strict body validation. The service then reacquires a transactional administration lock, verifies that the actor's session is still active, and reloads their permissions before changing data. An audit record is committed in the same transaction. Stale versions require a refresh.

Administrator/role management additionally requires membership in `super_admin`. An administrator cannot edit their own status/roles, built-in roles are immutable, and a role assigned to the actor cannot be edited by that actor. These rules preserve an active controlling administrator. Changing an administrator's access or a custom role's grants revokes affected sessions. Other already-running business requests retain their existing authorization behavior; this release does not claim universal cancellation of in-flight work.

Invitation tokens are 256-bit random values stored only as SHA-256 hashes, expire in 24 hours and are consumed on setup. The invitee supplies their own password. An encrypted authenticator secret and a separate 30-minute enrollment token are created; a valid TOTP is required before activation. Five failed codes exhaust enrollment. Only recovery-code hashes are persisted. Cancelled or expired setup can be renewed by a super administrator. Invite tokens are never put in URLs or browser storage, and the application does not send invitations automatically.

To enroll, choose **Accept invitation** on the sign-in page, enter the private invitation and a new password, add the displayed secret as a time-based authenticator account, then verify the code. Save the recovery codes before closing. Switching apps hides the authenticator setup key while retaining only the short-lived enrollment token in memory, so the invitee can return with a TOTP code. Closing or reloading incomplete setup requires invitation renewal. Recovery codes are cleared when hidden after enrollment. After activation, sign in with a fresh authenticator code.

Account restrictions use `user_bans`; timed bans do not permanently set `users.status = banned`. Removing one scope preserves other restrictions. Existing legacy `banned` status can be restored through account unban. Account bans revoke current user sessions. Ban/unban notification enqueueing respects the existing template and recipient-consent rules; it does not send messages during the HTTP transaction.

A fraud flag decision is an investigative record. It does not silently approve a withdrawal, release held money, change a referral reward or ban a user. Those actions retain their dedicated permission and review workflows.

Wallet requests cannot update balances directly. Approval locks the request/user and calls the existing ledger within the same transaction, using a deterministic transaction reference and idempotency key. The requester cannot review their own correction, including when both administrators are super administrators. A rejected or already-reviewed request cannot post funds. `wallet.adjust.approve` is granted to the super-admin role by migration; custom role grants remain explicit.

Manual retry requires `jobs.retry`, a reason and acknowledgement that Telegram may deliver a duplicate after an uncertain provider outcome. Published, cancelled, processing, stale, unlinked and permanently rejected deliveries are refused. A matching recorded failure attempt is required. Retry never edits the message payload or resets its attempt counter; it allows exactly one further attempt after 60 seconds, with a lifetime cap of 100 attempts. Reminder/broadcast eligibility, consent and schedule state are checked again before requeueing. Previously cancelled broadcasts stay cancelled in delivery rollups. Telegram sending remains an at-least-once boundary; no exactly-once provider delivery claim is made.

## API additions

All management routes below are under `/api/v1/admin/console`. Existing read routes continue to serve the catalog and tables.

| Route                                                               | Permission                                          |
| ------------------------------------------------------------------- | --------------------------------------------------- |
| `POST /administrators`                                              | `administrators.manage` plus super-admin membership |
| `PATCH /administrators/:id`                                         | `administrators.manage` plus super-admin membership |
| `POST /administrators/:id/invitation`                               | `administrators.manage` plus super-admin membership |
| `POST /administrators/:id/cancel-invitation`                        | `administrators.manage` plus super-admin membership |
| `POST /administrators/:id/revoke-sessions`                          | `administrators.manage` plus super-admin membership |
| `POST /roles`, `PATCH /roles/:id`                                   | `roles.manage` plus super-admin membership          |
| `GET /access-options`                                               | `administrators.view`                               |
| `POST /users/:id/ban`                                               | `users.ban`                                         |
| `POST /users/:id/unban`                                             | `users.unban`                                       |
| `POST /users/:id/revoke-sessions`                                   | `users.sessions.revoke`                             |
| `GET /fraud-monitor/:id/evidence`, `POST /fraud-monitor/:id/review` | `fraud.review`                                      |
| `GET /wallet-adjustments`                                           | `wallet.view`                                       |
| `POST /wallet-adjustments`                                          | `wallet.adjust`                                     |
| `POST /wallet-adjustments/:id/review`                               | `wallet.adjust.approve`                             |
| `POST /failed-jobs/:id/retry`                                       | `jobs.retry`                                        |

Public enrollment routes are `GET /api/v1/admin/auth/enrollment/csrf`, `POST /api/v1/admin/auth/enrollment/start`, and `POST /api/v1/admin/auth/enrollment/finish`. They retain IP/rate policies, and both POST routes require CSRF plus the relevant one-time invitation/enrollment token. They cannot accept role assignments, status or actor IDs from the invitee.

## Upgrade from 0.19.0

1. Back up the current database and application. Drain/stop web, worker and scheduler processes before migration.
2. Install the complete 0.19.1 source and run `npm ci` with optional native dependencies enabled. Keep the existing deployment environment and secrets.
3. Run `npm run migrate:dry-run`, then `npm run migrate`. Apply **023_complete_admin_operations.sql** through the existing runner; do not edit or reapply migrations 001–022. On a new installation, also run the existing seed workflow and first-super-admin setup.
4. Run `npm run build` and restart the web, worker and scheduler. Check readiness before reopening access.
5. Sign in as a super administrator, provision a second independent reviewer, and review custom-role grants for `wallet.adjust.approve` and `jobs.retry` as appropriate.

No new environment variable or dependency was introduced. The migration adds two tables, invitation/enrollment/version fields, linked failed-job records and two permissions. Existing terminal outbox failures are backfilled into the failure inventory; records without matching attempt evidence remain ineligible for retry. Do not roll back schema 023 after storing live corrections/enrollments without a coordinated backup/restore plan.

## Verification and Phase 20 boundary

**Final gate: PASS — 414/414 tests, zero failures or skips.** Lint, formatting, the environment example, schema checks, both CSS builds and project structure passed. The schema contains 23 migrations, 6 seeds, 77 tables and 259 named constraints.

See `PHASE-19-COMPLETE-QUALITY-GATE.json` and `PHASE-19-COMPLETE-QUALITY-CHECK.txt` for the executed final gate. Tests cover permission/CSRF denial, onboarding, token consumption, TOTP attempt limits, self-access protection, atomic audit, scoped restrictions, competing fraud/adjustment decisions, exact money values, insufficient funds, retries, UI forms and secret clearing, plus the earlier feature suites.

HTTP tests use injected services/repositories; domain concurrency tests use serialized in-memory transactions; DOM tests use jsdom. These verify application contracts, not live MySQL locking or native browser layout. Live MySQL/Redis migration and contention acceptance, browser/device and accessibility QA, real Telegram integration, deployment/load/backup testing remain Phase 20. No real provider payment, broadcast, invitation or Telegram message was sent during this work.

The previous `PHASE-19-ADMIN-PANEL-REPORT.md` and 0.19.0 quality files remain historical checkpoint evidence. This report and the complete quality gate describe the current release. The original free-only mystery reward rules remain unchanged.
