# Production release guide

The code is prepared for production configuration; the local WAMP environment is still development. Do not import `database/timeproof.sql` over an existing database.

## Install the upgrade

1. Make and retain a database/file backup.
2. Run `php database/migrate_hardening.php`. It creates the new tables and a prediction-history index and is safe to rerun.
3. Existing accounts must verify their email before publishing predictions or following people. They can still sign in and read existing records. The banner links to verification/resend. We do not falsely mark existing addresses as verified. In development, the verification page provides a preview of the signed-in user's own verification email link after requesting one, so local testing can proceed without an SMTP relay. This preview is disabled in production.
4. Clear deployment caches and test with an ordinary user and each staff role.

## Hosting configuration

Configure Turnstile keys before setting the app to production: production authentication fails closed without them. Follow [BOT_PROTECTION.md](BOT_PROTECTION.md) for form verification, account/IP limits, and the Cloudflare WAF/DNS/origin settings that must be enabled on the hosting account.

For a fresh shared-hosting installation, select an **empty** database created in the hosting control panel and import `database/timeproof_production_hosting.sql` directly. It omits `CREATE DATABASE`, `USE`, and explicit `DEFINER` accounts. Set `TIMEPROOF_DB_NAME` to that hosting database name. Do not re-export through a GUI before upload: it can add the local `root@localhost` definer again.

If an import already created the tables but failed with error 1227 on a view, select that same database and import `database/repair_production_views.sql`. This recreates the views only and does not remove table data. The importing account needs ordinary view/schema privileges, not the ability to impersonate local root. If a GUI left a placeholder table with a view's name, inspect that object before removing anything; the repair intentionally does not drop tables. Running `php database/export_clean_production.php` regenerates all three export files; `php database/prepare_hosting_export.php` builds the hosting/repair files from the existing clean SQL export.

Point the web server document root to `public/`. Keep `app/`, `config/`, `database/`, `cron/`, `tools/`, backups, logs, and evidence storage outside direct HTTP access. Apache denial files are included as an additional safeguard. On Nginx, configure equivalent denial rules. Set TLS and PHP correctly on the host; application settings cannot install a TLS certificate or configure the reverse proxy for you.

Install dependencies with `composer install --no-dev --optimize-autoloader`. Configure the root `.env` file using `.env.example`, or use server/process environment variables (which take precedence). See `CONFIGURATION.md` for SMTP and Paystack setup:

- `TIMEPROOF_APP_ENV=production`
- `TIMEPROOF_APP_URL=https://your-domain.example` (the public application URL)
- `TIMEPROOF_DB_HOST`, `TIMEPROOF_DB_PORT`, `TIMEPROOF_DB_NAME`, `TIMEPROOF_DB_USER`, `TIMEPROOF_DB_PASSWORD`
- `TIMEPROOF_MAIL_FROM` and authenticated PHPMailer SMTP settings: `TIMEPROOF_SMTP_HOST`, `TIMEPROOF_SMTP_PORT`, `TIMEPROOF_SMTP_ENCRYPTION`, `TIMEPROOF_SMTP_USERNAME`, `TIMEPROOF_SMTP_PASSWORD`
- `TIMEPROOF_BACKUP_DIR`: an existing private directory outside the public web directory
- `TIMEPROOF_BACKUP_KEY`: 64 hexadecimal characters generated from 32 cryptographically random bytes; keep it separately from backups
- `PAYSTACK_SECRET_KEY` for paid subscriptions; Google OAuth credentials are optional

Use a restricted database account for the application, and a separate account with schema privileges for migrations and recovery. Grant the application INSERT/SELECT only on immutable `prediction_events` and `prediction_commits` tables; use separate schema/recovery credentials. Do not expose PHP/Xdebug output in production; logging goes to the server's private error log. Rotate logs and control file permissions. Encrypted backups also contain authentication data and email verification links; restrict access and choose a retention policy.

Run `php tools/production_check.php` under the same environment as the web application. A pass verifies configuration prerequisites, not live delivery, TLS, provider callbacks, or real user usability. Organizer subscriptions now provide challenge quotas, with Pro branding/CSV exports and Organization teams. See CHALLENGES.md for prices, organizer result submission and resolution rules.

## Scheduled work and monitoring

Run the following through Windows Task Scheduler or the production host's scheduler, with the application's environment and project directory:

| Command | Schedule |
|---|---|
| `php cron/reveal_predictions.php` | Every minute |
| `php cron/score_challenges.php` | Every minute |
| `php cron/advertising.php` | Every minute |
| `php cron/send_emails.php` | Every minute |
| `php cron/verification_reminders.php` | Hourly |
| `php cron/cleanup.php` | Daily |
| `php cron/backup.php` | Daily, or more often based on recovery needs |
| `php cron/check_health.php` | Every five minutes |

Jobs use database locks to prevent overlapping instances. `job_runs` records their outcomes. Reveal failures and rejected email batches produce a nonzero exit code. Failed email deliveries retry after five minutes, up to five attempts; the health check detects exhausted retries. Configure your hosting monitor to alert you when the health command exits nonzero. This repository does not register external alerts or install scheduled tasks automatically.

## Backup and restore drill

`cron/backup.php` creates an authenticated AES-256-GCM encrypted archive of a consistent database snapshot and immutable evidence files. The archive includes checksums and schema/row counts. Retain an off-host copy and retain the key separately. Large databases should also use database-native or hosting-managed backups; this application's snapshot implementation materializes each table while exporting.

To restore, create a **different empty database** and an **empty private evidence directory**. Set `TIMEPROOF_RESTORE_DB_NAME` and `TIMEPROOF_RESTORE_EVIDENCE_DIR`, then run:

`php tools/restore_backup.php /private/path/snapshot.tpbackup`

Recovery refuses to overwrite an occupied database or evidence directory, and the CLI refuses the application's current database name. Wrong keys, modified records, or incomplete archives are rejected before creating tables. Schema changes cannot be rolled back atomically in MySQL; if a restore fails after authentication, inspect the isolated recovery target and retry in a new empty target. Validate counts, prediction fingerprints, certificate validity, uploaded-file hashes, and sign-in before switching the application to recovered data. The automated integration suite performs a recovery drill in disposable databases and directories.

## Release verification

Run `php -d xdebug.mode=off tests/integration.php`, `node tests/frontend.cjs`, `node tests/review_filters.cjs`, `node tests/certificate_sharing.cjs`, and `php -d xdebug.mode=off tests/certificate_image.php`.

Then manually exercise registration → verification email → sign-in → following → followers-only prediction → reveal → evidence → independent review → certificate download/share → amendment → certificate revocation. Check mobile layouts, actual inbox/spam delivery, password resets, account suspension, blocked-user behavior, reports and independent staff review. Public records are public even when an account is blocked; blocking is not a substitute for private visibility.

Server-side hourly request limits currently allow 30 prediction submissions, 60 follow actions, 30 evidence submissions, 20 report/block actions; login permits 15 attempts per user or 60 per IP per 15 minutes. Registration/password-reset requests have separate limits. These are safety limits, not plan quotas. Review them against beta traffic before launch.

Review and approve the product's moderation, privacy, retention, and billing policies. Existing legal pages describe product behavior but are not a substitute for jurisdiction-specific review. Run a small real-user beta before a public rollout.

## Clean production database

`database/timeproof_production.sql` is a clean export of the current schema, including its view, with only global categories, plans, and badges retained. User accounts and all activity tables are empty. Politics & Governance is included as an active category. The export creates a new `timeproof_production` database and must not be imported over an occupied database. Set `TIMEPROOF_DB_NAME=timeproof_production` on the production host when ready; the local app still uses its existing database. Register and verify the first production account, then use `tools/make_super_admin.php` under that production database environment to create the first super admin.

## Report notifications and feedback

New account and prediction reports notify independent active administrators and super admins in the app. Open duplicate reports do not generate duplicate alerts. The reporting user can track the report under My reports; resolving or dismissing it queues an email containing the staff feedback and creates an inbox notification. Report-feedback emails are service messages and are sent regardless of optional email preferences. The staff feedback field is shared with the reporting user; do not enter internal-only notes there. Email delivery still requires the configured transport and scheduled email worker.


Advertising deployments: run `php database/migrate_advertising.php`; see `ADVERTISING.md` for prices, approval, payment/refund handling and measurement limits.
