Files
familysync/.planning/phases/12-initial-setup-wizard/12-CONTEXT.md
T
Lucas BergerandClaude Opus 4.8 3ed9a42845 docs(12): capture phase context
Restored onto phase-12 branch — discuss-phase originally committed the
context (dc41073) on the phase-18 branch by mistake.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 11:24:43 -04:00

227 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 12: Initial Setup Wizard - Context
**Gathered:** 2026-06-15
**Status:** Ready for planning
<domain>
## Phase Boundary
Phase 12 delivers the **first-run, pre-auth setup wizard** that bootstraps a fresh FamilySync
instance through a validated, step-by-step flow instead of hand-editing config — and reworks the
admin bootstrap so the operator who completes setup becomes the admin.
**Delivers:**
- A standalone `/setup` page (the only screen outside the OIDC guard), reached when the instance
is not yet configured, that **collects non-secret config**, **validates** live connectivity
(DB / OIDC / VAPID / Fastmail CalDAV), provisions the **first local user + credential**, and
**locks** (`setup_complete` + 423 guard) on completion.
- A **minimal-env-kernel + DB-backed-config** model: the running app reads non-secret config from
`app_config` (written by the wizard) instead of requiring it all in env.
- A **repo helper script** to generate the bootstrap secrets before first boot.
- The **WR-01 bootstrap rework**: a pre-OIDC local user, claimed by the first OIDC login.
**NOT in this phase:**
- The visual redesign of the wizard from scratch — `12-UI-SPEC.md` already contracts the look/feel
(but see the ⚠ note: its *validate-only* assumption is partially superseded — Steps 24 need
rework; this phase revises the UI-SPEC, it does not re-derive the design system).
- Full local-auth / no-OIDC operating mode (deferred — see Deferred Ideas).
- Multi-provider credentials beyond Fastmail/CalDAV (Phase 10 D-04 generic shape only).
- Any new crypto or a duplicate credential-storage path (reuse Phase 10's helper).
</domain>
<decisions>
## Implementation Decisions
### Wizard nature — minimal env kernel + DB-backed config
- **D-01: Minimal env kernel.** Only the irreducible bootstrap floor stays in env (it cannot live
in the DB it protects/reaches): **DB connection** (chicken-and-egg), **`SESSION_SECRET`**,
**`APP_PASSWORD_ENCRYPTION_KEY`** (storing it beside the ciphertext it decrypts defeats the
encryption — SC-3 / Pitfall 10), **`VAPID_PRIVATE_KEY`** (SC-3 bars it from the DB), and OIDC
**`client_secret`**.
- **D-02: Non-secret config moves to `app_config`.** The wizard **collects via form fields and
writes** the non-secret, runtime-read config to `app_config`: **app/external URL**, **OIDC issuer
+ client_id**, **VAPID public key**. Runtime consumers (auth middleware boot config, push,
broker) read these from `app_config` rather than env. This is the operator's "config lives in the
DB" goal, bounded by the D-01 floor.
- **D-03: Env resolution precedence.** Kernel env values come from **Docker-provided `process.env`
first, falling back to a `.env` file** (standard dotenv precedence). `.env` is not eliminated —
it shrinks to the kernel.
- **⚠ Supersedes the validate-only `12-UI-SPEC.md`.** Steps 3/4 now need **input fields** (they
collect config, not just validate env). The UI-SPEC must be revised before/within planning — see
Canonical References.
### Restart & resume — no mid-wizard restart
- **D-04: Full kernel defined before first boot.** The operator sets the entire env kernel
(DB connection + all secrets) **before the container's first boot**, via an **Unraid template /
clear bootstrap instructions**. The container comes up already holding its secrets, so the wizard
**never forces a paste-and-restart mid-flow**. The mid-wizard restart problem is designed out.
- **D-05: Secret generation → repo helper script.** Generation moves OUT of the wizard to a
**repo helper script** (e.g. `npm run generate-secrets`) that prints all four values
(`SESSION_SECRET`, `APP_PASSWORD_ENCRYPTION_KEY`, VAPID public + private) formatted for pasting,
generating the VAPID pair with the app's own `web-push` lib for an exact match.
- **D-06: Stateless resume.** No persisted step cursor. Because the kernel is present at boot, the
validation steps simply re-pass on any refresh/re-entry; the env + DB *are* the progress state.
- **⚠ SETUP-03 deviation.** SETUP-03 says "*the wizard* generates secrets." Under this model the
wizard does **not** generate; the helper script does, at provisioning time. UI-SPEC Step 2
("Generate Secrets" + copy + acknowledge) is **dropped/reworked**. Capture as a requirements
deviation for the planner/researcher.
### Credential + admin — pre-OIDC local user, claimed at first login
- **D-07: Pre-OIDC local user.** The wizard provisions a **local user row** (no OIDC identity yet)
that holds the **first validated Fastmail credential** and the **pending-admin** status. Schema:
`users.oidc_iss` / `oidc_sub` become **nullable**, plus a **claimed/pending marker**, so a user
can exist before OIDC and be adopted later.
- **D-08: First-login-claims.** The first OIDC login **after `setup_complete`** **claims/merges**
the single unclaimed local user — populating its `oidc_iss`/`oidc_sub`, keeping the credential +
`is_admin`. This **repurposes Phase 10's first-login-wins** (D-01 there) from "creates a new
admin" to "claims the pending admin," and is the **WR-01 bootstrap rework** Phase 10 flagged for
Phase 12. No email coupling (respects D-10 identity model). Threat model: only household members
can reach Authelia OIDC at all, so the claim window is acceptable for a 2-person self-hosted app.
- **D-09: Credential stored via setup endpoint reusing the shared helper.** A pre-auth `/api/setup/*`
endpoint stores the local user's credential by calling the **shared
`validateEncryptAndStoreCredential` helper** internally (no new crypto, no duplicated logic).
CalDAV PROPFIND validation (SC-2) runs here against the entered app password.
**⚠ Deviation from the literal roadmap constraint** "do NOT create `/api/setup/credentials`
reuse the Phase 10 admin routes": a pre-auth wizard physically cannot call the admin-gated
`/api/admin/credentials`. The deviation honors the constraint's **spirit** (reuse the helper /
no new crypto) while satisfying the pre-auth requirement. Flag for the researcher to confirm the
exact endpoint shape.
### Completion signal & 423 guard
- **D-10: Defense-in-depth guard.** Each setup-route invocation locks (**423**) if
**`app_config.setup_complete` is true OR the system is already effectively configured**
(a `member_credentials` row exists AND VAPID env present) — **re-evaluated fresh every call,
never cached at startup**. Satisfies SC-4 (the flag) and SC-5 (the live check), and protects
manually/upgrade-configured instances that never set the flag. The wizard **only flips
`setup_complete` once preconditions are met**, so it cannot self-lock mid-flow.
### Claude's Discretion
- Exact reworked step list (e.g. Welcome / Config-collect / Validate / Credential / Complete) and
per-step field grouping — planner's call, consistent with the revised UI-SPEC.
- Exact `/api/setup/*` route paths and the `app_config` key naming for the new non-secret config —
planner's call, following the existing `routes/*.ts` Hono + `app_config` key/value pattern
(already used for `household_timezone`).
- The migration packaging for the nullable-OIDC-identity + claimed-marker schema change
(Drizzle generate+migrate, never push).
- Whether runtime config reads from `app_config` are cached per-process or read per-request —
planner's call, balancing the SC-5 "every invocation" intent for the guard specifically.
</decisions>
<canonical_refs>
## Canonical References
**Downstream agents MUST read these before planning or implementing.**
### Requirements & roadmap
- `.planning/REQUIREMENTS.md` — SETUP-01..04 (full wording). **Note the captured deviations:**
SETUP-03 ("wizard generates secrets") is reworked → repo helper script (D-05); SETUP-01's
"wizard defines config" is realized as collect-to-`app_config` for non-secret values only (D-02),
with the secret/DB floor staying in env (D-01).
- `.planning/ROADMAP.md` §Phase 12 — goal, 5 success criteria, pitfalls, and the hard constraints.
**Two literal constraints are deliberately deviated** (with rationale above): "wizard generates
secrets" (D-05) and "no `/api/setup/credentials`, reuse admin routes" (D-09). The researcher must
reconcile these explicitly.
- `.planning/ROADMAP.md` lines ~160-170 — v1.1 DB-foundation note (`app_config.setup_complete`
created in Phase 10, consumed here) and the shared `/api/admin` surface constraint.
### UI design contract (PARTIALLY SUPERSEDED — must be revised)
- `.planning/phases/12-initial-setup-wizard/12-UI-SPEC.md` — the design system, tokens, surfaces,
copywriting, and a11y contract still hold. **BUT its core *validate-only* assumption is
superseded by D-02 (Steps 3/4 collect config → need input fields) and D-04/D-05 (Step 2
"Generate Secrets" is dropped — generation is pre-boot).** Revise the UI-SPEC's Wizard-Steps and
Interaction-Contract sections before/within planning; do not implement Steps 24 as currently
written.
### Prior-phase context this phase builds on
- `.planning/phases/10-admin-role-settings/10-CONTEXT.md` — D-01 first-login-wins (tightened here
to first-login-claims), D-03 `isAdmin` on `/api/me`, D-04 generic provider credential shape,
D-07 self-service `SetupBanner`/`CredentialSheet`, the shared
`validateEncryptAndStoreCredential` helper, and the `app_config` table/`setup_complete` column.
- `.planning/codebase/ARCHITECTURE.md`, `STRUCTURE.md`, `CONVENTIONS.md` — API/PWA layout, route +
schema + frontend conventions to match.
### Key source files
- `apps/api/src/auth/user.ts` — the documented first-login-wins hook (l.108-122) that this phase
**tightens** to gate on `setup_complete` and **repurposes** to claim the pending local user (D-08).
- `apps/api/src/db/schema.ts``users` (make `oidc_iss`/`oidc_sub` nullable + add claimed marker,
D-07), `app_config` (new non-secret config keys, D-02), `member_credentials` (per-user, reused).
- `apps/api/src/routes/admin.ts` — existing `app_config` upsert pattern (`household_timezone`,
l.196-263) and the `validateEncryptAndStoreCredential` reuse target (D-09).
- `apps/api/src/broker/crypto.ts``encryptPassword`/`decryptPassword` (reuse, no changes).
- `apps/api/src/broker/client.ts``createDAVClient`/`fetchCalendars` for CalDAV PROPFIND
validation (SC-2).
- `apps/api/src/index.ts` — route mounting + middleware order; `/api/setup/*` mounts **before** the
OIDC guard (like `/health`).
- `apps/api/src/lib/householdTimezone.ts` — existing example of an `app_config`-backed runtime read
(pattern to follow for D-02 config reads).
- `apps/pwa/src/App.tsx` — the app-level gate that redirects to `/setup` when unconfigured
(per UI-SPEC §Routing); `apps/pwa/src/components/SetupBanner.tsx` + `CredentialSheet.tsx` — the
self-service credential flow reused post-login.
</canonical_refs>
<code_context>
## Existing Code Insights
### Reusable Assets
- `validateEncryptAndStoreCredential` (Phase 10) — the single validate→encrypt→store path; D-09
calls it from the pre-auth setup endpoint against the local user id.
- `broker/crypto.ts` + `broker/client.ts` — credential encryption + CalDAV PROPFIND validation;
reused unchanged for SC-2/SC-3.
- `app_config` key/value table + the `household_timezone` upsert/read pattern
(`routes/admin.ts`, `lib/householdTimezone.ts`) — the template for D-02's non-secret config
storage and runtime reads.
- `SetupBanner` + `CredentialSheet` (self-service mode, D-07 of Phase 10) — the post-login
credential UX; complements the wizard rather than duplicating it.
- First-login-wins block in `auth/user.ts` — written specifically to be tightened here (its inline
comment names this phase).
### Established Patterns
- Routes are per-feature Hono routers under `apps/api/src/routes/`; `/api/setup/*` mounts before the
OIDC guard (only `/health`-style pre-auth surface today).
- Identity is `oidc_iss + oidc_sub`, never email (D-10) — D-08 claim must NOT introduce email-keyed
matching.
- Schema migrations via `drizzle-kit generate` + `migrate`, never `push` ([[drizzle-mariadb-push-unsafe]]).
- PWA routing is declarative `react-router` in `App.tsx`; server state via TanStack Query.
### Integration Points
- `app_config.setup_complete` (created Phase 10) → flipped here on completion; read by the gate +
the 423 guard (D-10) + the tightened first-login claim (D-08).
- New `app_config` non-secret keys (D-02) → read by auth-config boot, push, and the PWA (e.g. VAPID
public key fetched rather than baked into the build).
- Nullable-OIDC-identity + claimed marker (D-07) → consumed by `upsertUser`/login (D-08) and is the
seed the deferred local-auth phase extends.
- `GET /api/setup/status` (pre-OIDC) → drives the `App.tsx` redirect-to-`/setup` gate.
</code_context>
<specifics>
## Specific Ideas
- The operator explicitly wants **config to live in the DB**, with `.env` reduced to a Docker-fed
(or `.env`-fallback) kernel — the wizard is the source of truth for non-secret config (D-01/D-02/D-03).
- The operator runs an **Unraid** deployment and envisions an **Unraid template** (or equivalent
clear instructions) that fully provisions the env kernel before first boot (D-04) — this is why
the in-wizard generate-and-restart dance is intentionally removed.
- The credential/local-user model is framed as **provider-agnostic**: "when we switch from Fastmail
to a generic provider, the first user who gets provisioned will need to enter this" — keep the
Phase 10 generic provider shape (D-04 there); Fastmail/CalDAV remains the only implementation.
- The local user is explicitly conceived as a "**local user who gets merged into an OIDC user once
that's set up**" (D-07/D-08).
</specifics>
<deferred>
## Deferred Ideas
- **Local-auth / no-OIDC operating mode** — the operator wants the option to run FamilySync
**entirely on local DB users with no OIDC**, and wire OIDC in later (they use it this way
themselves). **Its own future phase.** Phase 12's pre-OIDC local-user provisioning (D-07) is the
deliberate **foundation** that phase extends — capture now, build later. (Roadmap/backlog: add a
phase.)
None of the above is in scope for Phase 12 beyond laying the local-user groundwork.
</deferred>
---
*Phase: 12-initial-setup-wizard*
*Context gathered: 2026-06-15*