---
phase: 03-event-write-back-pwa-install
plan: 08
type: execute
wave: 5
depends_on: ['03-04', '03-06', '03-07']
files_modified:
  - .planning/phases/03-event-write-back-pwa-install/03-GATE2-RESULTS.md
autonomous: false
requirements: [CAL-04, CAL-05, CAL-06, PWA-01, PWA-02]
user_setup:
  - service: authelia
    why: 'Gate 2 verifies real Authelia OIDC login over the public Pangolin URL including the iOS standalone-PWA flow (success criterion 6, D-14)'
    env_vars:
      - name: OIDC_AUTH_EXTERNAL_URL
        source: 'Set to the public familysync URL (e.g. https://familysync.<domain>) so redirect_uri is correct behind Pangolin'
    dashboard_config:
      - task: 'Register FamilySync as an OIDC confidential client (code flow + PKCE S256, client_secret_basic) and ensure the redirect_uri matches the public URL'
        location: 'Authelia configuration'
      - task: 'Expose familysync through Pangolin/Newt (Mode A local test rig is sufficient — Unraid prod optional until go-live, D-15)'
        location: 'Pangolin / Newt connector'

must_haves:
  truths:
    - 'A real member completes Authelia OIDC login over the public Pangolin URL in a desktop browser; the session persists across a browser restart'
    - 'On iPhone, the member installs FamilySync to the Home Screen and completes login WITHOUT the redirect breaking out of standalone mode'
    - 'Each member shows a distinct, stable color (AUTH-03) in the live deploy'
    - 'Create, edit, and delete an event end-to-end through the live deploy; the change appears in the native Fastmail app on the next sync (CAL-04/05/06)'
    - 'The installed PWA opens full-screen with no browser chrome on iOS and Android (PWA-01/02)'
  artifacts:
    - path: '.planning/phases/03-event-write-back-pwa-install/03-GATE2-RESULTS.md'
      provides: 'Recorded Gate 2 verification results (pass/fail per checklist row)'
  key_links:
    - from: 'live deploy'
      to: 'docs/deployment.md Gate 2 checklist'
      via: 'operator executes each row'
      pattern: 'Gate 2'
---

<objective>
Execute and record the Phase 1 Gate 2 live-verification carried into Phase 3 (success
criterion 6, D-14/D-15): real Authelia OIDC login over the public Pangolin URL — most
critically the iOS standalone-PWA login flow — plus session persistence, distinct stable
member colors, and an end-to-end create/edit/delete through the live stack. This is the
first real external auth test and the load-bearing check for the non-technical member.

Purpose: all prior plans build behind the dev-auth bypass (D-13). Nothing has proven the
OIDC redirect survives iOS standalone mode or that writes round-trip to Fastmail in a real
deploy. This plan closes that gap and records the outcome.

Output: 03-GATE2-RESULTS.md with a pass/fail line per Gate 2 checklist row.
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@docs/deployment.md
@.planning/phases/03-event-write-back-pwa-install/03-RESEARCH.md
</context>

<tasks>

<task type="auto">
  <name>Task 1: Build and deploy FamilySync to the Mode A local test rig behind Pangolin</name>
  <files>.planning/phases/03-event-write-back-pwa-install/03-GATE2-RESULTS.md</files>
  <read_first>
    - docs/deployment.md (§Mode A local test rig setup; §Pangolin idle/read timeout requirements; §Gate 2 checklist at line ~215)
    - .planning/PROJECT.md (D-14 dev-auth bypass context; D-15 Mode A local Newt rig)
    - apps/api/src/index.ts (OIDC guard mounts only when devBypassActive is false — production build must NOT set DEV_AUTH_BYPASS)
  </read_first>
  <action>
    Per docs/deployment.md Mode A: produce a production build (NODE_ENV=production so the OIDC guard is mounted, dev-bypass OFF), serve the PWA static build + API, and expose it through the local Newt connector / Pangolin test subdomain. Confirm `OIDC_AUTH_EXTERNAL_URL` is set to the public URL and the Authelia client redirect_uri matches. Confirm the public `/health` responds over the tunnel. Create `03-GATE2-RESULTS.md` and record the deploy details (URL, build SHA, date) as the header before the checklist. If any infra step requires operator-only credentials/config, stop and surface it via the checkpoint in Task 2 rather than guessing.
  </action>
  <verify>
    <automated>cd /home/luc/Projects/familysync && pnpm --filter @familysync/pwa build && pnpm --filter @familysync/api build && test -f .planning/phases/03-event-write-back-pwa-install/03-GATE2-RESULTS.md</automated>
  </verify>
  <acceptance_criteria>
    - Both apps build clean for production.
    - 03-GATE2-RESULTS.md exists with the deploy header (URL, build, date).
    - Public `/health` reachable through the tunnel (record the curl result in the file).
  </acceptance_criteria>
  <done>A production build is live on the Mode A rig behind Pangolin; results file scaffolded.</done>
</task>

<task type="checkpoint:human-verify" gate="blocking-human">
  <name>Task 2: [Gate 2] Live Authelia OIDC + iOS standalone login + distinct colors</name>
  <read_first>
    - docs/deployment.md (§Gate 2 checklist, esp. row 4 — iOS PWA Add-to-Home-Screen + standalone login)
    - .planning/phases/03-event-write-back-pwa-install/03-RESEARCH.md (§Pitfall 2 — iOS standalone OIDC redirect; symptom = stuck in Safari after login; fix = scope:'/' + /callback not SW-intercepted)
  </read_first>
  <action>Execute the docs/deployment.md Gate 2 checklist on the live public URL: real Authelia OIDC desktop login, session persistence across a browser restart, distinct stable per-member colors, and the load-bearing iOS Add-to-Home-Screen + standalone login (redirect must not break out of standalone). Record each row PASS/FAIL in 03-GATE2-RESULTS.md; on iOS failure apply the Pitfall-2 remedy and retest.</action>
  <what-built>The full Phase 3 stack (write-back + outbox worker + PWA install) is deployed to the Mode A rig behind the public Pangolin URL with real Authelia OIDC (dev-bypass OFF). The service worker denylist (Plan 07) keeps `/callback` server-handled.</what-built>
  <how-to-verify>
    Work through docs/deployment.md §Gate 2 checklist on the live public URL and record each row in 03-GATE2-RESULTS.md:
    1. Desktop browser: open the public URL, complete Authelia OIDC login, land on the app — no Fastmail credentials prompted.
    2. Restart the browser, revisit — still logged in (session persists, AUTH-02).
    3. Confirm each of the two members shows a distinct, stable color (AUTH-03).
    4. iPhone: open in Safari, follow the in-app iOS install walkthrough, Add to Home Screen, launch standalone. Complete login — confirm the redirect does NOT break out of standalone (you stay in the app, not dropped to Safari). This is the load-bearing check (Pitfall 2).
    5. Confirm the installed PWA opens full-screen with no browser chrome on iOS and Android (PWA-01).
  </how-to-verify>
  <acceptance_criteria>
    - Each Gate 2 checklist row recorded PASS in 03-GATE2-RESULTS.md, especially the iOS standalone login row.
    - If iOS standalone login FAILS: record the symptom, apply the Pitfall-2 remedy (verify manifest scope:'/' + start_url:'/', confirm /callback is in the SW denylist and reaches the server), redeploy, retest.
  </acceptance_criteria>
  <resume-signal>Type "gate2 pass" with the iOS standalone result, or describe the failure (e.g. "stuck in Safari after login").</resume-signal>
</task>

<task type="checkpoint:human-verify" gate="blocking-human">
  <name>Task 3: [Gate 2] End-to-end create / edit / delete round-trips to Fastmail</name>
  <read_first>
    - .planning/phases/03-event-write-back-pwa-install/03-UI-SPEC.md (§Interaction Contract — sync-state feedback, delete interaction)
    - .planning/phases/03-event-write-back-pwa-install/03-CONTEXT.md (success criteria 1-3: create/edit/delete appear in native Fastmail on next sync)
  </read_first>
  <action>On the live deploy, perform create (timed, all-day, weekly recurring), edit, and delete through the UI and confirm each round-trips to the native Fastmail app on the next sync; observe the SyncStateToast Syncing→Saved flow and (if reproducible) a 412 conflict re-fetch. Record each result in 03-GATE2-RESULTS.md.</action>
  <what-built>Create/edit/delete UI (EventForm, popover footer, delete dialog), the outbox worker, and the polled SyncStateToast are all live on the deploy.</what-built>
  <how-to-verify>
    On the live deploy (logged in as a real member):
    1. Create a timed event and an all-day event (and one weekly recurring event). Confirm the "Syncing…" toast → "Saved", and that each event appears in the native Fastmail app within the next sync cycle (CAL-04/CAL-07).
    2. Edit an existing event's title and time; confirm the change persists in Fastmail (CAL-05).
    3. Delete an event via the two-tap confirmation; confirm it disappears from all views on the next sync (CAL-06).
    4. (Optional, if reproducible) Trigger a 412 conflict by editing the same event from the Fastmail app first; confirm the conflict toast appears and the calendar re-fetches (D-08).
  </how-to-verify>
  <acceptance_criteria>
    - Create (timed + all-day + recurring), edit, and delete each recorded PASS in 03-GATE2-RESULTS.md with confirmation they round-tripped to the native Fastmail app.
    - Sync toast behavior (Syncing → Saved; persistent error on failure) observed and recorded.
  </acceptance_criteria>
  <resume-signal>Type "writeback pass" with the Fastmail round-trip results, or describe any write that did not appear.</resume-signal>
</task>

</tasks>

<threat_model>

## Trust Boundaries

| Boundary                         | Description                                           |
| -------------------------------- | ----------------------------------------------------- |
| public internet → Pangolin → app | First real external exposure of the auth + write path |

## STRIDE Threat Register

| Threat ID | Category               | Component                                                 | Disposition | Mitigation Plan                                                                                                                                             |
| --------- | ---------------------- | --------------------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| T-03-23   | Spoofing               | dev-auth bypass accidentally active in the live deploy    | mitigate    | Production build sets NODE_ENV=production → bypass forced OFF, OIDC unconditionally mounted (index.ts); verify the login is real Authelia, not the dev user |
| T-03-24   | Information Disclosure | OIDC redirect_uri mismatch leaking codes or failing login | mitigate    | OIDC_AUTH_EXTERNAL_URL set to the public URL; Authelia client redirect_uri matches (deployment.md)                                                          |
| T-03-25   | Tampering              | SW intercepting /callback in the live build               | mitigate    | Plan 07 denylist verified against the production build; Gate 2 row 4 confirms standalone login end-to-end                                                   |

</threat_model>

<verification>
- Both apps build for production; public /health reachable through the tunnel.
- 03-GATE2-RESULTS.md records PASS for: desktop OIDC login, session persistence, distinct colors, iOS standalone login, full-screen install, and create/edit/delete Fastmail round-trips.
</verification>

<success_criteria>

- Success criterion 6 satisfied: live Authelia OIDC over Pangolin works including iOS standalone-PWA login; sessions persist; distinct stable colors.
- Success criteria 1-5 confirmed live: create/edit/delete round-trip to Fastmail; installable + full-screen on iOS and Android.
  </success_criteria>

<output>
Create `.planning/phases/03-event-write-back-pwa-install/03-08-SUMMARY.md` when done.
</output>
