# fix-reset-db-script (P2 hygiene — fold of two queued follow-ups) **Status:** RESOLVED 2026-05-26 (PR #25, squash commit `3ab9bf8`) **Priority:** P2 hygiene (not a security blocker; `npm run reset-db` is dev-only and currently broken on Node 22, so blast radius is low — but the bug pattern is the same as the P0-grade weak-creds shape that `drop-public-setup` already fixed once) **Convoy owner:** parent (no architect — fold of two well-scoped follow-ups; single-file fix following an established proven pattern) **Opened:** 2026-05-25 **Merged:** 2026-05-26 ## Problem (3 bugs in 1 file) `scripts/reset-db.js` carries three known bugs surfaced by the `pick-a-name` architect audit (2026-05-24) and ratified for fix in this session: 1. **CJS-in-ESM environment** (lines 10, 12, 142): `require()` calls in an ESM file (`package.json "type": "module"` since `bump-next-js`). `npm run reset-db` throws `ReferenceError: require is not defined` on Node 22.x. **Same bug pattern that hit `setup-neon-db.js` pre-`drop-public-setup` Brief 2.** Fix is the same fix. 2. **Hardcoded weak admin password** (line 143: `bcrypt.hash('admin123', 12)`): identical anti-pattern to the one `drop-public-setup` Brief 1 removed from `setup-neon-db.js`. Should require `ADMIN_INITIAL_PASSWORD` env var, fail loud if unset. 3. **Password echoed to stdout** (line 156: `console.log(' Admin Password: admin123')`): explicit security anti-pattern. `setup-neon-db.js` post-`drop-public-setup` does NOT echo the password — same convention applies here. ## Fix (mirror `setup-neon-db.js` exactly) The fix shape is fully derived from the post-`drop-public-setup` `scripts/setup-neon-db.js` file shipped at `b63b509`. Verbatim mirror: - Replace CJS `require('dotenv').config()` with ESM `import dotenv from 'dotenv'; dotenv.config({ path: '.env.local' })` - Replace CJS `const { neon } = require('@neondatabase/serverless')` with ESM `import { neon } from '@neondatabase/serverless'` - Replace inline CJS `const bcrypt = require('bcryptjs')` (line 142) with top-level ESM `import bcrypt from 'bcryptjs'` - Add the same fail-loud `ADMIN_INITIAL_PASSWORD` env-var check at the top of the `async function resetDatabase()` body, with the same helpful error message that points to README.md - Replace `bcrypt.hash('admin123', 12)` with `bcrypt.hash(adminPassword, 12)` - Update the admin email to the post-`pick-a-name` canonical (`admin@deckhearth.com` — already correct in the file at line 147, by the B2 sweep) - Replace the `Admin Password: admin123` log line with `Admin user ready (email: admin@deckhearth.com)` (matching `setup-neon-db.js` line 157) - Add `ON CONFLICT (email) DO NOTHING` to the INSERT (matching `setup-neon-db.js` line 149 — defensive against double-run) ## Scope - **In scope:** `scripts/reset-db.js` only. - **Out of scope:** any other `scripts/*.js` files (none have the same bugs — `setup-neon-db.js` already fixed, migration script already ESM, the rest don't ship admin creds). ## Why no architect This is a **proven-pattern fold** — both `convert-reset-db-to-esm` and `purge-weak-creds-from-helpers` were architect-recommended in `pick-a-name` for "may fold if more such bugs accumulate in helper scripts." All 3 bugs are in 1 file; the fix shape is verbatim-mirror of the post-`drop-public-setup` `setup-neon-db.js`. No new decisions; no new precedents; no surface for an architect to add value. Parent applies the fix, runs the bounded checks, opens the PR. If anything surprising surfaces (a 4th bug, a different bcrypt API, etc.), the parent stops and dispatches an architect mid-execution. ## Acceptance criteria - `node --check scripts/reset-db.js` exit 0 - `npm run lint` exit 1 with 128 problems (baseline preserved; no regression) - `npm run test:run` 21/21 pass (no test surface touched; verification only) - Grep: 0 occurrences of `require(` in `scripts/reset-db.js` - Grep: 0 occurrences of `admin123` in `scripts/reset-db.js` - Grep: 0 occurrences of `Admin Password` in `scripts/reset-db.js` - Grep: `ADMIN_INITIAL_PASSWORD` referenced (1 hit) ## Operator action required pre-merge - **None pre-merge** (no schema change, no env-var addition). - **Optional post-merge:** if the operator wants to verify the fix works end-to-end, they can run `npm run reset-db` against a **non-prod** Neon branch (the script drops all tables — DO NOT run against prod). The script will refuse to run if `ADMIN_INITIAL_PASSWORD` is not set in `.env.local`, with the same helpful error message `setup-neon-db.js` already uses. ## Known constraints - The script is **destructive** (drops all tables). We do NOT live-test it in this convoy. Boot-the-brief is syntax + lint + vitest only. Live verification is the operator's optional post-merge action. - This convoy does NOT add ESLint `no-restricted-syntax` rule against CJS `require()` in `scripts/**`. That would be a separate `lint-against-cjs-in-esm-scripts` convoy. Surfaced here so future helper scripts don't re-introduce the bug. ## Out of scope (queued follow-ups) - `lint-against-cjs-in-esm-scripts` (NEW, P3 polish): add ESLint rule to prevent any future `require()` in `scripts/**` once `package.json` has `"type": "module"`. - `add-neon-return-shape-rule` (P3 polish, surfaced 2026-05-25 by PR #24): codify the `neon()` vs `@vercel/postgres` return-shape difference as a rule. **May fold into `single-sql-client`** which would eliminate the dual-client problem entirely. ## Owns Parent (single-file proven-pattern fix; no architect or implementer subagent required). ## As-shipped Single squash commit `3ab9bf8` (PR #25, merged 2026-05-26). Parent-owned end-to-end per the convoy spec — no architect, no implementer subagent dispatched. Mirror-the-pattern fix exactly as planned; no mid-execution surprises that would have forced an architect bounce. **Diff: 2 files, +161 / -18.** `scripts/reset-db.js` +55 / -18 (the actual fix); `.convoys/fix-reset-db-script.md` +124 (the planning document, committed atomically with the fix). **The three bugs, atomically resolved:** 1. **CJS-in-ESM** (was lines 10, 12, 142): three `require()` calls replaced by ESM top-level imports (`import dotenv from 'dotenv'`, `import { neon } from '@neondatabase/serverless'`, `import bcrypt from 'bcryptjs'`). `npm run reset-db` now executes on Node 22.x instead of throwing `ReferenceError: require is not defined`. 2. **Hardcoded weak admin password** (was line 143): replaced `bcrypt.hash('admin123', 12)` with `bcrypt.hash(adminPassword, 12)`, where `adminPassword` is read from `process.env.ADMIN_INITIAL_PASSWORD` with a fail-loud check at the top of `resetDatabase()`. The check exits with code 1 BEFORE opening any DB connection, matching `setup-neon-db.js`'s post-`drop-public-setup` shape verbatim. 3. **Password echoed to stdout** (was line 156): replaced the `console.log(' Admin Password: admin123')` line with `console.log(' Admin user ready (email: admin@deckhearth.com)')` — exactly the line `setup-neon-db.js` line 157 uses post-DPS. **Bonus defensive shape:** the seed `INSERT` now uses `ON CONFLICT (email) DO NOTHING` (matching `setup-neon-db.js` line 149) so a double-run doesn't error on the existing admin row. This is defensive only; the convoy is destructive (drops all tables first), so the only realistic "existing admin row" scenario is operator confusion between setup-db and reset-db. **Verification (all gates green at merge):** - `node --check scripts/reset-db.js` → exit 0 - `npm run lint` → 128 problems (baseline preserved, no regression) - `npm run test:run` → 21/21 pass - Grep `scripts/reset-db.js`: 0 `require(` | 0 `admin123` | 0 `'Admin Password'` | 3 `ADMIN_INITIAL_PASSWORD` references (docstring + const + error msg) - CI on PR #25: Lint ✓ (39s) | Vitest 21/21 ✓ (28s) | Playwright smoke 3/3 ✓ (1m5s) | `forbidden-endpoints` ✓ (5s) | `forbidden-cors-headers` ✓ (4s) | Vercel preview deploy ✓ | Aggregate gate ✓ - `Screenshot diff`: not triggered (script-only PR — `paths:` filter excludes `scripts/**`, so the queued `tighten-visual-diff-path-filter` follow-up correctly did NOT fire here) **Live verification deferred per convoy spec.** The script is destructive (drops all tables); we did not exercise it against a Neon branch in this convoy. If the operator wants end-to-end proof, the optional post-merge action is `npm run reset-db` against a throwaway Neon branch with `ADMIN_INITIAL_PASSWORD` set (and verify that unsetting it triggers the fail-loud exit with the helpful error message). **Cross-validation finding (organic, not a planned AC).** The `Playwright smoke` 3/3 PASS on a script-only PR confirms that `adopt-playwright-smoke`'s smoke spec is correctly insensitive to `scripts/**` edits — the deployed preview is unaffected by changes to dev-only utility scripts, and the smoke spec correctly green-lights the deployment. This is the fifth consecutive convoy where the same 3-test smoke spec has defended the auth surface (PR #15 Layout default-user → PR #19 CORS-tighten → PR #20 rate-limiting → PR #21 pick-a-name → PR #25 reset-db-fix) without anyone writing a dedicated test. **Operator action required going forward:** **none.** No new env vars (`ADMIN_INITIAL_PASSWORD` was already required by `setup-neon-db.js` post-`drop-public-setup`; this convoy adds nothing new to the env contract). No schema change. No infra change. **Surfaced follow-up (newly queued):** `lint-against-cjs-in-esm-scripts` (P3 polish) — add an ESLint `no-restricted-syntax` rule against `require(` calls in `scripts/**` once `package.json` has `"type": "module"`. Would have caught both this convoy AND the `drop-public-setup` Brief 2 bug at lint time. Filed in `.convoys/ship-readiness.md` § Queued convoys. **`purge-weak-creds-from-helpers` scope reduction.** This convoy satisfies the `scripts/reset-db.js` portion of the queued `purge-weak-creds-from-helpers` follow-up. Remaining scope of that queued convoy: `scripts/create-test-users.js` (alice/bob test fixtures) and `TESTING_GUIDE.md` (documents the weak creds). Both are out of scope here per the convoy spec's single-file boundary.