# User Preference Bootstrap Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Seed a new principal's private settings from complete legacy settings so a first session can configure and start Pi. **Architecture:** `buildApp` continues to resolve settings through the principal-bound harness runner. When `preferencesGet()` returns an empty object, it derives the existing effective legacy settings from `SETTINGS_FILE`, persists them once via `preferencesSet()`, and returns that same object. A non-empty private object remains authoritative. **Tech Stack:** TypeScript, Fastify, Vitest. ## Global Constraints - Never overwrite a non-empty private preference object. - Persist only provider, model, thinking, and workspace values; no credentials enter preferences. - Keep storage failures fail-closed through the existing 503 route contract. - Follow TDD: observe the regression test fail before adding implementation. --- ### Task 1: Bootstrap legacy settings for an empty private profile **Files:** - Modify: `backend/test/routes-settings.test.ts` - Modify: `backend/src/app.ts` **Interfaces:** - Consumes: `ThtRunner.preferencesGet(): Promise>`, `ThtRunner.preferencesSet(settings): Promise`, `loadSettings(config)`, and `effectiveSettings(config, settings)`. - Produces: `getSettings(principal): Promise` that returns a complete persisted profile for first-time principals. - [ ] **Step 1: Write the failing regression tests** ```ts test("GET /settings seeds an empty private profile from complete legacy settings once", async () => { // Seed SETTINGS_FILE with local-qwen/qwen3.6-35b-a3b/low. // Make preferencesGet return {} and record preferencesSet calls. // Assert the first GET returns and persists all four settings, and a second GET does not write again. }); test("GET /settings keeps a non-empty private profile authoritative", async () => { // Seed different legacy settings, return an existing private profile, // and assert no preferencesSet call occurs. }); ``` - [ ] **Step 2: Run the focused test file and verify RED** Run: `cd backend && npx vitest run test/routes-settings.test.ts` Expected: the first test fails because the current resolver returns only defaults and never calls `preferencesSet`. - [ ] **Step 3: Implement the minimal resolver change** ```ts const stored = await runner.preferencesGet(); if (Object.keys(stored).length === 0) { const seeded = effectiveSettings(config, loadSettings(config)); await runner.preferencesSet(seeded); return seeded; } return effectiveSettings(config, stored); ``` - [ ] **Step 4: Run focused tests and TypeScript verification** Run: `cd backend && npx vitest run test/routes-settings.test.ts && npx tsc --noEmit -p .` Expected: exit 0. - [ ] **Step 5: Run backend regression suite** Run: `cd backend && npx vitest run && npm run build` Expected: exit 0. ### Task 2: Preserve DWH artifact ownership across the optional session-storage field **Files:** - Modify: `harness/tests/test_dwh_preprocess_job.py` - Modify: `harness/tht/jobs/dwh_pipeline.py` **Interfaces:** - Consumes: `config_dwh_binding(cfg)` and Pydantic's `Config.model_dump(mode="json")`. - Produces: a DWH binding whose `config_fingerprint` excludes only `session_storage`. - [ ] **Step 1: Write the failing regression test** ```python def test_session_storage_does_not_change_the_dwh_artifact_binding(tmp_path): assert config_dwh_binding(config()) == config_dwh_binding(config(session_storage={...})) ``` - [ ] **Step 2: Run the focused test and verify RED** Run: `cd harness && .venv/bin/pytest tests/test_dwh_preprocess_job.py::test_session_storage_does_not_change_the_dwh_artifact_binding -q` Expected: FAIL because the full configuration JSON currently includes `session_storage`. - [ ] **Step 3: Implement the minimal DWH-only fingerprint** ```python payload = cfg.model_dump(mode="json") payload.pop("session_storage", None) config_fingerprint = fingerprint(json.dumps(payload, separators=(",", ":"), ensure_ascii=False)) ``` - [ ] **Step 4: Run focused and complete harness verification** Run: `cd harness && .venv/bin/pytest tests/test_dwh_preprocess_job.py -q && .venv/bin/pytest -q && .venv/bin/ruff check tht/jobs/dwh_pipeline.py tests/test_dwh_preprocess_job.py` Expected: exit 0.