# Decisions

This document records confirmed and unresolved architectural decisions for Made in Heaven Wedding Reels. It is intentionally conservative: where the master specification leaves behavior open, the behavior remains unresolved until explicitly decided.

## Confirmed Decisions

| ID | Decision | Rationale | Source |
|---|---|---|---|
| DEC-001 | Version 1 is entertainment only: no wagers, payments, cash prizes, or gambling claims. | Required by product purpose and acceptance criterion AC-20. | `MASTER_SPEC.md` sections 1, 24 |
| DEC-002 | Outcomes are server-authoritative. The client never calculates an authoritative win. | Prevents cheating and preserves configurable celebration rate. | Sections 6.2, 7.2, 22 |
| DEC-003 | Reel strips are visual/animation structures, not the source of odds. | Natural strip odds are too rare and conflict with the social goal. | Sections 6.2, README outcome rule |
| DEC-004 | There are exactly 20 logical identities: IDs 1-3 protected and IDs 4-20 replaceable. | Core domain invariant. | Section 6.1 |
| DEC-005 | Protected images share match class `MADE_IN_HEAVEN`; any three protected images form a protected match. | Required protected semantics. | Section 6.4 |
| DEC-006 | Normal win default rate is 0.05 and admin range is 0.02 to 0.10. | Required gameplay pacing. | Section 6.3 |
| DEC-007 | Protected event default rate is 0.005 and separate from normal win rate. | Required Made in Heaven pacing. | Section 6.3 |
| DEC-008 | Conditional on a normal win, target selection is uniform across the 17 replaceable identities. | Equal odds requirement. | Sections 2, 6.3, 22 |
| DEC-009 | Initial replacement pause policy is `finish_in_progress`. | Recommended for Version 1 simplicity and safety. | Section 8 |
| DEC-010 | Version 1 allows only one outstanding photo entitlement per wedding. | Simplest policy for overlapping wins. | Section 8 |
| DEC-011 | Replacement commits require a database transaction plus distributed lock. | Prevents concurrent corruption. | Sections 8, 17 |
| DEC-012 | Asset rows are immutable; replacements change references rather than overwriting files. | Preserves history and auditability. | Sections 10, 13 |
| DEC-013 | AirBridge is discovery only and must never carry private guest data, permanent credentials, images, or live state. | Privacy and architecture requirement. | Section 11 |
| DEC-014 | PWA and MV3 extension must share core code. | Acceptance criterion AC-18. | Sections 4, 12, 22 |
| DEC-015 | REST is used for commands/uploads/bootstrap/history; realtime is used for announcements/state/presence. | Matches transport design. | Section 12.4 |
| DEC-016 | WebSocket must have polling fallback every 3-5 seconds. | Required restricted-environment behavior. | Sections 12.4, 19 |
| DEC-017 | The implementation target is PHP 8.3, Apache, MySQL 8, Redis, PWA, and Chrome MV3. | Confirmed stack for backend, persistence, realtime coordination, browser install, and extension packaging. | Section 4.3 |
| DEC-018 | Frontend implementation uses vanilla JavaScript modules, CSS3, and HTML5. | User preference and lowest dependency surface for PWA/MV3/mobile reliability. | User confirmation, 2026-07-15 |
| DEC-019 | PWA, Chrome extension, and admin console share browser behavior through `packages/core`, `packages/ui`, and `packages/platform`; app folders stay thin entry points. | Keeps one JavaScript codebase while allowing packaging-specific adapters. | User confirmation, 2026-07-15 |
| DEC-020 | Event-time image setup/update is mobile-first; ordinary desktop access is read-only for image-changing actions. | Matches the event workflow where capture/update happens on mobile devices. | User confirmation, 2026-07-15 |
| DEC-021 | Docker is used only where useful for local development, repeatable testing, CI, or deployment. | Keeps runtime reproducible without hiding the PHP/Apache/MySQL/Redis architecture. | User confirmation, 2026-07-15 |
| DEC-022 | PHP is the final authorization boundary for upload, capture entitlement, replacement, restoration, and moderation requests. | Client device detection is convenience only and cannot grant image-update rights. | User confirmation, 2026-07-15 |
| DEC-023 | Authenticated administrators may perform limited emergency moderation controls from desktop when server-side permissions allow it. | Prevents desktop guest updates while preserving practical wedding safety controls. | User confirmation, 2026-07-15 |
| DEC-024 | The realtime gateway uses the mature Node `ws` library instead of hand-written WebSocket framing. | Reduces protocol risk and enables heartbeat/backpressure behavior. | Phase 3 hardening, 2026-07-15 |
| DEC-025 | Realtime publication is internal-only and requires a service credential or future Redis/outbox consumer path. | Guest clients must not publish authoritative events directly. | Phase 3 hardening, 2026-07-15 |
| DEC-026 | Realtime subscriptions are authenticated and wedding-scoped. | A client can receive only events permitted by its session/wedding scope. | Phase 3 hardening, 2026-07-15 |
| DEC-027 | AirBridge Phase 6 integrates the existing `AirbridgeModemAPI.startListening(onPacketDecoded)`/`stopListening()` facade through adapters instead of defining a second modem callback API. | Keeps Wedding Reels compatible with the existing AirBridge modem project and isolates app-specific join normalization from DSP/audio code. | Phase 6 AirBridge discovery, 2026-07-15 |

## Proposed Technical Decisions For Approval

These are recommendations, not product-rule changes.

| ID | Proposal | Why it fits |
|---|---|---|
| PROP-001 | Use `pnpm` workspaces for the monorepo. | Fast, simple workspace support for Vite apps and shared packages. |
| PROP-002 | Use native JavaScript modules with optional Vite packaging only when bundling is needed. | Keeps source simple while preserving a path to PWA/MV3 production bundles. |
| PROP-003 | Do not introduce React, Vue, Lit, or another component framework unless explicitly approved later. | Current direction is vanilla JavaScript modules, CSS3, and HTML5. |
| PROP-004 | Use Slim 4 + PHP-DI for REST routing. | Minimal PHP framework that avoids overbuilding while providing routing/middleware. |
| PROP-005 | Use Doctrine DBAL repositories instead of a full ORM. | Keeps transaction boundaries and SQL behavior explicit for a stateful game. |
| PROP-006 | Use Phinx for database migrations. | Simple Composer-friendly migration tool compatible with MySQL/MariaDB. |
| PROP-007 | Use Node.js `ws` gateway plus Redis pub/sub/streams for realtime. | Keeps long-running WebSocket connections out of Apache/PHP while REST remains PHP. |
| PROP-008 | Use PHPUnit, Vitest, and Playwright. | Covers PHP services, TypeScript packages, and browser/PWA/MV3 smoke flows. |
| PROP-009 | Use local protected filesystem storage first, behind an object-storage interface. | Matches spec and leaves a clean path to S3-compatible storage. |

## Unresolved Decisions

| ID | Decision needed | Notes/Risk |
|---|---|---|
| UNR-001 | Exact admin authentication provider and MFA mechanism. | Spec strongly recommends MFA but does not choose provider. |
| UNR-003 | Exact media retention/deletion policy and whether originals are retained. | Spec says original retained only when configured; policy must be explicit. |
| UNR-004 | Maximum image upload size in bytes and pixel count. | Spec gives max source dimension before upload but not byte limit. |
| UNR-005 | Moderation default: `winner_preview` is schema default, but full product default should be confirmed. | Master config lists three modes. |
| UNR-006 | Whether gallery is private by default or shareable by default. | Spec requires configurable policy but does not set default. |
| UNR-007 | Exact AirBridge payload signing/integrity shape. | Depends on existing AirBridge protocol details. |
| UNR-008 | Exact WebSocket auth token format and lifetime. | Needs coordination with session/token service. |
| UNR-009 | Whether protected Made in Heaven event can occur independently of normal-win draw ordering or after normal-win failure. | Spec says separate configurable rate; implementation should document draw order. |
| UNR-010 | Whether queued overlapping wins become `CELEBRATION_ONLY` immediately or after revalidation. | Spec suggests simplest Version 1 policy but leaves details open. |
| UNR-011 | Exact browser support cutoffs for iOS Safari and Android Chrome. | Needed for camera/PWA QA matrix. |
| UNR-012 | Production hosting/deployment topology. | Apache/PHP plus Node realtime and Redis require process supervision decisions. |

## Risk Register

| Risk | Impact | Mitigation |
|---|---|---|
| iPhone camera behavior differs between Safari and installed PWA. | Winner cannot capture or upload reliably. | Build `getUserMedia` plus file-input fallback; run manual iPhone Safari and Home Screen tests. |
| iOS/PWA lifecycle suspends timers or WebSocket while backgrounded. | Missed announcements or stale state. | Use sequence gap recovery, visibility-change refresh, polling fallback, and disable spin until fresh state. |
| WebSocket blocked or unstable on venue networks. | No realtime announcements. | Polling fallback every 3-5 seconds, bounded backoff, full state refresh on reconnect. |
| MV3 CSP rejects dynamic code or remote executable scripts. | Extension fails review/load. | Vite MV3 build with bundled JS only; automated scan for remote executable code. |
| Image processing accepts malformed or oversized files. | Security/performance risk. | MIME sniffing, pixel/byte limits, decode/re-encode, metadata stripping, server variants. |
| Concurrent replacements race. | Corrupted current occupants/history. | One entitlement policy, Redis/distributed lock, DB transaction, idempotency, state-version revalidation. |
| Outcome engine accidentally uses strip odds. | Product pacing and equal target selection fail. | Statistical tests and code review boundary: strips map outcomes but never choose wins. |
| AirBridge mistaken for data transport. | Privacy/security breach. | Adapter only emits join event; server validates short-lived token; no images or credentials acoustically. |


## Locked Frontend Stack

The frontend source stack is HTML5, CSS3, and vanilla JavaScript ES modules. Do not add React, Vue, Lit, Svelte, JSX, Bootstrap, Tailwind, jQuery, or another frontend framework unless a later explicit architectural decision approves it.

PWA, Chrome extension, admin, and history entry points remain thin wrappers around shared modules in `packages/core`, `packages/ui`, and `packages/platform`.

## Desktop Image-Update Policy

Desktop browsers and the desktop Chrome extension may view live reels, announcements, event status, history, galleries, and authorized exports. Ordinary desktop guest sessions must not capture, upload, replace, restore, moderate, or otherwise change reel images.

Frontend device detection may hide or disable controls, but PHP must enforce every image-changing endpoint using session role, event state, entitlement ownership, and approved client-capability policy. A normal desktop guest image-changing request returns structured `403 DESKTOP_UPDATE_NOT_ALLOWED`.


## Proposed Contract Changes From Phase 3 Runtime Hardening

No governing contract files were changed in this pass. Proposed additions for a future OpenAPI/protocol update:

- Document `/internal/publish` as an internal-only realtime gateway endpoint protected by `x-internal-realtime-token` or a Redis/outbox consumer unavailable to guest clients.
- Document realtime subscription authentication and wedding-scoped authorization for `/ws` and `/events`.
- Document gateway rejection codes for malformed envelopes, unknown event types, wrong wedding scope, stale sequence, oversized payload, missing internal credential, and non-PHP/MySQL authoritative source.

## Proposed Contract Changes From Phase 6 AirBridge Discovery

No governing contract files were changed in this pass. Proposed additions for a future OpenAPI/protocol update:

- Document the normalized AirBridge join payload fields: `protocolVersion`, `messageType: WEDDING_JOIN`, `weddingPublicId`, `joinToken`, `environment`, `integrityValid`, `receivedAt`, and `expiresAt`.
- Document that acoustic AirBridge payloads are discovery hints only and must be resolved by `POST /api/v1/join/resolve` before any session/bootstrap action.
- Document that `/api/v1/join/resolve` returns no admin grant, no image-mutation grant, no spin result, and no replacement entitlement.
- Document AirBridge rejection codes for unsupported protocol version, wrong message type, invalid integrity, expired token, replayed token, wrong environment, and wedding-not-joinable.
