#!/usr/bin/env node // scripts/verify-phase-4.mjs // Source: Phase 4 SRV-01..14 composite verify gate. // // Sequential orchestrator running every Phase 4 lint + workspace test + // schema-emit gate + ADR lint suite + traceable-reqs check. First non-zero exit fails. // // Usage: node scripts/verify-phase-4.mjs OR: pnpm verify:phase-4 // Exit: 0 success, 1 first-failed-step. // // NOTE: This is the Wave-1 SCAFFOLD. Plans 04-02..04-13 each append their step rows. // Plan 04-13 (final wave) lands the complete steps array. import { spawnSync } from 'node:child_process'; const isWindows = process.platform === 'win32'; // WR-14 fix: cheap lints (regex against committed source) run BEFORE // the full workspace test suite so a developer who accidentally commits // a clipboard-RCE token / protocol-sync drift / unsorted manifest // surfaces the failure in seconds rather than after a 2-minute test // run that may pass for unrelated reasons. Order rationale: // 1. Phase 3 carry-over — a Phase 3 regression invalidates everything // 2. Workspace typecheck — fastest type-level safety net // 3. Cheap regex lints — Phase 4 forcing functions // 4. Drizzle gates — schema-sync / emit-check // 5. Workspace test — slowest step; ordering it last means the cheap // stuff fails-first and saves CI time // 6. Traceable-reqs check — anchored last per CLAUDE.md hard rule // Phase 3 carry-over is platform-bound: protocol-doc:verify byte-compares // the committed protocol.json against a fresh re-emit derived from the // extracted GML source. Git's `text=auto` normalization gives Linux LF and // Windows CRLF for those GML files; the resulting opcode hashes differ // across hosts and produce false-positive drift on whichever platform // didn't generate the committed snapshot. Phase 3 STATE.md flagged this // as "human_needed — Linux CI canonical verify-phase-3 deferred per // HUMAN-UAT.md". `SKIP_PHASE_3_CARRYOVER=1` (set in deploy-staging.yml) // matches the deferral; locally on the host that produced the snapshot // the gate runs as before. Fix path: re-emit on Linux + commit, then drop // this gate. const SKIP_PHASE_3 = process.env.SKIP_PHASE_3_CARRYOVER === '1'; // `traceable-reqs` is an out-of-tree binary the operator installs locally // (CLAUDE.md §"Requirements Traceability" links to BigscreenVR/traceable-reqs). // CLAUDE.md notes the CI hard-gate is deferred to Phase 5 (DEP-04) — and // even there only when the repo can install the binary on Linux runners. // `SKIP_TRACE_CHECK=1` matches that deferral; locally `pnpm trace:check` // runs as before. const SKIP_TRACE = process.env.SKIP_TRACE_CHECK === '1'; const steps = [ // Phase 3 carry-over — gated by SKIP_PHASE_3_CARRYOVER (see above). ...(SKIP_PHASE_3 ? [] : [['Phase 3 carry-over: verify-phase-3', 'pnpm', ['verify:phase-3']]]), // Workspace surface (filled out as packages gain real content) ['Workspace: typecheck', 'pnpm', ['-r', 'typecheck']], // Phase 4 lint forcing-functions (D-25). Plans 04-02..04-12 each append // their step row here. Runs BEFORE Workspace: test (WR-14) so a regex // failure surfaces in seconds, not after the integration suite. ['Lint: protocol-sync', 'pnpm', ['lint:protocol-sync']], ['Lint: game-logic-purity', 'pnpm', ['lint:game-logic-purity']], ['Lint: better-auth-schema-sync', 'pnpm', ['lint:better-auth-schema-sync']], ['Lint: rate-limit-budgets', 'pnpm', ['lint:rate-limit-budgets']], ['Lint: no-clipboard-rce', 'pnpm', ['lint:no-clipboard-rce']], ['Lint: room-layout', 'pnpm', ['lint:room-layout']], ['ADR 0004 lint', 'pnpm', ['lint:adr:0004']], // Drizzle emit-check (paths now point at packages/db/) ['Drizzle: emit-check', 'pnpm', ['db:emit-check']], ['Drizzle: schema-sync', 'pnpm', ['lint:schema-sync']], ['Drizzle: source-comments', 'pnpm', ['lint:source-comments']], // Slow integration test surface — last before the trace check. ['Workspace: test', 'pnpm', ['-r', 'test']], // Trace check — closes CLAUDE.md hard rule #12 ("pnpm trace:check must pass") ...(SKIP_TRACE ? [] : [['Traceable-reqs: check', 'pnpm', ['trace:check']]]), ]; let failed = null; for (const [label, cmd, args] of steps) { process.stdout.write(`\n=== ${label} ===\n>>> ${cmd} ${args.join(' ')}\n`); const r = spawnSync(cmd, args, { encoding: 'utf-8', shell: isWindows, stdio: 'inherit' }); if (r.status !== 0) { failed = { label, cmd, args, status: r.status }; break; } } if (failed) { process.stderr.write( `\nverify-phase-4 FAILED at step '${failed.label}': ${failed.cmd} ${failed.args.join(' ')} (exit ${failed.status})\n` ); process.stderr.write(`Fix the failing step and re-run \`pnpm verify:phase-4\`.\n`); process.exit(1); } process.stdout.write(`\nverify-phase-4: OK (${steps.length} steps green)\n`); process.exit(0);