Fill in fix-reset-db-script.md § As-shipped with the merged-state record
(PR #25, squash 3ab9bf8, +161/-18) — three bugs atomically resolved by
mirroring setup-neon-db.js post-drop-public-setup shape; CI green
including 5th-consecutive smoke-spec cross-validation; lint-against-
cjs-in-esm-scripts surfaced as new P3 queue entry.
Update ship-readiness.md Queued convoys:
- convert-reset-db-to-esm → RESOLVED (audit-trail kept, not removed)
- purge-weak-creds-from-helpers → scope reduced (reset-db.js half done;
create-test-users.js + TESTING_GUIDE.md remain)
- lint-against-cjs-in-esm-scripts → NEW (P3 polish; would have caught
both DPS-B2 and PR #25 at lint time)
Co-authored-by: Cursor <cursoragent@cursor.com>
10 KiB
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:
- CJS-in-ESM environment (lines 10, 12, 142):
require()calls in an ESM file (package.json "type": "module"sincebump-next-js).npm run reset-dbthrowsReferenceError: require is not definedon Node 22.x. Same bug pattern that hitsetup-neon-db.jspre-drop-public-setupBrief 2. Fix is the same fix. - Hardcoded weak admin password (line 143:
bcrypt.hash('admin123', 12)): identical anti-pattern to the onedrop-public-setupBrief 1 removed fromsetup-neon-db.js. Should requireADMIN_INITIAL_PASSWORDenv var, fail loud if unset. - Password echoed to stdout (line 156:
console.log(' Admin Password: admin123')): explicit security anti-pattern.setup-neon-db.jspost-drop-public-setupdoes 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 ESMimport dotenv from 'dotenv'; dotenv.config({ path: '.env.local' }) - Replace CJS
const { neon } = require('@neondatabase/serverless')with ESMimport { neon } from '@neondatabase/serverless' - Replace inline CJS
const bcrypt = require('bcryptjs')(line 142) with top-level ESMimport bcrypt from 'bcryptjs' - Add the same fail-loud
ADMIN_INITIAL_PASSWORDenv-var check at the top of theasync function resetDatabase()body, with the same helpful error message that points to README.md - Replace
bcrypt.hash('admin123', 12)withbcrypt.hash(adminPassword, 12) - Update the admin email to the post-
pick-a-namecanonical (admin@deckhearth.com— already correct in the file at line 147, by the B2 sweep) - Replace the
Admin Password: admin123log line withAdmin user ready (email: admin@deckhearth.com)(matchingsetup-neon-db.jsline 157) - Add
ON CONFLICT (email) DO NOTHINGto the INSERT (matchingsetup-neon-db.jsline 149 — defensive against double-run)
Scope
- In scope:
scripts/reset-db.jsonly. - Out of scope: any other
scripts/*.jsfiles (none have the same bugs —setup-neon-db.jsalready 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.jsexit 0npm run lintexit 1 with 128 problems (baseline preserved; no regression)npm run test:run21/21 pass (no test surface touched; verification only)- Grep: 0 occurrences of
require(inscripts/reset-db.js - Grep: 0 occurrences of
admin123inscripts/reset-db.js - Grep: 0 occurrences of
Admin Passwordinscripts/reset-db.js - Grep:
ADMIN_INITIAL_PASSWORDreferenced (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-dbagainst a non-prod Neon branch (the script drops all tables — DO NOT run against prod). The script will refuse to run ifADMIN_INITIAL_PASSWORDis not set in.env.local, with the same helpful error messagesetup-neon-db.jsalready 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-syntaxrule against CJSrequire()inscripts/**. That would be a separatelint-against-cjs-in-esm-scriptsconvoy. 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 futurerequire()inscripts/**oncepackage.jsonhas"type": "module".add-neon-return-shape-rule(P3 polish, surfaced 2026-05-25 by PR #24): codify theneon()vs@vercel/postgresreturn-shape difference as a rule. May fold intosingle-sql-clientwhich 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:
- 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-dbnow executes on Node 22.x instead of throwingReferenceError: require is not defined. - Hardcoded weak admin password (was line 143): replaced
bcrypt.hash('admin123', 12)withbcrypt.hash(adminPassword, 12), whereadminPasswordis read fromprocess.env.ADMIN_INITIAL_PASSWORDwith a fail-loud check at the top ofresetDatabase(). The check exits with code 1 BEFORE opening any DB connection, matchingsetup-neon-db.js's post-drop-public-setupshape verbatim. - Password echoed to stdout (was line 156): replaced the
console.log(' Admin Password: admin123')line withconsole.log(' Admin user ready (email: admin@deckhearth.com)')— exactly the linesetup-neon-db.jsline 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 0npm run lint→ 128 problems (baseline preserved, no regression)npm run test:run→ 21/21 pass- Grep
scripts/reset-db.js: 0require(| 0admin123| 0'Admin Password'| 3ADMIN_INITIAL_PASSWORDreferences (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 excludesscripts/**, so the queuedtighten-visual-diff-path-filterfollow-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.