# Phase 4 Report - Camera Capture and Atomic Replacement

Date: 2026-07-15
Scope: Prompt 5 / Phase 4 only, with user-approved environment policy to proceed despite blocked PHP/MySQL/Redis runtime verification.

## Summary

Implemented the Phase 4 capture/replacement scaffold without adding a frontend framework or weakening server authority. The client only enables capture after the server returns a normal-win replacement entitlement. Ordinary desktop guests remain read-only for image-changing actions. Protected Made in Heaven identities remain non-replaceable.

Implemented:

- Mobile-first capture UI with `getUserMedia` support and `<input type="file" accept="image/*" capture="user">` fallback.
- Preview, remove, reorder, consent, resize, orientation-aware decode, and metadata-stripping re-encode through canvas/WebP before upload.
- Replacement API client for upload, commit, and cancel.
- Server-issued replacement entitlement creation after completed normal wins.
- One active replacement entitlement per wedding in Version 1.
- Immutable replacement image variant pipeline: archive, reel, thumbnail, SHA-256 hashes.
- Replacement upload staging separate from replacement commit.
- Replacement commit scaffold with idempotency, wedding-scoped lock, protected-symbol rejection, old occupant preservation, occupant history, and durable `SYMBOL_REPLACED` event/outbox payload.
- Arrival and departure announcement payloads including added and removed occupants.
- Phase 4 migration for replacement entitlements, occupant images, occupant history, and replacement commits.
- PHPUnit test specification file for runtime execution under PHP 8.3/Docker/CI.

Not implemented in this phase:

- Final exports.
- AirBridge integration.
- Phase 5 history/export administration.

## Files Changed

Backend:

- `server/src/Services/AssetService.php`
- `server/src/Services/ReplacementEntitlementService.php`
- `server/src/Services/ReplacementWorkflowService.php`
- `server/src/Services/RealtimeEventService.php`
- `server/src/Services/SpinAuthorizationService.php`
- `server/src/Controllers/ReplacementController.php`
- `server/src/Controllers/SpinController.php`
- `server/public/index.php`
- `server/database/migrations/004_phase4_replacements.sql`
- `server/tests/Unit/ReplacementWorkflowTest.php`

Frontend/shared:

- `packages/core/src/replacement-client.js`
- `packages/core/src/spin-controller.js`
- `packages/core/src/index.js`
- `packages/ui/src/camera-view.js`
- `packages/ui/src/index.js`
- `apps/web/index.html`
- `apps/web/src/main.js`
- `apps/web/src/styles.css`

Tests/tooling/docs:

- `tests/phase4/run-phase4-tests.mjs`
- `tests/phase0/ci-local.mjs`
- `package.json`
- `docs/PHASE4_REPORT.md`

## Server Authority and Replacement Flow

1. A client requests a spin authorization.
2. The server decides the spin result.
3. The client completes the spin using the server commitment.
4. Only if the completed spin is a normal win, `ReplacementEntitlementService` creates or returns the one active replacement entitlement for the wedding.
5. The mobile client stages one to three images using that server-issued entitlement token.
6. The server validates real image content, size, dimensions, pixel count, decodes and re-encodes images, creates immutable variants, and records staged upload metadata.
7. The client commits with an idempotency key.
8. The server validates session ownership, entitlement status, expiry, upload stage, target symbol, protected-symbol prohibition, and desktop policy.
9. The server updates the current occupant only after the new images are validated and staged.
10. The old occupant is preserved in occupant history.
11. A durable `SYMBOL_REPLACED` event is appended with both arrival and departure sides.

## Replacement Event Payload

The `SYMBOL_REPLACED` payload includes:

- `symbolId`
- `symbolLabel`
- `addedOccupant`
- `removedOccupant`
- `arrivalMessage`
- `departureMessage`
- `privateRemovedGuestEncouragement`

## Commands Run

```sh
node --check packages/ui/src/camera-view.js
node --check packages/core/src/replacement-client.js
node --check apps/web/src/main.js
node tests/phase4/run-phase4-tests.mjs
npm run ci:local
node -e "JSON.parse(...)"
php -v
docker --version
```

## Test Results

`node tests/phase4/run-phase4-tests.mjs` passed:

```text
ok - mobile capture state supports one-to-three images, remove, and reorder helpers
ok - capture UI uses getUserMedia and mobile file capture fallback without desktop enabling
ok - client consumes only server-issued replacement entitlement
ok - server mints entitlements only from completed normal wins and enforces one active per wedding
ok - server image pipeline validates content and stores immutable variants
ok - replacement commit is designed as locked transaction with idempotency and old occupant preservation
ok - ordinary desktop guest replacement attempts receive server-side desktop rejection
ok - replacement announcement carries both arrival and departure payloads
ok - PHP replacement runtime test specifications are present for Docker/CI execution
ok - Phase 4 routes and migration are present without Phase 5 scope creep
phase 4 tests passed
```

`npm run ci:local` passed Phase 0, Phase 1, Phase 2, Phase 3, and Phase 4 checks.

JSON package/manifest parsing passed.

## Runtime Blockers Carried Forward

The user explicitly allowed Phase 4 implementation to proceed while these remain blocked in the current Codex environment. They remain release-blocking.

Unavailable commands:

```text
php -v
zsh:1: command not found: php

docker --version
zsh:1: command not found: docker
```

Previously confirmed unavailable in this environment:

```text
composer --version
mysql --version
redis-server --version
```

Still pending under PHP 8.3/Docker/CI:

- PHP syntax checks against every PHP file.
- Composer validation and dependency install.
- PHPUnit tests for RealtimeEventService, RealtimeController, state recovery, event listing, admin authorization, desktop image-mutation rejection, and replacement workflow.
- MySQL migrations 001 through 004 on a clean MySQL 8 database.
- Apache/PHP endpoint tests for canonical wedding state, polling events, authorized admin event publication, unauthorized publication rejection, paused/resumed state, replacement upload, replacement commit, replacement cancellation, and desktop guest rejection.
- Redis/durable outbox bridge execution proving only committed PHP/MySQL events are published.
- True concurrent commit test using MySQL transaction plus Redis/distributed lock.
- Real image fixture validation through GD/Imagick under PHP 8.3.

## Requirement Coverage Status

Available local checks cover source structure and JavaScript behavior for:

- Mobile capture helper behavior.
- Desktop capture UI disablement.
- Client dependence on server-issued entitlements.
- Server scaffold for one active entitlement per wedding.
- Immutable image variants.
- Protected identity replacement rejection.
- Idempotency and lock scaffolding.
- Old occupant preservation in source flow.
- `SYMBOL_REPLACED` event payload shape.

Runtime execution remains required before these can be considered fully verified:

- Mobile guest replaces default art.
- Mobile guest replaces another guest.
- Removed guest remains in history.
- Removed guest can later win back onto a reel.
- Expired, forged, wrong-session, and already-used entitlements are rejected over HTTP/PHPUnit.
- Duplicate commit is idempotent under runtime persistence.
- Concurrent commits cannot create multiple current occupants.
- Upload failure, cancellation, timeout, and moderation rejection preserve the old occupant.
- Realtime clients receive one ordered replacement event from a committed PHP/MySQL transaction.

## Notes

The FileDatabase implementation mirrors the intended transaction and lock shape but is not a substitute for the required MySQL transaction plus Redis/distributed lock runtime validation. No governing contract files were modified.
