# Phase 6 Report - AirBridge Discovery Integration

Date: 2026-07-15

## Scope

Phase 6 implemented AirBridge discovery and join integration only.

This phase did not change:

- authoritative game engine behavior
- spin result selection
- replacement workflow
- image upload/capture workflow
- history model
- moderation behavior
- final snapshot rules
- export package format
- AirBridge DSP/modem source
- Phase 7 production hardening

## Existing AirBridge API Integrated

The existing AirBridge modem project was inspected before adding Wedding Reels integration. The reusable modem facade is:

```js
const modem = new AirbridgeModemAPI(config);
await modem.init();
modem.startListening(onPacketDecoded);
modem.stopListening();
```

Wedding Reels integrates that API through `createBrowserAirbridgeAdapter(...)`. The adapter does not invent a second modem callback API. It receives decoded modem packets through `startListening(onPacketDecoded)` and allows a `packetDecoder` hook to translate the modem packet into the normalized Wedding Reels join payload.

## Normalized AirBridge Join Payload

The shared core normalizer accepts only discovery payloads with:

- `protocolVersion`
- `messageType: "WEDDING_JOIN"`
- `weddingPublicId`
- `joinToken`
- `environment`
- `integrityValid`
- `issuedAt` / `receivedAt`
- `expiresAt`
- optional modem metadata

Accepted payloads dispatch:

```js
window.dispatchEvent(new CustomEvent("airbridge:join", {
  detail: {
    protocolVersion,
    weddingPublicId,
    joinToken,
    environment,
    integrityValid,
    receivedAt,
    expiresAt
  }
}));
```

The acoustic payload is not authentication. It is resolved through `POST /api/v1/join/resolve` before any join/bootstrap behavior.

## Files Changed

- `package.json`
- `apps/web/index.html`
- `apps/web/src/main.js`
- `apps/web/src/styles.css`
- `apps/extension/src/launch.html`
- `apps/extension/src/launch.js`
- `packages/core/src/airbridge-join.js`
- `packages/core/src/bootstrap-state.js`
- `packages/core/src/index.js`
- `packages/platform/src/airbridge/browser-adapter.js`
- `packages/platform/src/airbridge/extension-adapter.js`
- `packages/platform/src/airbridge/mock-adapter.js`
- `packages/platform/src/airbridge/noop-adapter.js`
- `packages/platform/src/index.js`
- `server/public/index.php`
- `server/src/Controllers/JoinController.php`
- `server/src/Services/JoinResolutionService.php`
- `server/database/migrations/006_phase6_airbridge_join_resolution.sql`
- `server/database/seeds/demo-wedding.json`
- `server/tests/Unit/AirbridgeJoinResolutionTest.php`
- `tests/phase0/ci-local.mjs`
- `tests/phase6/run-phase6-tests.mjs`
- `docs/DECISIONS.md`
- `docs/PHASE6_REPORT.md`

## Behaviorally Verified

The JavaScript Phase 6 test suite verifies:

- valid browser/PWA AirBridge join normalization
- valid Chrome extension AirBridge join normalization through extension messaging
- no-op unsupported platform behavior
- listening-disabled behavior
- duplicate acoustic pulse deduplication
- expired token rejection
- invalid integrity rejection
- wrong protocol version rejection
- wrong message type rejection
- wrong wedding environment rejection
- server rejection propagation
- user-declined navigation
- existing-session same-wedding event dispatch without duplicate acoustic handling
- AirBridge join never grants admin rights
- AirBridge join never grants image-mutation rights
- adapter resource cleanup
- microphone permission denial reports `permission-denied` without starting listening
- browser adapter uses the existing `AirbridgeModemAPI.startListening(onPacketDecoded)` callback shape

## Statically Verified

The available tests inspect PHP/server code but cannot execute PHP in this environment:

- `/api/v1/join/resolve` route is wired.
- `JoinResolutionService` validates token shape, source, protocol version, environment, expiration, replay, wedding status, and source.
- `JoinResolutionService` returns no admin grant, no image-mutation grant, and no replacement-entitlement grant.
- `JoinResolutionService` records token hashes instead of raw tokens in the audit path.
- Migration `006_phase6_airbridge_join_resolution.sql` defines `join_tokens` and `join_resolution_audit`.
- PHPUnit specs exist for valid AirBridge resolution, rejected expired/tampered/wrong-environment/replayed tokens, no mutation/admin grants, and privacy-safe audit.
- Chrome MV3 manifest contains no remote executable code.

## Runtime-Blocked Release Checks

These remain blocked because the local Codex environment still does not provide PHP, Composer, Docker, MySQL, or Redis:

- PHP 8.3 syntax checks against every PHP file.
- Composer validation and dependency installation.
- PHPUnit execution for join resolution.
- Applying migrations `001` through `006` to a clean MySQL 8 database.
- Running Apache/PHP, MySQL, Redis, and the realtime gateway together.
- Exercising `POST /api/v1/join/resolve` over HTTP.
- Verifying replay rejection with durable token rows.
- Verifying rate limiting against repeated invalid token attempts.
- Verifying privacy-safe audit rows in MySQL.
- Carry-forward blockers from Phases 2 through 5, including PHP runtime tests, MySQL/Redis transaction/lock tests, realtime durable outbox tests, replacement concurrency tests, and actual export archive generation.

## Commands Run

Passed:

```text
node --check packages/core/src/airbridge-join.js
node --check packages/platform/src/airbridge/browser-adapter.js
node --check packages/platform/src/airbridge/extension-adapter.js
node --check packages/platform/src/airbridge/mock-adapter.js
node --check apps/web/src/main.js
node --check apps/extension/src/launch.js
node --check tests/phase6/run-phase6-tests.mjs
node tests/phase6/run-phase6-tests.mjs
npm run ci:local
node -e "... JSON parse check ..."
```

Phase 6 test result:

```text
ok - normalizes valid browser/PWA AirBridge join payload
ok - rejects expired token, invalid integrity, wrong protocol, wrong message, and wrong environment
ok - deduplicates duplicate acoustic detections and dispatches normalized airbridge:join event once
ok - resolves short token through HTTPS and never grants admin or image-mutation rights
ok - server rejection and user-declined navigation are surfaced
ok - mock and no-op adapters cover unsupported platform and listening-disabled behavior
ok - browser adapter uses existing AirbridgeModemAPI startListening callback and cleans up resources
ok - microphone permission denied is reported without starting listener
ok - extension adapter reuses shared normalization and extension messaging only
ok - tampered token and replay cases are covered by server-side test specs
ok - Phase 6 server route and service are present without changing game engine workflow
phase 6 tests passed
```

Full local CI result:

```text
phase 0 tests passed
phase 1 tests passed
phase 2 tests passed
phase 3 tests passed
phase 4 tests passed
phase 5 tests passed
phase 6 tests passed
local CI checks passed
```

Blocked locally:

```text
php -v                  -> command not found
composer --version      -> command not found
docker --version        -> command not found
mysql --version         -> command not found
redis-server --version  -> command not found
```

## Security and Privacy Boundaries

AirBridge does not transport:

- guest photographs
- reel state
- wedding history
- session credentials
- permanent authentication tokens
- admin credentials
- replacement entitlements
- spin results

The resolver returns discovery/bootstrap information only. Admin and image-mutation grants are explicitly false.

## User Consent and Microphone Behavior

The PWA and extension expose explicit AirBridge listening buttons. The browser adapter does not call `startListening(...)` until the user enables listening.

The adapter stops the modem listener on disable/destroy and clears duplicate-pulse state.

## Proposed Contract Notes

`docs/DECISIONS.md` records the Phase 6 proposed protocol/OpenAPI notes rather than editing governing contract files directly.

## Status

Phase 6 is implementation-complete for JavaScript/static validation.

Backend runtime verification remains blocked and release-blocking until PHP 8.3, Composer, MySQL 8, Redis, and Docker or equivalent local services are available.
