/**
 * Master Phase 0 smoke runner.
 *
 * Runs every p0-*.ts smoke script in sequence and aggregates pass/fail.
 * Exit non-zero if any one fails. Designed to be the single command an
 * operator runs locally before merging hardening/phase-0 to main:
 *
 *   npx tsx scripts/smoke/run-all.ts
 *
 * Each child script is offline-safe; no dev server required.
 */
import './_lib'; // populates env vars
import { spawnSync } from 'child_process';
import path from 'path';
import { ok, fail } from './_lib';

const SCRIPTS = [
    // Phase 0
    'p0-2-ssrf.ts',
    'p0-3-channel-encrypt.ts',
    'p0-4-ai-scope.ts',
    'p0-5-monitor-scope.ts',
    'p0-6-2fa-brute.ts',
    'p0-7-users-list.ts',
    'p0-8-env-strict.ts',
    'p0-9-softdelete.ts',
    'p0-10-prisma-log.ts',
    'p0-11-headers.ts',
    'p0-12-body-limit.ts',
    // Phase 1
    'p1-1-retry-interval.ts',
    'p1-2-rule-conditions.ts',
    'p1-7-email-dedup.ts',
    'p1-10-report-scheduler.ts',
    'p1-11-outbox-backoff.ts',
    // Phase 2
    'p2-1-claim.ts',
    'p2-5-rollup-tick.ts',
    'p2-6-dashboard-merge.ts',
    'p2-8-worker-resilience.ts',
    'p2-10-deep-health.ts',
    // Theme A (2FA-on-login)
    'a-2fa-flow.ts',
    // Theme B (errorClass classification)
    'b-errorclass.ts',
    // Theme C (maintenance windows)
    'c-maintenance-window.ts',
    // Theme D (observability)
    'd-observability.ts',
    // PR-A (May 2026): IDOR fixes on notification-rules + synthetic-tests
    // (pra-2-synthetic-tests-authz.ts dropped in audit2-17e along with the
    // SyntheticTest feature itself.)
    'pra-1-notification-rules-authz.ts',
    // PR-B (May 2026): reliability — heartbeat sweeper, up-hysteresis,
    // per-monitor dedup, /api/health/deep auth, worker lock TTL
    'prb-1-heartbeat-sweeper.ts',
    'prb-2-up-hysteresis.ts',
    'prb-4-health-probe-auth.ts',
    'prb-5-worker-lock-ttl.ts',
    // PR-C (May 2026): hygiene — SSE listener metric
    'prc-2-sse-listeners-metric.ts',
    // PR-18 (May 2026): CSP nonce wiring — drops unsafe-inline/eval
    // from script-src by emitting a per-request nonce in middleware.
    'pr18-csp-nonce.ts',
    // PR-D (May 2026): AI chat tool-calling — 6 new operational-data
    // tools (maintenance windows, notification rules/channels, audit
    // log, outbox status, monitor latency history) with permission
    // scoping + ADMIN_READ_ONLY consistency fix.
    'pr-d-ai-tools.ts',
    // PR-19 (May 2026): public status-page email subscribers
    // (subscribe/confirm/unsubscribe + incident fan-out).
    'pr19-status-subscribers.ts',
    // PR-23 (May 2026): audit-log export (CSV + CEF, local rotation).
    'pr23-audit-export.ts',
    // PR-26 (May 2026): escalation enforcement — NotificationRule.
    // escalationDelay actually defers + cancels on recovery.
    'pr26-escalation.ts',
    // PR-27 (May 2026): PagerDuty + Microsoft Teams channels.
    'pr27-pagerduty-teams.ts',
    // PR-28 (May 2026): OpenTelemetry bootstrap contract.
    'pr28-otel.ts',
    // Hotfix (May 2026): login form must not be able to leak
    // credentials to the URL via a native HTML form submit. Asserts
    // 5 defence layers stay in place against future refactors.
    'hotfix-login-credential-leak.ts',
    // Hotfix (May 2026): enabling/disabling 2FA must refresh the JWT
    // so middleware doesn't bounce the user back to /enroll-2fa with
    // a stale token. Asserts both the auth.ts trigger handling and
    // the TwoFactorSettings session-refresh calls stay in place.
    'hotfix-2fa-stale-jwt.ts',
    // audit3-2 (May 2026): RulesEngineService now caches NotificationRule
    // findMany on a 30 s TTL, invalidated by /api/notification-rules
    // POST/PUT/DELETE. Eliminates ~1500 deep-joined queries/min on
    // flapping 500-monitor fleets.
    'audit3-2-rules-cache.ts',
    // audit3-followup (May 2026): boot-time AuditLog hash-chain tail
    // verification — Prometheus counter `audit_chain_verified_total`
    // surfaces tampering within one Passenger restart cycle.
    'audit3-boot-verify-wiring.ts',
    // audit3-3 (May 2026): RollupService pushed the per-hour aggregation
    // into MySQL via INSERT ... SELECT ... GROUP BY ... ON DUPLICATE
    // KEY UPDATE; the prior findMany-per-monitor pattern OOMed at 500
    // monitors × 7 days retention.
    'audit3-3-rollup-sql.ts',
    // audit3-4 (May 2026): every worker tick now has a re-entry guard
    // so slow DB / network can't cause stacked overlapping invocations.
    'audit3-4-tick-guards.ts',
    // audit3-5 (May 2026): email header injection — CRLF strip in
    // EmailService.send + Zod refine on incident.title. Closes a path
    // where an EDITOR could blind-Bcc every alert email.
    'audit3-5-email-crlf.ts',
    // audit3-6 (May 2026): /api/status/subscribe latency padded to a
    // floor so timing can no longer enumerate confirmed subscribers.
    'audit3-6-subscriber-timing.ts',
    // audit3-7 (May 2026): incident timeline title + description now
    // HTML-escaped at the API response so consumers can't render an
    // EDITOR-planted XSS payload via dangerouslySetInnerHTML.
    'audit3-7-timeline-xss.ts',
    // Hotfix (May 2026): EventBus must remain a true singleton across
    // module re-evaluations. Turbopack's server-bundle output evaluated
    // event-bus.ts twice (static-import chain in notification-worker.ts
    // vs dynamic-import chain in monitor-engine.ts), producing two
    // EventEmitter instances — emit on one, listen on the other,
    // dropping every alert. globalThis pin via Symbol.for() guards
    // against future regressions of the same shape.
    'hotfix-event-bus-singleton.ts',
    // EA-1 / PR-32 (May 2026): tamper-evident AuditLog. BEFORE
    // UPDATE/DELETE triggers + sha256 hash chain. Static assertions
    // on schema + migration + AuditService; live trigger verification
    // documented in docs/runbooks/audit-log-tamper-evidence.md.
    'ea-1-audit-immutable.ts',
    // EA-2 / PR-33 (May 2026): DNS-rebinding TOCTOU closed in
    // safe-fetch via pinned-IP fetch (resolve once, SNI preserved).
    'ea-2-dns-rebinding.ts',
    // EA-3 / PR-34 (May 2026): /api/status no longer leaks monitor.url
    // to unauth callers; Monitor.displayName provides a public label.
    'ea-3-status-leak.ts',
    // EA-4 / PR-35 (May 2026): drop unused jsonPath/jsonExpectedValue
    // columns + jsonpath-plus dep (silently-broken field cleanup).
    'ea-4-jsonpath-kill.ts',
    // EA-5 / PR-36 (May 2026): Playwright spec closes PR-31's 2FA-
    // disable JWT-replay verification gap (test.fixme'd pending fixture).
    'ea-5-2fa-runtime-test.ts',
    // EA-6 / PR-37 (May 2026): Monitor.retries default lowered 3→2,
    // exposed in UI. Guards the engine's existing hysteresis behavior
    // (requiredDowns = monitor.retries) against regression.
    'ea-6-consecutive-failures.ts',
    // EA-7 / PR-38 (May 2026): outbound webhook HMAC signing.
    'ea-7-webhook-hmac.ts',
    // EA-8 / PR-39 (May 2026): single-instance guard via MySQL
    // GET_LOCK + deploy docs.
    'ea-8-single-instance.ts',
    // AUDIT-1 (May 2026): XFF spoofing closed — rate-limiter +
    // 2fa-challenge route now go through trusted-ip helper.
    'audit-1-xff-spoofing.ts',
    // AUDIT-2 (May 2026): no hardcoded fallback for NEXTAUTH_SECRET.
    'audit-2-fallback-secret.ts',
    // AUDIT-3 (May 2026): TOTP secrets encrypted at rest via
    // secret-vault envelope; read path stays back-compat with
    // legacy plaintext rows.
    'audit-3-totp-encrypt.ts',
    // AUDIT-7 (May 2026): AES-GCM v2 envelope with optional AAD —
    // ciphertext binds to row identifier; row-swap attack detected.
    'audit-7-aead-aad.ts',
    // AUDIT-2 #1 (May 2026): rate-limiter cleanup honours each
    // record's own windowMs. Previously the hardcoded 60s threshold
    // shortened every windowMs > 60s, making the login 5/hour limit
    // effectively 5/60s — a working brute-force bypass.
    'audit2-1-rate-limiter-cleanup.ts',
    // AUDIT-2 #3 (May 2026): manual/*.sql re-encoded as proper Prisma
    // migrations under prisma/migrations/<timestamp>_<name>/migration.sql
    // so the entrypoint's `prisma migrate deploy` actually has
    // something to apply. Previously deploy was a no-op while the app
    // booted expecting columns that didn't exist.
    'audit2-3-prisma-migrate-real.ts',
    // AUDIT-2 #4 (May 2026): scripts/worker.ts imports resolve via
    // the tsconfig @/ alias instead of relative ../lib paths that
    // pointed at a directory that did not exist. npm run worker
    // boots cleanly.
    'audit2-4-worker-imports.ts',
    // AUDIT-2 #5 (May 2026): envelope prefix lengthened from v1:/v2:
    // (which collided with plaintext values starting with "v1:") to
    // enc_v1:/enc_v2:. Legacy prefixes still recognised on read for
    // back-compat with rows encrypted before the rename; migration
    // script at scripts/maint/reencrypt-legacy-prefixes.ts.
    'audit2-5-encryption-prefix.ts',
    // AUDIT-2 #7 (May 2026): /api/dashboard/kpi rewritten to use a
    // single raw-SQL aggregate (correlated subquery on Heartbeat.id
    // → uses the monitorId+createdAt index) + Promise.all on the
    // remaining queries + 30s in-process cache. Measured end-to-end:
    // 2 min → 1.8 s (67×) on Evidence Action's WAN-DB deployment.
    'audit2-7-kpi-perf.ts',
    // AUDIT-2 #8 (May 2026): MonitorService.getMonitorsForDashboard
    // no longer N+1's on the heartbeats include. Two new batched
    // raw-SQL helpers (latest + recent) replace it. 135 s -> 4 s
    // measured on the dashboard initial load with 29 monitors.
    'audit2-8-dashboard-monitors-perf.ts',
    // AUDIT-2 #13 / #16 (May 2026): maintenance-window lookup cached
    // with 10s TTL in monitor-engine. Eliminates per-check
    // prisma.maintenanceWindow.findMany (500 queries/min at 500
    // monitors/60s). Invalidated on mutation by the API route.
    'audit2-16-maintenance-cache.ts',
    // AUDIT-2 #15 (May 2026): six Tier-1 hygiene fixes from the
    // followups doc — SSRF allowlist pin, error sanitization on
    // public routes, NOC cookie 30d, TLS-ignore ADMIN-only,
    // ASSUME_BEHIND_PROXY env rename, worker exit-on-uncaughtException.
    'audit2-15-tier1-hygiene.ts',
    // AUDIT-2 #17e/#17f (May 2026): SyntheticTest feature dropped
    // (half-shipped, 0 rows); 'dns' monitor type dropped (no strategy
    // implementation, 0 rows).
    'audit2-17ef-dead-code-drop.ts',
    // AUDIT-2 Tier-2 sec (May 2026): #2 loopback/ULA gate behind a
    // second-tier env opt-in, #3 connect-IP regression test, #6 audit-log
    // hash chain v2 (length-prefixed canonicalisation), #9 high-entropy
    // redactor on outbound-channel error bodies.
    'audit2-tier2-sec.ts',
    // AUDIT-2 #9 (May 2026): sparklines + SSE initial fetch rewritten
    // to use batched raw-SQL helpers. UNION ALL of per-monitor LIMIT
    // subqueries for sparklines (176 s -> 1.3 s, 135x). SSE initial
    // fetch uses fetchLatestHeartbeats + fetchRecentHourlyHeartbeats
    // helpers + Promise.all (123 s -> 3.3 s, 37x). Dashboard "Live"
    // badge no longer waits 2 min before flipping from "Reconnect".
    'audit2-9-sparklines-sse-perf.ts',
    // AUDIT-2 #11–#14 (May 2026): remaining slow endpoints fixed.
    // - /api/notifications: $transaction batches findMany+count.
    // - /api/incidents: many-to-many countries/monitors batched
    //   via raw SQL on the join tables.
    // - /api/analytics/{kpi,trend,insights}: 30s in-process
    //   response cache; subsequent reloads within the window are
    //   instant.
    'audit2-11-incidents-notifications-analytics.ts',
    // AUDIT-2 #18 (May 2026): SLA Tracking feature fully removed
    // (unused, 0 rows in live DB). Drops SLATarget + SLAReport tables
    // and the sidebar menu + Executive Dashboard widget that read them.
    'audit2-18-sla-removal.ts',
    // audit3-followup (2026-05-29): User.passwordChangedAt + session-callback
    // iat comparison invalidates every JWT issued before a password rotation.
    // Closes the "stolen cookie keeps working after password reset" gap.
    'audit3-session-rotation.ts',
    // audit3-followup (2026-05-29): CSP violation reports flow into
    // /api/csp-report, counted by uptime_sentinel_csp_violations_total.
    // A parallel Content-Security-Policy-Report-Only header tests a
    // tighter style-src so operators get telemetry before flipping.
    'audit3-csp-report.ts',
    // audit3-followup (2026-05-29): two-project Jest config splits the
    // 570-test node suite from a new dom project (@testing-library/react
    // + jsdom). The smoke asserts the scaffold + 3 proof-point tests
    // stay in place so future contributors don't silently drop it.
    'audit3-component-test-scaffold.ts',
    // audit3-followup (2026-05-29): the 14-day supply-chain quarantine
    // rule that CLAUDE.md documents is now enforced (warn-only) by a CI
    // job. The structural smoke asserts the script + workflow + the
    // docs still align.
    'audit3-quarantine-ci.ts',
    // audit3-followup (2026-05-29): route-test coverage floor. The
    // smoke ratchet prevents test deletion regressions; the actual
    // target (≈30% coverage) is reached incrementally by each future
    // route-adding PR including a matching test.
    'audit3-route-test-coverage.ts',
    // audit3-followup (2026-05-29): secret-vault gained a KEYRING mode
    // (enc_v3:<kid>:... envelopes) so key rotation is zero-downtime.
    // The smoke asserts the round-trip, post-rotation readability,
    // AAD enforcement, and runbook/env.example wiring.
    'audit3-keyring-rotation.ts',
    // audit3-followup (2026-05-29): brute-force counters (login-attempts +
    // twofa-attempts) migrated from per-process in-memory Maps to
    // MySQL-backed BruteForceCounter. The structural smoke catches
    // regressions: schema model, migration file, wrapper delegation,
    // and sync-caller-without-await drops.
    'audit3-brute-force-persistent.ts',
    // audit3-followup (2026-05-29): every API route must either use the
    // withAuth helper, import a recognised auth helper, or be on the
    // PUBLIC_ROUTES allowlist with a one-line justification. Catches
    // newly-shipped routes that skip auth.
    'audit3-withauth-coverage.ts',
    // audit3-followup (2026-05-30): cPanel deploy.sh must `prisma migrate
    // deploy` BEFORE restarting Passenger and fail closed — closes the gap
    // that shipped code ahead of schema and 401'd every login (AccessDenied).
    'audit3-migrate-deploy-guard.ts',
    // T6 (2026-06-02): runtime boot guard complementing the deploy-path guard
    // above — warns loudly if the running code expects unapplied migrations
    // (the cPanel/Passenger bypass: bare pull + restart without ./deploy.sh).
    't6-schema-drift-boot-guard.ts',
    // audit3-followup (2026-05-30): login-less Executive dashboard secured by
    // admin-minted, device-locked executive_display tokens (replacing the old
    // executive_session JWT handoff privesc). Asserts legacy auth removed,
    // proxy cleaned, ADMIN-only generation, device lock, token-gated metrics.
    'audit3-executive-display-tokens.ts',
    // 2026-05-30: Executive Network screen upgraded to a real d3-geo +
    // world-atlas vector map that plots each monitor at its resolved
    // lat/lon and zooms into a country on click. Asserts the geo data
    // contract (report route + MonitorRow) and that react-simple-maps
    // (React-18-capped, breaks on React 19) stays out of the tree.
    'network-geo-map.ts',
    // 2026-05-30: re-add 'dns' (Node dns/promises record monitor, removed in
    // audit2-17f for having no strategy) + add 'ssl' (Node tls cert-watch).
    // Asserts both are wired end-to-end (strategy/engine/zod/service/UI/tests).
    'monitor-types-dns-ssl.ts',
    // SSL-CERT (2026-05-30): HttpMonitorStrategy now captures the peer
    // certificate in the secureConnect handler (race-proof) instead of
    // relying on res.socket.getPeerCertificate() in the response hook, which
    // intermittently returned {} on the live dev server and left tlsExpiresAt
    // / tlsIssuer NULL -> 'SSL Pending' card + 'Not applicable' detail page.
    'ssl-cert-capture.ts',
    // wall-blank fix (2026-05-30): /executive/* pages were matcher-excluded
    // from the proxy, so they never got the PR-18 per-request CSP nonce; under
    // 'strict-dynamic' the browser blocked every nonce-less chunk → blank dark
    // /executive/wall in production. The fix routes the executive PAGE routes
    // through the auth-bypassed public-page branch (nonce + CSP) while keeping
    // /api/executive + /api/reports/executive matcher-excluded. This smoke
    // guards the proxy wiring so the exclusion can't silently come back.
    'wall-blank-executive-csp-nonce.ts',
    // perf (2026-05-30): /api/reports/executive aggregates the heartbeat
    // history and is polled every 30s by every Executive tab + the Wall —
    // it pegged the Node process at ~5 cores on the live box. Now served from
    // a short-TTL shared cache + single-flight. This smoke guards that wiring.
    'exec-report-cache.ts',
    // perf (2026-05-30): /api/reports/executive now aggregates uptime/latency
    // in SQL across raw Heartbeat + the HeartbeatHourly rollup (see
    // executive-metrics.ts) instead of loading ~1M raw rows into JS — fixes
    // both the CPU peg AND the silent under-report of 30d/12mo history once
    // raw retention purges data older than 7 days.
    'exec-report-rollup-agg.ts',
    // security (2026-05-30): sensitive MUTATING routes must role-gate, not just
    // require a session (audit3-withauth-coverage only proves the latter). Guards
    // the authz sweep: settings/countries/import/notifications-test role gates,
    // the Telegram webhook secret-token check, and webhook-secret masking.
    'authz-mutation-roles.ts',
    // retention (2026-05-31): the heatmap, analytics KPI/trend/insights and the
    // downtime report queried raw Heartbeat directly over windows up to 90 days.
    // Raw is retained only ~7 days (older data → HeartbeatHourly), so every day
    // past the boundary blanked once cleanup runs. They now route through the
    // boundary-split combiner in analytics-aggregation.ts. This smoke guards the
    // wiring (and that retention.ts stays in lockstep with the cleanup cron).
    'analytics-retention-split.ts',
    // perf (2026-05-31): explicit Prisma connection-pool config. Prisma's MySQL
    // pool defaults to num_physical_cpus*2+1 (5–9 on a small VPS), which the web
    // process can starve under concurrent analytics + outbox + SSE load. We
    // document connection_limit + the web/worker split in .env.example and apply
    // a conservative default in src/lib/prisma.ts WITHOUT overriding an operator's
    // explicit value. This smoke guards that wiring.
    'perf-db-pool.ts',
    // security/xss (2026-05-31): the admin email-template preview rendered
    // template.body via dangerouslySetInnerHTML, executing admin-authored HTML
    // in the dashboard origin. It now renders through SafeHtmlPreview — a
    // sandboxed <iframe srcDoc> with no allow-scripts. This smoke guards that
    // the dangerous path can't silently return.
    'xss-template-preview-sandbox.ts',
    // perf/db (2026-05-31): PRD §7 #14 — Heartbeat RANGE-partitioned on
    // createdAt (migration 20260531000001_heartbeat_partition). The
    // retention DELETE at tens of millions of rows is replaced by an
    // instant DROP PARTITION. Structural smoke: asserts the migration drops
    // the Monitor FK (MySQL forbids FKs on partitioned tables), the PK is
    // composite (id, createdAt), the table is RANGE BY TO_DAYS(createdAt)
    // with a MAXVALUE catch-all, the schema matches, and the runbook
    // documents the monthly add/drop-partition routine.
    'heartbeat-partition.ts',
    // security/ci (2026-05-31): external-review P1 batch (2nd half). Incident
    // create/update now enforce region-ownership (not role-only — an EDITOR
    // could previously target regions they don't manage); and the jest run is
    // memory-bounded (ts-jest isolatedModules + worker cap) so the full suite
    // stops OOMing and is a real gate again.
    'auditor-p1b-incident-authz.ts',
    // deploy (2026-05-31): Passenger boots server.js directly, NOT `next start`,
    // so Next's own auto-loading of .env never fires. AUTH_2FA_ENFORCED lived
    // only in .env on the prod host (cPanel App Manager didn't have it), so
    // process.env saw it as undefined — 2FA was silently disabled. server.js
    // now calls @next/env's loadEnvConfig before any other env read. This
    // smoke walks server.js to confirm the call is present and positioned
    // before next is required.
    'server-passenger-env-loading.ts',
    // security (2026-06-01): /api/search had NO scoping for non-ADMIN roles —
    // a VIEWER/EDITOR could surface monitor names/URLs owned by others and
    // titles of incidents in regions they don't manage. Route now adds
    // { userId } to monitor.findMany and an incident-region filter for
    // non-ADMIN roles. Auditor T1A.
    'auditor-search-idor.ts',
    // security (2026-06-01): /api/notifications/test passed user-supplied
    // smtpHost straight to nodemailer with no SSRF check. The prior
    // `resolveHostForIpv4` was conditional on smtpIgnoreTls AND didn't
    // reject private/loopback/metadata ranges anyway. Route now routes
    // smtpHost through resolveAndValidate (network-security/ssrf-guard)
    // UNCONDITIONALLY, passes the resolved IP to nodemailer (DNS rebinding
    // defense), keeps the original hostname for SNI. Auditor T1B.
    'auditor-smtp-ssrf.ts',
    // security (2026-06-01): TOTP secret encryption was added in AUDIT-3
    // but did NOT bind the ciphertext to a specific user via AAD. Cross-
    // row swaps could yield a valid envelope on the wrong user. Service
    // now passes AAD = `user:<id>:totp` to encrypt/decryptIfNeeded; all
    // verifyCode call sites pass userId; rebind-totp-secrets-aad.ts
    // migration upgrades existing enc_v1 rows. Auditor T1C.
    'auditor-totp-aad.ts',
    // security (2026-06-01): four routes hand-parsed request bodies with
    // only existence checks. Search, notifications/test, exec-token
    // POST/DELETE, and chat now each call <schema>.safeParse(input)
    // before using the data. Auditor T1D.
    'auditor-zod-validation.ts',
    // ops (2026-06-01): Heartbeat partition maintenance was a manual
    // runbook with pre-created partitions only through Aug 2026. Worker
    // now runs a daily PartitionMaintenanceService.tick() that
    // REORGANIZE pmax into future months and DROP partitions older
    // than the retention window. Auditor T2E.
    'auditor-partition-maintenance.ts',
    // perf (2026-06-01): monitor-engine made TWO separate heartbeat
    // findMany calls per probe — one for the retry-interval count, one
    // for state-eval historyBefore. They overlap (state-eval window is
    // always >= retry-interval window). T2G merges them into a single
    // bounded findMany; both branches slice the shared result.
    'auditor-monitor-engine-roundtrips.ts',
    // ui (2026-06-01): Button.tsx primitive referenced CSS tokens
    // (primary, destructive, accent) that don't exist outside /executive
    // and was imported by exactly ONE file. T3H deletes it; the sole
    // consumer (ChatMessage.tsx) now uses native <button> with concrete
    // Tailwind colors.
    // T9 (2026-06-02): replaced auditor-button-primitive-gone with an adoption
    // ratchet. The Button primitive is reintroduced token-backed + adopted; the
    // ratchet asserts raw <button> count only ever decreases.
    'ui-primitive-adoption.ts',
    // ui (2026-06-01): /incidents and /dashboard/incidents were
    // duplicate routes — separate page files, both hit /api/incidents,
    // no redirect. T3J deletes /incidents/* and migrates the remaining
    // references (SystemStatus.tsx Links + the playwright e2e spec) to
    // the canonical /dashboard/incidents URL the sidebar points at.
    'auditor-dup-incidents-route-gone.ts',
    // ui (2026-06-01): T3I batch 1 — migrate 5 high-visibility files
    // from native alert()/confirm() to the Toast + ConfirmDialog
    // systems. Remaining ~16 files listed in the smoke header for
    // follow-up PRs.
    'auditor-toast-migration-batch1.ts',
    // security (2026-06-01 follow-up): OAuth (Google/Entra) sign-ins
    // bypassed /api/auth/login → /api/auth/2fa-challenge entirely, so
    // a user with totpEnabled=true could sign in via SSO and never be
    // challenged for their second factor. NEW interstitial flow:
    //   - jwt callback sets requires2faOauth when an OAuth provider
    //     issues a JWT for a totpEnabled user;
    //   - middleware redirects to /2fa-challenge-oauth;
    //   - /api/auth/2fa-verify-oauth accepts the TOTP, marks an
    //     in-memory verification flag;
    //   - useSession().update() re-runs the jwt callback, which
    //     consumes the flag and clears requires2faOauth.
    'auditor-oauth-2fa-interstitial.ts',
    // security (2026-06-01): backup-code consumption race. TwoFactor-
    // Service.verifyBackupCode did a non-atomic read-modify-write of
    // the User.backupCodes JSON column; concurrent calls with the same
    // code could double-spend (one consumption persisted, both
    // callers got true). Fix: wrap in prisma.$transaction + SELECT
    // ... FOR UPDATE row lock. This smoke + the unit-test sibling
    // guard the wiring.
    'auditor-backup-code-race.ts',
    // 2026-06-15: outage-duration accuracy. Guards three fixes — recovery
    // emails reconstruct the true outage start (not a ~5-row window that
    // saturated to 9m 30s); the downtime report clamps ongoing outages to
    // now (not the filter end-of-day, which inflated a 1-min outage to
    // 582 min); and soft-deleted monitors are excluded from the report.
    'downtime-duration-accuracy.ts',
    // 2026-06-15 pre-launch hardening: the temporary debug/seed endpoints
    // (api/debug/env, api/debug/notifications, api/seed) were removed as
    // caller-less dead surface; this asserts they stay gone.
    'debug-endpoints-gone.ts',
    // 2026-06-15 pre-launch hardening: /api/chat caps completion tokens
    // (AI_CHAT_MAX_TOKENS) on the streaming call only, bounding per-turn spend.
    'ai-chat-token-cap.ts',
];

// p0-1-cron-auth.ts needs a live dev server, so it's skipped by default.
// Add to LIVE_ONLY to opt-in via SMOKE_LIVE=1.
const LIVE_ONLY = ['p0-1-cron-auth.ts'];

function runOne(name: string): boolean {
    const script = path.resolve(__dirname, name);
    const result = spawnSync(`npx tsx "${script}"`, {
        encoding: 'utf8',
        shell: true,
        env: process.env,
    });
    const passed = result.status === 0;
    if (!passed) {
        console.error(`---- ${name} stderr ----\n${(result.stderr || '').slice(0, 500)}\n---- ${name} stdout ----\n${(result.stdout || '').slice(-500)}`);
    }
    return passed;
}

function main(): void {
    const failed: string[] = [];
    for (const s of SCRIPTS) {
        if (runOne(s)) ok(`${s} passed`);
        else { failed.push(s); }
    }

    if (process.env.SMOKE_LIVE === '1') {
        for (const s of LIVE_ONLY) {
            if (runOne(s)) ok(`${s} passed (live)`);
            else failed.push(s);
        }
    } else {
        ok(`${LIVE_ONLY.length} live-only smoke(s) skipped`, 'set SMOKE_LIVE=1 + start dev server to include');
    }

    if (failed.length > 0) {
        fail('run-all', `failed scripts: ${failed.join(', ')}`);
    }
    ok('Phase 0 exit gate', `${SCRIPTS.length} smoke scripts passed`);
}

main();
