# Phase 7 Report - Runtime Hardening

Date: 2026-07-15

Status: PARTIAL

## Summary

Phase 7 did not add product features. Game outcome rules, win rates, protected Made in Heaven behavior, replacement semantics, history semantics, moderation semantics, final snapshot format, export contents, and AirBridge discovery-only purpose remain unchanged.

The major change is that the backend stack now actually runs in Docker with PHP 8.3, Apache, MySQL 8, Redis, and the Node `ws` realtime gateway.

## Runtime Versions

- Docker: 29.6.1
- Docker Compose: v5.3.0
- Docker PHP: 8.3.32
- Local PHP: 8.5.8
- Redis local: 8.8.0
- Playwright: 1.61.1
- PHPUnit: 11.5.56

## Files Changed

- `.env.example`
- `composer.json`
- `composer.lock`
- `docker-compose.yml`
- `infra/apache/000-default.conf`
- `infra/php/Dockerfile`
- `infra/php/production.ini`
- `infra/redis/redis.conf`
- `scripts/migrate.sh`
- `scripts/php-lint.sh`
- `scripts/runtime-verify.sh`
- `server/database/migrations/006_phase6_airbridge_join_resolution.sql`
- `server/public/index.php`
- `server/src/Services/AssetService.php`
- `server/src/Services/ReplacementEntitlementService.php`
- `server/src/Services/ReplacementWorkflowService.php`
- `tests/phase0/ci-local.mjs`
- `tests/phase7/run-phase7-tests.mjs`
- `package.json`
- `docs/ACCESSIBILITY_REVIEW.md`
- `docs/DEPLOYMENT_GUIDE.md`
- `docs/LOAD_TEST_REPORT.md`
- `docs/RELEASE_READINESS.md`
- `docs/ROLLBACK_GUIDE.md`
- `docs/SECURITY_REVIEW.md`
- `docs/PHASE7_REPORT.md`

## Fixes Made From Runtime Findings

- Added a custom PHP 8.3 Apache Docker image with Composer, GD/WebP, PDO MySQL, Redis extension, and MySQL client.
- Added Docker health checks and startup ordering.
- Changed MySQL image to `mysql:8.0`.
- Made the PHP host port configurable with `PHP_HTTP_PORT`; local runtime used `8081` because a Python server occupied `8080`.
- Fixed realtime container binding by honoring `REALTIME_HOST=0.0.0.0`.
- Fixed realtime health check to use `127.0.0.1`.
- Fixed Apache config placement for `ServerTokens`/`ServerSignature`.
- Forwarded `Authorization` headers into PHP via Apache `SetEnvIf`.
- Fixed Redis local Docker config so PHP can reach Redis over the container network.
- Added `--ssl=0` to migration script for the local MySQL client.
- Fixed Phase 6 migration foreign-key type to match `weddings.id BINARY(16)`.
- Made Phase 6 migration idempotent with `IF NOT EXISTS`.
- Fixed PHPStan findings in asset/replacement services.
- Mapped admin authorization failures to structured 403 instead of `server_error`.

## Commands And Results

Passed:

```text
docker --version
docker compose version
PHP_HTTP_PORT=8081 APP_BASE_URL=http://localhost:8081 docker compose up -d php
docker compose ps
curl -fsS http://127.0.0.1:8081/health
curl -fsS http://127.0.0.1:8081/api/v1/health/database
curl -fsS http://127.0.0.1:8081/api/v1/health/redis
curl -fsS http://127.0.0.1:8787/health
docker compose exec php php -v
docker compose exec php composer validate --strict
docker compose exec php composer install --no-interaction --prefer-dist
docker compose exec php composer lint
docker compose exec php composer analyse
docker compose exec php sh scripts/migrate.sh
docker compose exec php sh scripts/runtime-verify.sh
npm audit --audit-level=high
docker compose exec php composer audit
npm run ci:local
node tests/phase7/run-phase7-tests.mjs
npx playwright --version
npx playwright screenshot http://127.0.0.1:8081/health /tmp/wedding-reels-health.png
```

PHPUnit result:

```text
OK, but some tests were skipped!
Tests: 27, Assertions: 8, Skipped: 23.
```

This is a real execution pass, but not a full behavioral pass.

## Real HTTP Checks

Passed:

- `GET /health`
- `GET /api/v1/health/database`
- `GET /api/v1/health/redis`
- `GET /api/v1/weddings/demo-wedding/bootstrap`
- `POST /api/v1/join/resolve` rejects bad AirBridge token with `JOIN_TOKEN_REJECTED`.
- Ordinary desktop guest asset mutation rejects with `DESKTOP_UPDATE_NOT_ALLOWED`.
- Unauthorized admin event publication rejects with `admin_authorization_required`.
- Realtime public `/publish` rejects with `PUBLISH_NOT_PUBLIC`.

## Image Pipeline Check

Passed one real fixture:

- Generated an 8x8 PNG with PHP/GD.
- Uploaded it through the real admin asset endpoint.
- Server decoded it, re-encoded WebP, hashed it, and returned an asset record.

Still needed:

- JPEG, WebP, HEIC/HEIF behavior, large image, corrupt image, wrong extension, wrong MIME, animated format, EXIF orientation, metadata stripping, partial upload, duplicate hash, and one/two/three replacement image submissions.

## Remaining Critical Blockers

- Replace skipped PHPUnit placeholder specs with executable runtime tests.
- Prove concurrent replacement commits with MySQL transactions and Redis locks.
- Prove Redis lock expiry cannot corrupt state.
- Prove database transaction remains final authority if Redis is unavailable.
- Prove join-token replay/rate-limit behavior against MySQL rows.
- Run full browser/device matrix.
- Run real Docker load/chaos tests: spin burst, reconnect storm, Redis restart, gateway restart, Apache restart, p99 latency, CPU/memory/error rates.
- Complete CSRF/session-rotation/MFA integration.
- Complete production HTTPS/HSTS validation.
- Complete final ZIP export generation and checksum verification.

## Release Readiness

Not release-ready. Runtime infrastructure is now working and several previously blocked checks are now passing, but high-risk behavioral runtime tests remain incomplete.
