# Web Runtime Operations

## Purpose

The protected Express web core truthfully reports whether the process and its security-critical dependencies are ready. Phase 7 adds Telegram identity, Phase 8 adds Admin identity/RBAC, Phase 9 composes user/profile/dashboard APIs, Phase 10 adds the immutable ledger, Phase 11 adds server-verified task attempts and rewards, Phase 12 adds atomic referral activation, Phase 13 adds free-only Mystery Box claims, Phase 14 adds controlled manual withdrawals, Phase 15 adds lifecycle-controlled content and global configuration administration, Phase 16 adds transactional Telegram notification delivery, and Phase 17 adds ledger-derived earning history, privacy-safe leaderboards, closed-date rollups, and permission-separated reports.

## Startup contract

`node server.js` performs these operations in order:

1. Load and validate the complete deployment environment.
2. Create the structured application logger and lifecycle state.
3. Connect and ping Redis when distributed protection is selected.
4. Create the MySQL pool.
5. Acquire a connection, enforce UTC and strict SQL mode, and verify Oracle MySQL 8.0.34+.
6. Create cached dependency probes and the protected Express application.
7. Bind the configured host and port.
8. Install process handlers for `SIGTERM`, `SIGINT`, uncaught exceptions, and unhandled rejections.
9. Transition from `starting` to `ready`.

Any failure before step 8 closes created resources, records a redacted fatal event, leaves the lifecycle failed, and exits with a failure status. The application never reports readiness after a partial startup.

## HTTP endpoints

| Method | Path                               |     Success | Purpose                                                      |
| ------ | ---------------------------------- | ----------: | ------------------------------------------------------------ |
| GET    | `/`                                |         200 | Service name, version, implementation phase, and core status |
| GET    | `/api/v1`                          |         200 | Versioned alias of the service information endpoint          |
| GET    | `/health`                          |         200 | Liveness alias                                               |
| GET    | `/health/live`                     |         200 | Process can serve HTTP; independent of MySQL availability    |
| GET    | `/health/ready`                    |     200/503 | Lifecycle, bounded MySQL probe, and required Redis are ready |
| GET    | `/api/v1/health`                   |         200 | Versioned liveness alias                                     |
| GET    | `/api/v1/health/live`              |         200 | Versioned liveness endpoint                                  |
| GET    | `/api/v1/health/ready`             |     200/503 | Versioned readiness endpoint                                 |
| GET    | `/api/v1/security/csrf`            |         200 | Issues signed CSRF token and same-site cookie                |
| GET    | `/api/v1/bootstrap`                |         200 | Authenticated public configuration and user access state     |
| GET    | `/api/v1/dashboard`                |         200 | Server-derived dashboard projections and safe previews       |
| GET    | `/api/v1/profile`                  |         200 | Authenticated Telegram-linked profile                        |
| PATCH  | `/api/v1/profile`                  |         200 | CSRF-protected allowlisted preference update                 |
| GET    | `/api/v1/wallet`                   |         200 | Authoritative available, held, and lifetime-earned balances  |
| GET    | `/api/v1/wallet/history`           |         200 | Signed-cursor posted/reversed user transaction history       |
| GET    | `/api/v1/tasks`                    |         200 | Eligible task list without destination URLs                  |
| POST   | `/api/v1/tasks/:id/start`          |     200/201 | Idempotent server-timed task attempt                         |
| GET    | `/api/v1/task-attempts/:id/status` |         200 | Server timer and verification status                         |
| POST   | `/api/v1/task-attempts/:id/claim`  | 200/201/202 | Idempotent reward claim or pending manual review             |
| GET    | `/api/v1/mystery-box/status`       |         200 | Current free box and server-derived eligibility              |
| POST   | `/api/v1/mystery-box/claim`        |     200/201 | Idempotent free reward with atomic ledger settlement         |
| GET    | `/api/v1/withdrawal-methods`       |         200 | Active methods, destination fields, fees, and limits         |
| POST   | `/api/v1/withdrawals/quote`        |         201 | Five-minute server quote after authoritative eligibility     |
| POST   | `/api/v1/withdrawals`              |     200/201 | Idempotent request with atomic available-to-held movement    |
| GET    | `/api/v1/withdrawals`              |         200 | Authenticated user's paginated withdrawal history            |
| GET    | `/api/v1/withdrawals/:id`          |         200 | User-owned request with safe status timeline                 |
| GET    | `/api/v1/notifications`            |         200 | Authenticated inbox with optional unread filtering           |
| POST   | `/api/v1/notifications/read-all`   |         200 | CSRF-protected one-way read acknowledgement                  |
| POST   | `/api/v1/notifications/:id/read`   |         200 | Marks one user-owned notification read                       |

Every response has an `X-Request-ID` header and the same identifier in its JSON envelope. Health responses set `Cache-Control: no-store` and disclose no database host, user, schema name, SQL error, credentials, or stack trace.

## Health commands

```bash
npm run health:check:live
npm run health:check:ready
```

By default the command derives the endpoint from `APP_ORIGIN`, or from `APP_HOST` and `PORT` when the canonical origin is unavailable. An exact endpoint can be supplied without credentials:

```bash
node bin/health-check.js --ready --url=https://app.example.com/health/ready --timeout-ms=5000
node bin/health-check.js --live --json
```

Unknown options, non-HTTP protocols, embedded URL credentials, invalid JSON, oversized responses, redirects, timeouts, non-success HTTP statuses, and invalid response envelopes fail closed.

## Structured logging

Production requires JSON logs. Development may use the compact pretty format. Application logs go to standard output/error so cPanel, Passenger, PM2, systemd, Docker, or another process manager can capture and rotate them.

Request completion logs contain only method, path without query string, response status, response size when known, duration, and request ID. Request/response bodies and sensitive headers are not logged. Nested password, secret, token, cookie, authorization, session, Telegram init-data, credential, key, and DSN fields are redacted. Known bearer tokens, Telegram bot tokens, and URL passwords are also scrubbed from free text.

## Graceful shutdown

`SIGTERM` and `SIGINT` initiate one idempotent shutdown operation:

1. Lifecycle changes to `draining`, causing readiness to return 503.
2. HTTP stops accepting new connections and waits for active requests.
3. Redis closes after HTTP draining.
4. The MySQL pool closes last.
5. Lifecycle changes to `stopped` and the process can exit normally.

If `SHUTDOWN_TIMEOUT_MS` expires, remaining HTTP connections are force-closed and database pool closure is still attempted. An uncaught exception or unhandled rejection follows the same cleanup path with a failure exit code.

The Phase 17 background runtimes use the same lifecycle discipline. `npm run start:worker` drains the active leased outbox batch before closing MySQL. `npm run start:scheduler` interrupts polling, finishes notification materialization and the closed-date analytics rollup, releases its database lease, and then closes MySQL. Messaging details are documented in `NOTIFICATION-DELIVERY-SYSTEM.md`; analytics operations are documented in `ANALYTICS-AND-LEADERBOARDS.md`.

## HTTP server limits

| Setting                 | Phase 6 value |
| ----------------------- | ------------: |
| Headers timeout         |    15 seconds |
| Request timeout         |    30 seconds |
| Keep-alive timeout      |     5 seconds |
| Max requests per socket |         1,000 |

The runtime applies request body limits, Helmet/CSP, CORS, CSRF, global rate limiting, maintenance/ban providers, validation helpers, audit hooks, and secure error responses. Phase 9–14 user routes consume only the verified Telegram identity supplied by Phase 7. Wallet cursors are HMAC-signed, while task claims, Mystery Box claims, and withdrawal creation use separately scoped HMAC idempotency and server-owned UTC time. Withdrawal details are encrypted at rest, and sensitive Admin reads and review/payout decisions have separate permissions.

During maintenance, health, authentication, CSRF issuance, `/api/v1/bootstrap`, and the exact configured Telegram webhook path remain reachable. Other user APIs fail with `MAINTENANCE_ACTIVE`. Verified Admin routes remain available only when the authoritative maintenance window allows Admin access.

## cPanel handoff

For a future cPanel Node.js deployment, the application startup file is `server.js`. Configure every `.env.example` value in the hosting environment, run migrations against the intended MySQL database, and restart the Node.js application. If terminal access is unavailable, the hosting control panel or provider must supply an equivalent migration/start mechanism; never expose migration or environment-management commands as public web routes.
