From 11e8102a71c75fc19ce43c9ab10dee6c88e2bd68 Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Mon, 15 Jun 2026 13:40:04 -0400 Subject: [PATCH] =?UTF-8?q?feat(12-01):=20stub=20setupGuard.ts=20+=20setup?= =?UTF-8?q?.ts=20router=20=E2=80=94=20Wave-0=20import=20targets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add apps/api/src/lib/setupGuard.ts exporting isSetupLocked(): Promise (Wave-0 stub returns false; real DB impl ships in Plan 02) Doc comment enforces D-10: re-evaluate fresh on every call, never module-cache - Add apps/api/src/routes/setup.ts exporting setupRouter = new Hono() (empty router; handlers + index.ts mount added in Plan 02) Doc comment notes pre-auth surface position — before /api/* OIDC chain --- apps/api/src/lib/setupGuard.ts | 21 +++++++++++++++++++++ apps/api/src/routes/setup.ts | 17 +++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 apps/api/src/lib/setupGuard.ts create mode 100644 apps/api/src/routes/setup.ts diff --git a/apps/api/src/lib/setupGuard.ts b/apps/api/src/lib/setupGuard.ts new file mode 100644 index 0000000..d927e1d --- /dev/null +++ b/apps/api/src/lib/setupGuard.ts @@ -0,0 +1,21 @@ +/** + * setupGuard.ts — Phase 12 setup-wizard lock gate. + * + * Exports: isSetupLocked() — returns true when the setup wizard has already been + * completed and no further calls to the /api/setup/* surface should be accepted. + * + * Key constraint (D-10): NEVER cache the result at module level. + * This function MUST be called fresh at the top of every /api/setup/* handler so + * that a concurrent POST /api/setup/complete (Pitfall 8 / SETUP-04) is reflected + * immediately on the next call — even if two requests arrive within the same + * event-loop tick. The per-call freshness pattern mirrors db.select() in health.ts. + * + * Wave-0 stub (Plan 01): returns false (setup always appears incomplete). + * Real implementation (Plan 02): reads app_config.setup_complete + checks + * member_credentials + VAPID env (effective-config branch, D-10). + */ +export async function isSetupLocked(): Promise { + // STUB — Wave-0 placeholder. Real impl: Plan 02. + // Re-evaluated fresh on every call — NEVER cache at module level (D-10). + return false; +} diff --git a/apps/api/src/routes/setup.ts b/apps/api/src/routes/setup.ts new file mode 100644 index 0000000..eb58669 --- /dev/null +++ b/apps/api/src/routes/setup.ts @@ -0,0 +1,17 @@ +/** + * setup.ts — /api/setup/* route surface (Phase 12 initial-setup wizard). + * + * Mounted in index.ts: app.route('/api/setup', setupRouter) + * Mount position: BEFORE app.use('/api/*', devAuthBypass()) so the wizard + * is reachable pre-authentication — same pre-auth surface as /health (T-01-03). + * + * Wave-0 stub (Plan 01): empty router — all handlers are added in Plan 02. + * Plan 02 owns the index.ts mount as well, to keep file-ownership clean. + * + * Security note: every handler added in Plan 02 MUST call isSetupLocked() as + * its first statement and return 423 if locked (SETUP-04 / Pitfall 8 / D-10). + */ + +import { Hono } from 'hono'; + +export const setupRouter = new Hono();