# Phase 12: Initial Setup Wizard - Discussion Log

> **Audit trail only.** Do not use as input to planning, research, or execution agents.
> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.

**Date:** 2026-06-15
**Phase:** 12-initial-setup-wizard
**Areas discussed:** Wizard nature, Restart & resume, Credential + admin, Guard

---

## Wizard nature

### Q1 — Does the wizard write config, or validate-only?

| Option | Description | Selected |
|--------|-------------|----------|
| Validate-only (UI-SPEC) | Wizard never writes config; checks env, generates secrets to paste, validates, flips setup_complete | |
| Collect-and-write some | Wizard accepts some config via fields and persists it; contradicts the no-input UI-SPEC | ✓ |

**User's choice:** Collect-and-write some — "I'm okay to migrate away from a .env file entirely if that helps. The wizard could supersede it and config values live in the DB."

### Q2 — Which config to collect-and-write?

| Option | Description | Selected |
|--------|-------------|----------|
| App / external URL | Non-secret runtime URL → app_config | (folded into D-02) |
| OIDC client_id + issuer | Non-secret OIDC identifiers (caveat: boot-time read) | (folded into D-02) |
| VAPID public key | Non-secret half of keypair → app_config | (folded into D-02) |
| Nothing — reconsider | Keep validate-only after all | |

**User's choice (free text):** "I want the env variables to come through from docker or fall back to that .env file" + (Q3) "minimal kernel + db config."

### Q3 — How far to push config-into-DB?

| Option | Description | Selected |
|--------|-------------|----------|
| Minimal kernel + DB config | Env keeps bootstrap kernel only; wizard writes non-secret config to app_config | ✓ |
| Wizard validates env, writes only credential | Smallest, matches UI-SPEC | |
| Full DB-config migration | Move everything + refactor all consumers; scope risk | |

**User's choice:** Minimal kernel + DB config; env from Docker, `.env` fallback.
**Notes:** Hard floor surfaced and accepted: DB connection + ENCRYPTION_KEY + VAPID_PRIVATE_KEY + SESSION_SECRET + OIDC client_secret cannot leave env (chicken-and-egg / key-beside-ciphertext / SC-3).

---

## Restart & resume

### Q1 — How to handle re-entry after the post-secrets restart?

| Option | Description | Selected |
|--------|-------------|----------|
| Live re-detection, world is the state | No cursor; each step re-checks live env/DB; skip satisfied steps | |
| Persisted step cursor in app_config | Store setup_step; can drift from reality | |
| Always restart from Step 1 | Simplest but breaks the un-re-showable secrets step | |

**User's choice (free text / reframe):** "No, I'm envisioning an Unraid template or clear instructions to bootstrap the image and all of those variables should be defined before first boot."
**Notes:** This designs OUT the mid-wizard restart entirely — kernel is complete at first boot; the generate-secrets step leaves the wizard.

### Q2 — Where do the secret values come from before first boot?

| Option | Description | Selected |
|--------|-------------|----------|
| Documented commands in template/README | openssl + npx web-push generate-vapid-keys | |
| Helper script in the repo | npm run generate-secrets prints all four, VAPID via web-push lib | ✓ |
| Pre-boot generator endpoint/mode | App boots unconfigured to generate; reintroduces complexity | |

**User's choice:** Helper script in the repo.

---

## Credential + admin

### Q1 — How is the first Fastmail credential handled with no user row?

| Option | Description | Selected |
|--------|-------------|----------|
| Validate in wizard, store post-login via self-service | CalDAV-validate only; store later via SetupBanner; double-entry | |
| Store against a pending/first user row | Wizard reserves a user row + credential, reconcile at login | ✓ (refined) |
| No credential in wizard at all | Drops the CalDAV step; fails SC-2 | |

**User's choice (free text):** "When we switch from Fastmail to generic provider, the first user who gets provisioned will need to enter this. … we need a local user who will get merged into an OIDC user once that's set up."
**Notes:** Refined into the local-user → claim-at-first-login model (D-07/D-08); provider-agnostic framing.

### Q2 — How is the first OIDC login matched to the pending local user?

| Option | Description | Selected |
|--------|-------------|----------|
| First-login-claims | First OIDC login after setup_complete adopts the local user | ✓ |
| Match by email claim | Couples identity to email (fights D-10) | |
| Explicit claim code | One-time code; strongest but extra friction | |

**User's choice:** First-login-claims.
**Notes:** User added a deferred capability — a fully local (no-OIDC) operating mode, wiring OIDC in later — to be captured as its own future phase.

---

## Guard

### Q1 — What does the 423 guard trust per invocation?

| Option | Description | Selected |
|--------|-------------|----------|
| Flag OR live-config (defense in depth) | Lock if setup_complete OR (creds row + VAPID env), re-evaluated every call | ✓ |
| Flag only, read fresh each call | Single setup_complete flag, re-read per call | |
| Live-computed only (no flag) | No persisted flag; risks premature mid-flow lock | |

**User's choice:** Flag OR live-config (defense in depth).

---

## Claude's Discretion

- Exact reworked step list and per-step field grouping (consistent with the revised UI-SPEC).
- Exact `/api/setup/*` route paths and new `app_config` key names.
- Migration packaging for the nullable-OIDC-identity + claimed-marker schema change.
- Whether `app_config` runtime reads are per-process cached or per-request.

## Deferred Ideas

- **Local-auth / no-OIDC operating mode** — run entirely on local DB users, wire OIDC in later;
  its own future phase, built atop Phase 12's local-user foundation (D-07).
