Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
1 line
55 KiB
JSON
1 line
55 KiB
JSON
{"repo": "1966536805l-crypto/vantaapi-ranking", "files": {".env.example": {"b661cea5e1cb0005dd204115d2a6eedfbdefddc0": {"t": 1785747077.127366, "n": 3264, "h": "b5253094116dfec6", "c": "# JinMing Lab production launch env template.\n# Required production database: Postgres only. Do not use MySQL, SQLite, localhost, or a laptop database.\n# Use the pooled DATABASE_URL generated by Neon, Supabase, Vercel Postgres, or another managed Postgres provider.\nDATABASE_URL=\"postgresql://jinming_user:replace-with-strong-password@db.example.com:5432/jinming_lab?sslmode=require\"\nPOSTGRES_DB=\"jinming_lab\"\nPOSTGRES_USER=\"jinming_user\"\nPOSTGRES_PASSWORD=\"replace-with-strong-app-password\"\n\n# Required in production. Generate with: openssl rand -base64 48\nJWT_SECRET=\"replace-with-at-least-32-random-characters\"\n\n# Public registration should stay closed unless you deliberately open onboarding.\nENABLE_PUBLIC_REGISTRATION=\"false\"\n\n# Max 7 days. Enterprise baseline defaults to 24h.\nAUTH_SESSION_SECONDS=\"86400\"\n\n# Comma-separated canonical hosts accepted by proxy host-header guard.\n# Production: keep only canonical public hosts. Local dev may omit this variable or use 127.0.0.1:<port>.\nAPP_ALLOWED_HOSTS=\"vantaapi.com,www.vantaapi.com\"\n\n# Optional stable sitemap lastmod. Update on real content releases, not every request.\nSITEMAP_LASTMOD=\"2026-05-08T00:00:00.000Z\"\n\n# Edge protection mode. Keep normal for users. Switch to elevated or emergency during attacks.\nSECURITY_MODE=\"normal\"\n\n# Required for public launch if AI features are visible. Use a server-side provider key only; never expose it to the browser.\nAI_API_KEY=\"replace-with-server-side-ai-provider-token\"\nAI_BASE_URL=\"https://api.deepseek.com/v1\"\nAI_MODEL=\"deepseek-chat\"\nAI_TIMEOUT=\"12000\"\n\n# Recommended for public launch when GitHub Repo Analyzer is visible.\n# Use a least-privilege GitHub read token to raise public API quota.\nGITHUB_READ_TOKEN=\"replace-with-github-read-token\"\n\n# Optional self-hosted/local AI fallback. Works only when the Next.js server can reach Ollama.\n# For Vercel, keep this disabled unless OLLAMA_BASE_URL points to your own secured server.\nOLLAMA_ENABLED=\"false\"\nOLLAMA_BASE_URL=\"http://127.0.0.1:11434\"\nOLLAMA_MODEL=\"qwen2.5:3b\"\nOLLAMA_TIMEOUT=\"18000\"\nOLLAMA_API_KEY=\"\"\n\n# Optional low-friction bot protection for login/register only.\n# Production requires Turnstile by default; set both values before public launch.\nNEXT_PUBLIC_TURNSTILE_SITE_KEY=\"\"\nTURNSTILE_SECRET_KEY=\"\"\n\n# Optional, explicit seed-only admin initialization. No public default admin is created.\nSEED_ADMIN_EMAIL=\"\"\nSEED_ADMIN_PASSWORD=\"\"\nSEED_DEMO_USERS=\"false\"\nSEED_STUDENT_EMAIL=\"\"\nSEED_STUDENT_PASSWORD=\"\"\n\n# Production defaults to requiring Turnstile for login/register unless set to false intentionally.\nAUTH_TURNSTILE_REQUIRED=\"true\"\n\n# Recommended for public launch: set REDIS_URL to an Upstash or managed Redis URL.\n# Set true or provide REDIS_URL to enable distributed rate limits.\nENABLE_REDIS_RATE_LIMITS=\"true\"\nREDIS_RATE_LIMIT_FAIL_CLOSED=\"true\"\nREDIS_URL=\"\"\n\n# Required before enabling admin 2FA because TOTP secrets are encrypted at rest.\n# Generate with: openssl rand -hex 32\nENCRYPTION_KEY=\"replace-with-64-hex-chars\"\nCSRF_SECRET=\"replace-with-64-hex-chars\"\n\n# Keep true for production: admins without 2FA may only access the 2FA setup flow.\nADMIN_2FA_REQUIRED=\"true\"\n\n# C++ online runner must stay disabled unless moved to an isolated worker/sandbox.\nENABLE_CPP_RUNNER=\"false\"\n"}, "d602f5661727e14fe1d612c4ac6b980b5aec5433": {"t": 1785770272.2423623, "n": 3264, "h": "b5253094116dfec6", "c": "# JinMing Lab production launch env template.\n# Required production database: Postgres only. Do not use MySQL, SQLite, localhost, or a laptop database.\n# Use the pooled DATABASE_URL generated by Neon, Supabase, Vercel Postgres, or another managed Postgres provider.\nDATABASE_URL=\"postgresql://jinming_user:replace-with-strong-password@db.example.com:5432/jinming_lab?sslmode=require\"\nPOSTGRES_DB=\"jinming_lab\"\nPOSTGRES_USER=\"jinming_user\"\nPOSTGRES_PASSWORD=\"replace-with-strong-app-password\"\n\n# Required in production. Generate with: openssl rand -base64 48\nJWT_SECRET=\"replace-with-at-least-32-random-characters\"\n\n# Public registration should stay closed unless you deliberately open onboarding.\nENABLE_PUBLIC_REGISTRATION=\"false\"\n\n# Max 7 days. Enterprise baseline defaults to 24h.\nAUTH_SESSION_SECONDS=\"86400\"\n\n# Comma-separated canonical hosts accepted by proxy host-header guard.\n# Production: keep only canonical public hosts. Local dev may omit this variable or use 127.0.0.1:<port>.\nAPP_ALLOWED_HOSTS=\"vantaapi.com,www.vantaapi.com\"\n\n# Optional stable sitemap lastmod. Update on real content releases, not every request.\nSITEMAP_LASTMOD=\"2026-05-08T00:00:00.000Z\"\n\n# Edge protection mode. Keep normal for users. Switch to elevated or emergency during attacks.\nSECURITY_MODE=\"normal\"\n\n# Required for public launch if AI features are visible. Use a server-side provider key only; never expose it to the browser.\nAI_API_KEY=\"replace-with-server-side-ai-provider-token\"\nAI_BASE_URL=\"https://api.deepseek.com/v1\"\nAI_MODEL=\"deepseek-chat\"\nAI_TIMEOUT=\"12000\"\n\n# Recommended for public launch when GitHub Repo Analyzer is visible.\n# Use a least-privilege GitHub read token to raise public API quota.\nGITHUB_READ_TOKEN=\"replace-with-github-read-token\"\n\n# Optional self-hosted/local AI fallback. Works only when the Next.js server can reach Ollama.\n# For Vercel, keep this disabled unless OLLAMA_BASE_URL points to your own secured server.\nOLLAMA_ENABLED=\"false\"\nOLLAMA_BASE_URL=\"http://127.0.0.1:11434\"\nOLLAMA_MODEL=\"qwen2.5:3b\"\nOLLAMA_TIMEOUT=\"18000\"\nOLLAMA_API_KEY=\"\"\n\n# Optional low-friction bot protection for login/register only.\n# Production requires Turnstile by default; set both values before public launch.\nNEXT_PUBLIC_TURNSTILE_SITE_KEY=\"\"\nTURNSTILE_SECRET_KEY=\"\"\n\n# Optional, explicit seed-only admin initialization. No public default admin is created.\nSEED_ADMIN_EMAIL=\"\"\nSEED_ADMIN_PASSWORD=\"\"\nSEED_DEMO_USERS=\"false\"\nSEED_STUDENT_EMAIL=\"\"\nSEED_STUDENT_PASSWORD=\"\"\n\n# Production defaults to requiring Turnstile for login/register unless set to false intentionally.\nAUTH_TURNSTILE_REQUIRED=\"true\"\n\n# Recommended for public launch: set REDIS_URL to an Upstash or managed Redis URL.\n# Set true or provide REDIS_URL to enable distributed rate limits.\nENABLE_REDIS_RATE_LIMITS=\"true\"\nREDIS_RATE_LIMIT_FAIL_CLOSED=\"true\"\nREDIS_URL=\"\"\n\n# Required before enabling admin 2FA because TOTP secrets are encrypted at rest.\n# Generate with: openssl rand -hex 32\nENCRYPTION_KEY=\"replace-with-64-hex-chars\"\nCSRF_SECRET=\"replace-with-64-hex-chars\"\n\n# Keep true for production: admins without 2FA may only access the 2FA setup flow.\nADMIN_2FA_REQUIRED=\"true\"\n\n# C++ online runner must stay disabled unless moved to an isolated worker/sandbox.\nENABLE_CPP_RUNNER=\"false\"\n"}, "a4f467b33392d6a4e5e62079456dc5dd4a711567": {"t": 1785770273.7178009, "n": 3133, "h": "6f310d43ff605598", "c": "# JinMing Lab production launch env template.\n# Required production database: Postgres only. Do not use MySQL, SQLite, localhost, or a laptop database.\n# Use the pooled DATABASE_URL generated by Neon, Supabase, Vercel Postgres, or another managed Postgres provider.\nDATABASE_URL=\"postgresql://jinming_user:replace-with-strong-password@db.example.com:5432/jinming_lab?sslmode=require\"\nPOSTGRES_DB=\"jinming_lab\"\nPOSTGRES_USER=\"jinming_user\"\nPOSTGRES_PASSWORD=\"replace-with-strong-app-password\"\n\n# Required in production. Generate with: openssl rand -base64 48\nJWT_SECRET=\"replace-with-at-least-32-random-characters\"\n\n# Public registration should stay closed unless you deliberately open onboarding.\nENABLE_PUBLIC_REGISTRATION=\"false\"\n\n# Max 7 days. Enterprise baseline defaults to 24h.\nAUTH_SESSION_SECONDS=\"86400\"\n\n# Comma-separated canonical hosts accepted by proxy host-header guard.\n# Production: keep only canonical public hosts. Local dev may omit this variable or use 127.0.0.1:<port>.\nAPP_ALLOWED_HOSTS=\"vantaapi.com,www.vantaapi.com\"\n\n# Edge protection mode. Keep normal for users. Switch to elevated or emergency during attacks.\nSECURITY_MODE=\"normal\"\n\n# Required for public launch if AI features are visible. Use a server-side provider key only; never expose it to the browser.\nAI_API_KEY=\"replace-with-server-side-ai-provider-token\"\nAI_BASE_URL=\"https://api.deepseek.com/v1\"\nAI_MODEL=\"deepseek-chat\"\nAI_TIMEOUT=\"12000\"\n\n# Recommended for public launch when GitHub Repo Analyzer is visible.\n# Use a least-privilege GitHub read token to raise public API quota.\nGITHUB_READ_TOKEN=\"replace-with-github-read-token\"\n\n# Optional self-hosted/local AI fallback. Works only when the Next.js server can reach Ollama.\n# For Vercel, keep this disabled unless OLLAMA_BASE_URL points to your own secured server.\nOLLAMA_ENABLED=\"false\"\nOLLAMA_BASE_URL=\"http://127.0.0.1:11434\"\nOLLAMA_MODEL=\"qwen2.5:3b\"\nOLLAMA_TIMEOUT=\"18000\"\nOLLAMA_API_KEY=\"\"\n\n# Optional low-friction bot protection for login/register only.\n# Production requires Turnstile by default; set both values before public launch.\nNEXT_PUBLIC_TURNSTILE_SITE_KEY=\"\"\nTURNSTILE_SECRET_KEY=\"\"\n\n# Optional, explicit seed-only admin initialization. No public default admin is created.\nSEED_ADMIN_EMAIL=\"\"\nSEED_ADMIN_PASSWORD=\"\"\nSEED_DEMO_USERS=\"false\"\nSEED_STUDENT_EMAIL=\"\"\nSEED_STUDENT_PASSWORD=\"\"\n\n# Production defaults to requiring Turnstile for login/register unless set to false intentionally.\nAUTH_TURNSTILE_REQUIRED=\"true\"\n\n# Recommended for public launch: set REDIS_URL to an Upstash or managed Redis URL.\n# Set true or provide REDIS_URL to enable distributed rate limits.\nENABLE_REDIS_RATE_LIMITS=\"true\"\nREDIS_RATE_LIMIT_FAIL_CLOSED=\"true\"\nREDIS_URL=\"\"\n\n# Required before enabling admin 2FA because TOTP secrets are encrypted at rest.\n# Generate with: openssl rand -hex 32\nENCRYPTION_KEY=\"replace-with-64-hex-chars\"\nCSRF_SECRET=\"replace-with-64-hex-chars\"\n\n# Keep true for production: admins without 2FA may only access the 2FA setup flow.\nADMIN_2FA_REQUIRED=\"true\"\n\n# C++ online runner must stay disabled unless moved to an isolated worker/sandbox.\nENABLE_CPP_RUNNER=\"false\"\n"}, "7b0acb8e206eb449a07c9240c08457dedf28fef2": {"t": 1785770275.063081, "n": 2991, "h": "152588668527a079", "c": "# Production uses Postgres through Vercel/Neon.\n# Use the pooled DATABASE_URL generated by the Vercel Neon integration.\nDATABASE_URL=\"postgresql://vantaapi_user:replace-with-strong-password@db.example.com:5432/vantaapi?sslmode=require\"\nPOSTGRES_DB=\"vantaapi\"\nPOSTGRES_USER=\"vantaapi_user\"\nPOSTGRES_PASSWORD=\"replace-with-strong-app-password\"\n\n# Required in production. Generate with: openssl rand -base64 48\nJWT_SECRET=\"replace-with-at-least-32-random-characters\"\n\n# Public registration should stay closed unless you deliberately open onboarding.\nENABLE_PUBLIC_REGISTRATION=\"false\"\n\n# Max 7 days. Enterprise baseline defaults to 24h.\nAUTH_SESSION_SECONDS=\"86400\"\n\n# Comma-separated canonical hosts accepted by proxy host-header guard.\n# Production: keep only canonical public hosts. Local dev may omit this variable or use 127.0.0.1:<port>.\nAPP_ALLOWED_HOSTS=\"vantaapi.com,www.vantaapi.com\"\n\n# Edge protection mode. Keep normal for users. Switch to elevated or emergency during attacks.\nSECURITY_MODE=\"normal\"\n\n# Required for public launch if AI features are visible. Use a server-side provider key only; never expose it to the browser.\nAI_API_KEY=\"replace-with-server-side-ai-provider-token\"\nAI_BASE_URL=\"https://api.deepseek.com/v1\"\nAI_MODEL=\"deepseek-chat\"\nAI_TIMEOUT=\"12000\"\n\n# Required for public launch when GitHub Repo Analyzer is visible. Use a least-privilege read-only token.\nGITHUB_READ_TOKEN=\"replace-with-github-read-token\"\n\n# Optional self-hosted/local AI fallback. Works only when the Next.js server can reach Ollama.\n# For Vercel, keep this disabled unless OLLAMA_BASE_URL points to your own secured server.\nOLLAMA_ENABLED=\"false\"\nOLLAMA_BASE_URL=\"http://127.0.0.1:11434\"\nOLLAMA_MODEL=\"qwen2.5:3b\"\nOLLAMA_TIMEOUT=\"18000\"\nOLLAMA_API_KEY=\"\"\n\n# Optional low-friction bot protection for login/register only.\n# Production requires Turnstile by default; set both values before public launch.\nNEXT_PUBLIC_TURNSTILE_SITE_KEY=\"\"\nTURNSTILE_SECRET_KEY=\"\"\n\n# Redis for distributed production rate limits/audit pipelines.\nREDIS_HOST=\"127.0.0.1\"\nREDIS_PORT=\"6379\"\nREDIS_PASSWORD=\"\"\n\n# Optional, explicit seed-only admin initialization. No public default admin is created.\nSEED_ADMIN_EMAIL=\"\"\nSEED_ADMIN_PASSWORD=\"\"\nSEED_DEMO_USERS=\"false\"\nSEED_STUDENT_EMAIL=\"\"\nSEED_STUDENT_PASSWORD=\"\"\n\n# Production defaults to requiring Turnstile for login/register unless set to false intentionally.\nAUTH_TURNSTILE_REQUIRED=\"true\"\n\n# Set true or provide REDIS_URL to enable distributed rate limits.\nENABLE_REDIS_RATE_LIMITS=\"true\"\nREDIS_RATE_LIMIT_FAIL_CLOSED=\"true\"\nREDIS_URL=\"\"\n\n# Required before enabling admin 2FA because TOTP secrets are encrypted at rest.\n# Generate with: openssl rand -hex 32\nENCRYPTION_KEY=\"replace-with-64-hex-chars\"\nCSRF_SECRET=\"replace-with-64-hex-chars\"\n\n# Keep true for production: admins without 2FA may only access the 2FA setup flow.\nADMIN_2FA_REQUIRED=\"true\"\n\n# C++ online runner must stay disabled unless moved to an isolated worker/sandbox.\nENABLE_CPP_RUNNER=\"false\"\n"}, "320d7341dd60fba270e3ae85473061700f107515": {"t": 1785770276.6764328, "n": 2848, "h": "21ece10d86ea3533", "c": "# Production uses Postgres through Vercel/Neon.\n# Use the pooled DATABASE_URL generated by the Vercel Neon integration.\nDATABASE_URL=\"postgresql://vantaapi_user:replace-with-strong-password@db.example.com:5432/vantaapi?sslmode=require\"\nPOSTGRES_DB=\"vantaapi\"\nPOSTGRES_USER=\"vantaapi_user\"\nPOSTGRES_PASSWORD=\"replace-with-strong-app-password\"\n\n# Required in production. Generate with: openssl rand -base64 48\nJWT_SECRET=\"replace-with-at-least-32-random-characters\"\n\n# Public registration should stay closed unless you deliberately open onboarding.\nENABLE_PUBLIC_REGISTRATION=\"false\"\n\n# Max 7 days. Enterprise baseline defaults to 24h.\nAUTH_SESSION_SECONDS=\"86400\"\n\n# Comma-separated canonical hosts accepted by proxy host-header guard.\n# Production: keep only canonical public hosts. Local dev may omit this variable or use 127.0.0.1:<port>.\nAPP_ALLOWED_HOSTS=\"vantaapi.com,www.vantaapi.com\"\n\n# Edge protection mode. Keep normal for users. Switch to elevated or emergency during attacks.\nSECURITY_MODE=\"normal\"\n\n# Optional AI deep mode. Leave empty to use local instant tools and coaches only.\nAI_API_KEY=\"\"\nAI_BASE_URL=\"\"\nAI_MODEL=\"\"\nAI_TIMEOUT=\"12000\"\n\n# Optional deployer-side GitHub Repo Analyzer quota token. MVP still supports public repositories without user tokens.\nGITHUB_READ_TOKEN=\"\"\n\n# Optional self-hosted/local AI fallback. Works only when the Next.js server can reach Ollama.\n# For Vercel, keep this disabled unless OLLAMA_BASE_URL points to your own secured server.\nOLLAMA_ENABLED=\"false\"\nOLLAMA_BASE_URL=\"http://127.0.0.1:11434\"\nOLLAMA_MODEL=\"qwen2.5:3b\"\nOLLAMA_TIMEOUT=\"18000\"\nOLLAMA_API_KEY=\"\"\n\n# Optional low-friction bot protection for login/register only.\n# Production requires Turnstile by default; set both values before public launch.\nNEXT_PUBLIC_TURNSTILE_SITE_KEY=\"\"\nTURNSTILE_SECRET_KEY=\"\"\n\n# Redis for distributed production rate limits/audit pipelines.\nREDIS_HOST=\"127.0.0.1\"\nREDIS_PORT=\"6379\"\nREDIS_PASSWORD=\"\"\n\n# Optional, explicit seed-only admin initialization. No public default admin is created.\nSEED_ADMIN_EMAIL=\"\"\nSEED_ADMIN_PASSWORD=\"\"\nSEED_DEMO_USERS=\"false\"\nSEED_STUDENT_EMAIL=\"\"\nSEED_STUDENT_PASSWORD=\"\"\n\n# Production defaults to requiring Turnstile for login/register unless set to false intentionally.\nAUTH_TURNSTILE_REQUIRED=\"true\"\n\n# Set true or provide REDIS_URL to enable distributed rate limits.\nENABLE_REDIS_RATE_LIMITS=\"true\"\nREDIS_RATE_LIMIT_FAIL_CLOSED=\"true\"\nREDIS_URL=\"\"\n\n# Required before enabling admin 2FA because TOTP secrets are encrypted at rest.\n# Generate with: openssl rand -hex 32\nENCRYPTION_KEY=\"replace-with-64-hex-chars\"\nCSRF_SECRET=\"replace-with-64-hex-chars\"\n\n# Keep true for production: admins without 2FA may only access the 2FA setup flow.\nADMIN_2FA_REQUIRED=\"true\"\n\n# C++ online runner must stay disabled unless moved to an isolated worker/sandbox.\nENABLE_CPP_RUNNER=\"false\"\n"}, "e00de0d34a689a85db72fbe6c5a3c68345f8c43c": {"t": 1785770278.0089016, "n": 2894, "h": "8b7c01f9eeda38f4", "c": "# Production currently uses MySQL/MariaDB through Prisma.\n# Use a least-privilege database user; do not expose port 3306 publicly.\nDATABASE_URL=\"mysql://vantaapi_user:replace-with-strong-password@127.0.0.1:3306/vantaapi\"\nMARIADB_ROOT_PASSWORD=\"replace-with-strong-root-password\"\nMARIADB_DATABASE=\"vantaapi\"\nMARIADB_USER=\"vantaapi_user\"\nMARIADB_PASSWORD=\"replace-with-strong-app-password\"\n\n# Required in production. Generate with: openssl rand -base64 48\nJWT_SECRET=\"replace-with-at-least-32-random-characters\"\n\n# Public registration should stay closed unless you deliberately open onboarding.\nENABLE_PUBLIC_REGISTRATION=\"false\"\n\n# Max 7 days. Enterprise baseline defaults to 24h.\nAUTH_SESSION_SECONDS=\"86400\"\n\n# Comma-separated canonical hosts accepted by proxy host-header guard.\n# Production: keep only canonical public hosts. Local dev may omit this variable or use 127.0.0.1:<port>.\nAPP_ALLOWED_HOSTS=\"vantaapi.com,www.vantaapi.com\"\n\n# Edge protection mode. Keep normal for users. Switch to elevated or emergency during attacks.\nSECURITY_MODE=\"normal\"\n\n# Optional AI deep mode. Leave empty to use local instant tools and coaches only.\nAI_API_KEY=\"\"\nAI_BASE_URL=\"\"\nAI_MODEL=\"\"\nAI_TIMEOUT=\"12000\"\n\n# Optional deployer-side GitHub Repo Analyzer quota token. MVP still supports public repositories without user tokens.\nGITHUB_READ_TOKEN=\"\"\n\n# Optional self-hosted/local AI fallback. Works only when the Next.js server can reach Ollama.\n# For Vercel, keep this disabled unless OLLAMA_BASE_URL points to your own secured server.\nOLLAMA_ENABLED=\"false\"\nOLLAMA_BASE_URL=\"http://127.0.0.1:11434\"\nOLLAMA_MODEL=\"qwen2.5:3b\"\nOLLAMA_TIMEOUT=\"18000\"\nOLLAMA_API_KEY=\"\"\n\n# Optional low-friction bot protection for login/register only.\n# Production requires Turnstile by default; set both values before public launch.\nNEXT_PUBLIC_TURNSTILE_SITE_KEY=\"\"\nTURNSTILE_SECRET_KEY=\"\"\n\n# Redis for distributed production rate limits/audit pipelines.\nREDIS_HOST=\"127.0.0.1\"\nREDIS_PORT=\"6379\"\nREDIS_PASSWORD=\"\"\n\n# Optional, explicit seed-only admin initialization. No public default admin is created.\nSEED_ADMIN_EMAIL=\"\"\nSEED_ADMIN_PASSWORD=\"\"\nSEED_DEMO_USERS=\"false\"\nSEED_STUDENT_EMAIL=\"\"\nSEED_STUDENT_PASSWORD=\"\"\n\n# Production defaults to requiring Turnstile for login/register unless set to false intentionally.\nAUTH_TURNSTILE_REQUIRED=\"true\"\n\n# Set true or provide REDIS_URL to enable distributed rate limits.\nENABLE_REDIS_RATE_LIMITS=\"true\"\nREDIS_RATE_LIMIT_FAIL_CLOSED=\"true\"\nREDIS_URL=\"\"\n\n# Required before enabling admin 2FA because TOTP secrets are encrypted at rest.\n# Generate with: openssl rand -hex 32\nENCRYPTION_KEY=\"replace-with-64-hex-chars\"\nCSRF_SECRET=\"replace-with-64-hex-chars\"\n\n# Keep true for production: admins without 2FA may only access the 2FA setup flow.\nADMIN_2FA_REQUIRED=\"true\"\n\n# C++ online runner must stay disabled unless moved to an isolated worker/sandbox.\nENABLE_CPP_RUNNER=\"false\"\n"}, "47a31367e51e4c3948c7a14cbbe13d7298554c6b": {"t": 1785770279.2178285, "n": 2753, "h": "d14a1c9bd3a511c2", "c": "# Production currently uses MySQL/MariaDB through Prisma.\n# Use a least-privilege database user; do not expose port 3306 publicly.\nDATABASE_URL=\"mysql://vantaapi_user:replace-with-strong-password@127.0.0.1:3306/vantaapi\"\nMARIADB_ROOT_PASSWORD=\"replace-with-strong-root-password\"\nMARIADB_DATABASE=\"vantaapi\"\nMARIADB_USER=\"vantaapi_user\"\nMARIADB_PASSWORD=\"replace-with-strong-app-password\"\n\n# Required in production. Generate with: openssl rand -base64 48\nJWT_SECRET=\"replace-with-at-least-32-random-characters\"\n\n# Public registration should stay closed unless you deliberately open onboarding.\nENABLE_PUBLIC_REGISTRATION=\"false\"\n\n# Max 7 days. Enterprise baseline defaults to 24h.\nAUTH_SESSION_SECONDS=\"86400\"\n\n# Comma-separated canonical hosts accepted by proxy host-header guard.\n# Production: keep only canonical public hosts. Local dev may omit this variable or use 127.0.0.1:<port>.\nAPP_ALLOWED_HOSTS=\"vantaapi.com,www.vantaapi.com\"\n\n# Edge protection mode. Keep normal for users. Switch to elevated or emergency during attacks.\nSECURITY_MODE=\"normal\"\n\n# Optional AI deep mode. Leave empty to use local instant tools and coaches only.\nAI_API_KEY=\"\"\nAI_BASE_URL=\"\"\nAI_MODEL=\"\"\nAI_TIMEOUT=\"12000\"\n\n# Optional self-hosted/local AI fallback. Works only when the Next.js server can reach Ollama.\n# For Vercel, keep this disabled unless OLLAMA_BASE_URL points to your own secured server.\nOLLAMA_ENABLED=\"false\"\nOLLAMA_BASE_URL=\"http://127.0.0.1:11434\"\nOLLAMA_MODEL=\"qwen2.5:3b\"\nOLLAMA_TIMEOUT=\"18000\"\nOLLAMA_API_KEY=\"\"\n\n# Optional low-friction bot protection for login/register only.\n# Production requires Turnstile by default; set both values before public launch.\nNEXT_PUBLIC_TURNSTILE_SITE_KEY=\"\"\nTURNSTILE_SECRET_KEY=\"\"\n\n# Redis for distributed production rate limits/audit pipelines.\nREDIS_HOST=\"127.0.0.1\"\nREDIS_PORT=\"6379\"\nREDIS_PASSWORD=\"\"\n\n# Optional, explicit seed-only admin initialization. No public default admin is created.\nSEED_ADMIN_EMAIL=\"\"\nSEED_ADMIN_PASSWORD=\"\"\nSEED_DEMO_USERS=\"false\"\nSEED_STUDENT_EMAIL=\"\"\nSEED_STUDENT_PASSWORD=\"\"\n\n# Production defaults to requiring Turnstile for login/register unless set to false intentionally.\nAUTH_TURNSTILE_REQUIRED=\"true\"\n\n# Set true or provide REDIS_URL to enable distributed rate limits.\nENABLE_REDIS_RATE_LIMITS=\"true\"\nREDIS_RATE_LIMIT_FAIL_CLOSED=\"true\"\nREDIS_URL=\"\"\n\n# Required before enabling admin 2FA because TOTP secrets are encrypted at rest.\n# Generate with: openssl rand -hex 32\nENCRYPTION_KEY=\"replace-with-64-hex-chars\"\nCSRF_SECRET=\"replace-with-64-hex-chars\"\n\n# Keep true for production: admins without 2FA may only access the 2FA setup flow.\nADMIN_2FA_REQUIRED=\"true\"\n\n# C++ online runner must stay disabled unless moved to an isolated worker/sandbox.\nENABLE_CPP_RUNNER=\"false\"\n"}, "53ac19fa2ad258c21fabb975c717a7fb18c8510d": {"t": 1785770280.5784235, "n": 2634, "h": "2838fd6f8252fadd", "c": "# Production currently uses MySQL/MariaDB through Prisma.\n# Use a least-privilege database user; do not expose port 3306 publicly.\nDATABASE_URL=\"mysql://vantaapi_user:replace-with-strong-password@127.0.0.1:3306/vantaapi\"\nMARIADB_ROOT_PASSWORD=\"replace-with-strong-root-password\"\nMARIADB_DATABASE=\"vantaapi\"\nMARIADB_USER=\"vantaapi_user\"\nMARIADB_PASSWORD=\"replace-with-strong-app-password\"\n\n# Required in production. Generate with: openssl rand -base64 48\nJWT_SECRET=\"replace-with-at-least-32-random-characters\"\n\n# Public registration should stay closed unless you deliberately open onboarding.\nENABLE_PUBLIC_REGISTRATION=\"false\"\n\n# Max 7 days. Enterprise baseline defaults to 24h.\nAUTH_SESSION_SECONDS=\"86400\"\n\n# Comma-separated canonical hosts accepted by proxy host-header guard.\n# Production: keep only canonical public hosts. Local dev may omit this variable or use 127.0.0.1:<port>.\nAPP_ALLOWED_HOSTS=\"vantaapi.com,www.vantaapi.com\"\n\n# Optional AI deep mode. Leave empty to use local instant tools and coaches only.\nAI_API_KEY=\"\"\nAI_BASE_URL=\"\"\nAI_MODEL=\"\"\nAI_TIMEOUT=\"12000\"\n\n# Optional self-hosted/local AI fallback. Works only when the Next.js server can reach Ollama.\n# For Vercel, keep this disabled unless OLLAMA_BASE_URL points to your own secured server.\nOLLAMA_ENABLED=\"false\"\nOLLAMA_BASE_URL=\"http://127.0.0.1:11434\"\nOLLAMA_MODEL=\"qwen2.5:3b\"\nOLLAMA_TIMEOUT=\"18000\"\nOLLAMA_API_KEY=\"\"\n\n# Optional low-friction bot protection for login/register only.\n# Production requires Turnstile by default; set both values before public launch.\nNEXT_PUBLIC_TURNSTILE_SITE_KEY=\"\"\nTURNSTILE_SECRET_KEY=\"\"\n\n# Redis for distributed production rate limits/audit pipelines.\nREDIS_HOST=\"127.0.0.1\"\nREDIS_PORT=\"6379\"\nREDIS_PASSWORD=\"\"\n\n# Optional, explicit seed-only admin initialization. No public default admin is created.\nSEED_ADMIN_EMAIL=\"\"\nSEED_ADMIN_PASSWORD=\"\"\nSEED_DEMO_USERS=\"false\"\nSEED_STUDENT_EMAIL=\"\"\nSEED_STUDENT_PASSWORD=\"\"\n\n# Production defaults to requiring Turnstile for login/register unless set to false intentionally.\nAUTH_TURNSTILE_REQUIRED=\"true\"\n\n# Set true or provide REDIS_URL to enable distributed rate limits.\nENABLE_REDIS_RATE_LIMITS=\"true\"\nREDIS_RATE_LIMIT_FAIL_CLOSED=\"true\"\nREDIS_URL=\"\"\n\n# Required before enabling admin 2FA because TOTP secrets are encrypted at rest.\n# Generate with: openssl rand -hex 32\nENCRYPTION_KEY=\"replace-with-64-hex-chars\"\nCSRF_SECRET=\"replace-with-64-hex-chars\"\n\n# Keep true for production: admins without 2FA may only access the 2FA setup flow.\nADMIN_2FA_REQUIRED=\"true\"\n\n# C++ online runner must stay disabled unless moved to an isolated worker/sandbox.\nENABLE_CPP_RUNNER=\"false\"\n"}, "4691e284d43262db0f8cc7004c31b7d1929489e4": {"t": 1785770282.109712, "n": 2316, "h": "8b11979e711df668", "c": "# Production currently uses MySQL/MariaDB through Prisma.\n# Use a least-privilege database user; do not expose port 3306 publicly.\nDATABASE_URL=\"mysql://vantaapi_user:replace-with-strong-password@127.0.0.1:3306/vantaapi\"\nMARIADB_ROOT_PASSWORD=\"replace-with-strong-root-password\"\nMARIADB_DATABASE=\"vantaapi\"\nMARIADB_USER=\"vantaapi_user\"\nMARIADB_PASSWORD=\"replace-with-strong-app-password\"\n\n# Required in production. Generate with: openssl rand -base64 48\nJWT_SECRET=\"replace-with-at-least-32-random-characters\"\n\n# Public registration should stay closed unless you deliberately open onboarding.\nENABLE_PUBLIC_REGISTRATION=\"false\"\n\n# Max 7 days. Enterprise baseline defaults to 24h.\nAUTH_SESSION_SECONDS=\"86400\"\n\n# Comma-separated canonical hosts accepted by proxy host-header guard.\n# Production: keep only canonical public hosts. Local dev may omit this variable or use 127.0.0.1:<port>.\nAPP_ALLOWED_HOSTS=\"vantaapi.com,www.vantaapi.com\"\n\n# Optional AI deep mode. Leave empty to use local instant tools and coaches only.\nAI_API_KEY=\"\"\nAI_BASE_URL=\"\"\nAI_MODEL=\"\"\nAI_TIMEOUT=\"12000\"\n\n# Optional low-friction bot protection for login/register only.\n# Production requires Turnstile by default; set both values before public launch.\nNEXT_PUBLIC_TURNSTILE_SITE_KEY=\"\"\nTURNSTILE_SECRET_KEY=\"\"\n\n# Redis for distributed production rate limits/audit pipelines.\nREDIS_HOST=\"127.0.0.1\"\nREDIS_PORT=\"6379\"\nREDIS_PASSWORD=\"\"\n\n# Optional, explicit seed-only admin initialization. No public default admin is created.\nSEED_ADMIN_EMAIL=\"\"\nSEED_ADMIN_PASSWORD=\"\"\nSEED_DEMO_USERS=\"false\"\nSEED_STUDENT_EMAIL=\"\"\nSEED_STUDENT_PASSWORD=\"\"\n\n# Production defaults to requiring Turnstile for login/register unless set to false intentionally.\nAUTH_TURNSTILE_REQUIRED=\"true\"\n\n# Set true or provide REDIS_URL to enable distributed rate limits.\nENABLE_REDIS_RATE_LIMITS=\"true\"\nREDIS_RATE_LIMIT_FAIL_CLOSED=\"true\"\nREDIS_URL=\"\"\n\n# Required before enabling admin 2FA because TOTP secrets are encrypted at rest.\n# Generate with: openssl rand -hex 32\nENCRYPTION_KEY=\"replace-with-64-hex-chars\"\nCSRF_SECRET=\"replace-with-64-hex-chars\"\n\n# Keep true for production: admins without 2FA may only access the 2FA setup flow.\nADMIN_2FA_REQUIRED=\"true\"\n\n# C++ online runner must stay disabled unless moved to an isolated worker/sandbox.\nENABLE_CPP_RUNNER=\"false\"\n"}}, "scripts/prelaunch-check.js": {"b661cea5e1cb0005dd204115d2a6eedfbdefddc0": {"t": 1786068489.6108255, "n": 26070, "h": "64db321fe4e47912", "c": "#!/usr/bin/env node\n\n/**\n * Production launch gate for JinMing Lab.\n * Checks server configuration without printing secret values.\n */\n\n// eslint-disable-next-line @typescript-eslint/no-require-imports\nconst fs = require(\"fs\");\n// eslint-disable-next-line @typescript-eslint/no-require-imports\nconst path = require(\"path\");\n// eslint-disable-next-line @typescript-eslint/no-require-imports\nconst dotenv = require(\"dotenv\");\n// eslint-disable-next-line @typescript-eslint/no-require-imports\nconst { PrismaClient, UserRole } = require(\"@prisma/client\");\n\nconst root = process.cwd();\nlet pass = 0;\nlet warn = 0;\nlet fail = 0;\nlet databaseUrlLooksProductionReady = false;\nconst loadedEnvFiles = [];\nconst launchActions = [];\n\nfunction read(file) {\n try {\n return fs.readFileSync(path.join(root, file), \"utf8\");\n } catch {\n return \"\";\n }\n}\n\nfunction ok(name, message = \"\") {\n pass += 1;\n console.log(`✅ ${name}${message ? `: ${message}` : \"\"}`);\n}\n\nfunction caution(name, message) {\n warn += 1;\n console.log(`⚠️ ${name}: ${message}`);\n}\n\nfunction bad(name, message) {\n fail += 1;\n console.log(`❌ ${name}: ${message}`);\n}\n\nfunction action(title, detail) {\n launchActions.push({ title, detail });\n}\n\nfunction loadEnvFile(file) {\n const full = path.join(root, file);\n if (!fs.existsSync(full)) return {};\n loadedEnvFiles.push(file);\n return dotenv.parse(fs.readFileSync(full, \"utf8\"));\n}\n\nconst explicitEnvFile = process.env.PRELAUNCH_ENV_FILE;\nconst ignoreProcessEnv =\n Boolean(explicitEnvFile) && process.env.PRELAUNCH_IGNORE_PROCESS_ENV !== \"false\";\nconst processEnvPriority = process.env.PRELAUNCH_PROCESS_ENV_PRIORITY === \"true\";\nfunction mergeNonEmpty(target, source) {\n for (const [key, value] of Object.entries(source)) {\n if (String(value || \"\").trim() || !(key in target)) target[key] = value;\n }\n return target;\n}\n\nconst env = {};\nif (processEnvPriority && !ignoreProcessEnv) mergeNonEmpty(env, process.env);\nif (explicitEnvFile) {\n mergeNonEmpty(env, loadEnvFile(explicitEnvFile));\n} else {\n mergeNonEmpty(env, loadEnvFile(\".env\"));\n mergeNonEmpty(env, loadEnvFile(\".env.production\"));\n mergeNonEmpty(env, loadEnvFile(\".env.vercel.production.local\"));\n}\nif (!ignoreProcessEnv && !processEnvPriority) mergeNonEmpty(env, process.env);\n\nfunction envValue(name) {\n return String(env[name] || \"\").trim();\n}\n\nfunction looksPlaceholder(value) {\n return !value || /replace|generate|change-me|example|password|secret|your-|<.*>|xxx|todo/i.test(value);\n}\n\nfunction checkSecret(name, options = {}) {\n const value = envValue(name);\n const min = options.min || 32;\n if (looksPlaceholder(value)) return bad(`env:${name}`, \"missing or still a placeholder\");\n if (value.length < min) return bad(`env:${name}`, `must be at least ${min} characters`);\n if (options.hex && !/^[a-f0-9]+$/i.test(value)) return bad(`env:${name}`, \"must be hex encoded\");\n ok(`env:${name}`, \"configured with production-shaped value\");\n}\n\nfunction checkToken(name, options = {}) {\n const value = envValue(name);\n const min = options.min || 20;\n if (looksPlaceholder(value)) {\n bad(`env:${name}`, \"missing or still a placeholder\");\n action(`Set ${name}`, `Add a real provider value in Vercel Production environment variables. Keep it server-side and never commit it.`);\n return;\n }\n if (value.length < min) return bad(`env:${name}`, `looks too short; expected at least ${min} characters`);\n if (/\\s/.test(value)) return bad(`env:${name}`, \"must not contain whitespace\");\n if (options.prefixes && !options.prefixes.some((prefix) => value.startsWith(prefix))) {\n return caution(`env:${name}`, `token does not use a common prefix (${options.prefixes.join(\", \")}); verify provider value manually`);\n }\n ok(`env:${name}`, \"configured; value hidden\");\n}\n\nfunction checkRecommendedToken(name, options = {}) {\n const value = envValue(name);\n const min = options.min || 20;\n if (looksPlaceholder(value)) {\n action(`Optional: set ${name}`, \"Add a GitHub fine-grained read token to raise GitHub API quota for launch audits.\");\n return caution(`env:${name}`, \"not configured; feature will use a fallback or lower quota\");\n }\n if (value.length < min) {\n return caution(`env:${name}`, `looks short; expected at least ${min} characters`);\n }\n if (/\\s/.test(value)) {\n return caution(`env:${name}`, \"contains whitespace; verify provider value manually\");\n }\n if (options.prefixes && !options.prefixes.some((prefix) => value.startsWith(prefix))) {\n return caution(`env:${name}`, `token does not use a common prefix (${options.prefixes.join(\", \")}); verify provider value manually`);\n }\n ok(`env:${name}`, \"configured; value hidden\");\n}\n\nfunction checkDatabasePassword() {\n const value = envValue(\"DATABASE_URL\");\n if (looksPlaceholder(value)) {\n bad(\"env:DATABASE_URL\", \"missing or still a placeholder\");\n action(\"Set DATABASE_URL\", \"Use a production Postgres URL from Neon, Supabase, Vercel Postgres, or another cloud database. Do not use localhost or MySQL for this Prisma schema.\");\n return;\n }\n try {\n const parsed = new URL(value);\n if (![\"postgres:\", \"postgresql:\"].includes(parsed.protocol)) {\n action(\"Replace DATABASE_URL\", \"The current DATABASE_URL is not Postgres. Create or connect a production Postgres database, then set DATABASE_URL in Vercel Production.\");\n return bad(\"env:DATABASE_URL\", \"must be a valid postgres/postgresql URL\");\n }\n if ([\"127.0.0.1\", \"localhost\"].includes(parsed.hostname)) {\n action(\"Move database off localhost\", \"Production deployments cannot use your laptop database. Set DATABASE_URL to a reachable cloud Postgres host.\");\n return bad(\"env:DATABASE_URL\", \"must use a reachable production database host\");\n }\n const password = decodeURIComponent(parsed.password || \"\");\n if (looksPlaceholder(password) || password.length < 16) {\n action(\"Rotate database password\", \"Generate a strong database password in the database provider dashboard and update DATABASE_URL in Vercel.\");\n return bad(\"env:DATABASE_URL\", \"database password must be strong and not a placeholder\");\n }\n if ([\"root\", \"admin\"].includes(parsed.username.toLowerCase())) {\n caution(\"env:DATABASE_URL\", \"avoid root/admin DB users in production if possible\");\n }\n databaseUrlLooksProductionReady = true;\n ok(\"env:DATABASE_URL\", \"database password is present and non-placeholder\");\n } catch {\n bad(\"env:DATABASE_URL\", \"must be a valid postgres/postgresql URL\");\n }\n}\n\nfunction includesAny(file, patterns) {\n const body = read(file);\n return patterns.some((pattern) => pattern.test(body));\n}\n\nfunction checkAiProviderConfig() {\n checkToken(\"AI_API_KEY\", { min: 24 });\n\n const baseUrl = envValue(\"AI_BASE_URL\") || \"https://api.deepseek.com/v1\";\n try {\n const parsed = new URL(baseUrl);\n if (![\"https:\", \"http:\"].includes(parsed.protocol)) {\n bad(\"env:AI_BASE_URL\", \"must be an HTTP(S) URL\");\n } else if (parsed.protocol !== \"https:\" && ![\"127.0.0.1\", \"localhost\"].includes(parsed.hostname)) {\n bad(\"env:AI_BASE_URL\", \"must use HTTPS unless it is a local development endpoint\");\n } else {\n ok(\"env:AI_BASE_URL\", \"configured or using secure default\");\n }\n } catch {\n bad(\"env:AI_BASE_URL\", \"must be a valid URL when set\");\n }\n\n if (envValue(\"OLLAMA_ENABLED\") === \"true\") {\n const ollamaUrl = envValue(\"OLLAMA_BASE_URL\") || \"http://127.0.0.1:11434\";\n try {\n const parsed = new URL(ollamaUrl);\n if ([\"127.0.0.1\", \"localhost\"].includes(parsed.hostname)) {\n caution(\"env:OLLAMA_BASE_URL\", \"local Ollama works only when the app server runs beside Ollama; Vercel cannot reach your laptop\");\n } else if (parsed.protocol !== \"https:\") {\n bad(\"env:OLLAMA_BASE_URL\", \"remote Ollama endpoints must use HTTPS and server-side auth\");\n } else if (!envValue(\"OLLAMA_API_KEY\")) {\n bad(\"env:OLLAMA_API_KEY\", \"remote Ollama endpoints need a server-side auth token\");\n } else {\n ok(\"env:OLLAMA_BASE_URL\", \"remote fallback is protected\");\n }\n } catch {\n bad(\"env:OLLAMA_BASE_URL\", \"must be a valid URL when Ollama is enabled\");\n }\n } else {\n ok(\"env:OLLAMA_ENABLED\", \"disabled unless deliberately deployed beside the app\");\n }\n}\n\nfunction checkGitHubToken() {\n checkRecommendedToken(\"GITHUB_READ_TOKEN\", { min: 24, prefixes: [\"github_pat_\", \"ghp_\"] });\n}\n\nfunction checkTurnstileConfig() {\n const required = envValue(\"AUTH_TURNSTILE_REQUIRED\") === \"true\" || envValue(\"AUTH_TURNSTILE_REQUIRED\") !== \"false\";\n const siteKey = envValue(\"NEXT_PUBLIC_TURNSTILE_SITE_KEY\");\n const secret = envValue(\"TURNSTILE_SECRET_KEY\");\n if (!required) return caution(\"env:AUTH_TURNSTILE_REQUIRED\", \"Turnstile is not required; enable it before public launch if auth stays open\");\n if (looksPlaceholder(siteKey) || looksPlaceholder(secret)) {\n action(\"Configure Cloudflare Turnstile\", \"Create a Turnstile widget in Cloudflare, then set NEXT_PUBLIC_TURNSTILE_SITE_KEY and TURNSTILE_SECRET_KEY in Vercel Production.\");\n return bad(\"env:TURNSTILE\", \"NEXT_PUBLIC_TURNSTILE_SITE_KEY and TURNSTILE_SECRET_KEY are required when auth Turnstile is enabled\");\n }\n ok(\"env:TURNSTILE\", \"auth bot protection is configured\");\n}\n\nfunction checkHostAllowlist() {\n const value = envValue(\"APP_ALLOWED_HOSTS\");\n if (!value) {\n ok(\"env:APP_ALLOWED_HOSTS\", \"using secure built-in production host defaults\");\n return;\n }\n\n const hosts = value.split(\",\").map((item) => item.trim().toLowerCase()).filter(Boolean);\n if (hosts.some((host) => host === \"*\" || host.includes(\" \"))) {\n return bad(\"env:APP_ALLOWED_HOSTS\", \"must contain exact hostnames only\");\n }\n if (!hosts.includes(\"vantaapi.com\") && !hosts.includes(\"www.vantaapi.com\")) {\n return caution(\"env:APP_ALLOWED_HOSTS\", \"does not include vantaapi.com/www.vantaapi.com; verify launch domain\");\n }\n ok(\"env:APP_ALLOWED_HOSTS\", \"host allowlist is explicit\");\n}\n\nfunction checkRedisConfig() {\n const enabled = envValue(\"ENABLE_REDIS_RATE_LIMITS\") === \"true\" || Boolean(envValue(\"REDIS_URL\"));\n if (!enabled) {\n action(\"Recommended: set REDIS_URL\", \"Use Upstash Redis or another managed Redis to make rate limits consistent across serverless instances.\");\n caution(\"env:REDIS_URL\", \"not configured; in-memory rate limits will work but are weaker on serverless scale\");\n return;\n }\n\n const redisUrl = envValue(\"REDIS_URL\");\n if (!redisUrl) {\n action(\"Set REDIS_URL\", \"ENABLE_REDIS_RATE_LIMITS is true, so REDIS_URL must point to a managed Redis instance.\");\n return bad(\"env:REDIS_URL\", \"ENABLE_REDIS_RATE_LIMITS is true but REDIS_URL is missing\");\n }\n\n try {\n const parsed = new URL(redisUrl);\n if ([\"127.0.0.1\", \"localhost\"].includes(parsed.hostname)) {\n return bad(\"env:REDIS_URL\", \"must not point to localhost in production\");\n }\n ok(\"env:REDIS_URL\", \"distributed rate limit store is configured\");\n } catch {\n bad(\"env:REDIS_URL\", \"must be a valid URL when Redis rate limits are enabled\");\n }\n}\n\nasync function checkAdmin2FAState() {\n if (!databaseUrlLooksProductionReady) {\n action(\"Verify admin 2FA after database is fixed\", \"After DATABASE_URL points to production Postgres, create or seed an ADMIN user and complete 2FA before public launch.\");\n bad(\"db:admin-2fa\", \"skipped because DATABASE_URL is not production-ready\");\n return;\n }\n\n const prisma = new PrismaClient({ datasources: { db: { url: envValue(\"DATABASE_URL\") } } });\n try {\n const admins = await prisma.user.findMany({\n where: { role: UserRole.ADMIN },\n select: { email: true, twoFactorEnabled: true, twoFactorConfirmedAt: true },\n });\n\n if (admins.length === 0) {\n action(\"Create an ADMIN user\", \"Seed or create one admin account in production, then log in and complete 2FA.\");\n bad(\"db:admin-2fa\", \"no ADMIN user exists; create one before launch\");\n return;\n }\n\n const unprotected = admins.filter((admin) => !admin.twoFactorEnabled || !admin.twoFactorConfirmedAt);\n if (unprotected.length > 0) {\n action(\"Complete admin 2FA\", \"Log in as every admin account and finish 2FA setup before public launch.\");\n bad(\"db:admin-2fa\", `${unprotected.length}/${admins.length} admin account(s) have not completed 2FA`);\n return;\n }\n\n ok(\"db:admin-2fa\", `${admins.length} admin account(s) have completed 2FA`);\n } catch (error) {\n bad(\"db:admin-2fa\", error instanceof Error ? `database check failed: ${error.message}` : \"database check failed\");\n } finally {\n await prisma.$disconnect().catch(() => undefined);\n }\n}\n\nfunction checkBrandResidue() {\n const publicBrandResidue = [];\n for (const base of [\"app\", \"components\", \"lib\"]) {\n const dir = path.join(root, base);\n if (!fs.existsSync(dir)) continue;\n const stack = [dir];\n while (stack.length) {\n const current = stack.pop();\n for (const entry of fs.readdirSync(current, { withFileTypes: true })) {\n const full = path.join(current, entry.name);\n if (entry.isDirectory()) stack.push(full);\n else if (/\\.(ts|tsx|css|md)$/.test(entry.name)) {\n const body = fs.readFileSync(full, \"utf8\");\n if (/VantaAPI|vantaapi-ranking|Immortal|immortal/.test(body)) {\n publicBrandResidue.push(path.relative(root, full));\n }\n }\n }\n }\n }\n\n if (publicBrandResidue.length) {\n bad(\"brand:public-residue\", `cleanup needed in ${Array.from(new Set(publicBrandResidue)).slice(0, 12).join(\", \")}`);\n } else {\n ok(\"brand:public-residue\", \"no VantaAPI/vantaapi-ranking/Immortal residue in app, components, or lib\");\n }\n}\n\nfunction checkRetiredPublicSurface() {\n const publicEntryFiles = [\n \"app/page.tsx\",\n \"app/search/page.tsx\",\n \"app/sitemap.ts\",\n \"components/ConsoleNav.tsx\",\n \"components/layout/GlobalSearchLauncher.tsx\",\n \"lib/site-search.ts\",\n \"lib/tool-definitions.ts\",\n ];\n const retiredRoutes = [\n \"/rankings\",\n \"/ranking\",\n \"/comments\",\n \"/comment\",\n \"/report\",\n \"/reports\",\n \"/games\",\n \"/projects\",\n \"/questions\",\n \"/api/rankings\",\n \"/api/comments\",\n \"/api/report\",\n \"/api/reports\",\n \"/api/ai-review\",\n \"/api/cpp/run\",\n ];\n const exposed = [];\n const wrappers = [`\"`, `'`, \"`\"];\n\n for (const file of publicEntryFiles) {\n const body = read(file);\n for (const route of retiredRoutes) {\n const linked = wrappers.some((prefix) =>\n [`${prefix}${route}${prefix}`, `${prefix}${route}?`, `${prefix}${route}/`, `${prefix}${route}#`].some((needle) =>\n body.includes(needle),\n ),\n );\n if (linked) exposed.push(`${file} -> ${route}`);\n }\n }\n\n if (exposed.length) {\n action(\"Hide retired public entries\", \"Remove ranking, comments, report, games, projects, questions, AI review, and C++ run links from public navigation, search, sitemap, and tool definitions.\");\n bad(\"surface:retired-routes\", `public entry still exposes ${exposed.slice(0, 10).join(\", \")}`);\n } else {\n ok(\"surface:retired-routes\", \"retired routes are not linked from public homepage/search/nav/sitemap/tool definitions\");\n }\n}\n\nfunction checkRetiredApiResponses() {\n const retiredApiFiles = [\n \"app/api/categories/route.ts\",\n \"app/api/comments/route.ts\",\n \"app/api/rankings/route.ts\",\n \"app/api/rankings/[id]/route.ts\",\n \"app/api/rankings/leaderboard/route.ts\",\n \"app/api/rankings/like/route.ts\",\n \"app/api/report/route.ts\",\n \"app/api/reports/route.ts\",\n \"app/api/ai-review/route.ts\",\n \"app/api/cpp/run/route.ts\",\n \"app/api/admin/rankings/route.ts\",\n \"app/api/admin/rankings/[id]/route.ts\",\n ];\n const helper = read(\"lib/retired-api.ts\");\n const helperHasHeaders =\n helper.includes('\"Cache-Control\": \"no-store\"') &&\n helper.includes('\"X-Robots-Tag\": \"noindex, nofollow, noarchive\"') &&\n helper.includes(\"status: 410\");\n const weak = [];\n\n for (const file of retiredApiFiles) {\n const body = read(file);\n if (!body) {\n weak.push(`${file} missing`);\n continue;\n }\n const usesHelper = body.includes(\"retiredApi(\") && helperHasHeaders;\n const inlineHardened =\n body.includes(\"status: 410\") &&\n body.includes('\"Cache-Control\": \"no-store\"') &&\n body.includes('\"X-Robots-Tag\": \"noindex, nofollow, noarchive\"');\n if (!usesHelper && !inlineHardened) weak.push(file);\n }\n\n if (weak.length) {\n action(\"Harden retired API responses\", \"Retired APIs should return 410 with no-store and X-Robots-Tag noindex headers.\");\n bad(\"api:retired-responses\", `weak retired API response in ${weak.slice(0, 10).join(\", \")}`);\n } else {\n ok(\"api:retired-responses\", \"retired APIs return hardened 410 no-store noindex responses\");\n }\n}\n\nfunction checkRetiredPageRedirects() {\n const retiredPages = {\n \"app/games/page.tsx\": [\"/\", \"/tools/github-repo-analyzer\"],\n \"app/projects/page.tsx\": [\"/\", \"/tools/github-repo-analyzer\"],\n \"app/questions/page.tsx\": [\"/\", \"/tools/github-repo-analyzer\"],\n \"app/report/page.tsx\": [\"/tools/github-repo-analyzer\"],\n };\n const weak = [];\n\n for (const [file, allowedTargets] of Object.entries(retiredPages)) {\n const body = read(file);\n const redirectsToAllowedTarget = allowedTargets.some((target) =>\n body.includes(`redirect(\"${target}\")`) ||\n body.includes(`redirect('${target}')`) ||\n body.includes(`redirect(localizedHref(\"${target}\", language))`) ||\n body.includes(`redirect(localizedHref('${target}', language))`),\n );\n if (!redirectsToAllowedTarget) weak.push(file);\n }\n\n if (weak.length) {\n action(\"Retarget retired public pages\", \"Retired public pages should redirect to the homepage or GitHub Launch Audit, not old learning, ranking, project, or account surfaces.\");\n bad(\"pages:retired-redirects\", `retired pages need safer redirects in ${weak.join(\", \")}`);\n } else {\n ok(\"pages:retired-redirects\", \"retired public pages redirect to the focused product surface\");\n }\n}\n\nfunction checkFocusedSitemap() {\n const body = read(\"app/sitemap.ts\");\n const requiredRoutes = [\n \"/tools\",\n \"/tools/github-repo-analyzer\",\n \"/tools/prompt-optimizer\",\n \"/tools/bug-finder\",\n \"/tools/api-request-generator\",\n \"/tools/dev-utilities\",\n \"/tools/learning-roadmap\",\n \"/programming\",\n \"/english/word-typing\",\n \"/search\",\n \"/security\",\n \"/privacy\",\n \"/terms\",\n ];\n const offFocusRoutes = [\n \"/today\",\n \"/english\",\n \"/cpp\",\n \"/learn\",\n \"/languages\",\n \"/wrong\",\n \"/dashboard\",\n \"/progress\",\n \"/games\",\n \"/projects\",\n \"/questions\",\n \"/report\",\n ];\n const hasRoute = (route) => {\n if (body.includes(`\"${route}\"`) || body.includes(`'${route}'`)) return true;\n if (route.startsWith(\"/tools/\")) {\n const slug = route.replace(\"/tools/\", \"\");\n return body.includes(`\"${slug}\"`) || body.includes(`'${slug}'`);\n }\n return false;\n };\n const missing = requiredRoutes.filter((route) => !hasRoute(route));\n const exposed = offFocusRoutes.filter((route) => body.includes(`\"${route}\"`) || body.includes(`'${route}'`));\n\n if (missing.length) {\n bad(\"seo:sitemap-focus\", `missing core routes: ${missing.join(\", \")}`);\n return;\n }\n if (exposed.length) {\n action(\"Focus sitemap\", \"Keep sitemap focused on GitHub Launch Audit, AI tools, programming route, the public word typing page, search, security, privacy, and terms. Do not include old learning or retired pages.\");\n bad(\"seo:sitemap-focus\", `off-focus routes in sitemap: ${exposed.join(\", \")}`);\n return;\n }\n ok(\"seo:sitemap-focus\", \"sitemap is focused on launch audit and developer tools\");\n}\n\nfunction checkFocusedRobots() {\n const body = read(\"app/robots.ts\");\n const blockedRoutes = [\n \"/api/\",\n \"/admin/\",\n \"/dashboard\",\n \"/progress\",\n \"/wrong\",\n \"/today\",\n \"/english\",\n \"/cpp\",\n \"/learn\",\n \"/languages\",\n \"/mistakes\",\n \"/status\",\n \"/login\",\n \"/register\",\n \"/games\",\n \"/projects\",\n \"/questions\",\n \"/report\",\n ];\n const missing = blockedRoutes.filter((route) => !body.includes(`\"${route}\"`) && !body.includes(`'${route}'`));\n if (missing.length) {\n action(\"Focus robots.txt\", \"Block old learning, account, retired, and API surfaces from crawler discovery while the public product is focused on GitHub Launch Audit and developer tools.\");\n bad(\"seo:robots-focus\", `missing disallow entries: ${missing.join(\", \")}`);\n return;\n }\n if (!body.includes(\"https://vantaapi.com/sitemap.xml\")) {\n bad(\"seo:robots-focus\", \"robots sitemap must point to production sitemap\");\n return;\n }\n ok(\"seo:robots-focus\", \"robots disallows off-focus surfaces and points to sitemap\");\n}\n\nfunction checkProgrammingSurface() {\n const sitemap = read(\"app/sitemap.ts\");\n const robots = read(\"app/robots.ts\");\n const search = read(\"lib/site-search.ts\");\n const home = read(\"app/page.tsx\");\n const issues = [];\n\n if (!sitemap.includes('\"/programming\"') && !sitemap.includes(\"'/programming'\")) {\n issues.push(\"missing /programming from sitemap\");\n }\n if (robots.includes('\"/programming\"') || robots.includes(\"'/programming'\")) {\n issues.push(\"robots disallows /programming\");\n }\n if (!search.includes('href: \"/programming\"') && !search.includes(\"href: '/programming'\")) {\n issues.push(\"missing /programming from site search\");\n }\n if (!home.includes('localizedHref(\"/programming\"') && !home.includes(\"localizedHref('/programming'\")) {\n issues.push(\"missing /programming from homepage navigation\");\n }\n\n if (issues.length) {\n action(\"Keep programming lane visible\", \"Programming remains a core secondary product lane. Keep /programming in sitemap, search, and homepage navigation while world-language learning can stay hidden.\");\n bad(\"surface:programming-lane\", issues.join(\"; \"));\n return;\n }\n\n ok(\"surface:programming-lane\", \"programming stays visible while off-focus language learning remains hidden\");\n}\n\nfunction checkLaunchDocs() {\n const body = read(\"README.md\");\n const requiredCommands = [\n \"npm run launch:providers\",\n \"npm run launch:secrets\",\n \"npm run launch:production\",\n \"npm run content:check\",\n \"npm run build\",\n \"npm run language:smoke\",\n \"npm run launch:smoke\",\n ];\n const missing = requiredCommands.filter((command) => !body.includes(command));\n if (missing.length) {\n bad(\"docs:launch-path\", `README missing launch commands: ${missing.join(\", \")}`);\n return;\n }\n if (!body.includes(\"Use this short launch path\")) {\n action(\"Clarify README launch path\", \"Keep README launch instructions focused on the short production path before manual fallback details.\");\n return bad(\"docs:launch-path\", \"README must clearly mark the short launch path\");\n }\n ok(\"docs:launch-path\", \"README contains the focused launch command path\");\n}\n\nfunction checkIgnoredLocalLaunchDrafts() {\n const ignoreBody = read(\".gitignore\");\n const localDrafts = [\n \"DEPLOYMENT_READY.md\",\n \"PRODUCTION_SETUP_CHECKLIST.md\",\n \"README_PRODUCTION.md\",\n \"VERCEL_DEPLOYMENT_GUIDE.md\",\n \"VERCEL_ENV_SETUP.md\",\n \"scripts/add-vercel-env.sh\",\n \"scripts/setup-vercel-env.sh\",\n \"scripts/verify-and-deploy.sh\",\n ];\n const missing = localDrafts.filter((file) => !ignoreBody.includes(file));\n if (missing.length) {\n action(\"Ignore local launch drafts\", \"Keep generated deployment drafts out of Git so README remains the canonical launch path.\");\n bad(\"repo:local-launch-drafts\", `.gitignore missing local draft entries: ${missing.join(\", \")}`);\n return;\n }\n ok(\"repo:local-launch-drafts\", \"local deployment draft files are ignored\");\n}\n\nasync function main() {\n console.log(\"🚀 JinMing Lab prelaunch gate\\n\");\n if (loadedEnvFiles.length) console.log(`Loaded env files: ${loadedEnvFiles.join(\", \")}\\n`);\n\n checkSecret(\"JWT_SECRET\", { min: 32 });\n checkSecret(\"CSRF_SECRET\", { min: 64, hex: true });\n checkSecret(\"ENCRYPTION_KEY\", { min: 64, hex: true });\n checkDatabasePassword();\n checkHostAllowlist();\n\n if (envValue(\"ADMIN_2FA_REQUIRED\") === \"false\") bad(\"env:ADMIN_2FA_REQUIRED\", \"must not be false before launch\");\n else ok(\"env:ADMIN_2FA_REQUIRED\", \"2FA enforcement is enabled or using secure default\");\n\n await checkAdmin2FAState();\n checkTurnstileConfig();\n checkRedisConfig();\n checkAiProviderConfig();\n checkGitHubToken();\n\n if (envValue(\"ENABLE_CPP_RUNNER\") === \"true\") bad(\"env:ENABLE_CPP_RUNNER\", \"online C++ execution must remain disabled\");\n else ok(\"env:ENABLE_CPP_RUNNER\", \"C++ runner is disabled or not explicitly enabled\");\n\n if (includesAny(\"lib/cpp-runner.ts\", [/ENABLE_CPP_RUNNER !== \"true\"/]) && includesAny(\".env.example\", [/ENABLE_CPP_RUNNER=\"false\"/])) {\n ok(\"cpp-runner:default-off\", \"code and env example keep runner opt-in/off\");\n } else {\n bad(\"cpp-runner:default-off\", \"runner default-off guard is missing\");\n }\n\n checkBrandResidue();\n checkRetiredPublicSurface();\n checkRetiredApiResponses();\n checkRetiredPageRedirects();\n checkFocusedSitemap();\n checkFocusedRobots();\n checkProgrammingSurface();\n checkLaunchDocs();\n checkIgnoredLocalLaunchDrafts();\n\n console.log(`\\nSummary: pass=${pass} warn=${warn} fail=${fail}`);\n if (launchActions.length) {\n console.log(\"\\nNext launch actions:\");\n const uniqueActions = [];\n const seen = new Set();\n for (const item of launchActions) {\n const key = `${item.title}:${item.detail}`;\n if (seen.has(key)) continue;\n seen.add(key);\n uniqueActions.push(item);\n }\n uniqueActions.slice(0, 8).forEach((item, index) => {\n console.log(`${index + 1}. ${item.title}`);\n console.log(` ${item.detail}`);\n });\n }\n if (fail > 0) process.exit(1);\n}\n\nmain().catch((error) => {\n bad(\"launch:check\", error instanceof Error ? error.message : \"unknown failure\");\n console.log(`\\nSummary: pass=${pass} warn=${warn} fail=${fail}`);\n if (launchActions.length) {\n console.log(\"\\nNext launch actions:\");\n launchActions.slice(0, 8).forEach((item, index) => {\n console.log(`${index + 1}. ${item.title}`);\n console.log(` ${item.detail}`);\n });\n }\n process.exit(1);\n});\n"}}}} |